{ $Id: $} { -------------------------------------------- cocoawsclipboard.pas - Cocoa internal classes -------------------------------------------- This unit contains the private classhierarchy for the Cocoa implemetations ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } unit CocoaWSClipboard; interface {$mode objfpc}{$H+} {$modeswitch objectivec2} uses CocoaAll, Classes, SysUtils, contnrs // fcl-image ,fpreadpng, fpwritepng, fpimage, fpreadbmp, fpwritebmp ,LCLType ,CocoaUtils, Cocoa_Extra; type TCocoaClipboardDataType = (ccdtText, ccdtCocoaStandard, // Formats supported natively by Mac OS X ccdtBitmap, // BMPs need conversion to PNG to work with other Mac OS X apps ccdtNonStandard { Formats that will only work in LCL apps } ); TCocoaClipboardData = class(TObject) // TClipboardFormat is a reference to a TClipboardData public MimeType: string; CocoaFormat: NSString; // utilized for ccdtCocoaStandard and ccdtNonStandard DataType: TCocoaClipboardDataType; constructor Create(AMimeType: string; ACocoaFormat: NSString; ADataType: TCocoaClipboardDataType); destructor Destroy; override; end; TCocoaPasteboardsRef = record pasteboard : NSPasteboard; changeCount: NSInteger; dataProc : TClipboardRequestEvent; isOwned : Boolean; end; TDynClipboardFormatArray = array of TClipboardFormat; { TCocoaWSClipboard } TCocoaWSClipboard = class(TObject) public PrimarySelection: NSPasteboard; SecondarySelection: NSPasteboard; ClipboardFormats: TFPObjectList; // of TCocoaClipboardData Pasteboards: array [TClipboardType] of TCocoaPasteboardsRef; constructor Create; destructor Destroy; override; procedure Sync; function FormatToMimeType(FormatID: TClipboardFormat): string; function GetData(ClipboardType: TClipboardType; FormatID: TClipboardFormat; Stream: TStream): boolean; function GetFormats(ClipboardType: TClipboardType; var Count: integer; var List: TDynClipboardFormatArray): boolean; function GetOwnership(ClipboardType: TClipboardType; OnRequestProc: TClipboardRequestEvent; FormatCount: integer; Formats: PClipboardFormat): boolean; function RegisterFormat(const AMimeType: string): TClipboardFormat; function GetClipboardDataForFormat(AFormat: TClipboardFormat): TCocoaClipboardData; function RegisterCocoaType(AType: NSString): TClipboardFormat; function CocoaTypeToMimeType(AType: NSString): string; end; implementation { TCocoaWSClipboard } constructor TCocoaWSClipboard.Create; var t : TClipboardType; begin inherited Create; PrimarySelection := NSPasteboard.pasteboardWithUniqueName(); SecondarySelection := NSPasteboard.pasteboardWithUniqueName(); ClipboardFormats := TFPObjectList.Create(True); Pasteboards[ctPrimarySelection].pasteboard := PrimarySelection; Pasteboards[ctSecondarySelection].pasteboard := SecondarySelection; Pasteboards[ctClipboard].pasteboard := NSPasteboard.generalPasteboard; for t := Low(TClipboardType) to High(TClipboardType) do if Assigned(Pasteboards[t].pasteboard) then Pasteboards[t].changeCount := Pasteboards[t].pasteboard.changeCount; end; destructor TCocoaWSClipboard.Destroy; begin PrimarySelection.releaseGlobally(); SecondarySelection.releaseGlobally(); ClipboardFormats.Free; inherited; end; procedure TCocoaWSClipboard.Sync; var ct : TClipboardType; pb : NSPasteboard; begin for ct := low(TClipboardType) to high(TClipboardType) do begin if not Pasteboards[ct].isOwned then Continue; pb := Pasteboards[ct].pasteboard; if not Assigned(pb) then Continue; if (pb.changeCount <> Pasteboards[ct].changeCount) then begin Pasteboards[ct].isOwned:=false; if Assigned(Pasteboards[ct].dataProc) then // notifying about the loss of ownership Pasteboards[ct].dataProc(0, nil); end; end; end; function TCocoaWSClipboard.FormatToMimeType(FormatID: TClipboardFormat ): string; var lFormat: TCocoaClipboardData; begin lFormat := GetClipboardDataForFormat(FormatID); if lFormat = nil then Result := '' else Result := lFormat.MimeType; end; function TCocoaWSClipboard.GetClipboardDataForFormat(AFormat: TClipboardFormat ): TCocoaClipboardData; begin Result := TCocoaClipboardData(AFormat); end; function PasteboardToPngData(pb: NSPasteboard): NSData; var rep : NSBitmapImageRep; begin rep := NSBitmapImageRep.imageRepWithPasteboard(pb); if Assigned(rep) then Result := rep.representationUsingType_properties(NSPNGFileType, nil) else Result := nil; end; function TCocoaWSClipboard.GetData(ClipboardType: TClipboardType; FormatID: TClipboardFormat; Stream: TStream): boolean; var pasteboard: NSPasteboard; lFormat: TCocoaClipboardData; lNSStr: NSString; // for text lStr: String; // for standard lNSData: NSData; lNSbytes: PByte; i: Integer; // for bitmap image: TFPCustomImage; lTmpStream: TMemoryStream; reader: TFPCustomImageReader; writer: TFPCustomImageWriter; begin Result := False; pasteboard := Pasteboards[ClipboardType].pasteboard; lFormat := GetClipboardDataForFormat(FormatID); if lFormat = nil then Exit; case lFormat.DataType of ccdtText: begin lNSStr := pasteboard.stringForType(lFormat.CocoaFormat); if lNSStr = nil then Exit; lStr := NSStringToString(lNSStr); Stream.Write(lStr[1], Length(lStr)); {$IFDEF VerboseClipboard} DebugLn('TCocoaWidgetSet.ClipboardGetData IsText Result=' + lStr); {$ENDIF} end; ccdtCocoaStandard, ccdtNonStandard: begin lNSData := pasteboard.dataForType(lFormat.CocoaFormat); if lNSData = nil then Exit; lNSbytes := lNSData.bytes; for i := 0 to lNSData.length-1 do Stream.WriteByte(lNSbytes[i]); end; // In Cocoa images are stored as PNG, convert to BMP for LCL app usage ccdtBitmap: begin lNSData := pasteboard.dataForType(lFormat.CocoaFormat); if lNSData = nil then begin lNSData := PasteboardToPngData(pasteboard); if not Assigned(lNSData) then Exit; end; lNSbytes := lNSData.bytes; Image := TFPMemoryImage.Create(10, 10); Reader := TFPReaderPNG.Create; Writer := TFPWriterBMP.Create; lTmpStream := TMemoryStream.Create; try for i := 0 to lNSData.length-1 do lTmpStream.WriteByte(lNSbytes[i]); lTmpStream.Position := 0; Image.LoadFromStream(lTmpStream, Reader); Image.SaveToStream(Stream, Writer); finally Image.Free; Reader.Free; Writer.Free; lTmpStream.Free; end; end; end; Result := True; end; function TCocoaWSClipboard.GetFormats(ClipboardType: TClipboardType; var Count: integer; var List: TDynClipboardFormatArray): boolean; var i: Integer; pb : NSPasteboard; tp : NSString; hasBmp: Boolean; hasImage: Boolean; // this a default macOS sharing format imgArr: NSArray; const ImgBmpFmt : string = 'image/bmp'; begin pb := Pasteboards[ClipboardType].pasteboard; Result := Assigned(pb); if not Result then Exit; i := 0; SetLength(List, pb.types.count); hasImage := false; hasBmp := false; imgArr := NSBitmapImageRep.imagePasteboardTypes; for tp in pb.types do begin List[i]:=RegisterCocoaType(tp); inc(i); if not hasImage and Assigned(imgArr) then begin hasImage := (imgArr.indexOfObject(tp) <> NSNotFound); end; if (tp.UTF8String = ImgBmpFmt) then hasBmp := true; end; if hasImage and not hasBmp then begin inc(i); SetLength(List, i); List[i-1] := RegisterFormat(ImgBmpFmt); end; Count := i; end; function TCocoaWSClipboard.GetOwnership(ClipboardType: TClipboardType; OnRequestProc: TClipboardRequestEvent; FormatCount: integer; Formats: PClipboardFormat): boolean; var pasteboard: NSPasteboard; i: Integer; lCurFormat: TCocoaClipboardData; DataStream: TMemoryStream; FormatToOwn: NSString; FormatToOwnArray: NSArray; // text format lText: string; lNSText: NSString; // non-text lNSData: NSData; // for bitmap image: TFPCustomImage; lTmpStream: TMemoryStream; reader: TFPCustomImageReader; writer: TFPCustomImageWriter; begin pasteboard := Pasteboards[ClipboardType].pasteboard; DataStream := TMemoryStream.Create; try FormatToOwnArray := nil; if FormatCount>0 then begin FormatToOwnArray := NSArray(NSMutableArray.array_); for i := 0 to FormatCount-1 do begin lCurFormat := TCocoaClipboardData(Formats[i]); if lCurFormat = nil then Continue; FormatToOwn := lCurFormat.CocoaFormat; NSMutableArray(FormatToOwnArray).addObject(FormatToOwn); end; end; if Assigned(FormatToOwnArray) and (FormatToOwnArray.count>0) then pasteboard.declareTypes_owner(FormatToOwnArray, nil); for i := 0 to FormatCount-1 do begin lCurFormat := TCocoaClipboardData(Formats[i]); if lCurFormat = nil then Continue; DataStream.Position := 0; DataStream.Size := 0; OnRequestProc(Formats[i], DataStream); case lCurFormat.DataType of ccdtText: begin DataStream.Position := 0; SetLength(lText, DataStream.Size); if DataStream.Size > 0 then DataStream.Read(lText[1], DataStream.Size); lNSText := NSStringUtf8(lText); pasteboard.setString_forType(lNSText, lCurFormat.CocoaFormat); lNsText.release; end; ccdtCocoaStandard, ccdtNonStandard: begin DataStream.Position := 0; lNSData := NSData.dataWithBytes_length(DataStream.Memory, DataStream.Size); pasteboard.setData_forType(lNSData, lCurFormat.CocoaFormat); end; ccdtBitmap: begin Image := TFPMemoryImage.Create(10, 10); Reader := TFPReaderBMP.Create; Writer := TFPWriterPNG.Create; lTmpStream := TMemoryStream.Create; try DataStream.Position := 0; Image.LoadFromStream(DataStream, Reader); Image.SaveToStream(lTmpStream, Writer); lTmpStream.Position := 0; lNSData := NSData.dataWithBytes_length(lTmpStream.Memory, lTmpStream.Size); pasteboard.setData_forType(lNSData, lCurFormat.CocoaFormat); finally Image.Free; Reader.Free; Writer.Free; lTmpStream.Free; end; end; end; end; finally DataStream.Free; end; Pasteboards[ClipboardType].pasteboard:=pasteboard; Pasteboards[ClipboardType].dataProc:=OnRequestProc; if Assigned(pasteboard) then begin Pasteboards[ClipboardType].changeCount:=pasteboard.changeCount; Pasteboards[ClipboardType].isOwned:=true; end else Pasteboards[ClipboardType].isOwned:=false; Result := True; end; function TCocoaWSClipboard.RegisterFormat(const AMimeType: string ): TClipboardFormat; var i: Integer; lCurData: TCocoaClipboardData; lNSStr: NSString = nil; lDataType: TCocoaClipboardDataType; begin Result := 0; // Check first if it was already registered for i := 0 to ClipboardFormats.Count-1 do begin lCurData := TCocoaClipboardData(ClipboardFormats.Items[i]); if lCurData.MimeType = AMimeType then begin Result := TClipboardFormat(lCurData); {$IFDEF VerboseClipboard} DebugLn('TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=' + AMimeType + ' Result='+DbgS(Result)); {$ENDIF} Exit; end; end; // if none was found, we need to register it lDataType := ccdtNonStandard; // See PredefinedClipboardMimeTypes for the most common mime-types case AMimeType of 'text/plain': begin lNSStr := NSPasteboardTypeString; lDataType := ccdtText; end; 'image/png': begin lNSStr := NSPasteboardTypePNG; lDataType := ccdtCocoaStandard; end; 'image/bmp': begin lNSStr := NSPasteboardTypePNG; lDataType := ccdtBitmap; end; else lNSStr := NSStringUtf8(AMimeType); lDataType := ccdtNonStandard; end; if lNSStr <> nil then begin lCurData := TCocoaClipboardData.Create(AMimeType, lNSStr, lDataType); ClipboardFormats.Add(lCurData); Result := TClipboardFormat(lCurData); end; end; function TCocoaWSClipboard.RegisterCocoaType(AType: NSString): TClipboardFormat; begin Result := RegisterFormat( CocoaTypeToMimeType(AType) ); end; function TCocoaWSClipboard.CocoaTypeToMimeType(AType: NSString): string; begin // "default" types must be mapped to a default LCL mime-type if AType.isEqualToString(NSPasteboardTypeString) then Result := 'text/plain' else Result := NSStringToString(AType); end; { TCocoaClipboardData } constructor TCocoaClipboardData.Create(AMimeType: string; ACocoaFormat: NSString; ADataType: TCocoaClipboardDataType); begin MimeType := AMimeType; CocoaFormat := ACocoaFormat; DataType := ADataType; end; destructor TCocoaClipboardData.Destroy; begin CocoaFormat.release; end; end.