Browse Source

* Clipboard working on gtk 3

Michaël Van Canneyt 5 months ago
parent
commit
be49941b9f

+ 126 - 0
src/base/fresnel.clipboard.pp

@@ -0,0 +1,126 @@
+unit fresnel.clipboard;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Types, fpImage;
+
+Type
+  EFresnelClipboard = Class(Exception);
+  TFresnelClipBoard = Class;
+  TFresnelClipBoardClass = Class of TFresnelClipBoard;
+
+  { TFresnelClipBoard }
+
+  TFresnelClipBoard = class abstract (TComponent)
+  private
+    class var _Instance : TFresnelClipBoard;
+    class function GetInstance: TFresnelClipBoard; static;
+  private
+    procedure SetAsText(const aValue: String);
+    function GetAsText: String;
+  Protected
+    Function DoHasClipboardType(const aMimeType : String) : boolean; virtual; abstract;
+    Function DoGetContentTypes : TStringDynArray; virtual; abstract;
+    // For text formats, TBytes content is UTF8.
+    Function DoGetClipboardAsType(const aMimeType : String; out aContents : TBytes) : string; virtual; abstract;
+    procedure DoSetClipboardAsType(const aMimeType : String; aContents : TBytes); virtual; abstract;
+  public
+    Class var _ClipboardClass : TFresnelClipBoardClass;
+    class destructor done;
+    Class Property Instance : TFresnelClipBoard Read GetInstance;
+  Public
+    Function GetContentTypes : TStringDynArray;
+    // aMimetype is "text/text" or "text", in which case it is assumed to be the first part.
+    Function HasType(const aMimeType : string) : boolean;
+    // Get clipboard data. aMimetype has the same form as HasType. Returns the exact type.
+    // For example Get
+    function GetAsType(const aMimeType : string; out aContents : TBytes) : string;
+    procedure SetAsType(const aMimeType : string; aContents : TBytes);
+    function HasText(strict : boolean) : Boolean;
+    Property AsText : String Read GetAsText Write SetAsText;
+  end;
+  TFLClipBoard = TFresnelClipBoard;
+
+Function Clipboard : TFresnelClipBoard;
+
+implementation
+
+uses fresnel.strconsts;
+
+Function Clipboard : TFresnelClipBoard;
+
+begin
+  Result:=TFresnelClipBoard.Instance;
+end;
+
+{ TFresnelClipBoard }
+
+function TFresnelClipBoard.GetAsText: String;
+
+var
+  lContent : TBytes;
+
+begin
+  if DoGetClipboardAsType('text',lContent)<>'' then
+    Result:=TEncoding.UTF8.GetAnsiString(lContent)
+  else
+    Result:='';
+end;
+
+class destructor TFresnelClipBoard.done;
+begin
+  _instance.Free;
+end;
+
+class function TFresnelClipBoard.GetInstance: TFresnelClipBoard;
+begin
+  if _Instance=Nil then
+    begin
+    if _ClipboardClass=nil then
+      Raise EFresnelClipboard.Create(rsErrNoClipboardSupport);
+    _instance:=_ClipboardClass.Create(Nil);
+    end;
+  Result:=_instance;
+end;
+
+procedure TFresnelClipBoard.SetAsText(const aValue: String);
+var
+  lContent : TBytes;
+begin
+  lContent:=TEncoding.UTF8.GetAnsiBytes(aValue);
+  DoSetClipboardAsType('text/text',lContent);
+end;
+
+function TFresnelClipBoard.GetContentTypes: TStringDynArray;
+begin
+  DoGetContentTypes;
+end;
+
+function TFresnelClipBoard.HasType(const aMimeType: string): boolean;
+begin
+  Result:=DoHasClipboardType(aMimeType);
+end;
+
+function TFresnelClipBoard.GetAsType(const aMimeType: string; out aContents: TBytes): string;
+begin
+  Result:=DoGetClipboardAsType(aMimeType,aContents);
+end;
+
+procedure TFresnelClipBoard.SetAsType(const aMimeType: string; aContents: TBytes);
+begin
+  DoSetClipboardAsType(aMimeType,aContents);
+end;
+
+function TFresnelClipBoard.HasText(strict: boolean): Boolean;
+begin
+  if Strict then
+    Result:=DoHasClipboardType('text/text')
+  else
+    Result:=DoHasClipboardType('text')
+end;
+
+end.
+

