lazarus-ccr/components/virtualtreeview/virtualstringtree.pas
christian_u 8adbc8ab54 Removed Ctl3D
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@751 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2009-03-25 10:24:27 +00:00

2869 lines
89 KiB
ObjectPascal
Raw Blame History

unit VirtualStringTree;
interface
uses
Classes, Types,SysUtils,StdCtrls,LMessages,Forms,LCLType,LCLProc,LCLIntf,
Graphics,virtualtrees,Controls;
type
// Options regarding strings (useful only for the string tree and descentants):
TVTStringOption = (
toSaveCaptions, // If set then the caption is automatically saved with the tree node, regardless of what is
// saved in the user data.
toShowStaticText, // Show static text in a caption which can be differently formatted than the caption
// but cannot be edited.
toAutoAcceptEditChange // Automatically accept changes during edit if the user finishes editing other then
// VK_RETURN or ESC. If not set then changes are cancelled.
);
const
DefaultStringOptions = [toSaveCaptions, toAutoAcceptEditChange];
AlignmentToDrawFlag: array[TAlignment] of Cardinal = (DT_LEFT, DT_RIGHT, DT_CENTER);
CaptionChunk = 3; // used by the string tree to store a node's caption
type
TVTStringOptions = set of TVTStringOption;
TCustomStringTreeOptions = class(TVirtualTreeOptions)
private
FStringOptions: TVTStringOptions;
procedure SetStringOptions(const Value: TVTStringOptions);
protected
property StringOptions: TVTStringOptions read FStringOptions write SetStringOptions default DefaultStringOptions;
property AnimationOptions;
property AutoOptions;
property MiscOptions;
property PaintOptions;
property SelectionOptions;
public
constructor Create(AOwner: TBaseVirtualTree); override;
procedure AssignTo(Dest: TPersistent); override;
end;
TStringTreeOptions = class(TCustomStringTreeOptions)
published
property AnimationOptions;
property AutoOptions;
property MiscOptions;
property PaintOptions;
property SelectionOptions;
property StringOptions;
end;
TCustomVirtualStringTree = class;
// Edit support classes.
TStringEditLink = class;
TVTEdit = class(TCustomEdit)
private
FRefLink: IVTEditLink;
FLink: TStringEditLink;
procedure CMAutoAdjust(var Message: TLMessage); message CM_AUTOADJUST;
procedure CMExit(var Message: TLMessage); message CM_EXIT;
procedure CMRelease(var Message: TLMessage); message CM_RELEASE;
procedure CNCommand(var Message: TLMCommand); message CN_COMMAND;
procedure WMChar(var Message: TLMChar); message LM_CHAR;
procedure WMDestroy(var Message: TLMDestroy); message LM_DESTROY;
procedure WMGetDlgCode(var Message: TLMNoParams {TWMGetDlgCode}); message LM_GETDLGCODE;
procedure WMKeyDown(var Message: TLMKeyDown); message LM_KEYDOWN;
protected
procedure AutoAdjustSize;
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(Link: TStringEditLink); reintroduce;
procedure Release; virtual;
//property AutoSelect; todo test, maybe it will come
property AutoSize;
property BorderStyle;
property CharCase;
//property HideSelection;
property MaxLength;
//property OEMConvert;
property PasswordChar;
end;
TStringEditLink = class(TInterfacedObject, IVTEditLink)
private
FEdit: TVTEdit; // A normal custom edit control.
FTree: TCustomVirtualStringTree; // A back reference to the tree calling.
FNode: PVirtualNode; // The node to be edited.
FColumn: TColumnIndex; // The column of the node.
FAlignment: TAlignment;
FTextBounds: TRect; // Smallest rectangle around the text.
FStopping: Boolean; // Set to True when the edit link requests stopping the edit action.
procedure SetEdit(const Value: TVTEdit);
public
constructor Create;
destructor Destroy; override;
function BeginEdit: Boolean; virtual; stdcall;
function CancelEdit: Boolean; virtual; stdcall;
property Edit: TVTEdit read FEdit write SetEdit;
function EndEdit: Boolean; virtual; stdcall;
function GetBounds: TRect; virtual; stdcall;
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; stdcall;
procedure ProcessMessage(var Message: TLMessage); virtual; stdcall;
procedure SetBounds(R: TRect); virtual; stdcall;
end;
// Describes the type of text to return in the text and draw info retrival events.
TVSTTextType = (
ttNormal, // normal label of the node, this is also the text which can be edited
ttStatic // static (non-editable) text after the normal text
);
// Describes the source to use when converting a string tree into a string for clipboard etc.
TVSTTextSourceType = (
tstAll, // All nodes are rendered. Initialization is done on the fly.
tstInitialized, // Only initialized nodes are rendered.
tstSelected, // Only selected nodes are rendered.
tstCutCopySet, // Only nodes currently marked as being in the cut/copy clipboard set are rendered.
tstVisible // Only visible nodes are rendered.
);
TVTPaintText = procedure(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType) of object;
TVSTGetTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType; var CellText: WideString) of object;
// New text can only be set for variable caption.
TVSTNewTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
NewText: WideString) of object;
TVSTShortenStringEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
Column: TColumnIndex; const S: WideString; TextSpace: Integer; RightToLeft: Boolean; var Result: WideString;
var Done: Boolean) of object;
{ TCustomVirtualStringTree }
TCustomVirtualStringTree = class(TBaseVirtualTree)
private
FDefaultText: WideString; // text to show if there's no OnGetText event handler (e.g. at design time)
FTextHeight: Integer; // true size of the font
FEllipsisWidth: Integer; // width of '...' for the current font
FInternalDataOffset: Cardinal; // offset to the internal data of the string tree
FOnPaintText: TVTPaintText; // triggered before either normal or fixed text is painted to allow
// even finer customization (kind of sub cell painting)
FOnGetText, // used to retrieve the string to be displayed for a specific node
FOnGetHint: TVSTGetTextEvent; // used to retrieve the hint to be displayed for a specific node
FOnNewText: TVSTNewTextEvent; // used to notify the application about an edited node caption
FOnShortenString: TVSTShortenStringEvent; // used to allow the application a customized string shortage
procedure GetRenderStartValues(Source: TVSTTextSourceType; var Node: PVirtualNode;
var NextNodeProc: TGetNextNodeProc);
function GetOptions: TStringTreeOptions;
function GetText(Node: PVirtualNode; Column: TColumnIndex): WideString;
procedure InitializeTextProperties(var PaintInfo: TVTPaintInfo);
procedure PaintNormalText(var PaintInfo: TVTPaintInfo; TextOutFlags: Integer; xText: WideString);
procedure PaintStaticText(const PaintInfo: TVTPaintInfo; TextOutFlags: Integer; const xText: WideString);
procedure ReadText(Reader: TReader);
procedure SetDefaultText(const Value: WideString);
procedure SetOptions(const Value: TStringTreeOptions);
procedure SetText(Node: PVirtualNode; Column: TColumnIndex; const Value: WideString);
procedure WriteText(Writer: TWriter);
protected
procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); override;
function CalculateTextWidth(xCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; xText: WideString): Integer; virtual;
function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
procedure DefineProperties(Filer: TFiler); override;
function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; override;
function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex): WideString; override;
function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex): WideString; override;
function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; xCanvas: TCanvas = nil): Integer; override;
procedure DoGetText(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
var xText: WideString); virtual;
function DoIncrementalSearch(Node: PVirtualNode; const xText: WideString): Integer; override;
procedure DoNewText(Node: PVirtualNode; Column: TColumnIndex; xText: WideString); virtual;
procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override;
procedure DoPaintText(Node: PVirtualNode; const xCanvas: TCanvas; Column: TColumnIndex;
TextType: TVSTTextType); virtual;
function DoShortenString(xCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const S: WideString; xWidth: Integer;
RightToLeft: Boolean; EllipsisWidth: Integer = 0): WideString; virtual;
function GetOptionsClass: TTreeOptionsClass; override;
procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect;
var xText: WideString); override;
function InternalData(Node: PVirtualNode): Pointer;
procedure MainColumnChanged; override;
function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType,
ChunkSize: Integer): Boolean; override;
procedure WriteChunks(Stream: TStream; Node: PVirtualNode); override;
property DefaultText: WideString read FDefaultText write SetDefaultText stored False;
property EllipsisWidth: Integer read FEllipsisWidth;
property TreeOptions: TStringTreeOptions read GetOptions write SetOptions;
property OnGetHint: TVSTGettextEvent read FOnGetHint write FOnGetHint;
property OnGetText: TVSTGetTextEvent read FOnGetText write FOnGetText;
property OnNewText: TVSTNewTextEvent read FOnNewText write FOnNewText;
property OnPaintText: TVTPaintText read FOnPaintText write FOnPaintText;
property OnShortenString: TVSTShortenStringEvent read FOnShortenString write FOnShortenString;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy;override;
function ComputeNodeHeight(xCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex): Integer; virtual;
function ContentToClipboard(Format: Word; Source: TVSTTextSourceType): HGLOBAL;
function ContentToHTML(Source: TVSTTextSourceType; xCaption: WideString = ''): string;
function ContentToRTF(Source: TVSTTextSourceType): string;
function ContentToText(Source: TVSTTextSourceType; Separator: Char): string;
function ContentToUnicode(Source: TVSTTextSourceType; Separator: WideChar): WideString;
function InvalidateNode(Node: PVirtualNode): TRect; override;
function Path(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; Delimiter: WideChar): WideString;
procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean); override;
property Text[Node: PVirtualNode; Column: TColumnIndex]: WideString read GetText write SetText;
end;
TVirtualStringTree = class(TCustomVirtualStringTree)
private
function GetOptions: TStringTreeOptions;
procedure SetOptions(const Value: TStringTreeOptions);
protected
function GetOptionsClass: TTreeOptionsClass; override;
public
property Canvas;
published
property Action;
property Align;
property Alignment;
property Anchors;
property AnimationDuration;
property AutoExpandDelay;
property AutoScrollDelay;
property AutoScrollInterval;
property Background;
property BackgroundOffsetX;
property BackgroundOffsetY;
property BorderStyle;
property ButtonFillMode;
property ButtonStyle;
property BorderWidth;
property ChangeDelay;
property CheckImageKind;
property ClipboardFormats;
property Color;
property Colors;
property Constraints;
property CustomCheckImages;
property DefaultNodeHeight;
property DefaultPasteMode;
property DefaultText;
property DragCursor;
property DragMode;
property DrawSelectionMode;
property EditDelay;
property Enabled;
property Font;
property Header;
property HintAnimation;
property HintMode;
property HotCursor;
property Images;
property IncrementalSearch;
property IncrementalSearchDirection;
property IncrementalSearchStart;
property IncrementalSearchTimeout;
property Indent;
property LineMode;
property LineStyle;
property Margin;
property NodeAlignment;
property NodeDataSize;
{$ifdef COMPILER_7_UP}
property ParentBackground;
{$endif COMPILER_7_UP}
property ParentColor default False;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property RootNodeCount;
property ScrollBarOptions;
property SelectionBlendFactor;
property SelectionCurveRadius;
property ShowHint;
property StateImages;
property TabOrder;
property TabStop default True;
property TextMargin;
property TreeOptions: TStringTreeOptions read GetOptions write SetOptions;
property Visible;
property WantTabs;
property OnAdvancedHeaderDraw;
property OnAfterCellPaint;
property OnAfterItemErase;
property OnAfterItemPaint;
property OnAfterPaint;
property OnBeforeCellPaint;
property OnBeforeItemErase;
property OnBeforeItemPaint;
property OnBeforePaint;
property OnChange;
property OnChecked;
property OnChecking;
property OnClick;
property OnCollapsed;
property OnCollapsing;
property OnColumnClick;
property OnColumnDblClick;
property OnColumnResize;
property OnCompareNodes;
{$ifdef COMPILER_5_UP}
property OnContextPopup;
{$endif COMPILER_5_UP}
// property OnCreateDragManager;
property OnCreateEditor;
property OnDblClick;
// property OnDragAllowed;
property OnDragOver;
property OnDragDrop;
property OnEditCancelled;
property OnEdited;
property OnEditing;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnExpanded;
property OnExpanding;
property OnFocusChanged;
property OnFocusChanging;
property OnFreeNode;
property OnGetCellIsEmpty;
property OnGetCursor;
property OnGetHeaderCursor;
property OnGetText;
property OnPaintText;
property OnGetHelpContext;
property OnGetImageIndex;
property OnGetHint;
property OnGetLineStyle;
property OnGetNodeDataSize;
property OnGetPopupMenu;
// property OnGetUserClipboardFormats;
property OnHeaderClick;
property OnHeaderDblClick;
property OnHeaderDraw;
property OnHeaderDrawQueryElements;
property OnHeaderMouseDown;
property OnHeaderMouseMove;
property OnHeaderMouseUp;
property OnHotChange;
property OnIncrementalSearch;
property OnInitChildren;
property OnInitNode;
property OnKeyAction;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnLoadNode;
property OnMeasureItem;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnNewText;
property OnNodeCopied;
property OnNodeCopying;
property OnNodeMoved;
property OnNodeMoving;
property OnPaintBackground;
property OnResetNode;
property OnResize;
property OnSaveNode;
property OnScroll;
property OnShortenString;
property OnStartDock;
property OnStartDrag;
property OnStateChange;
property OnStructureChange;
property OnUpdating;
end;
implementation
//----------------- TCustomStringTreeOptions ---------------------------------------------------------------------------
constructor TCustomStringTreeOptions.Create(AOwner: TBaseVirtualTree);
begin
inherited;
FStringOptions := DefaultStringOptions;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomStringTreeOptions.SetStringOptions(const Value: TVTStringOptions);
var
ChangedOptions: TVTStringOptions;
begin
if FStringOptions <> Value then
begin
// Exclusive ORing to get all entries wich are in either set but not in both.
ChangedOptions := FStringOptions + Value - (FStringOptions * Value);
FStringOptions := Value;
with Owner do
if (toShowStaticText in ChangedOptions) and not (csLoading in ComponentState) and HandleAllocated then
Invalidate;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomStringTreeOptions.AssignTo(Dest: TPersistent);
begin
if Dest is TCustomStringTreeOptions then
begin
with Dest as TCustomStringTreeOptions do
StringOptions := Self.StringOptions;
end;
// Let ancestors assign their options to the destination class.
inherited;
end;
constructor TVTEdit.Create(Link: TStringEditLink);
begin
inherited Create(nil);
ShowHint := False;
ParentShowHint := False;
// This assignment increases the reference count for the interface.
FRefLink := Link;
// This reference is used to access the link.
FLink := Link;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.CMAutoAdjust(var Message: TLMessage);
begin
AutoAdjustSize;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.CMExit(var Message: TLMessage);
begin
if Assigned(FLink) and not FLink.FStopping then
with FLink, FTree do
begin
if (toAutoAcceptEditChange in TreeOptions.StringOptions) then
DoEndEdit
else
DoCancelEdit;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.CMRelease(var Message: TLMessage);
begin
Free;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.CNCommand(var Message: TLMCommand);
begin
{todo if Assigned(FLink) and Assigned(FLink.FTree) and (Message.NotifyCode = EN_UPDATE) and
not (toGridExtensions in FLink.FTree.FOptions.FMiscOptions) and
not (vsMultiline in FLink.FNode.States) then
// Instead directly calling AutoAdjustSize it is necessary on Win9x/Me to decouple this notification message
// and eventual resizing. Hence we use a message to accomplish that.
if false and IsWinNT then
AutoAdjustSize
else
PostMessage(Handle, CM_AUTOADJUST, 0, 0);}
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.WMChar(var Message: TLMChar);
begin
if not (Message.CharCode in [VK_ESCAPE, VK_TAB]) then
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.WMDestroy(var Message: TLMDestroy);
begin
// If editing stopped by other means than accept or cancel then we have to do default processing for
// pending changes.
if Assigned(FLink) and not FLink.FStopping then
begin
with FLink, FTree do
begin
if (toAutoAcceptEditChange in TreeOptions.StringOptions) and Modified then
Text[FNode, FColumn] := FEdit.Text;
end;
FLink := nil;
FRefLink := nil;
end;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.WMGetDlgCode(var Message: TLMNoParams {TWMGetDlgCode});
begin
inherited;
Message.Result := Message.Result or DLGC_WANTALLKEYS or DLGC_WANTTAB or DLGC_WANTARROWS;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.WMKeyDown(var Message: TLMKeyDown);
// Handles some control keys.
var
Shift: TShiftState;
EndEdit: Boolean;
Tree: TBaseVirtualTree;
begin
case Message.CharCode of
// Pretend these keycodes were send to the tree.
VK_ESCAPE:
begin
Tree := FLink.FTree;
FLink.FTree.DoCancelEdit;
Tree.SetFocus;
end;
VK_RETURN:
begin
EndEdit := not (vsMultiline in FLink.FNode^.States);
if not EndEdit then
begin
// If a multiline node is being edited the finish editing only if Ctrl+Enter was pressed,
// otherwise allow to insert line breaks into the text.
Shift := KeyDataToShiftState(Message.KeyData);
EndEdit := ssCtrl in Shift;
end;
if EndEdit then
begin
Tree := FLink.FTree;
FLink.FTree.InvalidateNode(FLink.FNode);
FLink.FTree.DoEndEdit;
Tree.SetFocus;
end;
end;
VK_UP:
begin
if not (vsMultiline in FLink.FNode^.States) then
Message.CharCode := VK_LEFT;
inherited;
end;
VK_DOWN:
begin
if not (vsMultiline in FLink.FNode^.States) then
Message.CharCode := VK_RIGHT;
inherited;
end;
else
inherited;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.AutoAdjustSize;
// Changes the size of the edit to accomodate as much as possible of its text within its container window.
// NewChar describes the next character which will be added to the edit's text.
var
DC: HDC;
Size: TSize;
LastFont: THandle;
begin
if not (vsMultiline in FLink.FNode^.States) then
begin
// avoid flicker
//todowin SendMessage(Handle, WM_SETREDRAW, 0, 0);
DC := GetDC(Handle);
LastFont := SelectObject(DC, Font.Handle);
try
// Read needed space for the current text.
{$ifdef UNICODE}
GetTextExtentPoint32W(DC, PWideChar(Text), Length(Text), Size);
{$else}
GetTextExtentPoint32(DC, PChar(Text), Length(Text), Size);
{$endif}
Inc(Size.cx, 2 * FLink.FTree.FTextMargin);
// Repaint associated node if the edit becomes smaller.
if Size.cx < Width then
FLink.FTree.InvalidateNode(FLink.FNode);
if FLink.FAlignment = taRightJustify then
FLink.SetBounds(Rect(Left + Width - Size.cx, Top, Left + Width, Top + Height))
else
FLink.SetBounds(Rect(Left, Top, Left + Size.cx, Top + Height));
finally
SelectObject(DC, LastFont);
ReleaseDC(Handle, DC);
//todowin SendMessage(Handle, WM_SETREDRAW, 1, 0);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.CreateParams(var Params: TCreateParams);
begin
inherited;
// Only with multiline style we can use the text formatting rectangle.
// This does not harm formatting as single line control, if we don't use word wrapping.
with Params do
begin
Style := Style or 4 {todoES_MULTILINE};
if vsMultiline in FLink.FNode^.States then
Style := Style and not ({todoES_AUTOHSCROLL or} WS_HSCROLL) or WS_VSCROLL {todoor ES_AUTOVSCROLL};
if tsUseThemes in FLink.FTree.FStates then
begin
Style := Style and not WS_BORDER;
ExStyle := ExStyle or WS_EX_CLIENTEDGE;
end
else
begin
Style := Style or WS_BORDER;
ExStyle := ExStyle and not WS_EX_CLIENTEDGE;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.Release;
begin
if HandleAllocated then
PostMessage(Handle, CM_RELEASE, 0, 0);
end;
//----------------- TStringEditLink ------------------------------------------------------------------------------------
constructor TStringEditLink.Create;
begin
inherited;
FEdit := TVTEdit.Create(Self);
with FEdit do
begin
Visible := False;
BorderStyle := bsSingle;
AutoSize := False;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TStringEditLink.Destroy;
begin
FEdit.Release;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
function TStringEditLink.BeginEdit: Boolean; stdcall;
// Notifies the edit link that editing can start now. Descentants may cancel node edit
// by returning False.
begin
Result := not FStopping;
if Result then
begin
FEdit.Show;
FEdit.SelectAll;
FEdit.SetFocus;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TStringEditLink.SetEdit(const Value: TVTEdit);
begin
if Assigned(FEdit) then
FEdit.Free;
FEdit := Value;
end;
//----------------------------------------------------------------------------------------------------------------------
function TStringEditLink.CancelEdit: Boolean; stdcall;
begin
Result := not FStopping;
if Result then
begin
FStopping := True;
FEdit.Hide;
FTree.CancelEditNode;
FEdit.FLink := nil;
FEdit.FRefLink := nil;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TStringEditLink.EndEdit: Boolean; stdcall;
begin
Result := not FStopping;
if Result then
try
FStopping := True;
if FEdit.Modified then
FTree.Text[FNode, FColumn] := FEdit.Text;
FEdit.Hide;
FEdit.FLink := nil;
FEdit.FRefLink := nil;
except
FStopping := False;
raise;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TStringEditLink.GetBounds: TRect; stdcall;
begin
Result := FEdit.BoundsRect;
end;
//----------------------------------------------------------------------------------------------------------------------
function TStringEditLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
// Retrieves the true text bounds from the owner tree.
var
Text: WideString;
begin
Result := Tree is TCustomVirtualStringTree;
if Result then
begin
FTree := Tree as TCustomVirtualStringTree;
FNode := Node;
FColumn := Column;
// Initial size, font and text of the node.
FTree.GetTextInfo(Node, Column, FEdit.Font, FTextBounds, Text);
FEdit.Font.Color := clBlack;
FEdit.Parent := Tree;
RecreateWnd(FEdit);
FEdit.HandleNeeded;
FEdit.Text := Text;
if Column <= NoColumn then
begin
//b FEdit.BidiMode := FTree.BidiMode;
FAlignment := FTree.Alignment;
end
else
begin
//b FEdit.BidiMode := FTree.Header.Columns[Column].BidiMode;
FAlignment := FTree.Header.Columns[Column].Alignment;
end;
//b if FEdit.BidiMode <> bdLeftToRight then
//b ChangeBidiModeAlignment(FAlignment);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TStringEditLink.ProcessMessage(var Message: TLMessage); stdcall;
begin
FEdit.WindowProc(Message);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TStringEditLink.SetBounds(R: TRect); stdcall;
// Sets the outer bounds of the edit control and the actual edit area in the control.
var
Offset: Integer;
begin
if not FStopping then
begin
with R do
begin
// Set the edit's bounds but make sure there's a minimum width and the right border does not
// extend beyond the parent's left/right border.
if Left < 0 then
Left := 0;
if Right - Left < 30 then
begin
if FAlignment = taRightJustify then
Left := Right - 30
else
Right := Left + 30;
end;
if Right > FTree.ClientWidth then
Right := FTree.ClientWidth;
FEdit.BoundsRect := R;
// The selected text shall exclude the text margins and be centered vertically.
// We have to take out the two pixel border of the edit control as well as a one pixel "edit border" the
// control leaves around the (selected) text.
R := FEdit.ClientRect;
Offset := 2;
if tsUseThemes in FTree.FStates then
Inc(Offset);
InflateRect(R, -FTree.FTextMargin + Offset, Offset);
if not (vsMultiline in FNode^.States) then
OffsetRect(R, 0, FTextBounds.Top - FEdit.Top);
//todowin SendMessage(FEdit.Handle, EM_SETRECTNP, 0, Integer(@R));
end;
end;
end;
//----------------- TCustomVirtualString -------------------------------------------------------------------------------
constructor TCustomVirtualStringTree.Create(AOwner: TComponent);
begin
inherited;
FDefaultText := 'Node';
FInternalDataOffset := AllocateInternalDataArea(SizeOf(Cardinal));
end;
destructor TCustomVirtualStringTree.Destroy;
begin
inherited Destroy;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.GetRenderStartValues(Source: TVSTTextSourceType; var Node: PVirtualNode;
var NextNodeProc: TGetNextNodeProc);
begin
case Source of
tstInitialized:
begin
Node := GetFirstInitialized;
NextNodeProc := @GetNextInitialized;
end;
tstSelected:
begin
Node := GetFirstSelected;
NextNodeProc := @GetNextSelected;
end;
tstCutCopySet:
begin
Node := GetFirstCutCopy;
NextNodeProc := @GetNextCutCopy;
end;
tstVisible:
begin
Node := GetFirstVisible;
NextNodeProc := @GetNextVisible;
end;
else // tstAll
Node := GetFirst;
NextNodeProc := @GetNext;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.GetOptions: TStringTreeOptions;
begin
Result := FOptions as TStringTreeOptions;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.GetText(Node: PVirtualNode; Column: TColumnIndex): WideString;
begin
Assert(Assigned(Node), 'Node must not be nil.');
if not (vsInitialized in Node^.States) then
InitNode(Node);
Result := FDefaultText;
DoGetText(Node, Column, ttNormal, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.InitializeTextProperties(var PaintInfo: TVTPaintInfo);
// Initializes default values for customization in PaintNormalText.
begin
with PaintInfo do
begin
// Set default font values first.
Canvas.Font := Font;
{TODO if (toHotTrack in FOptions.PaintOptions) and (Node = FCurrentHotNode) then
begin
Canvas.Font.Style := Canvas.Font.Style + [fsUnderline];
Canvas.Font.Color := FColors.HotColor;
end;}
// Change the font color only if the node also is drawn in selected style.
if poDrawSelection in PaintOptions then
begin
if (Column = FocusedColumn) or (toFullRowSelect in FOptions.SelectionOptions) then
begin
if vsSelected in Node^.States then
begin
if Focused or (toPopupMode in FOptions.PaintOptions) then
Canvas.Font.Color := clHighlightText;
end;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.PaintNormalText(var PaintInfo: TVTPaintInfo; TextOutFlags: Integer;
xText: WideString);
// This method is responsible for painting the given test to target canvas (under consideration of the given rectangles).
// The text drawn here is considered as the normal text in a node.
// Note: NodeWidth is the actual width of the text to be drawn. This does not necessarily correspond to the width of
// the node rectangle. The clipping rectangle comprises the entire node (including tree lines, buttons etc.).
var
TripleWidth: Integer;
R: TRect;
DrawFormat: Cardinal;
Size: TSize;
begin
InitializeTextProperties(PaintInfo);
with PaintInfo do
begin
R := ContentRect;
//todo Canvas.TextFlags := 0;
// Multiline nodes don't need special font handling or text manipulation.
// Note: multiline support requires the Unicode version of DrawText, which is able to do word breaking.
// The emulation in this unit does not support this so we have to use the OS version. However
// DrawTextW is only available on NT/2000/XP and up. Hence there is only partial multiline support
// for 9x/Me.
if vsMultiline in Node^.States then
begin
InflateRect(R, -FTextMargin, 0);
DoPaintText(Node, Canvas, Column, ttNormal);
// Disabled node color overrides all other variants.
if (vsDisabled in Node^.States) or not Enabled then
Canvas.Font.Color := FColors.DisabledColor;
// The edit control flag will ensure that no partial line is displayed, that is, only lines
// which are (vertically) fully visible are drawn.
DrawFormat := DT_NOPREFIX or DT_WORDBREAK {todoor DT_END_ELLIPSIS} or DT_EDITCONTROL or AlignmentToDrawFlag[Alignment];
//b if BidiMode <> bdLeftToRight then
//b DrawFormat := DrawFormat or DT_RTLREADING;
end
else
begin
InflateRect(R, -FTextMargin, 0);
FFontChanged := False;
TripleWidth := FEllipsisWidth;
DoPaintText(Node, Canvas, Column, ttNormal);
if FFontChanged then
begin
// If the font has been changed then the ellipsis width must be recalculated.
TripleWidth := 0;
// Recalculate also the width of the normal text.
GetTextExtentPoint32W(Canvas.Handle, PWideChar(xText), Length(xText), Size);
NodeWidth := Size.cx + 2 * FTextMargin;
end;
// Disabled node color overrides all other variants.
if (vsDisabled in Node^.States) or not Enabled then
Canvas.Font.Color := FColors.DisabledColor;
DrawFormat := DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE;
//b if BidiMode <> bdLeftToRight then
//b DrawFormat := DrawFormat or DT_RTLREADING;
// Check if the text must be shortend.
if (Column > -1) and ((NodeWidth - 2 * FTextMargin) > R.Right - R.Left) then
begin
xText := DoShortenString(Canvas, Node, Column, xText, R.Right - R.Left, False{bBidiMode <> bdLeftToRight}, TripleWidth);
if Alignment = taRightJustify then
DrawFormat := DrawFormat or DT_RIGHT
else
DrawFormat := DrawFormat or DT_LEFT;
end
else
DrawFormat := DrawFormat or AlignmentToDrawFlag[Alignment];
end;
if not Canvas.TextStyle.Opaque then
SetBkMode(Canvas.Handle, TRANSPARENT)
else
SetBkMode(Canvas.Handle, OPAQUE);
DrawTextW(Canvas, PWideChar(xText), R, DrawFormat, False); //theo
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.PaintStaticText(const PaintInfo: TVTPaintInfo; TextOutFlags: Integer;
const xText: WideString);
// This method retrives and draws the static text bound to a particular node.
var
R: TRect;
DrawFormat: Cardinal;
begin
with PaintInfo do
begin
Canvas.Font := Font;
if toFullRowSelect in FOptions.SelectionOptions then
begin
if vsSelected in Node^.States then
begin
if Focused or (toPopupMode in FOptions.PaintOptions) then
Canvas.Font.Color := clHighlightText
else
Canvas.Font.Color := Font.Color;
end;
end;
DrawFormat := DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE;
//todo Canvas.TextFlags := 0;
DoPaintText(Node, Canvas, Column, ttStatic);
// Disabled node color overrides all other variants.
if (vsDisabled in Node^.States) or not Enabled then
Canvas.Font.Color := FColors.DisabledColor;
R := ContentRect;
if Alignment = taRightJustify then
Dec(R.Right, NodeWidth + FTextMargin)
else
Inc(R.Left, NodeWidth + FTextMargin);
if not Canvas.TextStyle.Opaque then
SetBkMode(Canvas.Handle, TRANSPARENT)
else
SetBkMode(Canvas.Handle, OPAQUE);
DrawTextW(Canvas, PWideChar(xText),R, DrawFormat, False); //theo
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.ReadText(Reader: TReader);
begin
case Reader.NextValue of
vaLString, vaString:
SetDefaultText(Reader.ReadString);
else
SetDefaultText(Reader.ReadWideString);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.SetDefaultText(const Value: WideString);
begin
if FDefaultText <> Value then
begin
FDefaultText := Value;
if not (csLoading in ComponentState) then
Invalidate;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.SetOptions(const Value: TStringTreeOptions);
begin
FOptions.Assign(Value);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.SetText(Node: PVirtualNode; Column: TColumnIndex; const Value: WideString);
begin
DoNewText(Node, Column, Value);
InvalidateNode(Node);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.WriteText(Writer: TWriter);
begin
Writer.WriteWideString(FDefaultText);
end;
//----------------------------------------------------------------------------------------------------------------------
{procedure TCustomVirtualStringTree.WMSetFont(var Msg: TWMSetFont);
// Whenever a new font is applied to the tree some default values are determined to avoid frequent
// determination of the same value.
var
MemDC: HDC;
Run: PVirtualNode;
TM: TTextMetric;
Size: TSize;
begin
inherited;
MemDC := CreateCompatibleDC(0);
try
SelectObject(MemDC, Msg.Font);
GetTextMetrics(MemDC, TM);
FTextHeight := TM.tmHeight;
GetTextExtentPoint32W(MemDC, '...', 3, Size);
FEllipsisWidth := Size.cx;
finally
DeleteDC(MemDC);
end;
// Have to reset all node widths.
Run := FRoot.FirstChild;
while Assigned(Run) do
begin
PInteger(InternalData(Run))^ := 0;
Run := GetNextNoInit(Run);
end;
end;}
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex);
// In the case a node spans several columns (if enabled) we need to determine how many columns.
// Note: the autospan feature can only be used with left-to-right layout.
begin
if (toAutoSpanColumns in FOptions.AutoOptions) and Header.UseColumns and True{b(PaintInfo.BidiMode = bdLeftToRight)} then
with Header.Columns, PaintInfo do
begin
// Start with the directly following column.
NextNonEmpty := GetNextVisibleColumn(Column);
// Auto spanning columns can only be used for left-to-right directionality because the tree is drawn
// from left to right. For RTL directionality it would be necessary to draw it from right to left.
// While this could be managed, it becomes impossible when directionality is mixed.
repeat
if (NextNonEmpty = InvalidColumn) or not ColumnIsEmpty(Node, NextNonEmpty) or
False{b(Items[NextNonEmpty].BidiMode <> bdLeftToRight)} then
Break;
Inc(CellRect.Right, Items[NextNonEmpty].Width);
NextNonEmpty := GetNextVisibleColumn(NextNonEmpty);
until False;
end
else
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.CalculateTextWidth(xCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
xText: WideString): Integer;
// determines the width of the given text
var
Size: TSize;
begin
Result := 2 * FTextMargin;
if Length(xText) > 0 then
begin
Canvas.Font := Font;
DoPaintText(Node, xCanvas, Column, ttNormal);
GetTextExtentPoint32W(xCanvas.Handle, PWideChar(xText), Length(xText), Size);
Inc(Result, Size.cx);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean;
// For hit tests it is necessary to consider cases where columns are empty and automatic column spanning is enabled.
// This method simply checks the given column's text and if this is empty then the column is considered as being empty.
begin
Result := Length(Text[Node, Column]) = 0;
// If there is no text then let the ancestor decide if the column is to be considered as being empty
// (e.g. by asking the application). If there is text then the column is never be considered as being empty.
if Result then
Result := inherited ColumnIsEmpty(Node, Column);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.DefineProperties(Filer: TFiler);
begin
inherited;
// Delphi still cannot handle wide strings properly while streaming
Filer.DefineProperty('WideDefaultText', @ReadText, @WriteText, FDefaultText <> 'Node');
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink;
begin
Result := inherited DoCreateEditor(Node, Column);
// Enable generic label editing support if the application does not have own editors.
if Result = nil then
Result := TStringEditLink.Create;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex): WideString;
begin
Result := inherited DoGetNodeHint(Node, Column);
if Assigned(FOnGetHint) then
FOnGetHint(Self, Node, Column, ttNormal, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex): WideString;
begin
Result := Text[Node, Column];
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; xCanvas: TCanvas = nil): Integer;
// Returns the text width of the given node in pixels.
// This width is stored in the node's data member to increase access speed.
var
Data: PInteger;
begin
if (Column > NoColumn) and (vsMultiline in Node^.States) then
Result := Header.Columns[Column].Width
else
begin
if xCanvas = nil then
xCanvas := Self.Canvas;
if Column = Header.MainColumn then
begin
// primary column or no columns
Data := InternalData(Node);
Result := Data^;
if Result = 0 then
begin
Data^ := CalculateTextWidth(xCanvas, Node, Column, Text[Node, Column]);
Result := Data^;
end;
end
else
// any other column
Result := CalculateTextWidth(xCanvas, Node, Column, Text[Node, Column]);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.DoGetText(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
var xText: WideString);
begin
if Assigned(FOnGetText) then
FOnGetText(Self, Node, Column, TextType, xText);
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.DoIncrementalSearch(Node: PVirtualNode; const xText: WideString): Integer;
// Since the string tree has access to node text it can do incremental search on its own. Use the event to
// override the default behavior.
begin
Result := 0;
if Assigned(FOnIncrementalSearch) then
FOnIncrementalSearch(Self, Node, xText, Result)
else
// Default behavior is to match the search string with the start of the node text.
if Pos(xText, GetText(Node, FocusedColumn)) <> 1 then
Result := 1;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.DoNewText(Node: PVirtualNode; Column: TColumnIndex; xText: WideString);
begin
if Assigned(FOnNewText) then
FOnNewText(Self, Node, Column, xText);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.DoPaintNode(var PaintInfo: TVTPaintInfo);
// Main output routine to print the text of the given node using the space provided in PaintInfo.ContentRect.
var
S: WideString;
TextOutFlags: Integer;
begin
// Set a new OnChange event for the canvas' font so we know if the application changes it in the callbacks.
// This long winded procedure is necessary because font changes (as well as brush and pen changes) are
// unfortunately not announced via the Canvas.OnChange event.
RedirectFontChangeEvent(PaintInfo.Canvas);
// Determine main text direction as well as other text properties.
TextOutFlags := ETO_CLIPPED {bor RTLFlag[PaintInfo.BidiMode <> bdLeftToRight]};
S := Text[PaintInfo.Node, PaintInfo.Column];
// Paint the normal text first...
if Length(S) > 0 then
PaintNormalText(PaintInfo, TextOutFlags, S);
// ... and afterwards the static text if not centered and the node is not multiline enabled.
if (Alignment <> taCenter) and not (vsMultiline in PaintInfo.Node^.States) and (toShowStaticText in TreeOptions.FStringOptions) then
begin
S := '';
with PaintInfo do
DoGetText(Node, Column, ttStatic, S);
if Length(S) > 0 then
PaintStaticText(PaintInfo, TextOutFlags, S);
end;
RestoreFontChangeEvent(PaintInfo.Canvas);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.DoPaintText(Node: PVirtualNode; const xCanvas: TCanvas; Column: TColumnIndex;
TextType: TVSTTextType);
begin
if Assigned(FOnPaintText) then
FOnPaintText(Self, xCanvas, Node, Column, TextType);
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.DoShortenString(xCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
const S: WideString; xWidth: Integer; RightToLeft: Boolean; EllipsisWidth: Integer = 0): WideString;
var
Done: Boolean;
begin
Done := False;
if Assigned(FOnShortenString) then
FOnShortenString(Self, xCanvas, Node, Column, S, xWidth, RightToLeft, Result, Done);
if not Done then
Result := ShortenString(xCanvas.Handle, S, xWidth, RightToLeft, EllipsisWidth);
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.GetOptionsClass: TTreeOptionsClass;
begin
Result := TCustomStringTreeOptions;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect;
var xText: WideString);
// Returns the font, the text and its bounding rectangle to the caller. R is returned as the closest
// bounding rectangle around Text.
var
NewHeight: Integer;
TM: TTextMetric;
begin
// Get default font and initialize the other parameters.
inherited GetTextInfo(Node, Column, AFont, R, xText);
Canvas.Font := AFont;
FFontChanged := False;
RedirectFontChangeEvent(Canvas);
DoPaintText(Node, Canvas, Column, ttNormal);
if FFontChanged then
begin
AFont.Assign(Canvas.Font);
GetTextMetrics(Canvas.Handle, TM);
NewHeight := TM.tmHeight;
end
else // Otherwise the correct font is already there and we only need to set the correct height.
NewHeight := FTextHeight;
RestoreFontChangeEvent(Canvas);
// Alignment to the actual text.
xText := Self.Text[Node, Column];
R := GetDisplayRect(Node, Column, True, not (vsMultiline in Node^.States));
if toShowHorzGridLines in TreeOptions.PaintOptions then
Dec(R.Bottom);
InflateRect(R, 0, -(R.Bottom - R.Top - NewHeight) div 2);
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
begin
if (Node = RootNode) or (Node = nil) then
Result := nil
else
Result := PChar(Node) + FInternalDataOffset;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.MainColumnChanged;
var
Run: PVirtualNode;
begin
inherited;
// Have to reset all node widths.
Run := RootNode^.FirstChild;
while Assigned(Run) do
begin
PInteger(InternalData(Run))^ := 0;
Run := GetNextNoInit(Run);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType,
ChunkSize: Integer): Boolean;
// read in the caption chunk if there is one
var
NewText: WideString;
begin
case ChunkType of
CaptionChunk:
begin
NewText := '';
if ChunkSize > 0 then
begin
SetLength(NewText, ChunkSize div 2);
Stream.Read(PWideChar(NewText)^, ChunkSize);
end;
// Do a new text event regardless of the caption content to allow removing the default string.
Text[Node, Header.MainColumn] := NewText;
Result := True;
end;
else
Result := inherited ReadChunk(Stream, Version, Node, ChunkType, ChunkSize);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.WriteChunks(Stream: TStream; Node: PVirtualNode);
// Adds another sibling chunk for Node storing the label if the node is initialized.
// Note: If the application stores a node's caption in the node's data member (which will be quite common) and needs to
// store more node specific data then it should use the OnSaveNode event rather than the caption autosave function
// (take out soSaveCaption from StringOptions). Otherwise the caption is unnecessarily stored twice.
var
xHeader: TChunkHeader;
S: WideString;
Len: Integer;
begin
inherited;
if (toSaveCaptions in TreeOptions.FStringOptions) and (Node <> RootNode) and
(vsInitialized in Node^.States) then
with Stream do
begin
// Read the node's caption (primary column only).
S := Text[Node, Header.MainColumn];
Len := 2 * Length(S);
if Len > 0 then
begin
// Write a new sub chunk.
xHeader.ChunkType := CaptionChunk;
xHeader.ChunkSize := Len;
Write(xHeader, SizeOf(xHeader));
Write(PWideChar(S)^, Len);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ComputeNodeHeight(xCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex): Integer;
// Default node height calculation for multi line nodes. This method can be used by the application to delegate the
// quite expensive computation to the string tree.
var
R: TRect;
S: WideString;
DrawFormat: Cardinal;
xBidiMode: Classes.TBidiMode;
xAlignment: TAlignment;
PaintInfo: TVTPaintInfo;
Dummy: TColumnIndex;
begin
Result := Node^.NodeHeight;
if vsMultiLine in Node^.States then
begin
S := Text[Node, Column];
R := GetDisplayRect(Node, Column, True);
DrawFormat := DT_TOP or DT_NOPREFIX or DT_CALCRECT or DT_WORDBREAK;
if Column <= NoColumn then
begin
xBidiMode := Self.BidiMode;
xAlignment := Self.Alignment;
end
else
begin
BidiMode := Header.Columns[Column].BidiMode;
xAlignment := Header.Columns[Column].Alignment;
end;
// if xBidiMode <> bdLeftToRight then
// ChangeBidiModeAlignment(Alignment);
// Allow for autospanning.
PaintInfo.Node := Node;
PaintInfo.BidiMode := xBidiMode;
PaintInfo.Column := Column;
PaintInfo.CellRect := R;
AdjustPaintCellRect(PaintInfo, Dummy);
if xBidiMode <> bdLeftToRight then
DrawFormat := DrawFormat or DT_RIGHT or DT_RTLREADING
else
DrawFormat := DrawFormat or DT_LEFT;
DrawTextW(xCanvas, PWideChar(S), PaintInfo.CellRect, DrawFormat, False); //theo
Result := PaintInfo.CellRect.Bottom - PaintInfo.CellRect.Top;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ContentToClipboard(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 = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">';
HTMLIntro = '<html><head><META http-equiv=Content-Type content="text/html; charset=utf-8">' +
'</head><body><!--StartFragment-->';
HTMLExtro = '<!--EndFragment--></body></html>';
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 +
SysUtils.Format('%s%.8d', [StartHTML, StartHTMLIndex]) + #13#10 +
SysUtils.Format('%s%.8d', [EndHTML, EndHTMLIndex]) + #13#10 +
SysUtils.Format('%s%.8d', [StartFragment, StartFragmentIndex]) + #13#10 +
SysUtils.Format('%s%.8d', [EndFragment, EndFragmentIndex]) + #13#10;
HTML := Description + DocType + HTMLIntro + HTML + HTMLExtro;
end;
//--------------- end local function ----------------------------------------
var
Data: Pointer;
DataSize: Cardinal;
S: string;
WS: WideString;
begin
Result := 0;
case Format of
CF_TEXT:
begin
S := ContentToText(Source, #9) + #0;
Data := PChar(@S);
DataSize := Length(S);
end;
CF_UNICODETEXT:
begin
WS := ContentToUnicode(Source, #9) + #0;
Data := PWideChar(WS);
DataSize := 2 * Length(WS);
end;
else
if Format = CF_CSV then
S := ContentToText(Source, ';'{todoListSeparator}) + #0
else
if (Format = CF_VRTF) or (Format = CF_VRTFNOOBJS) then
S := ContentToRTF(Source) + #0
else
if Format = CF_HTML then
begin
S := ContentToHTML(Source);
// Build a valid HTML clipboard fragment.
MakeFragment(S);
S := S + #0;
end;
Data := PChar(@S);
DataSize := Length(S);
end;
if DataSize > 0 then
begin
//x Result := GlobalAlloc(GHND or GMEM_SHARE, DataSize);
//x P := GlobalLock(Result);
//x Move(Data^, P^, DataSize);
//x GlobalUnlock(Result);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ContentToHTML(Source: TVSTTextSourceType; xCaption: WideString = ''): 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<>stemeier.
type
UCS2 = Word;
UCS4 = Cardinal;
const
MaximumUCS4: UCS4 = $7FFFFFFF;
ReplacementCharacter: UCS4 = $0000FFFD;
var
Buffer: TBufferedString;
//--------------- local functions -------------------------------------------
function ConvertSurrogate(S1, S2: UCS2): UCS4;
// Converts a pair of high and low surrogate into the corresponding UCS4 character.
const
SurrogateOffset = ($D800 shl 10) + $DC00 - $10000;
begin
Result := Word(S1) shl 10 + Word(S2) - SurrogateOffset;
end;
//---------------------------------------------------------------------------
function UTF16ToUTF8(const S: WideString): string;
// Converts the given Unicode text (which may contain surrogates) into
// the UTF-8 encoding used for the HTML clipboard format.
const
FirstByteMark: array[0..6] of Byte = ($00, $00, $C0, $E0, $F0, $F8, $FC);
var
Ch: UCS4;
I, J, T: Integer;
BytesToWrite: Cardinal;
begin
if Length(S) = 0 then
Result := ''
else
begin
// Make room for the result. Assume worst case, there are only short texts to convert.
SetLength(Result, 6 * Length(S));
T := 1;
I := 1;
while I <= Length(S) do
begin
Ch := UCS4(S[I]);
// Is the character a surrogate?
if (Ch and $FFFFF800) = $D800 then
begin
Inc(I);
// Check the following char whether it forms a valid surrogate pair with the first character.
if (I <= Length(S)) and ((UCS4(S[I]) and $FFFFFC00) = $DC00) then
Ch := ConvertSurrogate(UCS2(Ch), UCS2(S[I]))
else // Skip invalid surrogate value.
Continue;
end;
if Ch < $80 then
BytesToWrite := 1
else
if Ch < $800 then
BytesToWrite := 2
else
if Ch < $10000 then
BytesToWrite := 3
else
if Ch < $200000 then
BytesToWrite := 4
else
if Ch < $4000000 then
BytesToWrite := 5
else
if Ch <= MaximumUCS4 then
BytesToWrite := 6
else
begin
BytesToWrite := 2;
Ch := ReplacementCharacter;
end;
for J := BytesToWrite downto 2 do
begin
Result[T + J - 1] := Char((Ch or $80) and $BF);
Ch := Ch shr 6;
end;
Result[T] := Char(Ch or FirstByteMark[BytesToWrite]);
Inc(T, BytesToWrite);
Inc(I);
end;
SetLength(Result, T - 1); // set to actual length
end;
end;
//---------------------------------------------------------------------------
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(Char(Value));
Inc(I);
Value := 48 + (Component and $F);
if Value > $39 then
Inc(Value, 7);
Buffer.Add(Char(Value));
Inc(I);
WinColor := WinColor shr 8;
end;
end;
//---------------------------------------------------------------------------
procedure WriteStyle(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="{font:')
else
begin
Buffer.Add('.');
Buffer.Add(Name);
Buffer.Add('{font:');
end;
if fsUnderline in Font.Style then
Buffer.Add(' underline');
if fsItalic in Font.Style then
Buffer.Add(' italic');
if fsBold in Font.Style then
Buffer.Add(' bold');
Buffer.Add(Format(' %dpt "%s";', [Font.Size, Font.Name]));
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;
xText: WideString;
RenderColumns: Boolean;
Columns: TColumnsArray;
ColumnColors: array of string;
Index: Integer;
IndentWidth,
LineStyleText: string;
xAlignment: TAlignment;
// BidiMode: TBidiMode;
CellPadding: string;
begin
GetNextNode := nil;
Run := nil;
Buffer := TBufferedString.Create;
try
// For customization by the application or descentants we use again the redirected font change event.
RedirectFontChangeEvent(Canvas);
CellPadding := Format('padding-left:%dpx;padding-right:%0:dpx;', [FMargin]);
IndentWidth := IntToStr(FIndent);
AddHeader := ' ';
// Add title if adviced so by giving a caption.
if Length(xCaption) > 0 then
AddHeader := AddHeader + 'caption="' + UTF16ToUTF8(xCaption) + '"';
if Borderstyle <> bsNone then
AddHeader := AddHeader + Format('border="%d" frame=box', [BorderWidth + 1]);
// Create HTML table based on the tree structure. To simplify formatting we use styles defined in a small CSS area.
Buffer.Add('<style type="text/css">');
Buffer.AddnewLine;
WriteStyle('default', Font);
Buffer.AddNewLine;
WriteStyle('header', Header.Font);
Buffer.AddNewLine;
// Determine grid/table lines and create CSS for it.
// Vertical and/or horizontal border to show.
if LineStyle = lsSolid then
LineStyleText := 'solid;'
else
LineStyleText := 'dotted;';
if toShowHorzGridLines in FOptions.PaintOptions then
begin
Buffer.Add('.noborder{border-style:');
Buffer.Add(LineStyleText);
Buffer.Add(' border-bottom:1;border-left:0;border-right:0; border-top:0;');
Buffer.Add(CellPadding);
Buffer.Add('}');
end
else
begin
Buffer.Add('.noborder{border-style:none;');
Buffer.Add(CellPadding);
Buffer.Add('}');
end;
Buffer.AddNewLine;
Buffer.Add('.normalborder {border-top:none; border-left:none; ');
if toShowVertGridLines in FOptions.PaintOptions then
Buffer.Add('border-right:1 ' + LineStyleText)
else
Buffer.Add('border-right:none;');
if toShowHorzGridLines in FOptions.PaintOptions then
Buffer.Add('border-bottom:1 ' + LineStyleText)
else
Buffer.Add('border-bottom:none;');
Buffer.Add(CellPadding);
Buffer.Add('}');
Buffer.Add('</style>');
Buffer.AddNewLine;
// General table properties.
Buffer.Add('<table class="default" bgcolor=');
WriteColorAsHex(Color);
Buffer.Add(AddHeader);
Buffer.Add(' cellspacing="0" cellpadding=');
Buffer.Add(IntToStr(FMargin) + '>');
Buffer.AddNewLine;
Columns := nil;
ColumnColors := nil;
RenderColumns := Header.UseColumns;
if RenderColumns then
begin
Columns := Header.Columns.GetVisibleColumns;
SetLength(ColumnColors, Length(Columns));
end;
GetRenderStartValues(Source, Run, GetNextNode);
Save := Run;
MaxLevel := 0;
// The table consists of visible columns and rows as used in the tree, but the main tree column is splitted
// into several HTML columns to accomodate the indentation.
while Assigned(Run) do
begin
Level := GetNodeLevel(Run);
If Level > MaxLevel then
MaxLevel := Level;
Run := GetNextNode(Run);
end;
if RenderColumns then
begin
Buffer.Add('<tr class="header" style="');
Buffer.Add(CellPadding);
Buffer.Add('">');
Buffer.AddNewLine;
// Make the first row in the HTML table an image of the tree header.
for I := 0 to High(Columns) do
begin
Buffer.Add('<th height="');
Buffer.Add(IntToStr(Header.Height));
Buffer.Add('px"');
xAlignment := Columns[I].Alignment;
// Consider directionality.
//b if Columns[I].FBiDiMode <> bdLeftToRight then
//b begin
//b ChangeBidiModeAlignment(xAlignment);
//b Buffer.Add(' dir="rtl"');
//b end;
// Consider aligment.
case xAlignment 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 = 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(UTF16ToUTF8(Columns[I].Text));
Buffer.Add('</th>');
end;
Buffer.Add('</tr>');
Buffer.AddNewLine;
end;
// Now go through the tree.
Run := Save;
while Assigned(Run) do
begin
Level := GetNodeLevel(Run);
Buffer.Add(' <tr class="default">');
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.
Canvas.Font := Font;
FFontChanged := False;
DoPaintText(Run, Canvas, Index, ttNormal);
if Index = 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('<td class="noborder" width="');
Buffer.Add(IndentWidth);
Buffer.Add('" height="');
Buffer.Add(IntToStr(NodeHeight[Run]));
Buffer.Add('px"');
if not (coParentColor in Columns[I].Options) then
begin
Buffer.Add(' bgcolor=');
WriteColorAsHex(Columns[I].Color);
end;
Buffer.Add('>&nbsp;</td>');
end;
end
else
begin
for J := 1 to Level do
if J = 1 then
begin
Buffer.Add(' <td height="');
Buffer.Add(IntToStr(NodeHeight[Run]));
Buffer.Add('px">&nbsp;</td>');
end
else
Buffer.Add(' <td>&nbsp;</td>');
end;
end;
if FFontChanged then
begin
Buffer.Add(' <td class="normalborder" ');
WriteStyle('', Canvas.Font);
Buffer.Add(' height="');
Buffer.Add(IntToStr(NodeHeight[Run]));
Buffer.Add('px"');
end
else
begin
Buffer.Add(' <td class="normalborder" height="');
Buffer.Add(IntToStr(NodeHeight[Run]));
Buffer.Add('px"');
end;
if RenderColumns then
begin
xAlignment := Columns[I].Alignment;
//b BidiMode := Columns[I].BidiMode;
end
else
begin
xAlignment := Self.Alignment;
//b BidiMode := Self.BidiMode;
end;
// Consider directionality.
//b if BiDiMode <> bdLeftToRight then
//b begin
//b ChangeBidiModeAlignment(xAlignment);
//b Buffer.Add(' dir="rtl"');
//b end;
// Consider aligment.
case xAlignment 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 = 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('>');
xText := Self.Text[Run, Index];
if Length(xText) > 0 then
begin
xText := UTF16ToUTF8(xText);
Buffer.Add(xText);
end;
Buffer.Add('</td>');
end;
if not RenderColumns then
Break;
Inc(I);
end;
Run := GetNextNode(Run);
Buffer.Add(' </tr>');
Buffer.AddNewLine;
end;
Buffer.Add('</table>');
RestoreFontChangeEvent(Canvas);
Result := Buffer.AsString;
finally
Buffer.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ContentToRTF(Source: TVSTTextSourceType): string;
// Renders the current tree content (depending on Source) as RTF (rich text).
// Based on ideas and code from Frank van den Bergh and Andreas H<>stemeier.
var
Fonts: TStringList;
xColors: TList;
CurrentFontIndex,
CurrentFontColor,
CurrentFontSize: Integer;
Buffer: TBufferedString;
//--------------- local functions -------------------------------------------
procedure SelectFont(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 := xColors.IndexOf(Pointer(@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 := xColors.Add(Pointer(@Color));
Buffer.Add('\cf');
Buffer.Add(IntToStr(I + 1));
CurrentFontColor := I;
end;
end;
//---------------------------------------------------------------------------
procedure TextPlusFont(Text: WideString; 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
Buffer.Add(Format('\u%d\''3f', [SmallInt(Text[I])]));
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 : string;
xText: WideString;
Twips: Integer;
RenderColumns: Boolean;
Columns: TColumnsArray;
Index: Integer;
xAlignment: TAlignment;
// BidiMode: TBidiMode;
begin
Run := nil;
GetNextNode := nil;
Buffer := TBufferedString.Create;
try
// For customization by the application or descentants we use again the redirected font change event.
RedirectFontChangeEvent(Canvas);
Fonts := TStringList.Create;
xColors := TList.Create;
CurrentFontIndex := -1;
CurrentFontColor := -1;
CurrentFontSize := -1;
Columns := nil;
Tabs := '';
LastLevel := 0;
RenderColumns := Header.UseColumns;
if RenderColumns then
Columns := Header.Columns.GetVisibleColumns;
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 * ClientWidth / Screen.PixelsPerInch);
Buffer.Add('\cellx');
Buffer.Add(IntToStr(Twips));
end;
// Fill table header.
if RenderColumns then
begin
Buffer.Add('\pard\intbl');
for I := 0 to High(Columns) do
begin
xAlignment := Columns[I].Alignment;
//b BidiMode := Columns[I].BidiMode;
// Alignment is not supported with older RTF formats, however it will be ignored.
//b if BidiMode <> bdLeftToRight then
//b ChangeBidiModeAlignment(xAlignment);
case xAlignment of
taRightJustify:
Buffer.Add('\qr');
taCenter:
Buffer.Add('\qc');
end;
TextPlusFont(Columns[I].Text, Header.Font);
Buffer.Add('\cell');
end;
Buffer.Add('\row');
end;
// Now write the contents.
Run := Save;
while Assigned(Run) do
begin
I := 0;
while not RenderColumns or (I < Length(Columns)) do
begin
if RenderColumns then
begin
Index := Columns[I].Index;
xAlignment := Columns[I].Alignment;
//b BidiMode := Columns[I].BidiMode;
end
else
begin
Index := NoColumn;
xAlignment := Alignment;
//b BidiMode := Self.BidiMode;
end;
if not RenderColumns or (coVisible in Columns[I].Options) then
begin
xText := Self.Text[Run, Index];
Buffer.Add('\pard\intbl');
// Alignment is not supported with older RTF formats, however it will be ignored.
//b if BidiMode <> bdLeftToRight then
//b ChangeBidiModeAlignment(xAlignment);
case xAlignment of
taRightJustify:
Buffer.Add('\qr');
taCenter:
Buffer.Add('\qc');
end;
// Call back the application to know about font customization.
Canvas.Font := Font;
FFontChanged := False;
DoPaintText(Run, Canvas, Index, ttNormal);
if Index = Header.MainColumn then
begin
Level := 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(xText, Canvas.Font);
Buffer.Add('\cell');
end
else
begin
TextPlusFont(xText, Canvas.Font);
Buffer.Add('\cell');
end;
end
else
begin
TextPlusFont(xText, Canvas.Font);
Buffer.Add('\cell');
end;
end;
if not RenderColumns then
Break;
Inc(I);
end;
Buffer.Add('\row');
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 xColors.Count - 1 do
begin
J := ColorToRGB(TColor(xColors[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 + '}';
Result := S + Buffer.AsString + '}';
Fonts.Free;
xColors.Free;
RestoreFontChangeEvent(Canvas);
finally
Buffer.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ContentToText(Source: TVSTTextSourceType; Separator: Char): string;
// Renders the current tree content (depending on Source) as plain ANSI text.
// If an entry contains the separator char or double quotes then it is wrapped with double quotes
// and existing double quotes are duplicated.
// Note: Unicode strings are implicitely converted to ANSI strings based on the currently active user locale.
var
RenderColumns: Boolean;
Tabs: string;
GetNextNode: TGetNextNodeProc;
Run, Save: PVirtualNode;
Level, MaxLevel: Cardinal;
Columns: TColumnsArray;
LastColumn: TVirtualTreeColumn;
Index,
I: Integer;
xText: string;
Buffer: TBufferedString;
begin
Columns := nil;
Run := nil;
GetNextNode := nil;
Buffer := TBufferedString.Create;
try
RenderColumns := Header.UseColumns;
if RenderColumns then
Columns := Header.Columns.GetVisibleColumns;
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) do
begin
Level := GetNodeLevel(Run);
If Level > MaxLevel then
MaxLevel := Level;
Run := GetNextNode(Run);
end;
SetLength(Tabs, MaxLevel);
FillChar(PChar(@Tabs)^, MaxLevel, Separator);
// 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 = 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) do
begin
for I := 0 to High(Columns) do
begin
if coVisible in Columns[I].Options then
begin
Index := Columns[I].Index;
// This line implicitly converts the Unicode text to ANSI.
xText := Self.Text[Run, Index];
if Index = Header.MainColumn then
begin
Level := GetNodeLevel(Run);
Buffer.Add(Copy(Tabs, 1, Level));
// Wrap the text with quotation marks if it contains the separator character.
if (Pos(Separator, xText) > 0) or (Pos('"', xText) > 0) then
Buffer.Add(AnsiQuotedStr(xText, '"'))
else
Buffer.Add(xText);
Buffer.Add(Copy(Tabs, 1, MaxLevel - Level));
end
else
if (Pos(Separator, xText) > 0) or (Pos('"', xText) > 0) then
Buffer.Add(AnsiQuotedStr(xText, '"'))
else
Buffer.Add(xText);
if Columns[I] <> LastColumn then
Buffer.Add(Separator);
end;
end;
Run := GetNextNode(Run);
Buffer.AddNewLine;
end;
end
else
begin
while Assigned(Run) do
begin
// This line implicitly converts the Unicode text to ANSI.
xText := Self.Text[Run, NoColumn];
Level := GetNodeLevel(Run);
Buffer.Add(Copy(Tabs, 1, Level));
Buffer.Add(xText);
Buffer.AddNewLine;
Run := GetNextNode(Run);
end;
end;
Result := Buffer.AsString;
finally
Buffer.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.ContentToUnicode(Source: TVSTTextSourceType; Separator: WideChar): WideString;
// 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.
// Note: There is no QuotedStr function for Unicode in the VCL (like AnsiQuotedStr) so we have the limitation here
// that an entry must not contain double quotation marks, otherwise import into other programs might fail!
var
RenderColumns: Boolean;
Tabs: WideString;
GetNextNode: TGetNextNodeProc;
Run, Save: PVirtualNode;
Columns: TColumnsArray;
LastColumn: TVirtualTreeColumn;
Level, MaxLevel: Cardinal;
Index,
I: Integer;
xText: WideString;
Buffer: TWideBufferedString;
begin
Columns := nil;
Run := nil;
GetNextNode := nil;
Buffer := TWideBufferedString.Create;
try
RenderColumns := Header.UseColumns;
if RenderColumns then
Columns := Header.Columns.GetVisibleColumns;
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) do
begin
Level := GetNodeLevel(Run);
If Level > MaxLevel then
MaxLevel := Level;
Run := GetNextNode(Run);
end;
SetLength(Tabs, MaxLevel);
for I := 1 to MaxLevel do
Tabs[I] := Separator;
// 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 = 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) do
begin
for I := 0 to High(Columns) do
begin
if coVisible in Columns[I].Options then
begin
Index := Columns[I].Index;
xText := Self.Text[Run, Index];
if Index = Header.MainColumn then
begin
Level := GetNodeLevel(Run);
Buffer.Add(Copy(Tabs, 1, Level));
// Wrap the text with quotation marks if it contains the separator character.
if Pos(Separator, xText) > 0 then
begin
Buffer.Add('"');
Buffer.Add(xText);
Buffer.Add('"');
end
else
Buffer.Add(xText);
Buffer.Add(Copy(Tabs, 1, MaxLevel - Level));
end
else
if Pos(Separator, xText) > 0 then
begin
Buffer.Add('"');
Buffer.Add(xText);
Buffer.Add('"');
end
else
Buffer.Add(xText);
if Columns[I] <> LastColumn then
Buffer.Add(Separator);
end;
end;
Run := GetNextNode(Run);
Buffer.AddNewLine;
end;
end
else
begin
while Assigned(Run) do
begin
xText := Self.Text[Run, NoColumn];
Level := GetNodeLevel(Run);
Buffer.Add(Copy(Tabs, 1, Level));
Buffer.Add(xText);
Buffer.AddNewLine;
Run := GetNextNode(Run);
end;
end;
Result := Buffer.AsString;
finally
Buffer.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.InvalidateNode(Node: PVirtualNode): TRect;
begin
Result := inherited InvalidateNode(Node);
// Reset node width so changed text attributes are applied correctly.
if Assigned(Node) then
begin
PInteger(InternalData(Node))^ := 0;
// Reset height measured flag too to cause a re-issue of the OnMeasureItem event.
Exclude(Node^.States, vsHeightMeasured);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCustomVirtualStringTree.Path(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
Delimiter: WideChar): WideString;
// Constructs a string containing the node and all its parents. The last character in the returned path is always the
// given delimiter.
var
S: WideString;
begin
S := '';
if (Node = nil) or (Node = RootNode) then
Result := Delimiter
else
begin
Result := '';
while Node <> RootNode do
begin
DoGetText(Node, Column, TextType, S);
Result := S + Delimiter + Result;
Node := Node^.Parent;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCustomVirtualStringTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean);
begin
inherited;
// Reset node width so changed text attributes are applied correctly.
if Assigned(Node) and (Node <> RootNode) then
begin
PInteger(InternalData(Node))^ := 0;
// Reset height measured flag too to cause a re-issue of the OnMeasureItem event.
Exclude(Node^.States, vsHeightMeasured);
end;
end;
//----------------- TVirtualStringTree ---------------------------------------------------------------------------------
function TVirtualStringTree.GetOptions: TStringTreeOptions;
begin
Result := FOptions as TStringTreeOptions;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualStringTree.SetOptions(const Value: TStringTreeOptions);
begin
FOptions.Assign(Value);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualStringTree.GetOptionsClass: TTreeOptionsClass;
begin
Result := TStringTreeOptions;
end;
end.