mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-07-24 21:56:42 +02:00
477 lines
13 KiB
ObjectPascal
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.
|