+ 54 - 5
src/base/fresnel.edit.pp

@@ -7,7 +7,7 @@ interface
 uses
 uses
   Classes, fpImage, fpCSSResParser, fpCSSTree, fresnel.CursorTimer,
   Classes, fpImage, fpCSSResParser, fpCSSTree, fresnel.CursorTimer,
   Fresnel.Classes, Fresnel.Dom, Fresnel.Keys, Fresnel.Controls,
   Fresnel.Classes, Fresnel.Dom, Fresnel.Keys, Fresnel.Controls,
-  FCL.events, Fresnel.Events, Utf8Utils;
+  FCL.events, Fresnel.ClipBoard, Fresnel.Events, Utf8Utils;
 
 
 
 
 Type
 Type
@@ -62,6 +62,7 @@ Type
     procedure SetSelectionEnd(const aValue: Integer);
     procedure SetSelectionEnd(const aValue: Integer);
     procedure SetSelectionStart(const aValue: Integer);
     procedure SetSelectionStart(const aValue: Integer);
     Procedure NormalizeSelection;
     Procedure NormalizeSelection;
+    procedure SetSelectionText(const aValue: String);
   protected
   protected
     // char index -> X pos
     // char index -> X pos
     function CalcXOffset(aCharPos: Integer; aUseOffset: Boolean=true; aUseDrawText : Boolean = False): TFresnelLength;
     function CalcXOffset(aCharPos: Integer; aUseOffset: Boolean=true; aUseDrawText : Boolean = False): TFresnelLength;
@@ -117,7 +118,7 @@ Type
     // Max length
     // Max length
     Property MaxLength : Integer Read FMaxLength Write SetMaxLength;
     Property MaxLength : Integer Read FMaxLength Write SetMaxLength;
     // Get selection
     // Get selection
-    Property SelectionText : String Read GetSelectionText;
+    Property SelectionText : String Read GetSelectionText write SetSelectionText;
   end;
   end;
 
 
 
 
@@ -183,7 +184,9 @@ begin
   lStart:=PAnsiChar(FValue);
   lStart:=PAnsiChar(FValue);
   lDeletePos:=UTF8CodepointStart(lStart,Length(FValue),FSelectionStart);
   lDeletePos:=UTF8CodepointStart(lStart,Length(FValue),FSelectionStart);
   lDeleteEndPos:=UTF8CodepointStart(lStart,Length(FValue),FSelectionEnd);
   lDeleteEndPos:=UTF8CodepointStart(lStart,Length(FValue),FSelectionEnd);
-  Delete(FValue,1 + (lDeletePos-lStart),lDeleteEndPos-lDeletePos);
+  UTF8Delete(FValue,1 + (lDeletePos-lStart),lDeleteEndPos-lDeletePos);
+  FSelectionEnd:=FSelectionStart;
+  FCursorPos:=FSelectionStart;
   EditParamsChanged;
   EditParamsChanged;
 end;
 end;
 
 
@@ -231,10 +234,11 @@ procedure TEdit.HandleKeyDown(aEvent: TFresnelKeyEvent);
 var
 var
   lChanged : Boolean;
   lChanged : Boolean;
   lOffset : Integer;
   lOffset : Integer;
+  cb : TShortCutCombo;
 
 
 begin
 begin
   // Printable character has numkey >0
   // Printable character has numkey >0
-  if (aEvent.NumKey>0) and not (aEvent.CtrlKey) then
+  if (aEvent.NumKey>0) and not (aEvent.CtrlKey or aEvent.AltKey) then
     exit;
     exit;
   lChanged:=True;
   lChanged:=True;
   Case aEvent.NumKey of
   Case aEvent.NumKey of
@@ -281,7 +285,39 @@ begin
         FSelectionStart:=FCursorPos;
         FSelectionStart:=FCursorPos;
       end;
       end;
   else
   else
