cocoa: move clipboard (cocoa-pasteboard) related implementation into a separate unit. Update the behaviour of ClipboardGetFormats function to return the actual information from the pasteboard. #34121

git-svn-id: trunk@58695 -
This commit is contained in:
dmitry 2018-08-12 14:38:28 +00:00
parent db95c32b36
commit aa9cfe7f1a
7 changed files with 469 additions and 349 deletions

1
.gitattributes vendored
View File

@ -8884,6 +8884,7 @@ lcl/interfaces/cocoa/cocoawinapih.inc svneol=native#text/pascal
lcl/interfaces/cocoa/cocoawindows.pas svneol=native#text/plain lcl/interfaces/cocoa/cocoawindows.pas svneol=native#text/plain
lcl/interfaces/cocoa/cocoawsbuttons.pp svneol=native#text/pascal lcl/interfaces/cocoa/cocoawsbuttons.pp svneol=native#text/pascal
lcl/interfaces/cocoa/cocoawschecklst.pas svneol=native#text/plain lcl/interfaces/cocoa/cocoawschecklst.pas svneol=native#text/plain
lcl/interfaces/cocoa/cocoawsclipboard.pas svneol=native#text/plain
lcl/interfaces/cocoa/cocoawscomctrls.pas svneol=native#text/plain lcl/interfaces/cocoa/cocoawscomctrls.pas svneol=native#text/plain
lcl/interfaces/cocoa/cocoawscommon.pas svneol=native#text/plain lcl/interfaces/cocoa/cocoawscommon.pas svneol=native#text/plain
lcl/interfaces/cocoa/cocoawsdatepicker.pas svneol=native#text/plain lcl/interfaces/cocoa/cocoawsdatepicker.pas svneol=native#text/plain

View File

