You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RnQ/for.RnQ/VTV/Source/VirtualTrees.Export.pas

1125 lines
36 KiB
Plaintext

unit VirtualTrees.Export;
{$WARN UNSAFE_CODE OFF}
{$WARN IMPLICIT_STRING_CAST OFF}
{$WARN IMPLICIT_STRING_CAST_LOSS OFF}
interface
uses Winapi.Windows, System.SysUtils, Vcl.Graphics, System.Classes, Vcl.Forms,
Vcl.Controls, System.StrUtils, System.Generics.Collections,
VirtualTrees, VirtualTrees.Classes;
function ContentToHTML(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType; const Caption: string = ''): String;
function ContentToRTF(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType): RawByteString;
function ContentToUnicodeString(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType; const Separator: string): string;
function ContentToClipboard(Tree: TCustomVirtualStringTree; Format: Word; Source: TVSTTextSourceType): HGLOBAL;
procedure ContentToCustom(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType);
implementation
uses
UITypes;
type
TCustomVirtualStringTreeCracker = class(TCustomVirtualStringTree)
end;
const
WideCR = Char(#13);
WideLF = Char(#10);
function ContentToHTML(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType; const Caption: string): String;
// Renders the current tree content (depending on Source) as HTML text encoded in UTF-8.
// If Caption is not empty then it is used to create and fill the header for the table built here.
// Based on ideas and code from Frank van den Bergh and Andreas H<>rstemeier.
var
Buffer: TBufferedString;
//--------------- local functions -------------------------------------------
procedure WriteColorAsHex(Color: TColor);
var
WinColor: COLORREF;
I: Integer;
Component,
Value: Byte;
begin
Buffer.Add('#');
WinColor := ColorToRGB(Color);
I := 1;
while I <= 6 do
begin
Component := WinColor and $FF;
Value := 48 + (Component shr 4);
if Value > $39 then
Inc(Value, 7);
Buffer.Add(AnsiChar(Value));
Inc(I);
Value := 48 + (Component and $F);
if Value > $39 then
Inc(Value, 7);
Buffer.Add(AnsiChar(Value));
Inc(I);
WinColor := WinColor shr 8;
end;
end;
//---------------------------------------------------------------------------
procedure WriteStyle(const Name: String; Font: TFont);
// Creates a CSS style entry with the given name for the given font.
// If Name is empty then the entry is created as inline style.
begin
if Length(Name) = 0 then
Buffer.Add(' style="{')
else
begin
Buffer.Add('.');
Buffer.Add(Name);
Buffer.Add('{');
end;
Buffer.Add(Format('font-family: ''%s''; ', [Font.Name]));
if Font.Size < 0 then
Buffer.Add(Format('font-size: %dpx; ', [Font.Height]))
else
Buffer.Add(Format('font-size: %dpt; ', [Font.Size]));
Buffer.Add(Format('font-style: %s; ', [IfThen(fsItalic in Font.Style, 'italic', 'normal')]));
Buffer.Add(Format('font-weight: %s; ', [IfThen(fsBold in Font.Style, 'bold', 'normal')]));
Buffer.Add(Format('text-decoration: %s; ', [IfThen(fsUnderline in Font.Style, 'underline', 'none')]));
Buffer.Add('color: ');
WriteColorAsHex(Font.Color);
Buffer.Add(';}');
if Length(Name) = 0 then
Buffer.Add('"');
end;
//--------------- end local functions ---------------------------------------
var
I, J : Integer;
Level, MaxLevel: Cardinal;
AddHeader: String;
Save, Run: PVirtualNode;
GetNextNode: TGetNextNodeProc;
RenderColumns: Boolean;
Columns: TColumnsArray;
ColumnColors: array of String;
Index: Integer;
IndentWidth,
LineStyleText: String;
Alignment: TAlignment;
BidiMode: TBidiMode;
CellPadding: String;
CrackTree: TCustomVirtualStringTreeCracker;
lGetCellTextEventArgs: TVSTGetCellTextEventArgs;
begin
CrackTree := TCustomVirtualStringTreeCracker(Tree);
CrackTree.StartOperation(TVTOperationKind.okExport);
Buffer := TBufferedString.Create;
lGetCellTextEventArgs.ExportType := TVTExportType.etHtml;
try
// For customization by the application or descendants we use again the redirected font change event.
CrackTree.RedirectFontChangeEvent(CrackTree.Canvas);
CellPadding := Format('padding-left: %dpx; padding-right: %0:dpx;', [CrackTree.Margin]);
IndentWidth := IntToStr(CrackTree.Indent);
AddHeader := ' ';
// Add title if adviced so by giving a caption.
if Length(Caption) > 0 then
AddHeader := AddHeader + 'caption="' + Caption + '"';
if CrackTree.Borderstyle <> bsNone then
AddHeader := AddHeader + Format(' border="%d" frame=box', [CrackTree.BorderWidth + 1]);
Buffer.Add('');
// Create HTML table based on the CrackTree structure. To simplify formatting we use styles defined in a small CSS area.
Buffer.Add('');
Buffer.AddNewLine;
// General table properties.
Buffer.Add(''); '); '); '); '); '); '); ');
Buffer.AddNewLine;
Columns := nil;
ColumnColors := nil;
RenderColumns := CrackTree.Header.UseColumns;
if RenderColumns then
begin
Columns := CrackTree.Header.Columns.GetVisibleColumns;
SetLength(ColumnColors, Length(Columns));
end;
CrackTree.GetRenderStartValues(Source, Run, GetNextNode);
Save := Run;
MaxLevel := 0;
// The table consists of visible columns and rows as used in the CrackTree, but the main CrackTree column is splitted
// into several HTML columns to accomodate the indentation.
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
if (CrackTree.CanExportNode(Run)) then
begin
Level := CrackTree.GetNodeLevel(Run);
if Level > MaxLevel then
MaxLevel := Level;
end;
Run := GetNextNode(Run);
end;
if RenderColumns then
begin
if Assigned(CrackTree.OnBeforeHeaderExport) then
CrackTree.OnBeforeHeaderExport(CrackTree, etHTML);
Buffer.Add('
Buffer.Add(CellPadding);
Buffer.Add('">');
Buffer.AddNewLine;
// Make the first row in the HTML table an image of the CrackTree header.
for I := 0 to High(Columns) do
begin
if Assigned(CrackTree.OnBeforeColumnExport) then
CrackTree.OnBeforeColumnExport(CrackTree, etHTML, Columns[I]);
Buffer.Add(' Buffer.Add(IntToStr(CrackTree.Header.Height));
Buffer.Add('px"');
Alignment := Columns[I].CaptionAlignment;
// Consider directionality.
if Columns[I].BiDiMode <> bdLeftToRight then
begin
ChangeBidiModeAlignment(Alignment);
Buffer.Add(' dir="rtl"');
end;
// Consider aligment.
case Alignment of
taRightJustify:
Buffer.Add(' align=right');
taCenter:
Buffer.Add(' align=center');
else
Buffer.Add(' align=left');
end;
Index := Columns[I].Index;
// Merge cells of the header emulation in the main column.
if (MaxLevel > 0) and (Index = CrackTree.Header.MainColumn) then
begin
Buffer.Add(' colspan="');
Buffer.Add(IntToStr(MaxLevel + 1));
Buffer.Add('"');
end;
// The color of the header is usually clBtnFace.
Buffer.Add(' bgcolor=');
WriteColorAsHex(clBtnFace);
// Set column width in pixels.
Buffer.Add(' width="');
Buffer.Add(IntToStr(Columns[I].Width));
Buffer.Add('px">');
if Length(Columns[I].Text) > 0 then
Buffer.Add(Columns[I].Text);
Buffer.Add('');
if Assigned(CrackTree.OnAfterColumnExport) then
CrackTree.OnAfterColumnExport(CrackTree, etHTML, Columns[I]);
end;
Buffer.Add('
Buffer.AddNewLine;
if Assigned(CrackTree.OnAfterHeaderExport) then
CrackTree.OnAfterHeaderExport(CrackTree, etHTML);
end;
// Now go through the CrackTree.
Run := Save;
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
if ((not CrackTree.CanExportNode(Run)) or (Assigned(CrackTree.OnBeforeNodeExport) and (not CrackTree.OnBeforeNodeExport(CrackTree, etHTML, Run)))) then
begin
Run := GetNextNode(Run);
Continue;
end;
Level := CrackTree.GetNodeLevel(Run);
Buffer.Add('
Buffer.AddNewLine;
I := 0;
while (I < Length(Columns)) or not RenderColumns do
begin
if RenderColumns then
Index := Columns[I].Index
else
Index := NoColumn;
if not RenderColumns or (coVisible in Columns[I].Options) then
begin
// Call back the application to know about font customization.
CrackTree.Canvas.Font := CrackTree.Font;
CrackTree.FFontChanged := False;
CrackTree.DoPaintText(Run, CrackTree.Canvas, Index, ttNormal);
if Index = CrackTree.Header.MainColumn then
begin
// Create a cell for each indentation level.
if RenderColumns and not (coParentColor in Columns[I].Options) then
begin
for J := 1 to Level do
begin
Buffer.Add(' Buffer.Add(IndentWidth);
Buffer.Add('" height="');
Buffer.Add(IntToStr(CrackTree.NodeHeight[Run]));
Buffer.Add('px"');
if not (coParentColor in Columns[I].Options) then
begin
Buffer.Add(' bgcolor=');
WriteColorAsHex(Columns[I].Color);
end;
Buffer.Add('> 
end;
end
else
begin
for J := 1 to Level do
if J = 1 then
begin
Buffer.Add(' Buffer.Add(IntToStr(CrackTree.NodeHeight[Run]));
Buffer.Add('px" class="normalborder"> 
end
else
Buffer.Add('  
end;
end;
if CrackTree.FFontChanged then
begin
Buffer.Add('
WriteStyle('', CrackTree.Canvas.Font);
Buffer.Add(' height="');
Buffer.Add(IntToStr(CrackTree.NodeHeight[Run]));
Buffer.Add('px"');
end
else
begin
Buffer.Add(' Buffer.Add(IntToStr(CrackTree.NodeHeight[Run]));
Buffer.Add('px"');
end;
if RenderColumns then
begin
Alignment := Columns[I].Alignment;
BidiMode := Columns[I].BidiMode;
end
else
begin
Alignment := CrackTree.Alignment;
BidiMode := CrackTree.BidiMode;
end;
// Consider directionality.
if BiDiMode <> bdLeftToRight then
begin
ChangeBidiModeAlignment(Alignment);
Buffer.Add(' dir="rtl"');
end;
// Consider aligment.
case Alignment of
taRightJustify:
Buffer.Add(' align=right');
taCenter:
Buffer.Add(' align=center');
else
Buffer.Add(' align=left');
end;
// Merge cells in the main column.
if (MaxLevel > 0) and (Index = CrackTree.Header.MainColumn) and (Level < MaxLevel) then
begin
Buffer.Add(' colspan="');
Buffer.Add(IntToStr(MaxLevel - Level + 1));
Buffer.Add('"');
end;
if RenderColumns and not (coParentColor in Columns[I].Options) then
begin
Buffer.Add(' bgcolor=');
WriteColorAsHex(Columns[I].Color);
end;
Buffer.Add('>');
// Get the text
lGetCellTextEventArgs.Node := Run;
lGetCellTextEventArgs.Column := Index;
CrackTree.DoGetText(lGetCellTextEventArgs);
Buffer.Add(lGetCellTextEventArgs.CellText);
if not lGetCellTextEventArgs.StaticText.IsEmpty and (toShowStaticText in TStringTreeOptions(CrackTree.TreeOptions).StringOptions) then
Buffer.Add(' ' + lGetCellTextEventArgs.StaticText);
Buffer.Add('
end;
if not RenderColumns then
Break;
Inc(I);
end;
if Assigned(CrackTree.OnAfterNodeExport) then
CrackTree.OnAfterNodeExport(CrackTree, etHTML, Run);
Run := GetNextNode(Run);
Buffer.Add('
Buffer.AddNewLine;
end;
Buffer.Add('
');
CrackTree.RestoreFontChangeEvent(CrackTree.Canvas);
Result := Buffer.AsString;
finally
CrackTree.EndOperation(TVTOperationKind.okExport);
Buffer.Free;
end;
end;
function ContentToRTF(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType): RawByteString;
// Renders the current tree content (depending on Source) as RTF (rich text).
// Based on ideas and code from Frank van den Bergh and Andreas Hoerstemeier.
var
Fonts: TStringList;
Colors: TList;
CurrentFontIndex,
CurrentFontColor,
CurrentFontSize: Integer;
Buffer: TBufferedRawByteString;
//--------------- local functions -------------------------------------------
procedure SelectFont(const Font: string);
var
I: Integer;
begin
I := Fonts.IndexOf(Font);
if I > -1 then
begin
// Font has already been used
if I <> CurrentFontIndex then
begin
Buffer.Add('\f');
Buffer.Add(IntToStr(I));
CurrentFontIndex := I;
end;
end
else
begin
I := Fonts.Add(Font);
Buffer.Add('\f');
Buffer.Add(IntToStr(I));
CurrentFontIndex := I;
end;
end;
//---------------------------------------------------------------------------
procedure SelectColor(Color: TColor);
var
I: Integer;
begin
I := Colors.IndexOf(Color);
if I > -1 then
begin
// Color has already been used
if I <> CurrentFontColor then
begin
Buffer.Add('\cf');
Buffer.Add(IntToStr(I + 1));
CurrentFontColor := I;
end;
end
else
begin
I := Colors.Add(Color);
Buffer.Add('\cf');
Buffer.Add(IntToStr(I + 1));
CurrentFontColor := I;
end;
end;
//---------------------------------------------------------------------------
procedure TextPlusFont(const Text: string; Font: TFont);
var
UseUnderline,
UseItalic,
UseBold: Boolean;
I: Integer;
begin
if Length(Text) > 0 then
begin
UseUnderline := fsUnderline in Font.Style;
if UseUnderline then
Buffer.Add('\ul');
UseItalic := fsItalic in Font.Style;
if UseItalic then
Buffer.Add('\i');
UseBold := fsBold in Font.Style;
if UseBold then
Buffer.Add('\b');
SelectFont(Font.Name);
SelectColor(Font.Color);
if Font.Size <> CurrentFontSize then
begin
// Font size must be given in half points.
Buffer.Add('\fs');
Buffer.Add(IntToStr(2 * Font.Size));
CurrentFontSize := Font.Size;
end;
// Use escape sequences to note Unicode text.
Buffer.Add(' ');
// Note: Unicode values > 32767 must be expressed as negative numbers. This is implicitly done
// by interpreting the wide chars (word values) as small integers.
for I := 1 to Length(Text) do
begin
if (Text[I] = WideLF) then
Buffer.Add( '{\par}' )
else
if (Text[I] <> WideCR) then
begin
Buffer.Add(Format('\u%d\''3f', [SmallInt(Text[I])]));
Continue;
end;
end;
if UseUnderline then
Buffer.Add('\ul0');
if UseItalic then
Buffer.Add('\i0');
if UseBold then
Buffer.Add('\b0');
end;
end;
//--------------- end local functions ---------------------------------------
var
Level, LastLevel: Integer;
I, J: Integer;
Save, Run: PVirtualNode;
GetNextNode: TGetNextNodeProc;
S, Tabs : RawByteString;
Twips: Integer;
RenderColumns: Boolean;
Columns: TColumnsArray;
Index: Integer;
Alignment: TAlignment;
BidiMode: TBidiMode;
LocaleBuffer: array [0..1] of Char;
CrackTree: TCustomVirtualStringTreeCracker;
lGetCellTextEventArgs: TVSTGetCellTextEventArgs;
begin
CrackTree := TCustomVirtualStringTreeCracker(Tree);
Buffer := TBufferedRawByteString.Create;
lGetCellTextEventArgs.ExportType := TVTExportType.etRtf;
CrackTree.StartOperation(TVTOperationKind.okExport);
try
// For customization by the application or descendants we use again the redirected font change event.
CrackTree.RedirectFontChangeEvent(CrackTree.Canvas);
Fonts := TStringList.Create;
Colors := TList.Create;
CurrentFontIndex := -1;
CurrentFontColor := -1;
CurrentFontSize := -1;
Columns := nil;
Tabs := '';
LastLevel := 0;
RenderColumns := CrackTree.Header.UseColumns;
if RenderColumns then
Columns := CrackTree.Header.Columns.GetVisibleColumns;
CrackTree.GetRenderStartValues(Source, Run, GetNextNode);
Save := Run;
// First make a table structure. The \rtf and other header stuff is included
// when the font and color tables are created.
Buffer.Add('\uc1\trowd\trgaph70');
J := 0;
if RenderColumns then
begin
for I := 0 to High(Columns) do
begin
Inc(J, Columns[I].Width);
// This value must be expressed in twips (1 inch = 1440 twips).
Twips := Round(1440 * J / Screen.PixelsPerInch);
Buffer.Add('\cellx');
Buffer.Add(IntToStr(Twips));
end;
end
else
begin
Twips := Round(1440 * CrackTree.ClientWidth / Screen.PixelsPerInch);
Buffer.Add('\cellx');
Buffer.Add(IntToStr(Twips));
end;
// Fill table header.
if RenderColumns then
begin
if Assigned(CrackTree.OnBeforeHeaderExport) then
CrackTree.OnBeforeHeaderExport(CrackTree, etRTF);
Buffer.Add('\pard\intbl');
for I := 0 to High(Columns) do
begin
if Assigned(CrackTree.OnBeforeColumnExport) then
CrackTree.OnBeforeColumnExport(CrackTree, etRTF, Columns[I]);
Alignment := Columns[I].CaptionAlignment;
BidiMode := Columns[I].BidiMode;
// Alignment is not supported with older RTF formats, however it will be ignored.
if BidiMode <> bdLeftToRight then
ChangeBidiModeAlignment(Alignment);
case Alignment of
taLeftJustify:
Buffer.Add('\ql');
taRightJustify:
Buffer.Add('\qr');
taCenter:
Buffer.Add('\qc');
end;
TextPlusFont(Columns[I].Text, CrackTree.Header.Font);
Buffer.Add('\cell');
if Assigned(CrackTree.OnAfterColumnExport) then
CrackTree.OnAfterColumnExport(CrackTree, etRTF, Columns[I]);
end;
Buffer.Add('\row');
if Assigned(CrackTree.OnAfterHeaderExport) then
CrackTree.OnAfterHeaderExport(CrackTree, etRTF);
end;
// Now write the contents.
Run := Save;
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
if ((not CrackTree.CanExportNode(Run)) or
(Assigned(CrackTree.OnBeforeNodeExport) and (not CrackTree.OnBeforeNodeExport(CrackTree, etRTF, Run)))) then
begin
Run := GetNextNode(Run);
Continue;
end;
I := 0;
while not RenderColumns or (I < Length(Columns)) do
begin
if RenderColumns then
begin
Index := Columns[I].Index;
Alignment := Columns[I].Alignment;
BidiMode := Columns[I].BidiMode;
end
else
begin
Index := NoColumn;
Alignment := CrackTree.Alignment;
BidiMode := CrackTree.BidiMode;
end;
if not RenderColumns or (coVisible in Columns[I].Options) then
begin
// Get the text
lGetCellTextEventArgs.Node := Run;
lGetCellTextEventArgs.Column := Index;
CrackTree.DoGetText(lGetCellTextEventArgs);
Buffer.Add('\pard\intbl');
// Alignment is not supported with older RTF formats, however it will be ignored.
if BidiMode <> bdLeftToRight then
ChangeBidiModeAlignment(Alignment);
case Alignment of
taRightJustify:
Buffer.Add('\qr');
taCenter:
Buffer.Add('\qc');
end;
// Call back the application to know about font customization.
CrackTree.Canvas.Font := CrackTree.Font;
CrackTree.FFontChanged := False;
CrackTree.DoPaintText(Run, CrackTree.Canvas, Index, ttNormal);
if Index = CrackTree.Header.MainColumn then
begin
Level := CrackTree.GetNodeLevel(Run);
if Level <> LastLevel then
begin
LastLevel := Level;
Tabs := '';
for J := 0 to Level - 1 do
Tabs := Tabs + '\tab';
end;
if Level > 0 then
begin
Buffer.Add(Tabs);
Buffer.Add(' ');
TextPlusFont(lGetCellTextEventArgs.CellText, CrackTree.Canvas.Font);
end
else
begin
TextPlusFont(lGetCellTextEventArgs.CellText, CrackTree.Canvas.Font);
end;
end
else
begin
TextPlusFont(lGetCellTextEventArgs.CellText, CrackTree.Canvas.Font);
end;
if not lGetCellTextEventArgs.StaticText.IsEmpty and (toShowStaticText in TStringTreeOptions(CrackTree.TreeOptions).StringOptions) then
begin
CrackTree.DoPaintText(Run, CrackTree.Canvas, Index, ttStatic);
TextPlusFont(' ' + lGetCellTextEventArgs.StaticText, CrackTree.Canvas.Font);
end;//if static text
Buffer.Add('\cell');
end;
if not RenderColumns then
Break;
Inc(I);
end;
Buffer.Add('\row');
Buffer.AddNewLine;
if (Assigned(CrackTree.OnAfterNodeExport)) then
CrackTree.OnAfterNodeExport(CrackTree, etRTF, Run);
Run := GetNextNode(Run);
end;
Buffer.Add('\pard\par');
// Build lists with fonts and colors. They have to be at the start of the document.
S := '{\rtf1\ansi\ansicpg1252\deff0\deflang1043{\fonttbl';
for I := 0 to Fonts.Count - 1 do
S := S + Format('{\f%d %s;}', [I, Fonts[I]]);
S := S + '}';
S := S + '{\colortbl;';
for I := 0 to Colors.Count - 1 do
begin
J := ColorToRGB(TColor(Colors[I]));
S := S + Format('\red%d\green%d\blue%d;', [J and $FF, (J shr 8) and $FF, (J shr 16) and $FF]);
end;
S := S + '}';
if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, @LocaleBuffer[0], Length(LocaleBuffer)) <> 0) and (LocaleBuffer[0] = '0'{metric}) then
S := S + '\paperw16840\paperh11907'// This sets A4 landscape format
else
S := S + '\paperw15840\paperh12240';//[JAM:marder] This sets US Letter landscape format
// Make sure a small margin is used so that a lot of the table fits on a paper. This defines a margin of 0.5"
S := S + '\margl720\margr720\margt720\margb720';
Result := S + Buffer.AsString + '}';
Fonts.Free;
Colors.Free;
CrackTree.RestoreFontChangeEvent(CrackTree.Canvas);
finally
CrackTree.EndOperation(TVTOperationKind.okExport);
Buffer.Free;
end;
end;
function ContentToUnicodeString(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType; const Separator: string): string;
// Renders the current tree content (depending on Source) as Unicode text.
// If an entry contains the separator char then it is wrapped with double quotation marks.
var
Buffer: TBufferedString;
procedure CheckQuotingAndAppend(const pText: string);
begin
// Wrap the text with quotation marks if it contains the separator character.
if Pos(Separator, pText) > 0 then
Buffer.Add(AnsiQuotedStr(pText, '"'))
else
Buffer.Add(pText);
end;
var
RenderColumns: Boolean;
Tabs: string;
GetNextNode: TGetNextNodeProc;
Run, Save: PVirtualNode;
Columns: TColumnsArray;
LastColumn: TVirtualTreeColumn;
Level, MaxLevel: Cardinal;
Index,
I: Integer;
CrackTree: TCustomVirtualStringTreeCracker;
lGetCellTextEventArgs: TVSTGetCellTextEventArgs;
begin
CrackTree := TCustomVirtualStringTreeCracker(Tree);
Buffer := TBufferedString.Create;
lGetCellTextEventArgs.ExportType := TVTExportType.etText;
CrackTree.StartOperation(TVTOperationKind.okExport);
try
Columns := nil;
RenderColumns := CrackTree.Header.UseColumns;
if RenderColumns then
Columns := CrackTree.Header.Columns.GetVisibleColumns;
CrackTree.GetRenderStartValues(Source, Run, GetNextNode);
Save := Run;
// The text consists of visible groups representing the columns, which are separated by one or more separator
// characters. There are always MaxLevel separator chars in a line (main column only). Either before the caption
// to ident it or after the caption to make the following column aligned.
MaxLevel := 0;
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
Level := CrackTree.GetNodeLevel(Run);
if Level > MaxLevel then
MaxLevel := Level;
Run := GetNextNode(Run);
end;
Tabs := DupeString(Separator, MaxLevel);
// First line is always the header if used.
if RenderColumns then
begin
LastColumn := Columns[High(Columns)];
for I := 0 to High(Columns) do
begin
Buffer.Add(Columns[I].Text);
if Columns[I] <> LastColumn then
begin
if Columns[I].Index = CrackTree.Header.MainColumn then
begin
Buffer.Add(Tabs);
Buffer.Add(Separator);
end
else
Buffer.Add(Separator);
end;
end;
Buffer.AddNewLine;
end
else
LastColumn := nil;
Run := Save;
if RenderColumns then
begin
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
for I := 0 to High(Columns) do
begin
if coVisible in Columns[I].Options then
begin
Index := Columns[I].Index;
lGetCellTextEventArgs.Node := Run;
lGetCellTextEventArgs.Column := Index;
CrackTree.DoGetText(lGetCellTextEventArgs);
if Index = CrackTree.Header.MainColumn then
Buffer.Add(Copy(Tabs, 1, Integer(CrackTree.GetNodeLevel(Run)) * Length(Separator)));
if not lGetCellTextEventArgs.StaticText.IsEmpty and (toShowStaticText in TStringTreeOptions(CrackTree.TreeOptions).StringOptions) then
CheckQuotingAndAppend(lGetCellTextEventArgs.CellText + ' ' + lGetCellTextEventArgs.StaticText)
else
CheckQuotingAndAppend(lGetCellTextEventArgs.CellText);
if Index = CrackTree.Header.MainColumn then
Buffer.Add(Copy(Tabs, 1, Integer(MaxLevel - CrackTree.GetNodeLevel(Run)) * Length(Separator)));
if Columns[I] <> LastColumn then
Buffer.Add(Separator);
end;
end;
Run := GetNextNode(Run);
Buffer.AddNewLine;
end;
end
else
begin
lGetCellTextEventArgs.Column := NoColumn;
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
lGetCellTextEventArgs.Node := Run;
CrackTree.DoGetText(lGetCellTextEventArgs);
Level := CrackTree.GetNodeLevel(Run);
Buffer.Add(Copy(Tabs, 1, Integer(Level) * Length(Separator)));
Buffer.Add(lGetCellTextEventArgs.CellText);
Buffer.AddNewLine;
Run := GetNextNode(Run);
end;
end;
Result := Buffer.AsString;
finally
CrackTree.EndOperation(TVTOperationKind.okExport);
Buffer.Free;
end;
end;
function ContentToClipboard(Tree: TCustomVirtualStringTree; Format: Word; Source: TVSTTextSourceType): HGLOBAL;
// This method constructs a shareable memory object filled with string data in the required format. Supported are:
// CF_TEXT - plain ANSI text (Unicode text is converted using the user's current locale)
// CF_UNICODETEXT - plain Unicode text
// CF_CSV - comma separated plain ANSI text
// CF_VRTF + CF_RTFNOOBS - rich text (plain ANSI)
// CF_HTML - HTML text encoded using UTF-8
//
// Result is the handle to a globally allocated memory block which can directly be used for clipboard and drag'n drop
// transfers. The caller is responsible for freeing the memory. If for some reason the content could not be rendered
// the Result is 0.
//--------------- local function --------------------------------------------
procedure MakeFragment(var HTML: String);
// Helper routine to build a properly-formatted HTML fragment.
const
Version = 'Version:1.0'#13#10;
StartHTML = 'StartHTML:';
EndHTML = 'EndHTML:';
StartFragment = 'StartFragment:';
EndFragment = 'EndFragment:';
DocType = '';
HTMLIntro = '' +
'';
HTMLExtro = '';
NumberLengthAndCR = 10;
// Let the compiler determine the description length.
DescriptionLength = Length(Version) + Length(StartHTML) + Length(EndHTML) + Length(StartFragment) +
Length(EndFragment) + 4 * NumberLengthAndCR;
var
Description: String;
StartHTMLIndex,
EndHTMLIndex,
StartFragmentIndex,
EndFragmentIndex: Integer;
begin
// The HTML clipboard format is defined by using byte positions in the entire block where HTML text and
// fragments start and end. These positions are written in a description. Unfortunately the positions depend on the
// length of the description but the description may change with varying positions.
// To solve this dilemma the offsets are converted into fixed length strings which makes it possible to know
// the description length in advance.
StartHTMLIndex := DescriptionLength; // position 0 after the description
StartFragmentIndex := StartHTMLIndex + Length(DocType) + Length(HTMLIntro);
EndFragmentIndex := StartFragmentIndex + Length(HTML);
EndHTMLIndex := EndFragmentIndex + Length(HTMLExtro);
Description := Version +
System.SysUtils.Format('%s%.8d', [StartHTML, StartHTMLIndex]) + #13#10 +
System.SysUtils.Format('%s%.8d', [EndHTML, EndHTMLIndex]) + #13#10 +
System.SysUtils.Format('%s%.8d', [StartFragment, StartFragmentIndex]) + #13#10 +
System.SysUtils.Format('%s%.8d', [EndFragment, EndFragmentIndex]) + #13#10;
HTML := Description + DocType + HTMLIntro + HTML + HTMLExtro;
end;
//--------------- end local function ----------------------------------------
var
Data: Pointer;
DataSize: Cardinal;
S: AnsiString;
WS: string;
P: Pointer;
CrackTree: TCustomVirtualStringTreeCracker;
begin
CrackTree := TCustomVirtualStringTreeCracker(Tree);
Result := 0;
DataSize := 0;
Data := nil;
case Format of
CF_TEXT:
begin
S := AnsiString(ContentToUnicodeString(CrackTree, Source, #9) + #0);
Data := PAnsiChar(S);
DataSize := Length(S);
end;
CF_UNICODETEXT:
begin
WS := ContentToUnicodeString(CrackTree, Source, #9) + #0;
Data := PWideChar(WS);
DataSize := 2 * Length(WS);
end;
else
if Format = CF_CSV then
begin
S := AnsiString(ContentToUnicodeString(CrackTree, Source, FormatSettings.ListSeparator) + #0);
Data := PAnsiChar(S);
DataSize := Length(S);
end// CF_CSV
else if (Format = CF_VRTF) or (Format = CF_VRTFNOOBJS) then
begin
S := ContentToRTF(CrackTree, Source) + #0;
Data := PAnsiChar(S);
DataSize := Length(S);
end
else if Format = CF_HTML then
begin
WS := ContentToHTML(CrackTree, Source);
// Build a valid HTML clipboard fragment.
MakeFragment(WS);
S := S + #0;
Data := PChar(WS);
DataSize := Length(WS) * Sizeof(char);
end;
end;
if DataSize > 0 then
begin
Result := GlobalAlloc(GHND or GMEM_SHARE, DataSize);
P := GlobalLock(Result);
Move(Data^, P^, DataSize);
GlobalUnlock(Result);
end;
end;
procedure ContentToCustom(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType);
// Generic export procedure which polls the application at every stage of the export.
var
I: Integer;
Save, Run: PVirtualNode;
GetNextNode: TGetNextNodeProc;
RenderColumns: Boolean;
Columns: TColumnsArray;
CrackTree: TCustomVirtualStringTreeCracker;
begin
CrackTree := TCustomVirtualStringTreeCracker(Tree);
CrackTree.StartOperation(TVTOperationKind.okExport);
try
Columns := nil;
CrackTree.GetRenderStartValues(Source, Run, GetNextNode);
Save := Run;
RenderColumns := CrackTree.Header.UseColumns and ( hoVisible in CrackTree.Header.Options );
if Assigned(CrackTree.OnBeforeTreeExport) then
CrackTree.OnBeforeTreeExport(CrackTree, etCustom);
// Fill table header.
if RenderColumns then
begin
if Assigned(CrackTree.OnBeforeHeaderExport) then
CrackTree.OnBeforeHeaderExport(CrackTree, etCustom);
Columns := CrackTree.Header.Columns.GetVisibleColumns;
for I := 0 to High(Columns) do
begin
if Assigned(CrackTree.OnBeforeColumnExport) then
CrackTree.OnBeforeColumnExport(CrackTree, etCustom, Columns[I]);
if Assigned(CrackTree.OnColumnExport) then
CrackTree.OnColumnExport(CrackTree, etCustom, Columns[I]);
if Assigned(CrackTree.OnAfterColumnExport) then
CrackTree.OnAfterColumnExport(CrackTree, etCustom, Columns[I]);
end;
if Assigned(CrackTree.OnAfterHeaderExport) then
CrackTree.OnAfterHeaderExport(CrackTree, etCustom);
end;
// Now write the content.
Run := Save;
while Assigned(Run) and not CrackTree.OperationCanceled do
begin
if CrackTree.CanExportNode(Run) then
begin
if Assigned(CrackTree.OnBeforeNodeExport) then
CrackTree.OnBeforeNodeExport(CrackTree, etCustom, Run);
if Assigned(CrackTree.OnNodeExport) then
CrackTree.OnNodeExport(CrackTree, etCustom, Run);
if Assigned(CrackTree.OnAfterNodeExport) then
CrackTree.OnAfterNodeExport(CrackTree, etCustom, Run);
end;
Run := GetNextNode(Run);
end;
if Assigned(CrackTree.OnAfterTreeExport) then
CrackTree.OnAfterTreeExport(CrackTree, etCustom);
finally
CrackTree.EndOperation(TVTOperationKind.okExport);
end;
end;
end.