-    lChanged:=False;
+    if (aEvent.Key='c') and (aEvent.ShiftState=[ssCtrl]) then
+      begin
+      Writeln('OK');
+      end;
+
+    case ShortCutRegistry.CheckShortCut(TShortCutCombo.Create(aEvent.NumKey,aEvent.ShiftState)) of
+      scaCopy :
+        Clipboard.AsText:=SelectionText;
+      scaCut :
+        begin
+        Clipboard.AsText:=SelectionText;
+        DoDeleteSelection;
+        end;
+      scaPaste :
+        begin
+        DoDeleteSelection;
+        SelectionText:=Clipboard.AsText;
+        end;
+      scaSelectAll :
+        begin
+        FSelectionStart:=0;
+        FSelectionEnd:=UTF8Length(FValue);
+        lChanged:=true;
+        end;
+      scaUndo :
+        // todo
+        ;
+      scaRedo :
+        // todo
+        ;
+    else
+      lChanged:=False;
+    end;
   end;
   end;
   if lChanged then
   if lChanged then
     EditParamsChanged;
     EditParamsChanged;
@@ -750,6 +786,19 @@ begin
     end;
     end;
 end;
 end;
 
 
+procedure TEdit.SetSelectionText(const aValue: String);
+begin
+  DoDeleteSelection;
+  if aValue='' then
+    exit;
+  If FCursorPos<UTF8Length(FValue) then
+    UTF8Insert(aValue,FValue,FCursorPos+1)
+  else
+    FValue:=FValue+aValue;
+  FCursorPos:=FCursorPos+UTF8Length(aValue);
+  EditParamsChanged;
+end;
+
 procedure TEdit.HandleFocusChange(GotFocus: Boolean);
 procedure TEdit.HandleFocusChange(GotFocus: Boolean);
 
 
 begin
 begin

+ 180 - 2
src/base/fresnel.keys.pas

@@ -5,6 +5,8 @@ unit fresnel.keys;
 
 
 interface
 interface
 
 
+uses Classes;
+
 Type
 Type
   TKeyNames = Record
   TKeyNames = Record
   Const
   Const
@@ -756,11 +758,58 @@ Type
     class function GetArray : TKeyCodeNameArray;
     class function GetArray : TKeyCodeNameArray;
   end;
   end;
 
 
+  { TShortCutCombo }
+
+  TShortCutCombo = record
+    KeyCode : Integer;
+    Shift : TShiftState;
+    class operator = (a,b :TShortCutCombo) : Boolean;
+    constructor create(aKeyCode : Integer; aShift : TShiftState);
+  end;
+
+  TShortCutAction = (scaNone,scaCopy,scaCut,scaPaste,scaSelectAll,scaUndo,scaRedo);
+  TShortCutActions = set of TShortCutAction;
+
+  { TShortcutRegistry }
+
+  TShortcutRegistry = class(TObject)
+  private
+    class var _Instance: TShortcutRegistry;
+    class function GetInstance: TShortcutRegistry; static;
+  protected
+    const
+      DefaultCapacity = ord(high(TShortCutAction))+1;
+    type
+      TShortCutActionReg = record
+        Combo: TShortCutCombo;
+        Action : TShortCutAction;
+      end;
+      TShortCutActionRegArray = Array of TShortCutActionReg;
+  private
+    FList : TShortCutActionRegArray;
+    FCount : Integer;
+  protected
+    function Add(aCombo: TShortCutCombo; aAction: TShortcutAction): Integer;
+    function Delete(aCombo: TShortCutCombo): Boolean;
+    function IndexOf(aCombo: TShortCutCombo): Integer;
+    function Find(aCombo: TShortCutCombo): TShortCutAction;
+    procedure DoRegisterDefaultShortCuts; virtual;
+    procedure DoUnRegisterDefaultShortCuts; virtual;
+  Public
+    constructor Create(aCapacity: integer = DefaultCapacity);
+    procedure RegisterDefaultShortCuts;
+    procedure UnRegisterDefaultShortCuts; virtual;
+    procedure RegisterShortCut(aCombo : TShortCutCombo; aAction : TShortCutAction);
+    procedure UnRegisterShortCut(aCombo : TShortCutCombo);
+    Function CheckShortCut(aCombo : TShortCutCombo) : TShortCutAction;
+    class property Instance : TShortcutRegistry read GetInstance;
+  end;
 
 
 procedure EnumSpecialKeys(aCallback : TEnumKeysCallback);
 procedure EnumSpecialKeys(aCallback : TEnumKeysCallback);
 function GetSpecialKeyCount : Integer;
 function GetSpecialKeyCount : Integer;
 function GetSpecialKeyArray : TKeyCodeNameArray;
 function GetSpecialKeyArray : TKeyCodeNameArray;
 function GetSpecialKeyName(aCode : Integer) : String;
 function GetSpecialKeyName(aCode : Integer) : String;
