/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // // Mike Pinkerton // Netscape Communications // // See header file for details. // // Remaining Work: // * only convert data to clipboard on an app switch. // #include "nsCOMPtr.h" #include "nsClipboard.h" #include "nsVoidArray.h" #include "nsIClipboardOwner.h" #include "nsString.h" #include "nsIFormatConverter.h" #include "nsMimeMapper.h" #include "nsIComponentManager.h" #include "nsISupportsPrimitives.h" #include "nsXPIDLString.h" #include "nsPrimitiveHelpers.h" #include "nsIImageMac.h" #include "nsMemory.h" #include // // nsClipboard constructor // nsClipboard::nsClipboard() : nsBaseClipboard() { } // // nsClipboard destructor // nsClipboard::~nsClipboard() { } // // SetNativeClipboardData // // Take data off the transferrable and put it on the clipboard in as many formats // as are registered. // // NOTE: This code could all live in ForceDataToClipboard() and this could be a NOOP. // If speed and large data sizes are an issue, we should move that code there and only // do it on an app switch. // NS_IMETHODIMP nsClipboard :: SetNativeClipboardData ( PRInt32 aWhichClipboard ) { if ( aWhichClipboard != kGlobalClipboard ) return NS_ERROR_FAILURE; nsresult errCode = NS_OK; mIgnoreEmptyNotification = PR_TRUE; // make sure we have a good transferable if ( !mTransferable ) return NS_ERROR_INVALID_ARG; nsMimeMapperMac theMapper; #if TARGET_CARBON ::ClearCurrentScrap(); #else ::ZeroScrap(); #endif // get flavor list that includes all flavors that can be written (including ones // obtained through conversion) nsCOMPtr flavorList; errCode = mTransferable->FlavorsTransferableCanExport ( getter_AddRefs(flavorList) ); if ( NS_FAILED(errCode) ) return NS_ERROR_FAILURE; // For each flavor present (either directly in the transferable or that its // converter knows about) put it on the clipboard. Luckily, GetTransferData() // handles conversions for us, so we really don't need to know if a conversion // is required or not. PRUint32 cnt; flavorList->Count(&cnt); for ( PRUint32 i = 0; i < cnt; ++i ) { nsCOMPtr genericFlavor; flavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); nsCOMPtr currentFlavor ( do_QueryInterface(genericFlavor) ); if ( currentFlavor ) { nsXPIDLCString flavorStr; currentFlavor->ToString( getter_Copies(flavorStr) ); // find MacOS flavor ResType macOSFlavor = theMapper.MapMimeTypeToMacOSType(flavorStr); // get data. This takes converters into account. Different things happen for // different flavors, so do some special processing. void* data = nsnull; PRUint32 dataSize = 0; if ( strcmp(flavorStr,kUnicodeMime) == 0 ) { // we have unicode, put it on first as unicode nsCOMPtr genericDataWrapper; errCode = mTransferable->GetTransferData ( flavorStr, getter_AddRefs(genericDataWrapper), &dataSize ); nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, genericDataWrapper, &data, dataSize ); errCode = PutOnClipboard ( macOSFlavor, data, dataSize ); if ( NS_SUCCEEDED(errCode) ) { // we also need to put it on as 'TEXT' after doing the conversion to the platform charset. char* plainTextData = nsnull; PRUnichar* castedUnicode = NS_REINTERPRET_CAST(PRUnichar*, data); PRInt32 plainTextLen = 0; nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText ( castedUnicode, dataSize / 2, &plainTextData, &plainTextLen ); if ( plainTextData ) { errCode = PutOnClipboard ( 'TEXT', plainTextData, plainTextLen ); nsMemory::Free ( plainTextData ); } } } // if unicode else if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 ) { // we have an image, which is in the transferable as an nsIImage. Convert it // to PICT (PicHandle) and put those bits on the clipboard. The actual size // of the picture is the size of the handle, not sizeof(Picture). nsCOMPtr imageSupports; errCode = mTransferable->GetTransferData ( flavorStr, getter_AddRefs(imageSupports), &dataSize ); nsCOMPtr image ( do_QueryInterface(imageSupports) ); if ( image ) { PicHandle picture = nsnull; image->ConvertToPICT ( &picture ); if ( picture ) { errCode = PutOnClipboard ( 'PICT', *picture, ::GetHandleSize((Handle)picture) ); ::KillPicture ( picture ); } } else NS_WARNING ( "Image isn't an nsIImageMac in transferable" ); } else { // we don't know what we have. let's just assume it's unicode but doesn't need to be // translated to TEXT. nsCOMPtr genericDataWrapper; errCode = mTransferable->GetTransferData ( flavorStr, getter_AddRefs(genericDataWrapper), &dataSize ); nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, genericDataWrapper, &data, dataSize ); errCode = PutOnClipboard ( macOSFlavor, data, dataSize ); } nsMemory::Free ( data ); } } // foreach flavor in transferable // write out the mapping data in a special flavor on the clipboard. |mappingLen| // includes the NULL terminator. short mappingLen = 0; const char* mapping = theMapper.ExportMapping(&mappingLen); if ( mapping && mappingLen ) { errCode = PutOnClipboard ( nsMimeMapperMac::MappingFlavor(), mapping, mappingLen ); nsCRT::free ( NS_CONST_CAST(char*, mapping) ); } return errCode; } // SetNativeClipboardData // // PutOnClipboard // // Actually does the work of placing the data on the native clipboard // in the given flavor // nsresult nsClipboard :: PutOnClipboard ( ResType inFlavor, const void* inData, PRInt32 inLen ) { nsresult errCode = NS_OK; #if TARGET_CARBON ScrapRef scrap; ::GetCurrentScrap(&scrap); ::PutScrapFlavor( scrap, inFlavor, kScrapFlavorMaskNone, inLen, inData ); #else long numBytes = ::PutScrap ( inLen, inFlavor, inData ); if ( numBytes != noErr ) errCode = NS_ERROR_FAILURE; #endif return errCode; } // PutOnClipboard // // GetNativeClipboardData // // Take data off the native clip and put it on the transferable. // NS_IMETHODIMP nsClipboard :: GetNativeClipboardData ( nsITransferable * aTransferable, PRInt32 aWhichClipboard ) { if ( aWhichClipboard != kGlobalClipboard ) return NS_ERROR_FAILURE; nsresult errCode = NS_OK; // make sure we have a good transferable if ( !aTransferable ) return NS_ERROR_INVALID_ARG; // get flavor list that includes all acceptable flavors (including ones obtained through // conversion) nsCOMPtr flavorList; errCode = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) ); if ( NS_FAILED(errCode) ) return NS_ERROR_FAILURE; // create a mime mapper. It's ok for this to fail because the data may come from // another app which obviously wouldn't put our mime mapping data on the clipboard. char* mimeMapperData = nsnull; errCode = GetDataOffClipboard ( nsMimeMapperMac::MappingFlavor(), (void**)&mimeMapperData, 0 ); nsMimeMapperMac theMapper ( mimeMapperData ); if (mimeMapperData) nsCRT::free ( mimeMapperData ); // Now walk down the list of flavors. When we find one that is actually on the // clipboard, copy out the data into the transferable in that format. SetTransferData() // implicitly handles conversions. PRBool dataFound = PR_FALSE; PRUint32 cnt; flavorList->Count(&cnt); for ( PRUint32 i = 0; i < cnt; ++i ) { nsCOMPtr genericFlavor; flavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); nsCOMPtr currentFlavor ( do_QueryInterface(genericFlavor) ); if ( currentFlavor ) { nsXPIDLCString flavorStr; currentFlavor->ToString ( getter_Copies(flavorStr) ); // find MacOS flavor (don't add if not present) ResType macOSFlavor = theMapper.MapMimeTypeToMacOSType(flavorStr, PR_FALSE); void* clipboardData = nsnull; PRInt32 dataSize = 0L; nsresult loadResult = GetDataOffClipboard ( macOSFlavor, &clipboardData, &dataSize ); if ( NS_SUCCEEDED(loadResult) && clipboardData ) dataFound = PR_TRUE; else { // if we are looking for text/unicode and we fail to find it on the clipboard first, // try again with text/plain. If that is present, convert it to unicode. if ( strcmp(flavorStr, kUnicodeMime) == 0 ) { loadResult = GetDataOffClipboard ( 'TEXT', &clipboardData, &dataSize ); if ( NS_SUCCEEDED(loadResult) && clipboardData ) { const char* castedText = NS_REINTERPRET_CAST(char*, clipboardData); PRUnichar* convertedText = nsnull; PRInt32 convertedTextLen = 0; nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode ( castedText, dataSize, &convertedText, &convertedTextLen ); if ( convertedText ) { // out with the old, in with the new nsMemory::Free(clipboardData); clipboardData = convertedText; dataSize = convertedTextLen * 2; dataFound = PR_TRUE; } } // if plain text data on clipboard } // if looking for text/unicode } // else we try one last ditch effort to find our data if ( dataFound ) { if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 ) { // someone asked for an image, so we have to convert from PICT to the desired // image format #ifdef DEBUG printf ( "----------- IMAGE REQUESTED ----------" ); #endif } // if image requested else { // Assume that we have some form of textual data. The DOM only wants LF, so convert // from MacOS line endings to DOM line endings. nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &clipboardData, &dataSize ); // put it into the transferable nsCOMPtr genericDataWrapper; nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, clipboardData, dataSize, getter_AddRefs(genericDataWrapper) ); errCode = aTransferable->SetTransferData ( flavorStr, genericDataWrapper, dataSize ); } nsMemory::Free ( clipboardData ); // we found one, get out of this loop! break; } // if flavor found on clipboard } } // foreach flavor return errCode; } // // GetDataOffClipboard // // Actually does the work of retrieving the data from the native clipboard // with the given MacOS flavor // nsresult nsClipboard :: GetDataOffClipboard ( ResType inMacFlavor, void** outData, PRInt32* outDataSize ) { if ( !outData || !inMacFlavor ) return NS_ERROR_FAILURE; // set up default results. *outData = nsnull; if ( outDataSize ) *outDataSize = 0; // check if it is on the clipboard long offsetUnused = 0; #if TARGET_CARBON ScrapRef scrap; long dataSize; OSStatus err; err = ::GetCurrentScrap(&scrap); if (err != noErr) return NS_ERROR_FAILURE; err = ::GetScrapFlavorSize(scrap, inMacFlavor, &dataSize); if (err != noErr) return NS_ERROR_FAILURE; if (dataSize > 0) { char* dataBuff = (char*) nsMemory::Alloc(dataSize); if ( !dataBuff ) return NS_ERROR_OUT_OF_MEMORY; // ::GetScrapFlavorData can be very expensive when a conversion // is required (say the OS does the conversion from TEXT to utxt). Be // sure to only call this when pasting. We no longer use it in // CheckIfFlavorPresent() for this very reason. err = ::GetScrapFlavorData(scrap, inMacFlavor, &dataSize, dataBuff); NS_ASSERTION(err == noErr, "nsClipboard:: Error getting data off clipboard"); if ( err ) { nsMemory::Free(dataBuff); return NS_ERROR_FAILURE; } // put it into the transferable if ( outDataSize ) *outDataSize = dataSize; *outData = dataBuff; } #else long clipResult = ::GetScrap(NULL, inMacFlavor, &offsetUnused); if ( clipResult > 0 ) { Handle dataHand = ::NewHandle(0); if ( !dataHand ) return NS_ERROR_OUT_OF_MEMORY; long dataSize = ::GetScrap ( dataHand, inMacFlavor, &offsetUnused ); NS_ASSERTION(dataSize > 0, "nsClipboard:: Error getting data off the clipboard, size negative"); if ( dataSize > 0 ) { char* dataBuff = NS_REINTERPRET_CAST(char*, nsMemory::Alloc(dataSize)); if ( !dataBuff ) return NS_ERROR_OUT_OF_MEMORY; ::HLock(dataHand); ::BlockMoveData ( *dataHand, dataBuff, dataSize ); ::HUnlock(dataHand); ::DisposeHandle(dataHand); if ( outDataSize ) *outDataSize = dataSize; *outData = dataBuff; } else return NS_ERROR_FAILURE; } #endif /* TARGET_CARBON */ return NS_OK; } // GetDataOffClipboard // // HasDataMatchingFlavors // // Check the clipboard to see if we have any data that matches the given flavors. This // does NOT actually fetch the data. The items in the flavor list are nsISupportsString's. // // Handle the case where we ask for unicode and it's not there, but plain text is. We // say "yes" in that case, knowing that we will have to perform a conversion when we actually // pull the data off the clipboard. // NS_IMETHODIMP nsClipboard :: HasDataMatchingFlavors ( nsISupportsArray* aFlavorList, PRInt32 aWhichClipboard, PRBool * outResult ) { nsresult rv = NS_OK; *outResult = PR_FALSE; // assume there is nothing there we want. if ( aWhichClipboard != kGlobalClipboard ) return NS_OK; // create a mime mapper. It's ok for this to fail because the data may come from // another app which obviously wouldn't put our mime mapping data on the clipboard. char* mimeMapperData = nsnull; rv = GetDataOffClipboard ( nsMimeMapperMac::MappingFlavor(), (void**)&mimeMapperData, 0 ); nsMimeMapperMac theMapper ( mimeMapperData ); nsMemory::Free ( mimeMapperData ); PRUint32 length; aFlavorList->Count(&length); for ( PRUint32 i = 0; i < length; ++i ) { nsCOMPtr genericFlavor; aFlavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); nsCOMPtr flavorWrapper ( do_QueryInterface(genericFlavor) ); if ( flavorWrapper ) { nsXPIDLCString flavor; flavorWrapper->ToString ( getter_Copies(flavor) ); #ifdef NS_DEBUG if ( strcmp(flavor, kTextMime) == 0 ) NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" ); #endif // now that we have the flavor (whew!), run it through the mime mapper. If // the mapper returns a null flavor, then it ain't there. ResType macFlavor = theMapper.MapMimeTypeToMacOSType ( flavor, PR_FALSE ); if ( macFlavor ) { if ( CheckIfFlavorPresent(macFlavor) ) { *outResult = PR_TRUE; // we found one! break; } else { // if the client asked for unicode and it wasn't present, check if we have TEXT. // We'll handle the actual data substitution in GetNativeClipboardData(). if ( strcmp(flavor, kUnicodeMime) == 0 ) { if ( CheckIfFlavorPresent('TEXT') ) { *outResult = PR_TRUE; break; } } } } } } // foreach flavor return NS_OK; } // // CheckIfFlavorPresent // // A little utility routine for derminining if a given flavor is really there // PRBool nsClipboard :: CheckIfFlavorPresent ( ResType inMacFlavor ) { PRBool retval = PR_FALSE; #if TARGET_CARBON ScrapRef scrap = nsnull; OSStatus err = ::GetCurrentScrap(&scrap); if ( scrap ) { // the OS clipboard may contain promises and require conversions. Instead // of calling in those promises, we can use ::GetScrapFlavorInfoList() to // see the list of what could be there if we asked for it. This is really // fast. Iterate over the list, and if we find it, we're good to go. UInt32 flavorCount = 0; ::GetScrapFlavorCount ( scrap, &flavorCount ); ScrapFlavorInfo* flavorList = new ScrapFlavorInfo[flavorCount]; if ( flavorList ) { err = ::GetScrapFlavorInfoList ( scrap, &flavorCount, flavorList ); if ( !err && flavorList ) { for ( int i = 0; i < flavorCount; ++i ) { if ( flavorList[i].flavorType == inMacFlavor ) retval = PR_TRUE; } delete flavorList; } } } #else long offsetUnused = 0; long clipResult = ::GetScrap(NULL, inMacFlavor, &offsetUnused); if ( clipResult > 0 ) retval = PR_TRUE; // we found one! #endif return retval; } // CheckIfFlavorPresent