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 = ''; HTMLIntro = '
' + ''; HTMLExtro = ''; NumberLengthAndCR = 10; // Let the compiler determine the description length. DescriptionLength = Length(Version) + Length(StartHTML) + Length(EndHTML) + Length(StartFragment) + Length(EndFragment) + 4 * NumberLengthAndCR; var Description: string; StartHTMLIndex, EndHTMLIndex, StartFragmentIndex, EndFragmentIndex: Integer; begin // The HTML clipboard format is defined by using byte positions in the entire block where HTML text and // fragments start and end. These positions are written in a description. Unfortunately the positions depend on the // length of the description but the description may change with varying positions. // To solve this dilemma the offsets are converted into fixed length strings which makes it possible to know // the description length in advance. StartHTMLIndex := DescriptionLength; // position 0 after the description StartFragmentIndex := StartHTMLIndex + Length(DocType) + Length(HTMLIntro); EndFragmentIndex := StartFragmentIndex + Length(HTML); EndHTMLIndex := EndFragmentIndex + Length(HTMLExtro); Description := Version + 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(''); Buffer.AddNewLine; // General table properties. Buffer.Add('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(' | '); end; Buffer.Add('|||
---|---|---|---|
'); end; end else begin for J := 1 to Level do if J = 1 then begin Buffer.Add(' | '); end else Buffer.Add(' | '); end; end; if FFontChanged then begin Buffer.Add(' | 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(' | '); end; if not RenderColumns then Break; Inc(I); end; Run := GetNextNode(Run); Buffer.Add('