+function ShortCutRegistry : TShortcutRegistry;
 
 
 implementation
 implementation
 
 
@@ -784,6 +833,11 @@ begin
   Result:=TKeyCodes.Names[aCode];
   Result:=TKeyCodes.Names[aCode];
 end;
 end;
 
 
+function ShortCutRegistry: TShortcutRegistry;
+begin
+  Result:=TShortcutRegistry.Instance;
+end;
+
 procedure EnumSpecialKeys(aCallback : TEnumKeysCallback);
 procedure EnumSpecialKeys(aCallback : TEnumKeysCallback);
 
 
 begin
 begin
@@ -1108,8 +1162,6 @@ end;
 { TKeyCodes }
 { TKeyCodes }
 
 
 // Once we have sortbase, this can disappear.
 // Once we have sortbase, this can disappear.
-type
-  TIntegerArray = array of Integer;
 
 
 procedure Swap(var a, b: TKeyCodeName); inline;
 procedure Swap(var a, b: TKeyCodeName); inline;
 var
 var
@@ -1153,6 +1205,7 @@ begin
   end;
   end;
 end;
 end;
 
 
+
 class function TKeyCodes.indexOf(aKeyCode: Integer): Integer;
 class function TKeyCodes.indexOf(aKeyCode: Integer): Integer;
 
 
 var
 var
@@ -1236,6 +1289,131 @@ begin
   Result:=_keyArray;
   Result:=_keyArray;
 end;
 end;
 
 
