
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@751 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2869 lines
89 KiB
ObjectPascal
2869 lines
89 KiB
ObjectPascal
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('> </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"> </td>');
|
||
end
|
||
else
|
||
Buffer.Add(' <td> </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.
|
||
|