unit VirtualTrees.ClipBoard;

// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
//
// Alternatively, you may redistribute this library, use and/or modify it under the terms of the
// GNU Lesser General Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
// You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/.
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
// specific language governing rights and limitations under the License.
//
// The original code is VirtualTrees.pas, released September 30, 2000.
//
// The initial developer of the original code is digital publishing AG (Munich, Germany, www.digitalpublishing.de),
// written by Mike Lischke (public@soft-gems.net, www.soft-gems.net).
//
// Portions created by digital publishing AG are Copyright
// (C) 1999-2001 digital publishing AG. All Rights Reserved.
//----------------------------------------------------------------------------------------------------------------------


interface

{$WARN UNSAFE_TYPE OFF}
{$WARN UNSAFE_CAST OFF}

uses
  Winapi.Windows,
  Winapi.ActiveX,
  System.Classes,
  VirtualTrees;

type
  TClipboardFormatEntry = record
    ID: Word;
    Description: string;
  end;

var
  ClipboardDescriptions: array [1..CF_MAX - 1] of TClipboardFormatEntry = (
    (ID: CF_TEXT; Description: 'Plain text'), // Do not localize
    (ID: CF_BITMAP; Description: 'Windows bitmap'), // Do not localize
    (ID: CF_METAFILEPICT; Description: 'Windows metafile'), // Do not localize
    (ID: CF_SYLK; Description: 'Symbolic link'), // Do not localize
    (ID: CF_DIF; Description: 'Data interchange format'), // Do not localize
    (ID: CF_TIFF; Description: 'Tiff image'), // Do not localize
    (ID: CF_OEMTEXT; Description: 'OEM text'), // Do not localize
    (ID: CF_DIB; Description: 'DIB image'), // Do not localize
    (ID: CF_PALETTE; Description: 'Palette data'), // Do not localize
    (ID: CF_PENDATA; Description: 'Pen data'), // Do not localize
    (ID: CF_RIFF; Description: 'Riff audio data'), // Do not localize
    (ID: CF_WAVE; Description: 'Wav audio data'), // Do not localize
    (ID: CF_UNICODETEXT; Description: 'Unicode text'), // Do not localize
    (ID: CF_ENHMETAFILE; Description: 'Enhanced metafile image'), // Do not localize
    (ID: CF_HDROP; Description: 'File name(s)'), // Do not localize
    (ID: CF_LOCALE; Description: 'Locale descriptor'), // Do not localize
    (ID: CF_DIBV5; Description: 'DIB image V5') // Do not localize
  );


// OLE Clipboard and drag'n drop helper
procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; const List: TStrings); overload;
procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray); overload;
function GetVTClipboardFormatDescription(AFormat: Word): string;
procedure RegisterVTClipboardFormat(AFormat: Word; TreeClass: TVirtualTreeClass; Priority: Cardinal); overload;
function RegisterVTClipboardFormat(const Description: string; TreeClass: TVirtualTreeClass; Priority: Cardinal;
                                   tymed: Integer = TYMED_HGLOBAL; ptd: PDVTargetDevice = nil;
                                   dwAspect: Integer = DVASPECT_CONTENT; lindex: Integer = -1): Word; overload;

//----------------- TClipboardFormats ----------------------------------------------------------------------------------

type
  TClipboardFormatListEntry = class
  public
    Description: string;               // The string used to register the format with Winapi.Windows.
    TreeClass: TVirtualTreeClass;      // The tree class which supports rendering this format.
    Priority: Cardinal;                // Number which determines the order of formats used in IDataObject.
    FormatEtc: TFormatEtc;             // The definition of the format in the IDataObject.
  end;

  TClipboardFormatList = class
  strict private
    class function GetList(): TList; static;
    class property List: TList read GetList;
  protected
   class procedure Sort;
  public
    class procedure Add(const FormatString: string; AClass: TVirtualTreeClass; Priority: Cardinal; AFormatEtc: TFormatEtc);
    class procedure Clear;
    class procedure EnumerateFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray;  const AllowedFormats: TClipboardFormats = nil); overload;
    class procedure EnumerateFormats(TreeClass: TVirtualTreeClass; const Formats: TStrings); overload;
    class function FindFormat(const FormatString: string): TClipboardFormatListEntry; overload;
    class function FindFormat(const FormatString: string; var Fmt: Word): TVirtualTreeClass; overload;
    class function FindFormat(Fmt: Word; var Description: string): TVirtualTreeClass; overload;
  end;