+{ TShortCutCombo }
+
+class operator TShortCutCombo.=(a, b: TShortCutCombo): Boolean;
+begin
+  Result:=(a.KeyCode=b.KeyCode) and (a.Shift=b.Shift);
+end;
+
+constructor TShortCutCombo.create(aKeyCode: Integer; aShift: TShiftState);
+begin
+  KeyCode:=aKeyCode;
+  Shift:=aShift;
+end;
+
+{ TShortcutRegistry }
+
+class function TShortcutRegistry.GetInstance: TShortcutRegistry;
+begin
+  if _Instance=nil then
+    _Instance:=TShortcutRegistry.Create;
+  Result:=_Instance;
+end;
+
+procedure TShortcutRegistry.RegisterShortCut(aCombo: TShortCutCombo; aAction: TShortCutAction);
+
+begin
+  Add(aCombo,aAction);
+end;
+
+procedure TShortcutRegistry.UnRegisterShortCut(aCombo: TShortCutCombo);
+begin
+  Delete(aCombo);
+end;
+
+function TShortcutRegistry.CheckShortCut(aCombo: TShortCutCombo): TShortCutAction;
+begin
+  Result:=Find(aCombo);
+end;
+
+function TShortCutRegistry.IndexOf(aCombo: TShortCutCombo): Integer;
+
+begin
+  Result:=FCount-1;
+  While (Result>=0) and (FList[Result].Combo<>aCombo) do
+    Dec(Result);
+end;
+
+function TShortcutRegistry.Find(aCombo: TShortCutCombo): TShortCutAction;
+var
+  Idx : Integer;
+begin
+  Result:=TShortCutAction.scaNone;
+  Idx:=IndexOf(aCombo);
+  if Idx<>-1 then
+    Result:=FList[Idx].Action;
+end;
+
+function TShortCutRegistry.Add(aCombo: TShortCutCombo; aAction: TShortcutAction): Integer;
+begin
+  Result:=IndexOf(aCombo);
+  if Result=-1 then
+    begin
+    if FCount=Length(FList) then
+      SetLength(FList,FCount+10);
+    FList[FCount].Combo:=aCombo;
+    Result:=FCount;
+    Inc(FCount);
+    end;
+  FList[Result].Action:=aAction;
+end;
+
+function TShortCutRegistry.Delete(aCombo: TShortCutCombo): Boolean;
+var
+  Idx: Integer;
+begin
+  Result:=False;
+  Idx:=IndexOf(aCombo);
+  if Idx=-1 then
+    exit;
+  While (Idx<FCount-1) do
+    FList[Idx]:=FList[FCount-1];
+  Dec(FCount);
+  Result:=True;
+end;
+
+constructor TShortCutRegistry.Create(aCapacity: integer);
+begin
+  SetLength(FList,aCapacity);
+  FCount:=0;
+  RegisterDefaultShortCuts;
+end;
+
+procedure TShortcutRegistry.RegisterDefaultShortCuts;
+begin
+  DoRegisterDefaultShortCuts;
+end;
+
+procedure TShortcutRegistry.UnRegisterDefaultShortCuts;
+
+begin
+  DoUnRegisterDefaultShortCuts;
+end;
+
+procedure TShortcutRegistry.DoRegisterDefaultShortCuts;
+
+begin
+  RegisterShortCut(TShortCutCombo.create(Ord('c'),[ssCtrl]),scaCopy);
+  RegisterShortCut(TShortCutCombo.create(Ord('x'),[ssCtrl]),scaCut);
+  RegisterShortCut(TShortCutCombo.create(Ord('v'),[ssCtrl]),scaPaste);
+  RegisterShortCut(TShortCutCombo.create(Ord('z'),[ssCtrl]),scaUndo);
+  RegisterShortCut(TShortCutCombo.create(Ord('z'),[ssCtrl,ssShift]),scaRedo);
+  RegisterShortCut(TShortCutCombo.create(TKeyCodes.Insert,[ssCtrl]),scaCopy);
+  RegisterShortCut(TShortCutCombo.create(TKeyCodes.Insert,[ssShift]),scaPaste);
+end;
+
+procedure TShortcutRegistry.DoUnRegisterDefaultShortCuts;
+
+begin
+  UnRegisterShortCut(TShortCutCombo.create(Ord('c'),[ssCtrl]));
+  UnRegisterShortCut(TShortCutCombo.create(Ord('x'),[ssCtrl]));
+  UnRegisterShortCut(TShortCutCombo.create(Ord('v'),[ssCtrl]));
+  UnRegisterShortCut(TShortCutCombo.create(Ord('z'),[ssCtrl]));
+  UnRegisterShortCut(TShortCutCombo.create(Ord('z'),[ssCtrl,ssShift]));
+  UnRegisterShortCut(TShortCutCombo.create(TKeyCodes.Insert,[ssCtrl]));
+  UnRegisterShortCut(TShortCutCombo.create(TKeyCodes.Insert,[ssShift]));
+end;
 
 
 end.
 end.
 
 

+ 1 - 0
src/base/fresnel.strconsts.pas

@@ -23,6 +23,7 @@ resourcestring
   rsFormResourceSNotFoundForResourcelessFormsCreateNew = 'Form resource %s '
   rsFormResourceSNotFoundForResourcelessFormsCreateNew = 'Form resource %s '
     +'not found. For resourceless forms CreateNew constructor must be used.';
     +'not found. For resourceless forms CreateNew constructor must be used.';
   rsInvalidPropertyValue = 'Invalid property value';
   rsInvalidPropertyValue = 'Invalid property value';
+  rsErrNoClipboardSupport = 'No clipboard support was registered';
 
 
 implementation
 implementation
 
 

+ 5 - 0
src/base/fresnel.widgetset.pas

@@ -134,6 +134,11 @@ begin
   case aKey of
   case aKey of
     TKeyCodes.BackSpace : aType:=fitDeleteContentBackward;
     TKeyCodes.BackSpace : aType:=fitDeleteContentBackward;
     TKeyCodes.Delete : aType:=fitDeleteContentForward;
     TKeyCodes.Delete : aType:=fitDeleteContentForward;
