1{%MainUnit ../clipbrd.pp}
2
3{******************************************************************************
4                                  TClipBoard
5 ******************************************************************************
6
7 *****************************************************************************
8  This file is part of the Lazarus Component Library (LCL)
9
10  See the file COPYING.modifiedLGPL.txt, included in this distribution,
11  for details about the license.
12 *****************************************************************************
13
14  The clipboard is able to work with the windows and gtk behaviour/features.
15}
16
17{$I clipbrd_html.inc}
18
19{ TClipboard }
20
21constructor TClipboard.Create;
22begin
23  // default: create a normal Clipboard
24  Create(ctClipboard);
25end;
26
27constructor TClipboard.Create(AClipboardType: TClipboardType);
28begin
29  //DebugLn('[TClipboard.Create] A ',ClipboardTypeName[AClipboardType],' Self=',DbgS(Self));
30  inherited Create;
31  FClipboardType:=AClipboardType;
32end;
33
34destructor TClipboard.Destroy;
35begin
36  //DebugLn('[TClipboard.Destroy] A ',ClipboardTypeName[ClipboardType],' Self=',DbgS(Self));
37  OnRequest:=nil; // this will notify the owner
38  if FAllocated then begin
39    ClipboardGetOwnership(ClipboardType,nil,0,nil);
40    FAllocated:=false;
41  end;
42  Clear;
43  inherited Destroy;
44  //DebugLn('[TClipboard.Destroy] END ',ClipboardTypeName[ClipboardType]);
45end;
46
47function TClipboard.IndexOfCachedFormatID(FormatID: TClipboardFormat;
48  CreateIfNotExists: boolean): integer;
49var
50  NewSize: integer;
51  FormatAdded: Boolean;
52begin
53  //DebugLn('[TClipboard.IndexOfCachedFormatID] A ',ClipboardTypeName[ClipboardType]
54  //,' Format=',FormatID,' CreateIfNotExists=',CreateIfNotExists);
55  if FormatID=0 then begin
56    Result:=-1;
57    if CreateIfNotExists then
58      raise Exception.Create(
59        'IndexOfCachedFormatID: Internal Error: invalid FormatID 0 for '+
60        ClipboardTypeName[ClipboardType]);
61  end;
62  Result:=FCount-1;
63  while (Result>=0) and (FData[Result].FormatID<>FormatID) do
64    dec(Result);
65  FormatAdded:=false;
66  if (Result<0) and CreateIfNotExists then begin
67    // add new format
68    inc(FCount);
69    NewSize:=SizeOf(TClipboardData)*FCount;
70    ReallocMem(FData,NewSize);
71    Result:=FCount-1;
72    FData[Result].FormatID:=FormatID;
73    FData[Result].Stream:=TMemoryStream.Create;
74    FSupportedFormatsChanged:=true;
75    FormatAdded:=true;
76  end;
77  if not IsUpdating then begin
78    // CreateIfNotExists = true means changing the clipboard
79    // => we need OwnerShip for that
80    if CreateIfNotExists and (not GetOwnerShip) then begin
81      // getting ownership failed
82      if FormatAdded then begin
83        // undo: remove added format
84        // Note: This creates a little overhead in case of an error, but reduces
85        // overhead in case of everything works
86        FData[Result].Stream.Free;
87        NewSize:=SizeOf(TClipboardData)*FCount;
88        ReallocMem(FData,NewSize);
89      end;
90      Result:=-1;
91      raise Exception.Create('Unable to get clipboard ownership for '+
92        ClipboardTypeName[ClipboardType]);
93    end;
94  end;
95  //DebugLn('[TClipboard.IndexOfCachedFormatID] END ',ClipboardTypeName[ClipboardType]
96  //,' Format=',FormatID,' CreateIfNotExists=',CreateIfNotExists,' Result=',Result);
97end;
98
99function TClipboard.AddFormat(FormatID: TClipboardFormat;
100  Stream: TStream): Boolean;
101// copy Stream to a MemoryStream, add it to cache and tell the interface object
102var
103  OldPosition: TStreamSeekType;
104  i: integer;
105begin
106  //DebugLn('[TClipboard.AddFormat - Stream] A ',ClipboardTypeName[ClipboardType],' Format=',FormatID);
107  Result:=false;
108  BeginUpdate;
109  try
110    i:=IndexOfCachedFormatID(FormatID,true);
111    if i<0 then exit;
112    if FData[i].Stream<>Stream then begin
113      if Stream<>nil then begin
114        OldPosition:=Stream.Position;
115        FData[i].Stream.LoadFromStream(Stream);
116        Stream.Position:=OldPosition;
117      end else
118        FData[i].Stream.Clear;
119      FSupportedFormatsChanged:=true;
120    end;
121  finally
122    Result:=EndUpdate;
123  end;
124end;
125
126function TClipboard.AddFormat(FormatID: TClipboardFormat;
127  var Buffer; Size: Integer): Boolean;
128var i: integer;
129begin
130  //DebugLn('[TClipboard.AddFormat - Buffer] A ',ClipboardTypeName[ClipboardType],' Format=',FormatID,' Size=',Size);
131  Result:=false;
132  BeginUpdate;
133  try
134    i:=IndexOfCachedFormatID(FormatID,true);
135    if i<0 then exit;
136    FData[i].Stream.Clear;
137    if Size>0 then
138      FData[i].Stream.Write(Buffer,Size);
139  finally
140    Result:=EndUpdate;
141  end;
142end;
143
144function TClipboard.SetFormat(FormatID: TClipboardFormat;
145  Stream: TStream): Boolean;
146// copy Stream to a MemoryStream, set the cache and tell the interface object
147begin
148  BeginUpdate;
149  try
150    Clear;
151    AddFormat(FormatID,Stream);
152  finally
153    Result:=EndUpdate;
154  end;
155end;
156
157procedure TClipboard.Clear;
158var i: integer;
159begin
160  //DebugLn('[TClipboard.Clear] A ',ClipboardTypeName[ClipboardType]);
161  if FData<>nil then begin
162    for i:=0 to FCount-1 do
163      FData[i].Stream.Free;
164    FreeMem(FData,SizeOf(TClipboardData)*FCount);
165    FData:=nil;
166  end;
167  FCount:=0;
168  //DebugLn('[TClipboard.Clear] END ',ClipboardTypeName[ClipboardType]);
169end;
170
171procedure TClipboard.Open;
172// Open and Closed must be balanced.
173// When the Clipboard is Open, it will not read/write from/to the interface.
174// Instead it will collect all changes until Close is called.
175// It will then try to commit all changes as one block.
176begin
177  BeginUpdate;
178end;
179
180
181procedure TClipboard.Close;
182begin
183  EndUpdate;
184end;
185
186procedure TClipboard.InternalOnRequest(
187  const RequestedFormatID: TClipboardFormat; AStream: TStream);
188begin
189  //DebugLn('[TClipboard.InternalOnRequest] A ',ClipboardTypeName[ClipboardType]
190  //,' RequestedFormatID=',RequestedFormatID,' AStream=',AStream<>nil,' Allocated=',FAllocated);
191  if not FAllocated then exit;
192  if (RequestedFormatID=0) then begin
193    // loosing ownership
194    FAllocated:=false;
195    if Assigned(FOnRequest) then FOnRequest(RequestedFormatID,AStream);
196    FOnRequest:=nil;
197  end else begin
198    GetFormat(RequestedFormatID,AStream);
199  end;
200end;
201
202function TClipboard.GetOwnerShip: boolean;
203var
204  FormatList: PClipboardFormat;
205  i: integer;
206begin
207  if (not FAllocated) or FSupportedFormatsChanged then begin
208    GetMem(FormatList,SizeOf(TClipboardFormat)*FCount);
209    for i:=0 to FCount-1 do
210      FormatList[i]:=FData[i].FormatID;
211    //DebugLn(['[TClipboard.GetOwnerShip] A ',ClipboardTypeName[ClipboardType],' Allocated=',FAllocated]);
212    FAllocated:=true;
213    if not ClipboardGetOwnerShip(ClipboardType,@InternalOnRequest,FCount,
214                                 FormatList)
215    then
216      FAllocated:=false;
217    FreeMem(FormatList);
218    FSupportedFormatsChanged:=false;
219  end;
220  Result:=FAllocated;
221  //DebugLn('[TClipboard.GetOwnerShip] END ',ClipboardTypeName[ClipboardType],' Allocated=',FAllocated);
222end;
223
224procedure TClipboard.SetOnRequest(AnOnRequest: TClipboardRequestEvent);
225begin
226  if Assigned(FOnRequest) then
227    // tell the old owner, that it lost the ownership
228    FOnRequest(0,nil);
229  FOnRequest:=AnOnRequest;
230end;
231
232procedure TClipboard.BeginUpdate;
233begin
234  Inc(FOpenRefCount);
235end;
236
237function TClipboard.EndUpdate: Boolean;
238begin
239  if FOpenRefCount = 0 then
240    RaiseGDBException('TClipboard.EndUpdate');
241  Result:=true;
242  Dec(FOpenRefCount);
243  if FOpenRefCount = 0 then begin
244    if FSupportedFormatsChanged then begin
245      Result:=GetOwnerShip;
246      if not Result then
247        Clear;
248    end;
249  end;
250end;
251
252function TClipboard.IsUpdating: Boolean;
253begin
254  Result:=FOpenRefCount>0;
255end;
256
257function TClipboard.CanReadFromInterface: Boolean;
258begin
259  Result:=FAllocated and (not IsUpdating);
260end;
261
262function TClipboard.CanReadFromCache: Boolean;
263begin
264  Result:=FAllocated or IsUpdating;
265end;
266
267procedure TClipboard.OnDefaultFindClass(Reader: TReader;
268  const AClassName: string; var ComponentClass: TComponentClass);
269var
270  PersistentClass: TPersistentClass;
271begin
272  if Reader=nil then ;
273  PersistentClass:=FindClass(AClassName);
274  if (PersistentClass<>nil) and (PersistentClass.InheritsFrom(TComponent)) then
275    ComponentClass:=TComponentClass(PersistentClass);
276end;
277
278function TClipboard.GetFormat(FormatID: TClipboardFormat;
279  Stream: TStream): Boolean;
280// request data from interface object or copy cached data to Stream
281var i: integer;
282begin
283  //DebugLn('[TClipboard.GetFormat] A ',ClipboardTypeName[ClipboardType],' FormatID=',FormatID,' ',ClipboardFormatToMimeType(FormatID),' Allocated=',fAllocated);
284  Result:=false;
285  if Stream=nil then exit;
286  if FormatID=0 then exit;
287  if CanReadFromCache then begin
288    if Assigned(FOnRequest) then begin
289      FOnRequest(FormatID,Stream);
290      Result:=true;
291    end else begin
292      i:=IndexOfCachedFormatID(FormatID,false);
293      if i<0 then
294        Result:=false
295      else begin
296        FData[i].Stream.Position:=0;
297        if Stream is TMemoryStream then
298          TMemoryStream(Stream).SetSize(Stream.Position+FData[i].Stream.Size);
299        Stream.CopyFrom(FData[i].Stream,FData[i].Stream.Size);
300        Result:=true;
301      end;
302    end;
303  end else begin
304    // not the clipboard owner -> request data
305    Result:=ClipboardGetData(ClipboardType,FormatID,Stream);
306  end;
307  //DebugLn('[TClipboard.GetFormat] END ',ClipboardTypeName[ClipboardType],' FormatID=',FormatID,' Result=',Result);
308end;
309
310function TClipboard.SetComponent(Component: TComponent): Boolean;
311var
312  i: integer;
313  s: TMemoryStream;
314begin
315  BeginUpdate;
316  try
317    i:=IndexOfCachedFormatID(PredefinedClipboardFormat(pcfComponent),true);
318    s:=FData[i].Stream;
319    s.Clear;
320    WriteComponentAsBinaryToStream(s,Component);
321    s.Position:=0;
322    FSupportedFormatsChanged:=true;
323  finally
324    Result:=EndUpdate;
325  end;
326end;
327
328function TClipboard.SetComponentAsText(Component: TComponent): Boolean;
329var
330  MemStream: TMemoryStream;
331  s: string;
332begin
333  BeginUpdate;
334  MemStream:=nil;
335  try
336    MemStream:=TMemoryStream.Create;
337    WriteComponentAsTextToStream(MemStream,Component);
338    SetLength(s,MemStream.Size);
339    MemStream.Position:=0;
340    if s<>'' then
341      MemStream.Read(s[1],length(s));
342    AsText:=s;
343  finally
344    MemStream.Free;
345    Result:=EndUpdate;
346  end;
347end;
348
349function TClipboard.GetComponent(Owner, Parent: TComponent): TComponent;
350begin
351  Result:=nil;
352  GetComponent(Result,@OnDefaultFindClass,Owner,Parent);
353end;
354
355procedure TClipboard.GetComponent(var RootComponent: TComponent;
356  OnFindComponentClass: TFindComponentClassEvent; Owner: TComponent;
357  Parent: TComponent);
358var
359  MemStream: TMemoryStream;
360begin
361  MemStream:=TMemoryStream.Create;
362  try
363    if GetFormat(PredefinedClipboardFormat(pcfComponent),MemStream) then begin
364      MemStream.Position := 0;
365      ReadComponentFromBinaryStream(MemStream,RootComponent,
366                                    OnFindComponentClass,Owner,Parent);
367    end;
368  finally
369    MemStream.Free;
370  end;
371end;
372
373procedure TClipboard.GetComponentAsText(var RootComponent: TComponent;
374  OnFindComponentClass: TFindComponentClassEvent; Owner: TComponent;
375  Parent: TComponent);
376var
377  s: String;
378  MemStream: TMemoryStream;
379begin
380  MemStream:=nil;
381  try
382    MemStream:=TMemoryStream.Create;
383    s:=AsText;
384    if s<>'' then
385      MemStream.Write(s[1],length(s));
386    MemStream.Position:=0;
387    ReadComponentFromTextStream(MemStream,RootComponent,OnFindComponentClass,
388                                Owner,Parent);
389  finally
390    MemStream.Free;
391  end;
392end;
393
394function TClipboard.SetBuffer(FormatID: TClipboardFormat;
395  var Buffer; Size: Integer): Boolean;
396var i: integer;
397begin
398  BeginUpdate;
399  try
400    i:=IndexOfCachedFormatID(FormatID,true);
401    FData[i].Stream.Clear;
402    if Size>0 then begin
403      FData[i].Stream.Write(Buffer,Size);
404      FData[i].Stream.Position:=0;
405    end;
406    FSupportedFormatsChanged:=true;
407  finally
408    Result:=EndUpdate;
409  end;
410end;
411
412procedure TClipboard.SetTextBuf(Buffer: PChar);
413begin
414  if Buffer=nil then Buffer:=#0;
415  SetBuffer(PredefinedClipboardFormat(pcfText),Buffer^,StrLen(Buffer)+1);
416end;
417
418function TClipboard.GetTextBuf(Buffer: PChar; BufSize: Integer): Integer;
419var MemStream: TMemoryStream;
420begin
421  Result:=0;
422  if (Buffer=nil) or (BufSize=0) then exit;
423  MemStream:=TMemoryStream.Create;
424  try
425    if GetFormat(PredefinedClipboardFormat(pcfText),MemStream) then begin
426      MemStream.Position:=0;
427      Result:=BufSize;
428      if Result>MemStream.Size then Result:=integer(MemStream.Size);
429      if Result>0 then
430        MemStream.Read(Buffer^,Result);
431      Buffer[Result]:=#0;
432      Result:=StrLen(Buffer);
433    end;
434  finally
435    MemStream.Free;
436  end;
437end;
438
439procedure TClipboard.SetAsText(const Value: string);
440var s: string;
441  i: Integer;
442begin
443  //DebugLn('[TClipboard.SetAsText] A ',ClipboardTypeName[ClipboardType],' "',Value,'"');
444  if Assigned(FOnRequest) then exit;
445  if Value<>'' then
446    s:=Value
447  else
448    s:=#0;
449  Clear;
450  i := Length(Value);
451  if ClipboardFormatNeedsNullByte(pcfText) then
452    i := i + 1;
453  SetBuffer(PredefinedClipboardFormat(pcfText),s[1],i);
454  //DebugLn('[TClipboard.SetAsText] END ',ClipboardTypeName[ClipboardType],' "',Value,'"');
455end;
456
457function TClipboard.GetAsText: string;
458var
459  MemStream: TMemoryStream;
460  ASize: int64;
461begin
462  //DebugLn('[TClipboard.GetAsText] A ',ClipboardTypeName[ClipboardType]);
463  Result:='';
464  MemStream:=TMemoryStream.Create;
465  try
466    if GetFormat(PredefinedClipboardFormat(pcfText),MemStream) then begin
467      ASize:=MemStream.Size;
468      if (ASize>0) and (pchar(MemStream.Memory)[ASize-1]=#0) then
469        Dec(ASize);
470      MemStream.Position:=0;
471      SetLength(Result,ASize);
472      if ASize>0 then
473        MemStream.Read(Result[1],ASize);
474    end;
475  finally
476    MemStream.Free;
477  end;
478  //DebugLn('[TClipboard.GetAsText] END ',ClipboardTypeName[ClipboardType],' "',dbgstr(Result),'"');
479end;
480
481procedure TClipboard.SupportedFormats(List: TStrings);
482var cnt, i: integer;
483  FormatList: PClipboardFormat;
484begin
485  //DebugLn('[TClipboard.SupportedFormats]');
486  List.Clear;
487  if CanReadFromCache then begin
488    for i:=0 to FCount-1 do
489      List.Add(ClipboardFormatToMimeType(FData[i].FormatID));
490  end else begin
491    FormatList:=nil;
492    if ClipboardGetFormats(ClipboardType,cnt,FormatList) then begin
493      for i:=0 to cnt-1 do
494        List.Add(ClipboardFormatToMimeType(FormatList[i]));
495    end;
496    if FormatList<>nil then FreeMem(FormatList);
497  end;
498end;
499
500procedure TClipboard.SupportedFormats(var AFormatCount: integer;
501  var FormatList: PClipboardFormat);
502var i: integer;
503begin
504  AFormatCount:=0;
505  FormatList:=nil;
506  if CanReadFromCache then begin
507    if (FCount>0) then begin
508      GetMem(FormatList,SizeOf(TClipBoardFormat)*FCount);
509      for i:=0 to FCount-1 do
510        FormatList[i]:=FData[i].FormatID;
511      AFormatCount:=FCount;
512    end;
513  end else begin
514    ClipboardGetFormats(ClipboardType,AFormatCount,FormatList);
515  end;
516end;
517
518function TClipboard.SetSupportedFormats(AFormatCount: integer;
519  FormatList: PClipboardFormat): Boolean;
520var i: integer;
521begin
522  BeginUpdate;
523  try
524    Clear;
525    FCount:=AFormatCount;
526    GetMem(FData,SizeOf(TClipboardData)*FCount);
527    for i:=0 to FCount-1 do begin
528      FData[i].FormatID:=FormatList[i];
529      FData[i].Stream:=TMemoryStream.Create;
530    end;
531    FSupportedFormatsChanged:=true;
532  finally
533    Result:=EndUpdate;
534  end;
535end;
536
537function TClipboard.FindPictureFormatID: TClipboardFormat;
538var
539  List: PClipboardFormat;
540  cnt, i: integer;
541begin
542  //DebugLn('[TClipboard.FindPictureFormatID]');
543  List:=nil;
544  Result:=0;
545  cnt:=0;
546  try
547    if not CanReadFromCache then begin
548      if not ClipboardGetFormats(ClipboardType,cnt,List) then
549        exit;
550      for i:=0 to cnt-1 do begin
551        Result:=List[i];
552        if TPicture.SupportsClipboardFormat(Result) then
553          exit;
554      end;
555    end else begin
556      for i:=FCount-1 downto 0 do begin
557        Result:=FData[i].FormatID;
558        if TPicture.SupportsClipboardFormat(Result) then
559          exit;
560      end;
561    end;
562  finally
563    if List<>nil then FreeMem(List);
564  end;
565  Result:=0;
566end;
567
568function TClipboard.FindFormatID(const FormatName: string): TClipboardFormat;
569var
570  List: PClipboardFormat;
571  cnt, i: integer;
572begin
573  //DebugLn('[TClipboard.FindPictureFormatID]');
574  List:=nil;
575  Result:=0;
576  cnt:=0;
577  try
578    if not CanReadFromCache then begin
579      if not ClipboardGetFormats(ClipboardType,cnt,List) then
580        exit;
581      for i:=0 to cnt-1 do begin
582        Result:=List[i];
583        if CompareText(ClipboardFormatToMimeType(Result),FormatName)=0 then
584          exit;
585      end;
586    end else begin
587      for i:=FCount-1 downto 0 do begin
588        Result:=FData[i].FormatID;
589        if CompareText(ClipboardFormatToMimeType(Result),FormatName)=0 then
590          exit;
591      end;
592    end;
593  finally
594    if List<>nil then FreeMem(List);
595  end;
596  Result:=0;
597end;
598
599function TClipboard.HasPictureFormat: boolean;
600begin
601  Result:=FindPictureFormatID<>0;
602end;
603
604function TClipboard.HasFormat(FormatID: TClipboardFormat): Boolean;
605// ask widgetset
606var List: PClipboardFormat;
607  cnt, i: integer;
608begin
609  //DebugLn('[TClipboard.HasFormat] A ',ClipboardTypeName[ClipboardType],' Allocated=',FAllocated);
610  if FormatID<>0 then begin
611    if CanReadFromCache then
612      Result := (IndexOfCachedFormatID(FormatID,false)>=0)
613    else begin
614      if not ClipboardGetFormats(ClipboardType,cnt,List) then begin
615        Result:=false;
616        exit;
617      end;
618      i:=0;
619      //for i:=0 to cnt-1 do
620      //DebugLn('[TClipboard.HasFormat] ',FormatID,' ',List[i]);
621      while (i<cnt) and (List[i]<>FormatID) do inc(i);
622      Result := i<cnt;
623      if List<>nil then FreeMem(List);
624    end;
625    if not Result then begin
626      Result := (PredefinedClipboardFormat(pcfPicture)=FormatID) and (HasPictureFormat);
627    end;
628  end else
629    Result:=false;
630  //DebugLn('[TClipboard.HasFormat] END ',ClipboardTypeName[ClipboardType],' FormatID=',FormatID,' Result=',Result);
631end;
632
633function TClipboard.HasFormatName(const FormatName: string): Boolean;
634begin
635  Result:=FindFormatID(FormatName)<>0;
636end;
637
638procedure TClipboard.AssignToPicture(Dest: TPicture);
639var
640  FormatID: TClipboardFormat;
641begin
642  FormatID:=FindPictureFormatID;
643  if FormatID=0 then exit;
644  Dest.LoadFromClipboardFormatID(ClipboardType,FormatID);
645end;
646
647procedure TClipboard.AssignPicture(Source: TPicture);
648begin
649  AssignGraphic(Source.Graphic);
650end;
651
652function TClipboard.AssignToGraphic(Dest: TGraphic): boolean;
653var
654  MimeTypes: TStringList;
655  i: Integer;
656  GraphicFormatID: TClipboardFormat;
657begin
658  Result:=false;
659  MimeTypes:=TStringList.Create;
660  try
661    Dest.GetSupportedSourceMimeTypes(MimeTypes);
662    for i:=0 to MimeTypes.Count-1 do begin
663      GraphicFormatID:=FindFormatID(MimeTypes[i]);
664      if GraphicFormatID<>0 then begin
665        AssignToGraphic(Dest,GraphicFormatID);
666        Result:=true;
667        exit;
668      end;
669    end;
670  finally
671    MimeTypes.Free;
672  end;
673end;
674
675function TClipboard.AssignToGraphic(Dest: TGraphic; FormatID: TClipboardFormat
676  ): boolean;
677var
678  MemStream: TMemoryStream;
679begin
680  Result:=false;
681  if FormatID=0 then exit;
682  MemStream:=TMemoryStream.Create;
683  try
684    if not GetFormat(FormatID,MemStream) then exit;
685    MemStream.Position:=0;
686    Dest.LoadFromMimeStream(MemStream,ClipboardFormatToMimeType(FormatID));
687  finally
688    MemStream.Free;
689  end;
690  Result:=true;
691end;
692
693procedure TClipboard.AssignGraphic(Source: TGraphic);
694var
695  MimeType: String;
696  FormatID: TClipboardFormat;
697begin
698  MimeType := Source.MimeType;
699  FormatID:=ClipboardRegisterFormat(MimeType);
700  if FormatID<>0 then
701    AssignGraphic(Source,FormatID);
702end;
703
704procedure TClipboard.AssignGraphic(Source: TGraphic; FormatID: TClipboardFormat);
705var
706  MemStream: TMemoryStream;
707begin
708  MemStream:=TMemoryStream.Create;
709  try
710    Source.SaveToStream(MemStream);
711    MemStream.Position:=0;
712    SetFormat(FormatID,MemStream);
713  finally
714    MemStream.Free;
715  end;
716end;
717
718procedure TClipboard.Assign(Source: TPersistent);
719begin
720  if Source is TPicture then
721    AssignPicture(TPicture(Source))
722  else if Source is TGraphic then
723    AssignGraphic(TGraphic(Source))
724  else
725    inherited Assign(Source);
726end;
727
728procedure TClipboard.AssignTo(Dest: TPersistent);
729begin
730  if Dest is TPicture then
731    AssignToPicture(TPicture(Dest))
732  else if Dest is TGraphic then
733    AssignToGraphic(TGraphic(Dest))
734  else
735    inherited AssignTo(Dest);
736end;
737
738function TClipboard.GetFormatCount: Integer;
739// ask widgetset
740var List: PClipboardFormat;
741begin
742  //DebugLn('[TClipboard.GetFormatCount]');
743  if CanReadFromCache then
744    Result:=FCount
745  else begin
746    Result:=0;
747    if ClipboardGetFormats(ClipboardType,Result,List) then begin
748      if List<>nil then FreeMem(List);
749    end else
750      Result:=0;
751  end;
752end;
753
754function TClipboard.GetFormats(Index: Integer): TClipboardFormat;
755var
756  List: PClipboardFormat;
757  cnt: integer;
758begin
759  //DebugLn('[TClipboard.GetFormats] Index=',Index);
760  if CanReadFromCache then begin
761    if (Index<0) or (Index>=FCount) then
762      raise Exception.Create('TClipboard.GetFormats: Index out of bounds: Index='
763        +IntToStr(Index)+' Count='+IntToStr(FCount));
764    Result:=FData[Index].FormatID;
765  end else begin
766    if ClipboardGetFormats(ClipboardType,cnt,List) then begin
767      if (Index>=0) and (Index<cnt) then
768        Result:=List[Index]
769      else
770        Result:=0;
771      if List<>nil then FreeMem(List);
772    end else
773      Result:=0;
774  end;
775end;
776
777{ Retrieves html formatted text from the clipboard. If ExtractFragmentOnly is
778  true then only the relevant html fragment is returned, the rest of the html
779  string is dropped. The Office applications in Windows and Linux write the
780  full html code which can be retrieved with ExtractFragmentOnly = false.
781  In case of Windows, the MS header is automatically removed.}
782function TClipboard.GetAsHtml(ExtractFragmentOnly: Boolean): String;
783var
784  Stream: TMemoryStream;
785  bom: TBOM;
786  US: UnicodeString;
787begin
788  //debugln(['TClipboard.GetAsHtml: ExtractFragmentOnly = ',ExtractFragmentOnly]);
789  Result := '';
790  if (CF_HTML = 0) or not HasFormat(CF_HTML) then
791  begin
792    //debugln(['TClipboard.GetAsHtml: CF_HTML= ',CF_HTML,' HasFormat(CF_HTML) = ',HasFormat(CF_HTML)]);
793    exit;
794  end;
795
796  Stream := TMemoryStream.Create;
797  try
798    if not GetFormat(CF_HTML, Stream) then
799    begin
800      //debugln(['TClipboard.GetAsHtml: GetFormat(CF_HTML, stream) = False']);
801      exit;
802    end;
803    Stream.Write(#0#0, Length(#0#0));
804
805    bom := GetBomFromStream(Stream);
806    case Bom of
807      bomUtf8:
808        begin
809          Stream.Position := 3;
810          SetLength(Result, Stream.Size - 3);
811          Stream.Read(Result, Stream.Size - 3);
812          //ClipBoard may return a larger Stream than the size of the string
813          //this gets rid of it, since the string will end in a #0 (wide)char
814          Result := PAnsiChar(Result);
815          //debugln(['TClipboard.GetAsHtml: Found bomUtf8']);
816        end;
817      bomUTF16LE:
818        begin
819          Stream.Position := 2;
820          SetLength(US, Stream.Size - 2);
821          Stream.Read(US[1], Stream.Size - 2);
822          //ClipBoard may return a larger Stream than the size of the string
823          //this gets rid of it, since the string will end in a #0 (wide)char
824          US := PWideChar(US);
825          Result := Utf16ToUtf8(US);
826          //debugln(['TClipboard.GetAsHtml: FoundbomUtf16LE']);
827        end;
828      bomUtf16BE:
829        begin
830          //this may need swapping of WideChars????
831          Stream.Position := 2;
832          SetLength(US, Stream.Size - 2);
833          Stream.Read(US[1], Stream.Size - 2);
834          //ClipBoard may return a larger Stream than the size of the string
835          //this gets rid of it, since the string will end in a #0 (wide)char
836          US := PWideChar(US);
837          Result := Utf16ToUtf8(US);
838          //debugln(['TClipboard.GetAsHtml: Found bomUtf16BE']);
839        end;
840      bomUndefined:
841        begin
842          //assume the first byte is part of the string and it is some AnsiString
843          //CF_HTML returns a string encoded as UTF-8 on Windows
844          Result := PAnsiChar(Stream.Memory);
845          //debugln(['TClipboard.GetAsHtml: Found bomUndefined']);
846        end;
847    end;
848
849    if (Result <> '') then begin
850      if ExtractFragmentOnly then
851        Result := ExtractHtmlFragmentFromClipBoardHtml(Result)
852     {$IFDEF WINDOWS}
853      else
854        Result := ExtractHtmlFromClipboardHtml(Result);
855     {$ENDIF}
856    end;
857
858  finally
859    Stream.Free;
860  end;
861end;
862
863{ Adds html-formatted text to the clipboard. The main Office applications in
864  Windows and Linux require a valid and complete html text (i.e. with <html>
865  and <body> tags), therefore we insert them if they are not present.
866  In case of Windows, a specific header will be added,
867  otherwise the format will not be recognized by the clipboard.
868  }
869procedure TClipboard.SetAsHtml(Html: String; const PlainText: String);
870var
871  Stream: TStream;
872  IsValid: Boolean;
873begin
874  if CF_HTML = 0 then
875    exit;
876  //If the HTML does not have correct <html><body> and closing </body></html> insert them
877  MaybeInsertHtmlAndBodyTags(HTML, IsValid);
878  if not IsValid then
879    exit;
880
881  {$IFDEF WINDOWS}
882  Stream := TStringStream.Create(InsertClipHeader(Html));
883  {$ELSE}
884  Stream := TStringStream.Create(Html);
885  {$ENDIF}
886  try
887    Stream.Position := 0;
888    Clipboard.AddFormat(CF_HTML, Stream);
889
890    if (PlainText <> '') then
891    begin
892      Stream.Size := 0;
893      Stream.Position := 0;
894      Stream.WriteBuffer(Pointer(PlainText)^, Length(PlainText)+1); //Also write terminating zero
895      Stream.Position := 0;
896      ClipBoard.AddFormat(CF_TEXT, Stream);
897    end;
898
899  finally
900    Stream.Free;
901  end;
902end;
903
904procedure TClipboard.SetAsHtml(Html: String);
905begin
906  SetAsHtml(Html, '');
907end;
908
909