@ -28,8 +28,6 @@ interface
uses uses
// rtl+ftl // rtl+ftl
Types, Classes, SysUtils, Math, contnrs, Types, Classes, SysUtils, Math, contnrs,
// fcl-image
fpreadpng, fpwritepng, fpimage, fpreadbmp, fpwritebmp,
// carbon bindings // carbon bindings
MacOSAll, MacOSAll,
// interfacebase // interfacebase
@ -37,6 +35,7 @@ uses
// private // private
CocoaAll, CocoaPrivate, CocoaUtils, CocoaGDIObjects, CocoaAll, CocoaPrivate, CocoaUtils, CocoaGDIObjects,
cocoa_extra, CocoaWSMenus, CocoaWSForms, CocoaWindows, CocoaScrollers, cocoa_extra, CocoaWSMenus, CocoaWSForms, CocoaWindows, CocoaScrollers,
CocoaWSClipboard,
// LCL // LCL
LCLStrConsts, LMessages, LCLMessageGlue, LCLProc, LCLIntf, LCLType, LCLStrConsts, LMessages, LCLMessageGlue, LCLProc, LCLIntf, LCLType,
Controls, Forms, Themes, Menus, Controls, Forms, Themes, Menus,
@ -52,20 +51,6 @@ type
class function newWithFunc(afunc: TWSTimerProc): TCocoaTimerObject; message 'newWithFunc:'; class function newWithFunc(afunc: TWSTimerProc): TCocoaTimerObject; message 'newWithFunc:';
end; end;
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;
TAppDelegate = objcclass(NSObject, NSApplicationDelegateProtocol) TAppDelegate = objcclass(NSObject, NSApplicationDelegateProtocol)
procedure application_openFiles(sender: NSApplication; filenames: NSArray); procedure application_openFiles(sender: NSApplication; filenames: NSArray);
procedure applicationDidHide(notification: NSNotification); procedure applicationDidHide(notification: NSNotification);
@ -86,13 +71,6 @@ type
function nextEventMatchingMask_untilDate_inMode_dequeue(mask: NSUInteger; expiration: NSDate; mode: NSString; deqFlag: Boolean): NSEvent; override; function nextEventMatchingMask_untilDate_inMode_dequeue(mask: NSUInteger; expiration: NSDate; mode: NSString; deqFlag: Boolean): NSEvent; override;
end; end;
TCocoaPasteboardsRef = record
pasteboard : NSPasteboard;
changeCount: NSInteger;
dataProc : TClipboardRequestEvent;
isOwned : Boolean;
end;
{ TCocoaWidgetSet } { TCocoaWidgetSet }
TCocoaWidgetSet = class(TWidgetSet) TCocoaWidgetSet = class(TWidgetSet)
@ -121,18 +99,11 @@ type
// Sandboxing // Sandboxing
SandboxingOn: Boolean; SandboxingOn: Boolean;
fClipboard: TCocoaWSClipboard;
// Clipboard // Clipboard
PrimarySelection: NSPasteboard;
SecondarySelection: NSPasteboard;
ClipboardFormats: TFPObjectList; // of TCocoaClipboardData
ClipboardChangeCount: NSInteger;
Pasteboards: array [TClipboardType] of TCocoaPasteboardsRef;
procedure InitClipboard();
procedure FreeClipboard();
procedure SyncClipboard(); procedure SyncClipboard();
function GetClipboardDataForFormat(AFormat: TClipboardFormat): TCocoaClipboardData;
function PromptUser(const DialogCaption, DialogMessage: String; function PromptUser(const DialogCaption, DialogMessage: String;
DialogType: longint; Buttons: PLongint; ButtonCount, DefaultIndex, DialogType: longint; Buttons: PLongint; ButtonCount, DefaultIndex,

View File

@ -149,54 +149,9 @@ begin
Result := (WindowHandle <> 0) and (DC <> 0) and TCocoaContext(DC).isDesignDC; Result := (WindowHandle <> 0) and (DC <> 0) and TCocoaContext(DC).isDesignDC;
end; end;
procedure TCocoaWidgetSet.InitClipboard;
var
t : TClipboardType;
begin
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;
procedure TCocoaWidgetSet.FreeClipboard;
begin
PrimarySelection.releaseGlobally();
SecondarySelection.releaseGlobally();
ClipboardFormats.Free;
end;
procedure TCocoaWidgetSet.SyncClipboard(); procedure TCocoaWidgetSet.SyncClipboard();
var
ct : TClipboardType;
pb : NSPasteboard;
begin begin
for ct := low(TClipboardType) to high(TClipboardType) do begin fClipboard.Sync;
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 TCocoaWidgetSet.GetClipboardDataForFormat(AFormat: TClipboardFormat): TCocoaClipboardData;
begin
Result := TCocoaClipboardData(AFormat);
end; end;
{------------------------------------------------------------------------------ {------------------------------------------------------------------------------

View File

@ -170,7 +170,7 @@ begin
InitStockItems; InitStockItems;
InitClipboard(); // must be here otherwise clipboard calls before Application.Initialize crash fClipboard := TCocoaWSClipboard.Create; // must be here otherwise clipboard calls before Application.Initialize crash
end; end;
{------------------------------------------------------------------------------ {------------------------------------------------------------------------------
@ -193,7 +193,7 @@ begin
FreeSysColorBrushes; FreeSysColorBrushes;
FreeClipboard(); fClipboard.Free;
// The CocoaCaret is based WidgetSet timer. // The CocoaCaret is based WidgetSet timer.
// The GlobalCaret is freed in finalization section, which is called // The GlobalCaret is freed in finalization section, which is called
@ -612,20 +612,6 @@ begin
Result.func:=afunc; Result.func:=afunc;
end; 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;
procedure TAppDelegate.application_openFiles(sender: NSApplication; filenames: NSArray); procedure TAppDelegate.application_openFiles(sender: NSApplication; filenames: NSArray);
var var
lFiles: array of string; lFiles: array of string;

View File

@ -141,16 +141,11 @@ end;
Returns: The corresponding mime type as string Returns: The corresponding mime type as string
------------------------------------------------------------------------------} ------------------------------------------------------------------------------}
function TCocoaWidgetSet.ClipboardFormatToMimeType(FormatID: TClipboardFormat): string; function TCocoaWidgetSet.ClipboardFormatToMimeType(FormatID: TClipboardFormat): string;
var
lFormat: TCocoaClipboardData;
begin begin
{$IFDEF VerboseClipboard} {$IFDEF VerboseClipboard}
DebugLn('TCocoaWidgetSet.ClipboardFormatToMimeType FormatID: ' + DbgS(FormatID)); DebugLn('TCocoaWidgetSet.ClipboardFormatToMimeType FormatID: ' + DbgS(FormatID));
{$ENDIF} {$ENDIF}
Result := fClipboard.FormatToMimeType(FormatID);
lFormat := GetClipboardDataForFormat(FormatID);
if lFormat = nil then Exit;
Result := lFormat.MimeType;
end; end;
{------------------------------------------------------------------------------ {------------------------------------------------------------------------------
@ -163,84 +158,12 @@ end;
------------------------------------------------------------------------------} ------------------------------------------------------------------------------}
function TCocoaWidgetSet.ClipboardGetData(ClipboardType: TClipboardType; function TCocoaWidgetSet.ClipboardGetData(ClipboardType: TClipboardType;
FormatID: TClipboardFormat; Stream: TStream): boolean; 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 begin
Result := False;
{$IFDEF VerboseClipboard} {$IFDEF VerboseClipboard}
DebugLn('TCocoaWidgetSet.ClipboardGetData ClipboardType=' + DebugLn('TCocoaWidgetSet.ClipboardGetData ClipboardType=' +
ClipboardTypeName[ClipboardType] + ' FormatID: ' + DbgS(FormatID)); ClipboardTypeName[ClipboardType] + ' FormatID: ' + DbgS(FormatID));
{$ENDIF} {$ENDIF}
Result := fClipboard.GetData(ClipboardType, FormatID, Stream);
case ClipboardType of
ctPrimarySelection: pasteboard := PrimarySelection;
ctSecondarySelection: pasteboard := SecondarySelection;
ctClipboard: pasteboard := NSPasteboard.generalPasteboard;
end;
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 Exit;
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; end;
{------------------------------------------------------------------------------ {------------------------------------------------------------------------------
@ -251,28 +174,23 @@ end;
(you must free it yourself) (you must free it yourself)
Returns: If the function succeeds Returns: If the function succeeds
------------------------------------------------------------------------------} ------------------------------------------------------------------------------}
function TCocoaWidgetSet.ClipboardGetFormats(ClipboardType: TClipboardType; function TCocoaWidgetSet.ClipboardGetFormats(ClipboardType: TClipboardType;
var Count: integer; var List: PClipboardFormat): boolean; var Count: integer; var List: PClipboardFormat): boolean;
var var
ListDataSize, ListCount: Integer; fmt: TDynClipboardFormatArray;
i: Integer;
begin begin
Result := False;
{$IFDEF VerboseClipboard} {$IFDEF VerboseClipboard}
DebugLn('TCocoaWidgetSet.ClipboardGetFormats ClipboardType' + DebugLn('TCocoaWidgetSet.ClipboardGetFormats ClipboardType' +
ClipboardTypeName[ClipboardType]); ClipboardTypeName[ClipboardType]);
{$ENDIF} {$ENDIF}
fmt := nil;
ListCount := Min(ClipboardFormats.Count, Count); Result := fClipboard.GetFormats(ClipboardType, Count, fmt);
ListDataSize := SizeOf(Pointer) * ListCount; if Count > 0 then begin
List := GetMem(ListDataSize); GetMem(List, Count * sizeof(TClipboardFormat));
Move(fmt[0], List^, Count * sizeof(TClipboardFormat));
for i := 0 to ListCount - 1 do end else
begin List := nil;
List[i] := PtrUInt(ClipboardFormats.Items[i]);
end;
Result := True;
end; end;
{------------------------------------------------------------------------------ {------------------------------------------------------------------------------
@ -295,116 +213,12 @@ end;
function TCocoaWidgetSet.ClipboardGetOwnerShip(ClipboardType: TClipboardType; function TCocoaWidgetSet.ClipboardGetOwnerShip(ClipboardType: TClipboardType;
OnRequestProc: TClipboardRequestEvent; FormatCount: integer; OnRequestProc: TClipboardRequestEvent; FormatCount: integer;
Formats: PClipboardFormat): boolean; 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 begin
Result := False;
{$IFDEF VerboseClipboard} {$IFDEF VerboseClipboard}
DebugLn('TCocoaWidgetSet.ClipboardGetOwnerShip ClipboardType=' + DebugLn('TCocoaWidgetSet.ClipboardGetOwnerShip ClipboardType=' +
ClipboardTypeName[ClipboardType] + ' FormatCount: ' + DbgS(FormatCount)); ClipboardTypeName[ClipboardType] + ' FormatCount: ' + DbgS(FormatCount));
{$ENDIF} {$ENDIF}
Result := fClipboard.GetOwnership(ClipboardType, OnRequestProc, FormatCount, Formats);
case ClipboardType of
ctPrimarySelection: pasteboard := PrimarySelection;
ctSecondarySelection: pasteboard := SecondarySelection;
ctClipboard: pasteboard := NSPasteboard.generalPasteboard;
end;
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);
DataStream.Read(lText[1], DataStream.Size);
lNSText := NSStringUtf8(lText);
pasteboard.setString_forType(lNSText, lCurFormat.CocoaFormat);
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; end;
{------------------------------------------------------------------------------ {------------------------------------------------------------------------------
@ -414,64 +228,8 @@ end;
Returns: The registered Format identifier (TClipboardFormat) Returns: The registered Format identifier (TClipboardFormat)
------------------------------------------------------------------------------} ------------------------------------------------------------------------------}
function TCocoaWidgetSet.ClipboardRegisterFormat(const AMimeType: string): TClipboardFormat; function TCocoaWidgetSet.ClipboardRegisterFormat(const AMimeType: string): TClipboardFormat;
var
i: Integer;
lCurData: TCocoaClipboardData;
lNSStr: NSString = nil;
lDataType: TCocoaClipboardDataType;
begin begin
Result := 0; Result := fClipboard.RegisterFormat(AMimeType);
// 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
//hack: the name of constants is a hack
// should be replaced with either weaklinking
// or dynamic loading (dlsym)
lNSStr := NSSTR('public.utf8-plain-text'); // NSPasteboardTypeString; // commented out for OSX < 10.6 see #33672
lDataType := ccdtText;
end;
'image/png':
begin
lNSStr := NSSTR('public.png'); // NSPasteboardTypePNG
lDataType := ccdtCocoaStandard;
end;
'image/bmp':
begin
lNSStr := NSSTR('public.png'); // 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;
{$IFDEF VerboseClipboard} {$IFDEF VerboseClipboard}
DebugLn('TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=' + AMimeType DebugLn('TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=' + AMimeType
+ ' Result='+DbgS(Result)); + ' Result='+DbgS(Result));

View File

@ -0,0 +1,444 @@
unit CocoaWSClipboard;
interface
{$modeswitch objectivec2}
uses
CocoaAll, Classes, SysUtils, contnrs
// fcl-image
,fpreadpng, fpwritepng, fpimage, fpreadbmp, fpwritebmp
,LCLType
,CocoaUtils;
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;
const
// these constants are available starting MacOSX 10.6
// thus for earlier systems must be redeclared
_NSPasteboardTypeString : NSString = nil;
_NSPasteboardTypePNG : NSString = nil;
implementation
procedure InitConst;
begin
_NSPasteboardTypeString := NSSTR('public.utf8-plain-text');
_NSPasteboardTypePNG := NSSTR('public.png');
end;
{ 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 Exit;
Result := lFormat.MimeType;
end;
function TCocoaWSClipboard.GetClipboardDataForFormat(AFormat: TClipboardFormat
): TCocoaClipboardData;
begin
Result := TCocoaClipboardData(AFormat);
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 Exit;
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;
begin
pb := Pasteboards[ClipboardType].pasteboard;
if not Assigned(pb) then begin
Result := false;
Exit;
end;
i := 0;
SetLength(List, pb.types.count);
for tp in pb.types do
begin
List[i]:=RegisterCocoaType(tp);
inc(i);
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);
DataStream.Read(lText[1], DataStream.Size);
lNSText := NSStringUtf8(lText);
pasteboard.setString_forType(lNSText, lCurFormat.CocoaFormat);
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
//hack: the name of constants is a hack
// should be replaced with either weaklinking
// or dynamic loading (dlsym)
lNSStr := NSSTR('public.utf8-plain-text'); // NSPasteboardTypeString; // commented out for OSX < 10.6 see #33672
lDataType := ccdtText;
end;
'image/png':
begin
lNSStr := NSSTR('public.png'); // NSPasteboardTypePNG
lDataType := ccdtCocoaStandard;
end;
'image/bmp':
begin
lNSStr := NSSTR('public.png'); // 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/plan'
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;
initialization
InitConst;
end.

View File

@ -129,7 +129,7 @@ end;"/>
<License Value="modified LGPL-2 <License Value="modified LGPL-2
"/> "/>
<Version Major="1" Minor="9"/> <Version Major="1" Minor="9"/>
<Files Count="483"> <Files Count="484">
<Item1> <Item1>
<Filename Value="carbon/agl.pp"/> <Filename Value="carbon/agl.pp"/>
<AddToUsesPkgSection Value="False"/> <AddToUsesPkgSection Value="False"/>
@ -2391,6 +2391,11 @@ end;"/>
<AddToUsesPkgSection Value="False"/> <AddToUsesPkgSection Value="False"/>
<UnitName Value="CocoaScrollers"/> <UnitName Value="CocoaScrollers"/>
</Item483> </Item483>
<Item484>
<Filename Value="cocoa/cocoawsclipboard.pas"/>
<AddToUsesPkgSection Value="False"/>
<UnitName Value="cocoawsclipboard"/>
</Item484>
</Files> </Files>
<LazDoc Paths="../../docs/xml/lcl"/> <LazDoc Paths="../../docs/xml/lcl"/>
<i18n> <i18n>