+  else
+    if (aKey<0) or ((aShift+[ssShift])<>[ssShift]) then
+      Result:=False
+    else
+      aType:=fitInsertText
   end;
   end;
 end;
 end;
 
 

+ 4 - 0
src/base/fresnelbase.lpk

@@ -123,6 +123,10 @@
         <Filename Value="fresnel.cursortimer.pp"/>
         <Filename Value="fresnel.cursortimer.pp"/>
         <UnitName Value="fresnel.cursortimer"/>
         <UnitName Value="fresnel.cursortimer"/>
       </Item>
       </Item>
+      <Item>
+        <Filename Value="fresnel.clipboard.pp"/>
+        <UnitName Value="fresnel.clipboard"/>
+      </Item>
     </Files>
     </Files>
     <UsageOptions>
     <UsageOptions>
       <UnitPath Value="$(PkgOutDir)"/>
       <UnitPath Value="$(PkgOutDir)"/>

+ 1 - 1
src/base/fresnelbase.pas

@@ -10,7 +10,7 @@ interface
 uses
 uses
   Fresnel.Controls, Fresnel.DOM, Fresnel.Layouter, Fresnel.Renderer, FCL.Events, Fresnel.Events, Fresnel.Forms, Fresnel.WidgetSet, 
   Fresnel.Controls, Fresnel.DOM, Fresnel.Layouter, Fresnel.Renderer, FCL.Events, Fresnel.Events, Fresnel.Forms, Fresnel.WidgetSet, 
   Fresnel.Resources, Fresnel.StrConsts, Fresnel.Classes, Fresnel.Images, UTF8Utils, Fresnel.AsyncCalls, Fresnel.TextLayouter, 
   Fresnel.Resources, Fresnel.StrConsts, Fresnel.Classes, Fresnel.Images, UTF8Utils, Fresnel.AsyncCalls, Fresnel.TextLayouter, 
-  fresnel.keys, fresnel.edit, fresnel.cursortimer;
+  fresnel.keys, Fresnel.Edit, Fresnel.CursorTimer, fresnel.clipboard;
 
 
 implementation
 implementation
 
 

+ 228 - 2
src/gtk3/fresnel.gtk3.pas

@@ -21,7 +21,7 @@ uses
   {$ENDIF}
   {$ENDIF}
   // fresnel
   // fresnel
   Fresnel.Classes, Fresnel.Forms, Fresnel.WidgetSet, Fresnel.DOM,
   Fresnel.Classes, Fresnel.Forms, Fresnel.WidgetSet, Fresnel.DOM,
-  Fresnel.Events, FCL.Events, Fresnel.Keys;
+  Fresnel.Events, FCL.Events, Fresnel.Keys, Fresnel.ClipBoard;
 
 
 const
 const
   GTK3_LEFT_BUTTON = 1;
   GTK3_LEFT_BUTTON = 1;
@@ -105,6 +105,43 @@ type
     property FontEngineGtk3: TGtk3FontEngine read FFontEngine;
     property FontEngineGtk3: TGtk3FontEngine read FFontEngine;
   end;
   end;
 
 
+  { TGtkClipboard }
+
+  TGtkClipboard = class(TFresnelClipBoard)
+  private
+    Type
+
+      { TFormatEnumerator }
+
+      TFormatEnumerator = class
+        Clipboard : PGtkClipboard;
+        Formats : Array of string; // atom names
+        constructor create (aClipboard : PGtkClipboard);
+      end;
+
+      { TClipboardData }
+
+      TClipboardData = class
+        Clipboard : PGtkClipboard;
+        Data : TBytes;
+        Target : String;
+        constructor create (aClipboard : PGtkClipboard;aData : TBytes; aTargetName : string);
+      end;
+  Private
+    FClipboard : PGtkClipboard;//
+
+  protected
+    function MimeTypeToAtomName(const aMimeType : string) : string;
+    function AtomNameToMimeType(const aAtom : string) : string;
+    function DoGetClipboardAsType(const aMimeType: String; out aContents: TBytes): string; override;
+    function DoGetContentTypes: TStringDynArray; override;
+    function DoHasClipboardType(const aMimeType: String): boolean; override;
+    procedure DoSetClipboardAsType(const aMimeType: String; aContents: TBytes); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+
 var
 var
   Gtk3WidgetSet: TGtk3WidgetSet;
   Gtk3WidgetSet: TGtk3WidgetSet;
 
 
