lazarus/lcl/interfaces/cocoa/cocoawsclipboard.pas
2023-11-21 18:28:48 +08:00

477 lines
13 KiB
ObjectPascal

{ $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.