implementation

uses
  System.SysUtils;

var
  _List: TList = nil;  //Note - not using class constructors as they are not supported on C++ Builder. See also issue #

procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; const List: TStrings);

begin
  TClipboardFormatList.EnumerateFormats(TreeClass, List);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray);

begin
  TClipboardFormatList.EnumerateFormats(TreeClass, Formats);
end;

//----------------------------------------------------------------------------------------------------------------------

function GetVTClipboardFormatDescription(AFormat: Word): string;

begin
  if TClipboardFormatList.FindFormat(AFormat, Result) = nil then
    Result := '';
end;

//----------------------------------------------------------------------------------------------------------------------

procedure RegisterVTClipboardFormat(AFormat: Word; TreeClass: TVirtualTreeClass; Priority: Cardinal);

// Registers the given clipboard format for the given TreeClass.

var
  I: Integer;
  Buffer: array[0..2048] of Char;
  FormatEtc: TFormatEtc;

begin

  // Assumes a HGlobal format.
  FormatEtc.cfFormat := AFormat;
  FormatEtc.ptd := nil;
  FormatEtc.dwAspect := DVASPECT_CONTENT;
  FormatEtc.lindex := -1;
  FormatEtc.tymed := TYMED_HGLOBAL;

  // Determine description string of the given format. For predefined formats we need the lookup table because they
  // don't have a description string. For registered formats the description string is the string which was used
  // to register them.
  if AFormat < CF_MAX then
  begin
    for I := 1 to High(ClipboardDescriptions) do
      if ClipboardDescriptions[I].ID = AFormat then
      begin
        TClipboardFormatList.Add(ClipboardDescriptions[I].Description, TreeClass, Priority, FormatEtc);
        Break;
      end;
  end
  else
  begin
    GetClipboardFormatName(AFormat, Buffer, Length(Buffer));
    TClipboardFormatList.Add(Buffer, TreeClass, Priority, FormatEtc);
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function RegisterVTClipboardFormat(const Description: string; TreeClass: TVirtualTreeClass; Priority: Cardinal;
  tymed: Integer = TYMED_HGLOBAL; ptd: PDVTargetDevice = nil; dwAspect: Integer = DVASPECT_CONTENT;
  lindex: Integer = -1): Word;

// Alternative method to register a certain clipboard format for a given tree class. Registration with the
// clipboard is done here too and the assigned ID returned by the function.
// tymed may contain or'ed TYMED constants which allows to register several storage formats for one clipboard format.

var
  FormatEtc: TFormatEtc;

begin
  Result := RegisterClipboardFormat(PChar(Description));
  FormatEtc.cfFormat := Result;
  FormatEtc.ptd := ptd;
  FormatEtc.dwAspect := dwAspect;
  FormatEtc.lindex := lindex;
  FormatEtc.tymed := tymed;
  TClipboardFormatList.Add(Description, TreeClass, Priority, FormatEtc);
end;

//----------------------------------------------------------------------------------------------------------------------

class procedure TClipboardFormatList.Sort;

// Sorts all entry for priority (increasing priority value).

  //--------------- local function --------------------------------------------
  procedure QuickSort(L, R: Integer);

  var
    I, J: Integer;
    P, T: TClipboardFormatListEntry;

  begin
    repeat
      I := L;
      J := R;
      P := _List[(L + R) shr 1];
      repeat
        while TClipboardFormatListEntry(_List[I]).Priority < P.Priority do
          Inc(I);
        while TClipboardFormatListEntry(_List[J]).Priority > P.Priority do
          Dec(J);
        if I <= J then
        begin
          T := List[I];
          _List[I] := _List[J];
          _List[J] := T;
          Inc(I);
          Dec(J);
        end;
      until I > J;
      if L < J then
        QuickSort(L, J);
      L := I;
    until I >= R;
  end;
  //--------------- end local function ----------------------------------------