@@ -480,6 +517,7 @@ end;
 
 
 { TGtk3WSForm }
 { TGtk3WSForm }
 
 
+
 procedure TGtk3WSForm.SetFormBounds(const AValue: TFresnelRect);
 procedure TGtk3WSForm.SetFormBounds(const AValue: TFresnelRect);
 var
 var
   aRect: TGdkRectangle;
   aRect: TGdkRectangle;
@@ -923,7 +961,6 @@ var
   AId: String;
   AId: String;
 begin
 begin
   inherited Create(AOwner);
   inherited Create(AOwner);
-
   Gtk3WidgetSet:=Self;
   Gtk3WidgetSet:=Self;
   SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
   SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
   FWSFormClass:=TGtk3WSForm;
   FWSFormClass:=TGtk3WSForm;
@@ -947,6 +984,7 @@ begin
 
 
   FFontEngine:=TGtk3FontEngine.Create(nil);
   FFontEngine:=TGtk3FontEngine.Create(nil);
   TFresnelFontEngine.WSEngine:=FFontEngine;
   TFresnelFontEngine.WSEngine:=FFontEngine;
+  TFresnelClipboard._ClipboardClass:=TGtkClipboard;
 end;
 end;
 
 
 destructor TGtk3WidgetSet.Destroy;
 destructor TGtk3WidgetSet.Destroy;
@@ -1017,8 +1055,196 @@ begin
   aWSForm.CreateGtkWindow;
   aWSForm.CreateGtkWindow;
 end;
 end;
 
 
+{ TGtkClipboard }
+
+function TGtkClipboard.MimeTypeToAtomName(const aMimeType: string): string;
+var
+  lMime : String;
+begin
+  lMime:=lowercase(trim(aMimeType));
+  if pos('/',lMime)=0 then
+    lMime:=lMime+'/';
+  // todo: transform
+  if (pos('text/',lMime)=1) then
+    result:='STRING'
+  else
+    Result:=aMimeType;
+
+end;
+
+function TGtkClipboard.AtomNameToMimeType(const aAtom: string): string;
+begin
+  // todo: transform
+  if aAtom='STRING' then
+    result:='text/text';
+end;
+
+function TGtkClipboard.DoGetClipboardAsType(const aMimeType: String; out aContents: TBytes): string;
+
+var
+  lAtom:PGdkAtom;
+  lName : String;
+  lData : PGtkSelectionData;
+  lDataLen : Integer;
+  lDataName : PAnsiChar;
+
+begin
+  aContents:=[];
+  lName:=MimeTypeToAtomName(aMimeType);
+  lAtom:=TGdkAtom.intern(PAnsiChar(lName), False);
+  lData:=gtk_clipboard_wait_for_contents(fClipboard,lAtom);
+  if assigned(lData) then
+    begin
+    lDataLen:=gtk_selection_data_get_length(lData);
+    lAtom:=gtk_selection_data_get_data_type(lData);
+    lDataName:=gdk_atom_name(lAtom^);
+    if lDataName<>Nil then
+      begin
+      lName:=lDataName;
+      Result:=AtomNameToMimeType(lName);
+      g_free(lDataName);
+      end
+    else
+      Result:=aMimeType;
+    SetLength(aContents,lDataLen);
+    if lDataLen>0 then
+      move(gtk_selection_data_get_data(lData)^,aContents[0],lDataLen);
+    Result:=aMimeType;
+    end;
+end;
+
+procedure targets_received(clipboard : PGtkClipboard; atoms : PGdkAtom; n_atoms : cInt; user_data: gpointer ); cdecl;
+
+var
+  I,Count : integer;
+  lName : PAnsiChar;
+  lPasName : RawByteString;
+  lEnum : TGtkClipBoard.TFormatEnumerator absolute user_data;
+
+begin
+  if lEnum.Clipboard<>clipboard then // Safety
+    exit;
+  SetLength(lEnum.Formats,n_atoms);
+  Count:=0;
+  for i:=0 to n_atoms-1 do
+    begin
+    lName:=gdk_atom_name(atoms[i]);
+    if assigned(lName) then
+      begin
+      lPasName:=lName;
+      lEnum.Formats[Count]:=lPasName;
+      g_free(lName);
+      inc(Count);
+      end;
+    end;
+  SetLength(lEnum.Formats,Count);
+end;
+
+function TGtkClipboard.DoGetContentTypes: TStringDynArray;
+
+var
+  Enum : TFormatEnumerator;
+  i,len : Integer;
+
+begin
+  Result:=[];
+  Enum:=TFormatEnumerator.Create(FClipboard);
+  try
+    gtk_clipboard_request_targets(fclipboard, @targets_received, enum);
+    Len:=Length(enum.Formats);
+    SetLength(Result,Len);
+    For I:=0 to Len-1 do
+      Result[I]:=AtomNameToMimeType(enum.Formats[i]);
+  finally
+    enum.free;
+  end;
+end;
+
+function TGtkClipboard.DoHasClipboardType(const aMimeType: String): boolean;
+var
+  lTypes : TStringDynArray;
+  i : integer;
+begin
+  LTypes:=DoGetContentTypes;
+  Result:=False;
+  I:=Length(lTypes)-1;
+  While (I>=0) and Not Result do
+    begin
+    Result:=SameText(LTypes[i],aMimeType);
+    Dec(I);
+    end;
+end;
+
+procedure GetClipboardData(clipboard: PGtkClipboard; selection_data: PGtkSelectionData; info: guint; user_data_or_owner: gpointer); cdecl;
+var
+  ldata : TGtkClipboard.TClipboardData absolute user_data_or_owner;
+
+begin
+  if lData.Clipboard<>clipboard then
+    exit;
+  gtk_selection_data_set(selection_data,gdk_atom_intern(PAnsiChar(lData.Target),False),8,PByte(lData.Data),Length(lData.Data));
+end;
+
+procedure ClearClipboardData(clipboard: PGtkClipboard; user_data_or_owner: gpointer); cdecl;
+
+var
+  ldata : TGtkClipboard.TClipboardData absolute user_data_or_owner;
+
+begin
+  if lData.Clipboard<>clipboard then
+    exit;
+  lData.Free;
+end;
+
+
+procedure TGtkClipboard.DoSetClipboardAsType(const aMimeType: String; aContents: TBytes);
+
+var
+  lTargets : PGtkTargetEntry;
+  lData : TClipboardData;
+  lAtom : PGdkAtom;
+  lTargetName : string;
+
+begin
+  lTargetName:=MimeTypeToAtomName(aMimeType);
+  lAtom:=gdk_atom_intern(pansichar(lTargetName),false);
+  lTargets:=gtk_target_entry_new(Pansichar(lTargetName),0,0);
+  lData:=TClipboardData.Create(fClipboard,aContents,lTargetName);
+  gtk_clipboard_set_with_data(fclipboard,lTargets,1,@GetClipboardData,@ClearClipboardData,lData);
+end;
+
+constructor TGtkClipboard.Create(AOwner: TComponent);
+
+const
+  sClipboard = 'CLIPBOARD';
+
+var
+  lName:PgdkAtom;
+begin
+  inherited Create(AOwner);
+  lName:=TGdkAtom.intern(sClipboard,false);
+  FClipboard:=gtk_clipboard_get(lName);
+end;
+
+{ TGtkClipboard.TFormatEnumerator }
+
+constructor TGtkClipboard.TFormatEnumerator.create(aClipboard: PGtkClipboard);
+begin
+  Clipboard:=aClipboard;
+end;
+
+{ TGtkClipboard.TClipboardData }
+
+constructor TGtkClipboard.TClipboardData.create(aClipboard: PGtkClipboard; aData: TBytes; aTargetName: string);
+begin
+  Clipboard:=aClipBoard;
+  Data:=aData;
+  Target:=aTargetName;
+end;
+
 initialization
 initialization
   TGtk3WidgetSet.Create(nil);
   TGtk3WidgetSet.Create(nil);
+
 finalization
 finalization
   Gtk3WidgetSet.Free; // it will nil itself
   Gtk3WidgetSet.Free; // it will nil itself