begin
  if List.Count > 1 then
    QuickSort(0, List.Count - 1);
end;

//----------------------------------------------------------------------------------------------------------------------

class procedure TClipboardFormatList.Add(const FormatString: string; AClass: TVirtualTreeClass; Priority: Cardinal; AFormatEtc: TFormatEtc);

// Adds the given data to the internal list. The priority value is used to sort formats for importance. Larger priority
// values mean less priority.

var
  Entry: TClipboardFormatListEntry;

begin
  Entry := TClipboardFormatListEntry.Create;
  Entry.Description := FormatString;
  Entry.TreeClass := AClass;
  Entry.Priority := Priority;
  Entry.FormatEtc := AFormatEtc;
  List.Add(Entry);

  Sort;
end;

//----------------------------------------------------------------------------------------------------------------------

class procedure TClipboardFormatList.Clear;

var
  I: Integer;

begin
  if Assigned(_List) then begin
    for I := 0 to _List.Count - 1 do
      TClipboardFormatListEntry(List[I]).Free;
    _List.Clear;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

class procedure TClipboardFormatList.EnumerateFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray; const AllowedFormats: TClipboardFormats = nil);

// Returns a list of format records for the given class. If assigned the AllowedFormats is used to limit the
// enumerated formats to those described in the list.

var
  I, Count: Integer;
  Entry: TClipboardFormatListEntry;

begin
  SetLength(Formats, List.Count);
  Count := 0;
  for I := 0 to List.Count - 1 do
  begin
    Entry := List[I];
    // Does the tree class support this clipboard format?
    if TreeClass.InheritsFrom(Entry.TreeClass) then
    begin
      // Is this format allowed to be included?
      if (AllowedFormats = nil) or (AllowedFormats.IndexOf(Entry.Description) > -1) then
      begin
        // The list could change before we use the FormatEtc so it is best not to pass a pointer to the true FormatEtc
        // structure. Instead make a copy and send that.
        Formats[Count] := Entry.FormatEtc;
        Inc(Count);
      end;
    end;
  end;
  SetLength(Formats, Count);
end;

//----------------------------------------------------------------------------------------------------------------------

class procedure TClipboardFormatList.EnumerateFormats(TreeClass: TVirtualTreeClass; const Formats: TStrings);

// Returns a list of format descriptions for the given class.

var
  I: Integer;
  Entry: TClipboardFormatListEntry;

begin
  for I := 0 to List.Count - 1 do
  begin
    Entry := List[I];
    if TreeClass.InheritsFrom(Entry.TreeClass) then
      Formats.Add(Entry.Description);
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

class function TClipboardFormatList.FindFormat(const FormatString: string): TClipboardFormatListEntry;

var
  I: Integer;
  Entry: TClipboardFormatListEntry;

begin
  Result := nil;
  for I := List.Count - 1 downto 0 do
  begin
    Entry := List[I];
    if CompareText(Entry.Description, FormatString) = 0 then
    begin
      Result := Entry;
      Break;
    end;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

class function TClipboardFormatList.FindFormat(const FormatString: string; var Fmt: Word): TVirtualTreeClass;

var
  I: Integer;
  Entry: TClipboardFormatListEntry;

begin
  Result := nil;
  for I := List.Count - 1 downto 0 do
  begin
    Entry := List[I];
    if CompareText(Entry.Description, FormatString) = 0 then
    begin
      Result := Entry.TreeClass;
      Fmt := Entry.FormatEtc.cfFormat;
      Break;
    end;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

class function TClipboardFormatList.FindFormat(Fmt: Word; var Description: string): TVirtualTreeClass;

var
  I: Integer;
  Entry: TClipboardFormatListEntry;

begin
  Result := nil;
  for I := List.Count - 1 downto 0 do
  begin
    Entry := List[I];
    if Entry.FormatEtc.cfFormat = Fmt then
    begin
      Result := Entry.TreeClass;
      Description := Entry.Description;
      Break;
    end;
  end;
end;


class function TClipboardFormatList.GetList: TList;
begin
  if not Assigned(_List) then
    _List :=  TList.Create;
  Exit(_List);
end;

initialization

finalization

  TClipboardFormatList.Clear;
  FreeAndNil(_List);

end.
