
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@2593 8e941d3f-bd1b-0410-a28a-d453659cc2b4
32941 lines
1.1 MiB
32941 lines
1.1 MiB
unit VirtualTrees;
|
||
|
||
{$mode delphi}{$H+}
|
||
{$packset 1}
|
||
|
||
// Version 4.8.7
|
||
//
|
||
// The contents of this file are subject to the Mozilla Public License
|
||
// Version 1.1 (the "License"); you may not use this file except in compliance
|
||
// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
|
||
//
|
||
// Alternatively, you may redistribute this library, use and/or modify it under the terms of the
|
||
// GNU Lesser General Public License as published by the Free Software Foundation;
|
||
// either version 2.1 of the License, or (at your option) any later version.
|
||
// You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/.
|
||
//
|
||
// Software distributed under the License is distributed on an "AS IS" basis,
|
||
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
|
||
// specific language governing rights and limitations under the License.
|
||
//
|
||
// The original code is VirtualTrees.pas, released September 30, 2000.
|
||
//
|
||
// The initial developer of the original code is digital publishing AG (Munich, Germany, www.digitalpublishing.de),
|
||
// written by Mike Lischke (public@soft-gems.net, www.soft-gems.net).
|
||
//
|
||
// Portions created by digital publishing AG are Copyright
|
||
// (C) 1999-2001 digital publishing AG. All Rights Reserved.
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
//
|
||
// April 2010
|
||
// - Bug fix: Removed active column changing from TBaseVirtualTree.WMKeyDown to re-gain standard conforming
|
||
// behaviour for VK_NEXT and VK_PRIOR
|
||
// - Bug fix: Paint option toUseExplorerTheme works properly without defining columns
|
||
// - Bug fix: TBaseVirtualTree.PrepareBitmaps now correctly closes the theme handle
|
||
// January 2010
|
||
// - Bug fix: TBaseVirtualTree.AdjustTotalHeight now longer calculates wrong total heights if nodes have been
|
||
// made invisible
|
||
// - Bug fix: TCustomVirtualStringTree.OnMeasureTextWidth now works as intended
|
||
// - Bug fix: Added missing $IFDEFs concerning theming support
|
||
// - Bug fix: Removed default from properties TVirtualTreeColumn.Color and TVirtualTreeColumn.BiDiMode
|
||
// July 2009
|
||
// - Bug fix: TWorkerThread will no longer reference the tree after it has been destroyed (Mantis issue #384)
|
||
// - Bug fix: TBaseVirtualTree.InternalConnectNode checked the expanded state of the wrong node if Mode was
|
||
// amAddChildFirst or amAddChildLast
|
||
// June 2009
|
||
// - Bug fix: fixed some issues concerning the vista theme handling
|
||
// - Improvement: removed hidden node handling in this branch
|
||
// - Improvement: reverted header click handling to old version to keep compatibility in this branch
|
||
// - Improvement: removed TVTPaintOption toHideTreeLinesIfThemed
|
||
// May 2009
|
||
// - Improvement: new TVTMiscOption toEditOnClick, toEditOnDblClick to control if editing can be started with a single
|
||
// click or a double click
|
||
// - Bug fix: the internal pointers of TBufferedAnsiString are now PAnsiChar to work correctly with Delphi 2009
|
||
// April 2009
|
||
// - Bug fix: TBaseVirtualTree.GetVisibleParent no longer returns the given node in case it is fully visible
|
||
// - Improvement: fixed a potential issue in TVirtualTreeColumns.TotalWidth in case it is called before
|
||
// FPositionToIndex is initialized
|
||
// - Bug fix: TBaseVirtualTree.CollectSelectedNodesLTR and TBaseVirtualTree.CollectSelectedNodesRTL handle straight
|
||
// vertical selection rectangles no longer as empty
|
||
// - Bug fix: TCheckImageKind.ckSystemDefault now works as intended
|
||
// - Improvement: made the following methods of TBaseVirtualTree virtual: PrepareCell, AddChild, BeginUpdate,
|
||
// EndUpdate and SortTree
|
||
// - Improvement: made TBaseVirtualTree.PrepareCell protected
|
||
// - Improvement: moved some members of TVTEdit and TStringEditLink from private to protected
|
||
// - Improvement: re-designed header click handling
|
||
// - Improvement: new TVTPaintOption toShowHiddenNodes to globally ignore the hidden state of nodes
|
||
// - Improvement: individual nodes can now be hidden without affecting their children
|
||
// - Improvement: re-designed Explorer theme drawing
|
||
// - Bug fix: corrected allocation problems in TBufferedAnsiString and TWideBufferedString
|
||
// March 2009
|
||
// - Bug fix: fixed an issue in TVirtualTreeColumns.HandleClick that could lead to a case where no header click event
|
||
// is triggered
|
||
// - Bug fix: fixed an issue in TBaseVirtualTree.HandleHotTrack that could lead to an endless loop under certain
|
||
// conditions
|
||
// - Improvement: removed unused variables in TVirtualTreeColumn.ComputeHeaderLayout
|
||
// - Bug fix: corrected TBaseVirtualTree.GetVisibleParent
|
||
// - Improvement: extended hot node tracking to track the hot column too
|
||
// - Improvement: new THitPosition hiOnItemButtonExact used to draw hot buttons when using Windows Vista's Explorer
|
||
// theme
|
||
// - Improvement: new TVTPaintOption toHideTreeLinesIfThemed to consider toShowTreeLines only if running unthemed
|
||
// - Improvement: new TVTPaintOption toUseExplorerTheme to draw the tree like Windows Vista's Explorer treeview
|
||
// February 2009
|
||
// - Bug fix: reverted the implementation of DrawTextW back to the one prior to 4.8.1 as the line end detection
|
||
// lead to a compiler warning under Delphi 2009
|
||
// - Bug fix: corrected implementation of GetStringDrawRect to match its declaration (UnicodeString vs WideString)
|
||
// - Bug fix: the node focus will no longer change if a TVTMiscOption.toGridExtensions is set and one clicks right of
|
||
// (or left of, if right-to-left reading) the last column
|
||
// - Bug fix: fixed an issue with TVTHeader.Assign that could lead to an access violation if the header is created at
|
||
// runtime
|
||
// - Bug fix: one can no longer change a node's height with the right mouse button even if toNodeHeightResize and
|
||
// toRightClickSelect are set
|
||
// - Improvement: TVTAutoOption.toDisableAutoScrollOnFocus now works for nodes too
|
||
// - Improvement: new property TBaseVirtualTree.SelectionLocked to disable changing the selection
|
||
// - Improvement: made the dual-scroll effect in TBaseVirtualTree.ToggleNode much smoother
|
||
// - Bug fix: removed off-by-1 errors in TBaseVirtualTree.ToggleNode
|
||
// - Bug fix: added a check for FUpdateCount to TBaseVirtualTree.SetUpdateState as otherwise every call to
|
||
// TBaseVirtualTree.DoBeforeCellPaint to get the cell content margin within an Begin/EndUpdate-block would
|
||
// re-enable painting
|
||
// - Bug fix: TVTHeader.HandleMessage could provide a wrong column index to OnBeforeColumnWidthTracking in some cases
|
||
// - Improvement: new properties TBaseVirtualTree.OnBeforeAutoFitColumn, TBaseVirtualTree.OnAfterAutoFitColumn
|
||
// - Improvement: new procedures TBaseVirtualTree.CancelOperation, TBaseVirtualTree.BeginOperation,
|
||
// TBaseVirtualTree.EndOperation and new property TBaseVirtualTree.OperationCanceled to enable the
|
||
// application to stop (possibly) long-running operations
|
||
// - Improvement: integrated changes from Andreas Hausladen
|
||
// - Improvement: integrated changes from Dmitry Zegebart where applicable
|
||
// - Bug fix: removed off-by-1 error in TBaseVirtualTree.GetDisplayRect
|
||
// - Bug fix: changed the size of the buffer used in TBaseVirtualTree.PaintTree to paint the area below the last node
|
||
// as the bitmap was not completely erased using previous size under certain conditions
|
||
// - Bug fix: fixed TBaseVirtualTree.GetPreviousLevel
|
||
// January 2009
|
||
// - Bug fix: removed off-by-1 error in TBaseVirtualTree.GetBottomNode
|
||
// - Improvement: improved speed of TBaseVirtualTree.GetMaxColumnWidth when using UseSmartColumnWidth
|
||
// - Version is now 4.8.0
|
||
// December 2008
|
||
// - Bug fix: modified TBaseVirtualTree.UpdateHorizontalScrollbar and TBaseVirtualTree.UpdateVerticalScrollbar to
|
||
// recalculate the tree's dimensions even if an update is in progress
|
||
// - Improvement: renamed TVTHeaderState hsTracking and hsTrackPending to hsColumnWidthTracking and
|
||
// hsColumnWidthTrackPending
|
||
// - Improvement: modified TBaseVirtualTree.GetFirstVisible and TBaseVirtualTree.GetFirstVisibleNoInit to optionally
|
||
// take a node to specify where to start
|
||
// - Improvement: modified TVTAfterGetMaxColumnWidthEvent to make the result of TBaseVirtualTree.GetMaxColumnWidth
|
||
// changable
|
||
// - Bug fix: corrected TBaseVirtualTree.GetMaxColumnWidth to consider toFixedIndent and no longer take nodes into
|
||
// account that are just above or below the visible area
|
||
// - Improvement: new property TVirtualTreeColumns.DefaultWidth
|
||
// - Improvement: new property TVTHeader.FixedAreaConstraints (new class TVTFixedAreaConstraints) to limit the
|
||
// fixed area (header, fixed columns) to a percentage of the client area
|
||
// November 2008
|
||
// - Improvement: new cursor added: crVertSplit used for height tracking
|
||
// - Improvement: changed type of TVTHeader.Height from Cardinal to Integer to make boundary checks easier
|
||
// - Improvement: new properties TVTHeader.MinHeight and TVTHeader.MaxHeight
|
||
// - Improvement: new VirtualTreeStates tsNodeHeightTracking and tsNodeHeightTrackPending
|
||
// - Improvement: new HeaderStates hsHeightTracking and hsHeightTrackPending
|
||
// - Improvement: new TVTMiscOption toNodeHeightResize to allow changing node heights via mouse
|
||
// - Improvement: new TVTHeaderOption hoHeightResize to allow changing header height via mouse
|
||
// - Improvement: new properties TBaseVirtualTree.OnHeaderHeightTracking, TBaseVirtualTree.OnHeaderDblClickResize,
|
||
// TBaseVirtualTree.OnColumnWidthTracking, TBaseVirtualTree.OnColumnWidthDblClickResize,
|
||
// TBaseVirtualTree.OnNodeHeightTracking, TBaseVirtualTree.OnNodeHeightDblClickResize
|
||
// - Improvement: new function TVTHeader.ResizeColumns to resize multiple columns at once
|
||
// - Improvement: TVTHeader.DetermineSplitterIndex is no longer influenced by non-resizable columns
|
||
// - Bug fix: TBaseVirtualTree.ToggleNode now uses DoStateChange to modify FStates
|
||
// - Bug fix: TBaseVirtualTree.DoBeforeCellPaint now saves the update rect if CellPaintMode is cpmGetContentMargin
|
||
// and restores it afterwards
|
||
// - Improvement: modified TBaseVirtualTree.CmMouseWheel to handle mice with wheel delta < 120 correctly
|
||
// - Improvement: modified TVTHeader.LoadFromStream and WriteToStream to save ParentFont
|
||
// - Improvement: TVTHeader.Font is now only stored by Delphi if ParentFont is False (Mantis issue #217)
|
||
// - Bug fix: corrected TVTHeader.Create to set TVTHeader.FOptions correctly to the default value (Mantis issue #333)
|
||
// - Improvement: new TVTAnimationOption toAdvancedAnimatedToggle to scroll the node to be toggled animatedly instead
|
||
// of just scroll its child nodes animatedly
|
||
// - Improvement: added VirtualTreeState tsToggling to eliminate artefacts caused by TBaseVirtualTree.DoSetOffsetXY
|
||
// while toggling
|
||
// - Bug fix: corrected button handling when toFixedIndent is set
|
||
// - Improvement: redesigned TBaseVirtualTree.ToggleNode to harmonize the visual toggle behaviour independent of
|
||
// toChildrenAbove
|
||
// - Improvement: made TBaseVirtualTree.CanEdit public
|
||
// - Improvement: added parameter ConsiderChildrenAbove to TGetNextNodeProc
|
||
// - Improvement: modified all variants of TBaseVirtualTree.GetFirst and TBaseVirtualTree.GetLast to optionally
|
||
// consider toChildrenAbove
|
||
// October 2008
|
||
// - Bugfix: removed 'FVisibleCount := 0' from TBaseVirtualTree.Clear as this would lead to incorrect VisibleCount in
|
||
// read-only mode
|
||
// - Bugfix: fixed a condition in TBaseVirtualTree.ToggleCallback that could lead to artefacts
|
||
// - Improvement: changed the implementation of TBaseVirtualTree.GetNext/GetPrevious so that no penalties occur if
|
||
// toChildrenAbove is not set
|
||
// - Improvement: TBaseVirtualTree.ToggleNode will no longer leave nodes with state vsToggeling if an exception
|
||
// occurs
|
||
// - Improvement: improved behaviour of TBaseVirtualTree.ToggleNode in case toChildrenAbove is set
|
||
// - Bug fix: corrected TBaseVirtualTree.ScrollIntoView to behave as expected when no fixed columns exist
|
||
// - Bug fix: extended TBaseVirtualTree.InitializeLineImageAndSelectLevel to eliminate artifacts while scrolling with
|
||
// toChildrenAbove set
|
||
// - Bug fix: corrected CompareNodePositions to consider toChildrenAbove
|
||
// - Bug fix: corrected ToggleNode to scroll correctly if toChildrenAbove and toAnimatedToggle are set
|
||
// - Improvement: new TVTPaintOption toFixedIndent to draw the tree with a fixed ident (instead of node level
|
||
// dependent indents)
|
||
// - Improvement: new TVTPaintOption toChildrenAbove to draw children nodes above their parent
|
||
// August 2008
|
||
// - Improvement: redesigned and overloaded TBaseVirtualTree.ScrollIntoView in order to use vertical scrolling
|
||
// separately
|
||
// - Improvement: optimized TBaseVirtualTree.ScrollIntoView for horizontal scrolling
|
||
// - Improvement: in TBaseVirtualTree.WMKeyDown column navigation for VK_PRIOR and VK_NEXT is now handled in same way
|
||
// as row navigation
|
||
// - Improvement: new TVTHeaderOption hoDisableAnimatedResize to disable animated resize for all columns
|
||
// - Improvement: new TVTColumnOption coDisableAnimatedResize to disable animated resize for a specific column
|
||
// - Improvement: in TBaseVirtualTree.UpdateHorizontalScrollBar and TBaseVirtualTree.UpdateVerticalScrollBar scrollbar
|
||
// updates now avoided for tsUpdating in FStates
|
||
// July 2008
|
||
// - Improvement: in TBaseVirtualTree.WMHScroll the horizontal page scrolling now considers fixed columns
|
||
// - Improvement: in TBaseVirtualTree.ScrollIntoView the case of FFocusedColumn being invalid is considered
|
||
// - Improvement: in TBaseVirtualTree.HandleMouseDown DoFocusNode is not called if node focus did not change
|
||
// - Improvement: in TBaseVirtualTree.SetFocusedColumn the focused node will only be invalidate if it was actually
|
||
// scrolled into view
|
||
// - Improvement: new TVTColumnOption coAllowFocus to affect column focus behaviour
|
||
// - Improvement: new function TVTHeader.AllowFocus to check wether a column can be focused
|
||
// - Improvement: in TBaseVirtualTree.SetFocusedColumn the old colunm and the new column are both invalidated
|
||
// - Improvement: merged latest changes from Jim into current code base.
|
||
// June 2008
|
||
// - Improvement: new property TVirtualTreeColumns.Count
|
||
// - Bug fix: in TVirtualTreeColumns.AnimatedResize the column is validated (to avoid "List index out of bounds")
|
||
// - Improvement: the content retangle of the cell can be modified via the OnBeforeCellPaint event, the cell paint
|
||
// mode indicates wether OnBeforeCellPaint is called for painting the cell or just for getting the
|
||
// cell content margin
|
||
// - Improvement: new functions added: TBaseVirtualTree.DoGetCellContentMargins,
|
||
// TCustomVirtualDrawTree.DoGetCellContentMargin
|
||
// - Improvement: new property: TCustomVirtualDrawTree.OnGetCellContentMargin
|
||
// - Improvement: in TBaseVirtualTree.GetMaxColumnWidth the cell content margin is considered
|
||
// - Improvement: in TBaseVirtualTree.CMHintShow the cell content margin is considered for singleline tooltips
|
||
// - Improvement: new function added: TVTHeader.DoGetPopupMenu (to query the application via TreeView.FOnGetPopupMenu
|
||
// for a column specific header popup menu)
|
||
// - Improvement: new property added: TBaseVirtualTree.OnCanSplitterResizeColumn,
|
||
// new function added: TVirtualTreeColumns.GetScrollWidth
|
||
// - Improvement: horizontal page scrolling now uses the average column width (of all visible, non-fixed columns) as
|
||
// scroll amount
|
||
// - Improvement: procedure TBaseVirtualTree.CMMouseWheel redesigned
|
||
// - Bug fix: TVTHeader.DetermineSplitterIndex works correctly even when using fixed columns
|
||
// - Bug fix: on right-to-left BiDiMode TVirtualTreeColumns.PaintHeader respects (left) scroll bar correctly
|
||
// - Bug fix: for multiline tooltips also the column width is checked to determine the tooltip is needed or
|
||
// unnecessary
|
||
// - Improvement: the result value of GetUseSmartColumnWidth is initialized correctly
|
||
// - Improvement: added hoFullRepaintOnResize to TVTHeaderOption to enable full header repainting (instead of
|
||
// repainting all subsequent columns only) on resizing a column
|
||
// - Bug fix: horizontal page scrolling via mouse wheel now works correctly, i.e. in TBaseVirtualTree.CMMouseWheel
|
||
// ScrollCount includes GetVisibleFixedWidth and FIndent
|
||
// - Improvement: new TVTColumnOption coSmartResize to avoid contradicting the virtual paradigm
|
||
// - Improvement: horizontal scrolling via mouse wheel can be forced by holding the shift key
|
||
// - Improvement: new parameter for function TBaseVirtualTree.GetMaxColumnWidth added: UseSmartColumnWidth (to
|
||
// avoid contradicting the virtual paradigm, i.e. leave nodes out of consideration which are not in
|
||
// view)
|
||
// - Improvement: new parameters for TVTHeader.AutoFitColumns added: SmartAutoFitType, RangeStartCol and
|
||
// RangeEndCol
|
||
// - Improvement: new parameters for events FOUnknownnAfterAutoFitColumns, FOnBeforeAutoFitColumns, FOnAfterGetMaxColumnWidth
|
||
// and FOnBeforeGetMaxColumnWidth added
|
||
// - Version is now 4.6.0
|
||
// May 2008
|
||
// - Improvement: new properties: FOnAfterAutoFitColumns, FOnBeforeAutoFitColumns, FOnAfterGetMaxColumnWidth and
|
||
// FOnBeforeGetMaxColumnWidth
|
||
// - Bug fix: FDropTargetNode is considered in TBaseVirtualTree.DoFreeNode
|
||
// August 2007
|
||
// - for accessibility, added an OnGetImageText event that can be used to give accessible text to images used in nodes.
|
||
// - Implemented an ImageText property used by the VTAccessibility unit to retrieve text for a given node and its column.
|
||
// - Switched loading of accessibility libraries to dynamic from static to avoid problems in Win95
|
||
// June 2007
|
||
// - Bug fix: Fixed a problem with potentially large amount of nodes (larger than 2 billion) in
|
||
// TBaseVirtualTree.SetChildCount.
|
||
// - Bug fix: remove hint if any in case the tree loses the focus.
|
||
// - Improvement: TVirtualTreeColumns.HandleClick is now virtual, introduced TVTHeader.DoSetSortColumn.
|
||
// - Bug fix: compiler error due to old variable reference when enabling flat scrollbars.
|
||
// May 2007
|
||
// - Improvement: new functions: GetPreviousSelected, GetPreviousChecked, GetCheckedCount,
|
||
// GetPreviousCutCopy, GetCutCopyCount, GetFirstLeaf, GetNextLeaf,
|
||
// GetPreviousLeaf, GetFirstLevel, GetNextLevel, GetPreviousLevel
|
||
// - Improvement: new properties: CheckedCount, CutCopyCount
|
||
// - Improvement: DoFocusChanging for finding a valid column (TBaseVirtualTree.WMKeyDown)
|
||
// March 2007
|
||
// - Improvement: adjusted accessibility implementation to compile with pre-BDS IDEs.
|
||
// - If a column is not visible, MultiColumnAccessibility now will not include it.
|
||
// January 2007
|
||
// - Improvement: added code donation from Marco Zehe (with help from Sebastian Modersohn) which implements the
|
||
// MS accessibility interface for Virtual Treeview.
|
||
// December 2006
|
||
// - Improvement: bidi mode implementation finished (toAutoBidiColumnOrdering introduced)
|
||
// - Change: right-to-left flag removed from shorten string methods/events (not necessary)
|
||
// - Version is now 4.5.0
|
||
// November 2006
|
||
// - Bug fix: Total height is wrong on reading from stream
|
||
// September 2006
|
||
// - Bug fix: Mantis issue #326
|
||
// July 2006
|
||
// - Change: value for crHeaderSplit cursor conflicts with other resource IDs, so I changed it.
|
||
// - Published OnStartDrag in VirtualDrawTree.
|
||
// April 2006
|
||
// - Bug fix: check for MMX availabiltiy is missing in some places before calling MMX code
|
||
// - Bug fix: flag for VCL dragging was removed too late causing all kind of problems with mouse up code in VCL drag mode.
|
||
// - Bug fix: If the past mode in ProcessOLEData is amInsertAfter then nodes where inserted in the wrong order.
|
||
// March 2006
|
||
// - Bug fix: total count and total height is wrong after loading from stream
|
||
// - Bug fix: variable node height computation
|
||
// - Bug fix: FLastChangedNode was not reset in DoFreeNode
|
||
// February 2006
|
||
// - Improvement: GetFirstChecked now also has a default value for its state parameter.
|
||
// - Improvement: avoid potential reentrancy problems in paint code by checking for the paint state there.
|
||
// January 2006
|
||
// - Bug fix: disabled images are now drawn like enabled ones (with respect to position, indices etc.).
|
||
// - Improvement: New property BottomSpace, allows to specify an additional area below the last node in the tree.
|
||
// - Bug fix: VT.EndUpdate did not invalidate the cache so the cache was never used again after that.
|
||
// - Improvement: tree states for double clicks (left, middle, right).
|
||
// December 2005
|
||
// - Bug fix: check for column index for auto setting main column if the current one is deleted.
|
||
//
|
||
// For full document history see help file.
|
||
//
|
||
// Credits for their valuable assistance and code donations go to:
|
||
// Freddy Ertl, Marian Aldenh<6E>vel, Thomas Bogenrieder, Jim Kuenemann, Werner Lehmann, Jens Treichler,
|
||
// Paul Gallagher (IBO tree), Ondrej Kelle, Ronaldo Melo Ferraz, Heri Bender, Roland Bed<65>rftig (BCB)
|
||
// Anthony Mills, Alexander Egorushkin (BCB), Mathias Torell (BCB), Frank van den Bergh, Vadim Sedulin, Peter Evans,
|
||
// Milan Vandrovec (BCB), Steve Moss, Joe White, David Clark, Anders Thomsen, Igor Afanasyev, Eugene Programmer,
|
||
// Corbin Dunn, Richard Pringle, Uli Gerhardt, Azza, Igor Savkic, Daniel Bauten, Timo Tegtmeier, Dmitry Zegebart,
|
||
// Andreas Hausladen
|
||
// Beta testers:
|
||
// Freddy Ertl, Hans-J<>rgen Schnorrenberg, Werner Lehmann, Jim Kueneman, Vadim Sedulin, Moritz Franckenstein,
|
||
// Wim van der Vegt, Franc v/d Westelaken
|
||
// Indirect contribution (via publicly accessible work of those persons):
|
||
// Alex Denissov, Hiroyuki Hori (MMXAsm expert)
|
||
// Documentation:
|
||
// Markus Spoettl and toolsfactory GbR (http://www.doc-o-matic.com/, sponsoring Soft Gems development
|
||
// with a free copy of the Doc-O-Matic help authoring system), Sven H. (Step by step tutorial)
|
||
// CLX:
|
||
// Dmitri Dmitrienko (initial developer)
|
||
// Source repository:
|
||
// Subversion (server), TortoiseSVN (client tools), Fisheye (Web interface)
|
||
// Accessability implementation:
|
||
// Marco Zehe (with help from Sebastian Modersohn)
|
||
// LCL Port:
|
||
// Luiz Am<41>rico Pereira C<>mara
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
interface
|
||
|
||
{$I VTConfig.inc}
|
||
|
||
uses
|
||
{$ifdef Windows}
|
||
Windows,
|
||
ActiveX,
|
||
CommCtrl,
|
||
{$else}
|
||
FakeActiveX,
|
||
{$endif}
|
||
OleUtils,
|
||
LCLIntf,
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
DelphiCompat,
|
||
LclExt,
|
||
{$endif}
|
||
virtualpanningwindow,
|
||
VTGraphics, //alpha blend functions
|
||
{$ifdef DEBUG_VTV}
|
||
vtlogger,
|
||
{$endif}
|
||
LCLType, LResources, LMessages, Types,
|
||
SysUtils, Classes, Graphics, Controls, Forms, ImgList, StdCtrls, Menus, Printers,
|
||
SyncObjs, // Thread support
|
||
Clipbrd // Clipboard support
|
||
{$ifdef ThemeSupport}
|
||
, Themes , TmSchema
|
||
{$endif ThemeSupport}
|
||
{$ifdef EnableAccessible}
|
||
, oleacc // for MSAA IAccessible support
|
||
{$endif};
|
||
|
||
const
|
||
{$I lclconstants.inc}
|
||
|
||
{$if defined(LCLGtk) or defined(LCLGtk2)}
|
||
{$define Gtk}
|
||
{$endif}
|
||
|
||
{$if defined(Gtk)}
|
||
{$define ManualClipNeeded}
|
||
{$endif}
|
||
|
||
{$if defined(LCLGtk2) or defined(LCLCarbon) or defined(LCLQt)}
|
||
{$define ContextMenuBeforeMouseUp}
|
||
{$endif}
|
||
|
||
VTMajorVersion = 4;
|
||
VTMinorVersion = 8;
|
||
VTReleaseVersion = 7;
|
||
VTTreeStreamVersion = 2;
|
||
VTHeaderStreamVersion = 6; // The header needs an own stream version to indicate changes only relevant to the header.
|
||
|
||
CacheThreshold = 2000; // Number of nodes a tree must at least have to start caching and at the same
|
||
// time the maximum number of nodes between two cache entries.
|
||
FadeAnimationStepCount = 255; // Number of animation steps for hint fading (0..255).
|
||
ShadowSize = 5; // Size in pixels of the hint shadow. This value has no influence on Win2K and XP systems
|
||
// as those OSes have native shadow support.
|
||
|
||
// Special identifiers for columns.
|
||
NoColumn = -1;
|
||
InvalidColumn = -2;
|
||
|
||
// Indices for check state images used for checking.
|
||
ckEmpty = 0; // an empty image used as place holder
|
||
// radio buttons
|
||
ckRadioUncheckedNormal = 1;
|
||
ckRadioUncheckedHot = 2;
|
||
ckRadioUncheckedPressed = 3;
|
||
ckRadioUncheckedDisabled = 4;
|
||
ckRadioCheckedNormal = 5;
|
||
ckRadioCheckedHot = 6;
|
||
ckRadioCheckedPressed = 7;
|
||
ckRadioCheckedDisabled = 8;
|
||
// check boxes
|
||
ckCheckUncheckedNormal = 9;
|
||
ckCheckUncheckedHot = 10;
|
||
ckCheckUncheckedPressed = 11;
|
||
ckCheckUncheckedDisabled = 12;
|
||
ckCheckCheckedNormal = 13;
|
||
ckCheckCheckedHot = 14;
|
||
ckCheckCheckedPressed = 15;
|
||
ckCheckCheckedDisabled = 16;
|
||
ckCheckMixedNormal = 17;
|
||
ckCheckMixedHot = 18;
|
||
ckCheckMixedPressed = 19;
|
||
ckCheckMixedDisabled = 20;
|
||
// simple button
|
||
ckButtonNormal = 21;
|
||
ckButtonHot = 22;
|
||
ckButtonPressed = 23;
|
||
ckButtonDisabled = 24;
|
||
|
||
// Instead using a TTimer class for each of the various events I use Windows timers with messages
|
||
// as this is more economical.
|
||
ExpandTimer = 1;
|
||
EditTimer = 2;
|
||
ScrollTimer = 4;
|
||
ChangeTimer = 5;
|
||
StructureChangeTimer = 6;
|
||
SearchTimer = 7;
|
||
|
||
// Need to use this message to release the edit link interface asynchronously.
|
||
WM_CHANGESTATE = WM_APP + 32;
|
||
|
||
// Virtual Treeview does not need to be subclassed by an eventual Theme Manager instance as it handles
|
||
// Windows XP theme painting itself. Hence the special message is used to prevent subclassing.
|
||
CM_DENYSUBCLASSING = CM_BASE + 2000;
|
||
|
||
// Decoupling message for auto-adjusting the internal edit window.
|
||
CM_AUTOADJUST = CM_BASE + 2005;
|
||
|
||
// VT's own clipboard formats,
|
||
// Note: The reference format is used internally to allow to link to a tree reference
|
||
// to implement optimized moves and other back references.
|
||
CFSTR_VIRTUALTREE = 'Virtual Tree Data';
|
||
CFSTR_VTREFERENCE = 'Virtual Tree Reference';
|
||
CFSTR_HTML = 'HTML Format';
|
||
CFSTR_RTF = 'Rich Text Format';
|
||
CFSTR_RTFNOOBJS = 'Rich Text Format Without Objects';
|
||
CFSTR_CSV = 'CSV';
|
||
|
||
// Drag image helpers for Windows 2000 and up.
|
||
IID_IDropTargetHelper: TGUID = (D1: $4657278B; D2: $411B; D3: $11D2; D4: ($83, $9A, $00, $C0, $4F, $D9, $18, $D0));
|
||
IID_IDragSourceHelper: TGUID = (D1: $DE5BF786; D2: $477A; D3: $11D2; D4: ($83, $9D, $00, $C0, $4F, $D9, $18, $D0));
|
||
IID_IDropTarget: TGUID = (D1: $00000122; D2: $0000; D3: $0000; D4: ($C0, $00, $00, $00, $00, $00, $00, $46));
|
||
CLSID_DragDropHelper: TGUID = (D1: $4657278A; D2: $411B; D3: $11D2; D4: ($83, $9A, $00, $C0, $4F, $D9, $18, $D0));
|
||
|
||
SID_IDropTargetHelper = '{4657278B-411B-11D2-839A-00C04FD918D0}';
|
||
SID_IDragSourceHelper = '{DE5BF786-477A-11D2-839D-00C04FD918D0}';
|
||
SID_IDropTarget = '{00000122-0000-0000-C000-000000000046}';
|
||
|
||
// Help identifiers for exceptions. Application developers are responsible to link them with actual help topics.
|
||
hcTFEditLinkIsNil = 2000;
|
||
hcTFWrongMoveError = 2001;
|
||
hcTFWrongStreamFormat = 2002;
|
||
hcTFWrongStreamVersion = 2003;
|
||
hcTFStreamTooSmall = 2004;
|
||
hcTFCorruptStream1 = 2005;
|
||
hcTFCorruptStream2 = 2006;
|
||
hcTFClipboardFailed = 2007;
|
||
hcTFCannotSetUserData = 2008;
|
||
|
||
// Header standard split cursor.
|
||
crHeaderSplit = TCursor(63);
|
||
|
||
// Height changing cursor.
|
||
crVertSplit = TCursor(62);
|
||
//Panning Cursors
|
||
crVT_MOVEALL = TCursor(64);
|
||
crVT_MOVEEW = TCursor(65);
|
||
crVT_MOVENS = TCursor(66);
|
||
crVT_MOVENW = TCursor(67);
|
||
crVT_MOVESW = TCursor(68);
|
||
crVT_MOVENE = TCursor(69);
|
||
crVT_MOVESE = TCursor(70);
|
||
crVT_MOVEW = TCursor(71);
|
||
crVT_MOVEE = TCursor(72);
|
||
crVT_MOVEN = TCursor(73);
|
||
crVT_MOVES = TCursor(74);
|
||
|
||
UtilityImageSize = 16; // Needed by descendants for hittests.
|
||
|
||
{$if defined(LCLCarbon) or defined(LCLCocoa)}
|
||
ssCtrlOS = ssMeta; // Mac OS X fix
|
||
{$else}
|
||
ssCtrlOS = ssCtrl;
|
||
{$endif}
|
||
|
||
var // Clipboard format IDs used in OLE drag'n drop and clipboard transfers.
|
||
CF_VIRTUALTREE,
|
||
CF_VTREFERENCE,
|
||
CF_VRTF,
|
||
CF_VRTFNOOBJS, // Unfortunately CF_RTF* is already defined as being
|
||
// registration strings so I have to use different identifiers.
|
||
CF_HTML,
|
||
CF_CSV: Word;
|
||
|
||
MMXAvailable: Boolean; // necessary to know because the blend code uses MMX instructions
|
||
|
||
IsWinNT: Boolean; // Necessary to fix bugs in Win95/WinME (non-client area region intersection, edit resize)
|
||
// and to allow for check of system dependent hint animation.
|
||
IsWinVistaOrAbove: Boolean;
|
||
|
||
type
|
||
// The exception used by the trees.
|
||
EVirtualTreeError = class(Exception);
|
||
|
||
// Limits the speed interval which can be used for auto scrolling (milliseconds).
|
||
TAutoScrollInterval = 1..1000;
|
||
|
||
// Need to declare the correct WMNCPaint record as the VCL (D5-) doesn't.
|
||
TRealWMNCPaint = packed record
|
||
Msg: Cardinal;
|
||
Rgn: HRGN;
|
||
lParam: Integer;
|
||
Result: Integer;
|
||
end;
|
||
|
||
// Be careful when adding new states as this might change the size of the type which in turn
|
||
// changes the alignment in the node record as well as the stream chunks.
|
||
// Do not reorder the states and always add new states at the end of this enumeration in order to avoid
|
||
// breaking existing code.
|
||
TVirtualNodeState = (
|
||
vsInitialized, // Set after the node has been initialized.
|
||
vsChecking, // Node's check state is changing, avoid propagation.
|
||
vsCutOrCopy, // Node is selected as cut or copy and paste source.
|
||
vsDisabled, // Set if node is disabled.
|
||
vsDeleting, // Set when the node is about to be freed.
|
||
vsExpanded, // Set if the node is expanded.
|
||
vsHasChildren, // Indicates the presence of child nodes without actually setting them.
|
||
vsVisible, // Indicate whether the node is visible or not (independant of the expand states of its parents).
|
||
vsSelected, // Set if the node is in the current selection.
|
||
vsInitialUserData, // Set if (via AddChild or InsertNode) initial user data has been set which requires OnFreeNode.
|
||
vsAllChildrenHidden, // Set if vsHasChildren is set and no child node has the vsVisible flag set.
|
||
vsClearing, // A node's children are being deleted. Don't register structure change event.
|
||
vsMultiline, // Node text is wrapped at the cell boundaries instead of being shorted.
|
||
vsHeightMeasured, // Node height has been determined and does not need a recalculation.
|
||
vsToggling // Set when a node is expanded/collapsed to prevent recursive calls.
|
||
);
|
||
TVirtualNodeStates = set of TVirtualNodeState;
|
||
|
||
// States used in InitNode to indicate states a node shall initially have.
|
||
TVirtualNodeInitState = (
|
||
ivsDisabled,
|
||
ivsExpanded,
|
||
ivsHasChildren,
|
||
ivsMultiline,
|
||
ivsSelected
|
||
);
|
||
TVirtualNodeInitStates = set of TVirtualNodeInitState;
|
||
|
||
TVTScrollBarStyle = (
|
||
sbmRegular,
|
||
sbmFlat,
|
||
sbm3D
|
||
);
|
||
|
||
// Options per column.
|
||
TVTColumnOption = (
|
||
coAllowClick, // Column can be clicked (must be enabled too).
|
||
coDraggable, // Column can be dragged.
|
||
coEnabled, // Column is enabled.
|
||
coParentBidiMode, // Column uses the parent's bidi mode.
|
||
coParentColor, // Column uses the parent's background color.
|
||
coResizable, // Column can be resized.
|
||
coShowDropMark, // Column shows the drop mark if it is currently the drop target.
|
||
coVisible, // Column is shown.
|
||
coAutoSpring, // Column takes part in the auto spring feature of the header (must be resizable too).
|
||
coFixed, // Column is fixed and can not be selected or scrolled etc.
|
||
coSmartResize, // Column is resized to its largest entry which is in view (instead of its largest
|
||
// visible entry).
|
||
coAllowFocus, // Column can be focused.
|
||
coDisableAnimatedResize, // Column resizing is not animated.
|
||
coWrapCaption, // Caption could be wrapped across several header lines to fit columns width.
|
||
coUseCaptionAlignment // Column's caption has its own aligment.
|
||
);
|
||
TVTColumnOptions = set of TVTColumnOption;
|
||
|
||
// These flags are used to indicate where a click in the header happened.
|
||
TVTHeaderHitPosition = (
|
||
hhiNoWhere, // No column is involved (possible only if the tree is smaller than the client area).
|
||
hhiOnColumn, // On a column.
|
||
hhiOnIcon, // On the bitmap associated with a column.
|
||
hhiOnCheckbox // On the checkbox if enabled.
|
||
);
|
||
TVTHeaderHitPositions = set of TVTHeaderHitPosition;
|
||
|
||
// These flags are returned by the hit test method.
|
||
THitPosition = (
|
||
hiAbove, // above the client area (if relative) or the absolute tree area
|
||
hiBelow, // below the client area (if relative) or the absolute tree area
|
||
hiNowhere, // no node is involved (possible only if the tree is not as tall as the client area)
|
||
hiOnItem, // on the bitmaps/buttons or label associated with an item
|
||
hiOnItemButton, // on the button associated with an item
|
||
hiOnItemButtonExact, // exactly on the button associated with an item
|
||
hiOnItemCheckbox, // on the checkbox if enabled
|
||
hiOnItemIndent, // in the indentation area in front of a node
|
||
hiOnItemLabel, // on the normal text area associated with an item
|
||
hiOnItemLeft, // in the area to the left of a node's text area (e.g. when right aligned or centered)
|
||
hiOnItemRight, // in the area to the right of a node's text area (e.g. if left aligned or centered)
|
||
hiOnNormalIcon, // on the "normal" image
|
||
hiOnStateIcon, // on the state image
|
||
hiToLeft, // to the left of the client area (if relative) or the absolute tree area
|
||
hiToRight, // to the right of the client area (if relative) or the absolute tree area
|
||
hiUpperSplitter, // in the upper splitter area of a node
|
||
hiLowerSplitter // in the lower splitter area of a node
|
||
);
|
||
THitPositions = set of THitPosition;
|
||
|
||
TCheckType = (
|
||
ctNone,
|
||
ctTriStateCheckBox,
|
||
ctCheckBox,
|
||
ctRadioButton,
|
||
ctButton
|
||
);
|
||
|
||
// The check states include both, transient and fluent (temporary) states. The only temporary state defined so
|
||
// far is the pressed state.
|
||
TCheckState = (
|
||
csUncheckedNormal, // unchecked and not pressed
|
||
csUncheckedPressed, // unchecked and pressed
|
||
csCheckedNormal, // checked and not pressed
|
||
csCheckedPressed, // checked and pressed
|
||
csMixedNormal, // 3-state check box and not pressed
|
||
csMixedPressed // 3-state check box and pressed
|
||
);
|
||
|
||
TCheckImageKind = (
|
||
ckLightCheck, // gray cross
|
||
ckDarkCheck, // black cross
|
||
ckLightTick, // gray tick mark
|
||
ckDarkTick, // black tick mark
|
||
ckFlat, // flat images (no 3D border)
|
||
ckXP, // Windows XP style
|
||
ckCustom, // application defined check images
|
||
ckSystemFlat, // Flat system defined check images.
|
||
ckSystemDefault // Uses the system check images, theme aware.
|
||
);
|
||
|
||
// mode to describe a move action
|
||
TVTNodeAttachMode = (
|
||
amNoWhere, // just for simplified tests, means to ignore the Add/Insert command
|
||
amInsertBefore, // insert node just before destination (as sibling of destination)
|
||
amInsertAfter, // insert node just after destionation (as sibling of destination)
|
||
amAddChildFirst, // add node as first child of destination
|
||
amAddChildLast // add node as last child of destination
|
||
);
|
||
|
||
// modes to determine drop position further
|
||
TDropMode = (
|
||
dmNowhere,
|
||
dmAbove,
|
||
dmOnNode,
|
||
dmBelow
|
||
);
|
||
|
||
// operations basically allowed during drag'n drop
|
||
TDragOperation = (
|
||
doCopy,
|
||
doMove,
|
||
doLink
|
||
);
|
||
TDragOperations = set of TDragOperation;
|
||
|
||
TVTImageKind = (
|
||
ikNormal,
|
||
ikSelected,
|
||
ikState,
|
||
ikOverlay
|
||
);
|
||
|
||
TVTHintMode = (
|
||
hmDefault, // show the hint of the control
|
||
hmHint, // show node specific hint string returned by the application
|
||
hmHintAndDefault, // same as hmHint but show the control's hint if no node is concerned
|
||
hmTooltip // show the text of the node if it isn't already fully shown
|
||
);
|
||
|
||
// Indicates how to format a tooltip.
|
||
TVTTooltipLineBreakStyle = (
|
||
hlbDefault, // Use multi-line style of the node.
|
||
hlbForceSingleLine, // Use single line hint.
|
||
hlbForceMultiLine // Use multi line hint.
|
||
);
|
||
|
||
TMouseButtons = set of TMouseButton;
|
||
|
||
// Used to describe the action to do when using the OnBeforeItemErase event.
|
||
TItemEraseAction = (
|
||
eaColor, // Use the provided color to erase the background instead the one of the tree.
|
||
eaDefault, // The tree should erase the item's background (bitmap or solid).
|
||
eaNone // Do nothing. Let the application paint the background.
|
||
);
|
||
|
||
|
||
// There is a heap of switchable behavior in the tree. Since published properties may never exceed 4 bytes,
|
||
// which limits sets to at most 32 members, and because for better overview tree options are splitted
|
||
// in various sub-options and are held in a commom options class.
|
||
//
|
||
// Options to customize tree appearance:
|
||
TVTPaintOption = (
|
||
toHideFocusRect, // Avoid drawing the dotted rectangle around the currently focused node.
|
||
toHideSelection, // Selected nodes are drawn as unselected nodes if the tree is unfocused.
|
||
toHotTrack, // Track which node is under the mouse cursor.
|
||
toPopupMode, // Paint tree as would it always have the focus (useful for tree combo boxes etc.)
|
||
toShowBackground, // Use the background image if there's one.
|
||
toShowButtons, // Display collapse/expand buttons left to a node.
|
||
toShowDropmark, // Show the dropmark during drag'n drop operations.
|
||
toShowHorzGridLines, // Display horizontal lines to simulate a grid.
|
||
toShowRoot, // Show lines also at top level (does not show the hidden/internal root node).
|
||
toShowTreeLines, // Display tree lines to show hierarchy of nodes.
|
||
toShowVertGridLines, // Display vertical lines (depending on columns) to simulate a grid.
|
||
toThemeAware, // Draw UI elements (header, tree buttons etc.) according to the current theme if
|
||
// enabled (Windows XP+ only, application must be themed).
|
||
toUseBlendedImages, // Enable alpha blending for ghosted nodes or those which are being cut/copied.
|
||
toGhostedIfUnfocused, // Ghosted images are still shown as ghosted if unfocused (otherwise the become non-ghosted
|
||
// images).
|
||
toFullVertGridLines, // Display vertical lines over the full client area, not only the space occupied by nodes.
|
||
// This option only has an effect if toShowVertGridLines is enabled too.
|
||
toAlwaysHideSelection, // Do not draw node selection, regardless of focused state.
|
||
toUseBlendedSelection, // Enable alpha blending for node selections.
|
||
toStaticBackground, // Show simple static background instead of a tiled one.
|
||
toChildrenAbove, // Display child nodes above their parent.
|
||
toFixedIndent, // Draw the tree with a fixed indent.
|
||
toUseExplorerTheme // Use the explorer theme if run under Windows Vista (or above).
|
||
);
|
||
TVTPaintOptions = set of TVTPaintOption;
|
||
|
||
// Options to toggle animation support:
|
||
TVTAnimationOption = (
|
||
toAnimatedToggle, // Expanding and collapsing a node is animated (quick window scroll).
|
||
toAdvancedAnimatedToggle // Do some advanced animation effects when toggling a node.
|
||
);
|
||
TVTAnimationOptions = set of TVTAnimationOption;
|
||
|
||
// Options which toggle automatic handling of certain situations:
|
||
TVTAutoOption = (
|
||
toAutoDropExpand, // Expand node if it is the drop target for more than a certain time.
|
||
toAutoExpand, // Nodes are expanded (collapsed) when getting (losing) the focus.
|
||
toAutoScroll, // Scroll if mouse is near the border while dragging or selecting.
|
||
toAutoScrollOnExpand, // Scroll as many child nodes in view as possible after expanding a node.
|
||
toAutoSort, // Sort tree when Header.SortColumn or Header.SortDirection change or sort node if
|
||
// child nodes are added.
|
||
toAutoSpanColumns, // Large entries continue into next column(s) if there's no text in them (no clipping).
|
||
toAutoTristateTracking, // Checkstates are automatically propagated for tri state check boxes.
|
||
toAutoHideButtons, // Node buttons are hidden when there are child nodes, but all are invisible.
|
||
toAutoDeleteMovedNodes, // Delete nodes which where moved in a drag operation (if not directed otherwise).
|
||
toDisableAutoscrollOnFocus, // Disable scrolling a node or column into view if it gets focused.
|
||
toAutoChangeScale, // Change default node height automatically if the system's font scale is set to big fonts.
|
||
toAutoFreeOnCollapse, // Frees any child node after a node has been collapsed (HasChildren flag stays there).
|
||
toDisableAutoscrollOnEdit, // Do not center a node horizontally when it is edited.
|
||
toAutoBidiColumnOrdering // When set then columns (if any exist) will be reordered from lowest index to highest index
|
||
// and vice versa when the tree's bidi mode is changed.
|
||
);
|
||
TVTAutoOptions = set of TVTAutoOption;
|
||
|
||
// Options which determine the tree's behavior when selecting nodes:
|
||
TVTSelectionOption = (
|
||
toDisableDrawSelection, // Prevent user from selecting with the selection rectangle in multiselect mode.
|
||
toExtendedFocus, // Entries other than in the main column can be selected, edited etc.
|
||
toFullRowSelect, // Hit test as well as selection highlight are not constrained to the text of a node.
|
||
toLevelSelectConstraint, // Constrain selection to the same level as the selection anchor.
|
||
toMiddleClickSelect, // Allow selection, dragging etc. with the middle mouse button. This and toWheelPanning
|
||
// are mutual exclusive.
|
||
toMultiSelect, // Allow more than one node to be selected.
|
||
toRightClickSelect, // Allow selection, dragging etc. with the right mouse button.
|
||
toSiblingSelectConstraint, // Constrain selection to nodes with same parent.
|
||
toCenterScrollIntoView, // Center nodes vertically in the client area when scrolling into view.
|
||
toSimpleDrawSelection // Simplifies draw selection, so a node's caption does not need to intersect with the
|
||
// selection rectangle.
|
||
);
|
||
TVTSelectionOptions = set of TVTSelectionOption;
|
||
|
||
// Options which do not fit into any of the other groups:
|
||
TVTMiscOption = (
|
||
toAcceptOLEDrop, // Register tree as OLE accepting drop target
|
||
toCheckSupport, // Show checkboxes/radio buttons.
|
||
toEditable, // Node captions can be edited.
|
||
toFullRepaintOnResize, // Fully invalidate the tree when its window is resized (CS_HREDRAW/CS_VREDRAW).
|
||
toGridExtensions, // Use some special enhancements to simulate and support grid behavior.
|
||
toInitOnSave, // Initialize nodes when saving a tree to a stream.
|
||
toReportMode, // Tree behaves like TListView in report mode.
|
||
toToggleOnDblClick, // Toggle node expansion state when it is double clicked.
|
||
toWheelPanning, // Support for mouse panning (wheel mice only). This option and toMiddleClickSelect are
|
||
// mutal exclusive, where panning has precedence.
|
||
toReadOnly, // The tree does not allow to be modified in any way. No action is executed and
|
||
// node editing is not possible.
|
||
toVariableNodeHeight, // When set then GetNodeHeight will trigger OnMeasureItem to allow variable node heights.
|
||
toFullRowDrag, // Start node dragging by clicking anywhere in it instead only on the caption or image.
|
||
// Must be used together with toDisableDrawSelection.
|
||
toNodeHeightResize, // Allows changing a node's height via mouse.
|
||
toNodeHeightDblClickResize, // Allows to reset a node's height to FDefaultNodeHeight via a double click.
|
||
toEditOnClick, // Editing mode can be entered with a single click
|
||
toEditOnDblClick // Editing mode can be entered with a double click
|
||
);
|
||
TVTMiscOptions = set of TVTMiscOption;
|
||
|
||
// Options to control data export
|
||
TVTExportMode = (
|
||
emAll, // export all records (regardless checked state)
|
||
emChecked, // export checked records only
|
||
emUnchecked // export unchecked records only
|
||
);
|
||
|
||
const
|
||
DefaultPaintOptions = [toShowButtons, toShowDropmark, toShowTreeLines, toShowRoot, toThemeAware, toUseBlendedImages];
|
||
DefaultAnimationOptions = [];
|
||
DefaultAutoOptions = [toAutoDropExpand, toAutoTristateTracking, toAutoScrollOnExpand, toAutoDeleteMovedNodes];
|
||
DefaultSelectionOptions = [];
|
||
DefaultMiscOptions = [toAcceptOLEDrop, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning,
|
||
toEditOnClick];
|
||
DefaultColumnOptions = [coAllowClick, coDraggable, coEnabled, coParentColor, coParentBidiMode, coResizable,
|
||
coShowDropmark, coVisible, coAllowFocus];
|
||
|
||
type
|
||
TBaseVirtualTree = class;
|
||
TVirtualTreeClass = class of TBaseVirtualTree;
|
||
|
||
PVirtualNode = ^TVirtualNode;
|
||
|
||
TColumnIndex = type Integer;
|
||
TColumnPosition = type Cardinal;
|
||
|
||
// This record must already be defined here and not later because otherwise BCB users will not be able
|
||
// to compile (conversion done by BCB is wrong).
|
||
TCacheEntry = record
|
||
Node: PVirtualNode;
|
||
AbsoluteTop: Cardinal;
|
||
end;
|
||
|
||
TCache = array of TCacheEntry;
|
||
TNodeArray = array of PVirtualNode;
|
||
|
||
TCustomVirtualTreeOptions = class(TPersistent)
|
||
private
|
||
FOwner: TBaseVirtualTree;
|
||
FPaintOptions: TVTPaintOptions;
|
||
FAnimationOptions: TVTAnimationOptions;
|
||
FAutoOptions: TVTAutoOptions;
|
||
FSelectionOptions: TVTSelectionOptions;
|
||
FMiscOptions: TVTMiscOptions;
|
||
FExportMode: TVTExportMode;
|
||
procedure SetAnimationOptions(const Value: TVTAnimationOptions);
|
||
procedure SetAutoOptions(const Value: TVTAutoOptions);
|
||
procedure SetMiscOptions(const Value: TVTMiscOptions);
|
||
procedure SetPaintOptions(const Value: TVTPaintOptions);
|
||
procedure SetSelectionOptions(const Value: TVTSelectionOptions);
|
||
protected
|
||
property AnimationOptions: TVTAnimationOptions read FAnimationOptions write SetAnimationOptions
|
||
default DefaultAnimationOptions;
|
||
property AutoOptions: TVTAutoOptions read FAutoOptions write SetAutoOptions default DefaultAutoOptions;
|
||
property ExportMode: TVTExportMode read FExportMode write FExportMode default emAll;
|
||
property MiscOptions: TVTMiscOptions read FMiscOptions write SetMiscOptions default DefaultMiscOptions;
|
||
property PaintOptions: TVTPaintOptions read FPaintOptions write SetPaintOptions default DefaultPaintOptions;
|
||
property SelectionOptions: TVTSelectionOptions read FSelectionOptions write SetSelectionOptions
|
||
default DefaultSelectionOptions;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree); virtual;
|
||
|
||
procedure AssignTo(Dest: TPersistent); override;
|
||
|
||
property Owner: TBaseVirtualTree read FOwner;
|
||
end;
|
||
|
||
TTreeOptionsClass = class of TCustomVirtualTreeOptions;
|
||
|
||
TVirtualTreeOptions = class(TCustomVirtualTreeOptions)
|
||
published
|
||
property AnimationOptions;
|
||
property AutoOptions;
|
||
property ExportMode;
|
||
property MiscOptions;
|
||
property PaintOptions;
|
||
property SelectionOptions;
|
||
end;
|
||
|
||
// Used in the CF_VTREFERENCE clipboard format.
|
||
PVTReference = ^TVTReference;
|
||
TVTReference = record
|
||
Process: Cardinal;
|
||
Tree: TBaseVirtualTree;
|
||
end;
|
||
|
||
TVirtualNode = record
|
||
Index, // index of node with regard to its parent
|
||
ChildCount: Cardinal; // number of child nodes
|
||
NodeHeight: Word; // height in pixels
|
||
States: TVirtualNodeStates; // states describing various properties of the node (expanded, initialized etc.)
|
||
Align: Byte; // line/button alignment
|
||
CheckState: TCheckState; // indicates the current check state (e.g. checked, pressed etc.)
|
||
CheckType: TCheckType; // indicates which check type shall be used for this node
|
||
Dummy: Byte; // dummy value to fill DWORD boundary
|
||
TotalCount, // sum of this node, all of its child nodes and their child nodes etc.
|
||
TotalHeight: Cardinal; // height in pixels this node covers on screen including the height of all of its
|
||
// children
|
||
// Note: Some copy routines require that all pointers (as well as the data area) in a node are
|
||
// located at the end of the node! Hence if you want to add new member fields (except pointers to internal
|
||
// data) then put them before field Parent.
|
||
Parent, // reference to the node's parent (for the root this contains the treeview)
|
||
PrevSibling, // link to the node's previous sibling or nil if it is the first node
|
||
NextSibling, // link to the node's next sibling or nil if it is the last node
|
||
FirstChild, // link to the node's first child...
|
||
LastChild: PVirtualNode; // link to the node's last child...
|
||
Data: record end; // this is a placeholder, each node gets extra data determined by NodeDataSize
|
||
end;
|
||
|
||
// Structure used when info about a certain position in the tree is needed.
|
||
THitInfo = record
|
||
HitNode: PVirtualNode;
|
||
HitPositions: THitPositions;
|
||
HitColumn: TColumnIndex;
|
||
end;
|
||
|
||
// auto scroll directions
|
||
TScrollDirections = set of (
|
||
sdLeft,
|
||
sdUp,
|
||
sdRight,
|
||
sdDown
|
||
);
|
||
|
||
// OLE drag'n drop support
|
||
TFormatEtcArray = array of TFormatEtc;
|
||
TFormatArray = array of Word;
|
||
|
||
// IDataObject.SetData support
|
||
TInternalStgMedium = packed record
|
||
Format: TClipFormat;
|
||
Medium: TStgMedium;
|
||
end;
|
||
TInternalStgMediumArray = array of TInternalStgMedium;
|
||
|
||
TEnumFormatEtc = class(TInterfacedObject, IEnumFormatEtc)
|
||
private
|
||
FTree: TBaseVirtualTree;
|
||
FFormatEtcArray: TFormatEtcArray;
|
||
FCurrentIndex: Integer;
|
||
public
|
||
constructor Create(Tree: TBaseVirtualTree; AFormatEtcArray: TFormatEtcArray);
|
||
|
||
function Clone(out Enum: IEnumFormatEtc): HResult; stdcall;
|
||
function Next(celt: LongWord; out elt: FormatEtc;pceltFetched:pULong=nil): HResult; stdcall;
|
||
function Reset: HResult; stdcall;
|
||
function Skip(celt: LongWord): HResult; stdcall;
|
||
end;
|
||
|
||
// ----- OLE drag'n drop handling
|
||
|
||
IDropTargetHelper = interface(IUnknown)
|
||
[SID_IDropTargetHelper]
|
||
function DragEnter(hwndTarget: HWND; pDataObject: IDataObject; var ppt: TPoint; dwEffect: LongWord): HRESULT; stdcall;
|
||
function DragLeave: HRESULT; stdcall;
|
||
function DragOver(var ppt: TPoint; dwEffect: LongWord): HRESULT; stdcall;
|
||
function Drop(pDataObject: IDataObject; var ppt: TPoint; dwEffect: LongWord): HRESULT; stdcall;
|
||
function Show(fShow: Boolean): HRESULT; stdcall;
|
||
end;
|
||
|
||
PSHDragImage = ^TSHDragImage;
|
||
TSHDragImage = packed record
|
||
sizeDragImage: TSize;
|
||
ptOffset: TPoint;
|
||
hbmpDragImage: HBITMAP;
|
||
ColorRef: TColorRef;
|
||
end;
|
||
|
||
IDragSourceHelper = interface(IUnknown)
|
||
[SID_IDragSourceHelper]
|
||
function InitializeFromBitmap(var SHDragImage: TSHDragImage; pDataObject: IDataObject): HRESULT; stdcall;
|
||
function InitializeFromWindow(Window: HWND; var ppt: TPoint; pDataObject: IDataObject): HRESULT; stdcall;
|
||
end;
|
||
|
||
IVTDragManager = interface(IUnknown)
|
||
['{C4B25559-14DA-446B-8901-0C879000EB16}']
|
||
procedure ForceDragLeave; stdcall;
|
||
function GetDataObject: IDataObject; stdcall;
|
||
function GetDragSource: TBaseVirtualTree; stdcall;
|
||
function GetDropTargetHelperSupported: Boolean; stdcall;
|
||
function GetIsDropTarget: Boolean; stdcall;
|
||
|
||
property DataObject: IDataObject read GetDataObject;
|
||
property DragSource: TBaseVirtualTree read GetDragSource;
|
||
property DropTargetHelperSupported: Boolean read GetDropTargetHelperSupported;
|
||
property IsDropTarget: Boolean read GetIsDropTarget;
|
||
end;
|
||
|
||
// This data object is used in two different places. One is for clipboard operations and the other while dragging.
|
||
TVTDataObject = class(TInterfacedObject, IDataObject)
|
||
private
|
||
FOwner: TBaseVirtualTree; // The tree which provides clipboard or drag data.
|
||
FForClipboard: Boolean; // Determines which data to render with GetData.
|
||
FFormatEtcArray: TFormatEtcArray;
|
||
FInternalStgMediumArray: TInternalStgMediumArray; // The available formats in the DataObject
|
||
FAdviseHolder: IDataAdviseHolder; // Reference to an OLE supplied implementation for advising.
|
||
protected
|
||
function CanonicalIUnknown(TestUnknown: IUnknown): IUnknown;
|
||
function EqualFormatEtc(FormatEtc1, FormatEtc2: TFormatEtc): Boolean;
|
||
function FindFormatEtc(TestFormatEtc: TFormatEtc; const FormatEtcArray: TFormatEtcArray): integer;
|
||
function FindInternalStgMedium(Format: TClipFormat): PStgMedium;
|
||
function HGlobalClone(HGlobal: THandle): THandle;
|
||
function RenderInternalOLEData(const FormatEtcIn: TFormatEtc; var Medium: TStgMedium; var OLEResult: HResult): Boolean;
|
||
function StgMediumIncRef(const InStgMedium: TStgMedium; var OutStgMedium: TStgMedium;
|
||
CopyInMedium: Boolean; DataObject: IDataObject): HRESULT;
|
||
|
||
property ForClipboard: Boolean read FForClipboard;
|
||
property FormatEtcArray: TFormatEtcArray read FFormatEtcArray write FFormatEtcArray;
|
||
property InternalStgMediumArray: TInternalStgMediumArray read FInternalStgMediumArray write FInternalStgMediumArray;
|
||
property Owner: TBaseVirtualTree read FOwner;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree; ForClipboard: Boolean); virtual;
|
||
destructor Destroy; override;
|
||
|
||
function DAdvise(const FormatEtc: TFormatEtc; advf: DWord; const advSink: IAdviseSink; out dwConnection: DWord):
|
||
HResult; virtual; stdcall;
|
||
function DUnadvise(dwConnection: DWord): HResult; virtual; stdcall;
|
||
Function EnumDAdvise(out enumAdvise : IEnumStatData):HResult;virtual;StdCall;
|
||
function EnumFormatEtc(Direction: DWord; out EnumFormatEtc: IEnumFormatEtc): HResult; virtual; stdcall;
|
||
function GetCanonicalFormatEtc(const pformatetcIn : FORMATETC;Out pformatetcOut : FORMATETC):HResult; virtual; STDCALl;
|
||
function GetData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium): HResult; virtual; stdcall;
|
||
function GetDataHere(const FormatEtc: TFormatEtc; out Medium: TStgMedium): HResult; virtual; stdcall;
|
||
function QueryGetData(const FormatEtc: TFormatEtc): HResult; virtual; stdcall;
|
||
function SetData(const FormatEtc: TFormatEtc; const Medium: TStgMedium; DoRelease: BOOL): HResult; virtual; stdcall;
|
||
end;
|
||
|
||
// TVTDragManager is a class to manage drag and drop in a Virtual Treeview.
|
||
TVTDragManager = class(TInterfacedObject, IVTDragManager, IDropSource, IDropTarget)
|
||
private
|
||
FOwner, // The tree which is responsible for drag management.
|
||
FDragSource: TBaseVirtualTree; // Reference to the source tree if the source was a VT, might be different than
|
||
// the owner tree.
|
||
FIsDropTarget: Boolean; // True if the owner is currently the drop target.
|
||
FDataObject: IDataObject; // A reference to the data object passed in by DragEnter (only used when the owner
|
||
// tree is the current drop target).
|
||
FDropTargetHelper: IDropTargetHelper; // Win2k > Drag image support
|
||
FFullDragging: BOOL; // True, if full dragging is currently enabled in the system.
|
||
|
||
function GetDataObject: IDataObject; stdcall;
|
||
function GetDragSource: TBaseVirtualTree; stdcall;
|
||
function GetDropTargetHelperSupported: Boolean; stdcall;
|
||
function GetIsDropTarget: Boolean; stdcall;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree); virtual;
|
||
destructor Destroy; override;
|
||
|
||
function DragEnter(const DataObject: IDataObject; KeyState: LongWord; Pt: TPoint;
|
||
var Effect: LongWord): HResult; stdcall;
|
||
function DragLeave: HResult; stdcall;
|
||
function DragOver(KeyState: LongWord; Pt: TPoint; var Effect: LongWord): HResult; stdcall;
|
||
function Drop(const DataObject: IDataObject; KeyState: LongWord; Pt: TPoint; var Effect: LongWord): HResult; stdcall;
|
||
procedure ForceDragLeave; stdcall;
|
||
{$IF (FPC_FULLVERSION < 020601) and DEFINED(LCLWin32)}
|
||
function GiveFeedback(Effect: Longint): HResult; stdcall;
|
||
function QueryContinueDrag(EscapePressed: BOOL; KeyState: Longint): HResult; stdcall;
|
||
{$ELSE}
|
||
function GiveFeedback(Effect: LongWord): HResult; stdcall;
|
||
function QueryContinueDrag(EscapePressed: BOOL; KeyState: LongWord): HResult; stdcall;
|
||
{$ENDIF}
|
||
end;
|
||
|
||
PVTHintData = ^TVTHintData;
|
||
TVTHintData = record
|
||
Tree: TBaseVirtualTree;
|
||
Node: PVirtualNode;
|
||
Column: TColumnIndex;
|
||
HintRect: TRect; // used for draw trees only, string trees get the size from the hint string
|
||
DefaultHint: String; // used only if there is no node specific hint string available
|
||
// or a header hint is about to appear
|
||
HintText: String; // set when size of the hint window is calculated
|
||
HintInfo: PHintInfo;
|
||
end;
|
||
|
||
// The trees need an own hint window class because of Unicode output and adjusted font.
|
||
|
||
{ TVirtualTreeHintWindow }
|
||
|
||
TVirtualTreeHintWindow = class(THintWindow)
|
||
private
|
||
FHintData: TVTHintData;
|
||
procedure WMShowWindow(var Message: TLMShowWindow); message LM_SHOWWINDOW;
|
||
public
|
||
function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; override;
|
||
procedure Paint; override;
|
||
end;
|
||
|
||
// Drag image support for the tree.
|
||
TVTTransparency = 0..255;
|
||
TVTBias = -128..127;
|
||
|
||
// Simple move limitation for the drag image.
|
||
TVTDragMoveRestriction = (
|
||
dmrNone,
|
||
dmrHorizontalOnly,
|
||
dmrVerticalOnly
|
||
);
|
||
|
||
TVTDragImageStates = set of (
|
||
disHidden, // Internal drag image is currently hidden (always hidden if drag image helper interfaces are used).
|
||
disInDrag, // Drag image class is currently being used.
|
||
disPrepared, // Drag image class is prepared.
|
||
disSystemSupport // Running on Windows 2000 or higher. System supports drag images natively.
|
||
);
|
||
|
||
// Class to manage header and tree drag image during a drag'n drop operation.
|
||
TVTDragImage = class
|
||
private
|
||
FOwner: TBaseVirtualTree;
|
||
FBackImage, // backup of overwritten screen area
|
||
FAlphaImage, // target for alpha blending
|
||
FDragImage: TBitmap; // the actual drag image to blend to screen
|
||
FImagePosition, // position of image (upper left corner) in screen coordinates
|
||
FLastPosition: TPoint; // last mouse position in screen coordinates
|
||
FTransparency: TVTTransparency; // alpha value of the drag image (0 - fully transparent, 255 - fully opaque)
|
||
FPreBlendBias, // value to darken or lighten the drag image before it is blended
|
||
FPostBlendBias: TVTBias; // value to darken or lighten the alpha blend result
|
||
FFade: Boolean; // determines whether to fade the drag image from center to borders or not
|
||
FRestriction: TVTDragMoveRestriction; // determines in which directions the drag image can be moved
|
||
FColorKey: TColor; // color to make fully transparent regardless of any other setting
|
||
FStates: TVTDragImageStates; // Determines the states of the drag image class.
|
||
function GetVisible: Boolean; // True if the drag image is currently hidden (used only when dragging)
|
||
protected
|
||
procedure InternalShowDragImage(ScreenDC: HDC);
|
||
procedure MakeAlphaChannel(Source, Target: TBitmap);
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree);
|
||
destructor Destroy; override;
|
||
|
||
function DragTo(const P: TPoint; ForceRepaint: Boolean): Boolean;
|
||
procedure EndDrag;
|
||
function GetDragImageRect: TRect;
|
||
procedure HideDragImage;
|
||
procedure PrepareDrag(DragImage: TBitmap; const ImagePosition, HotSpot: TPoint; const DataObject: IDataObject);
|
||
procedure RecaptureBackground(Tree: TBaseVirtualTree; R: TRect; VisibleRegion: HRGN; CaptureNCArea,
|
||
ReshowDragImage: Boolean);
|
||
procedure ShowDragImage;
|
||
function WillMove(const P: TPoint): Boolean;
|
||
|
||
property ColorKey: TColor read FColorKey write FColorKey default clWindow;
|
||
property Fade: Boolean read FFade write FFade default False;
|
||
property MoveRestriction: TVTDragMoveRestriction read FRestriction write FRestriction default dmrNone;
|
||
property PostBlendBias: TVTBias read FPostBlendBias write FPostBlendBias default 0;
|
||
property PreBlendBias: TVTBias read FPreBlendBias write FPreBlendBias default 0;
|
||
property Transparency: TVTTransparency read FTransparency write FTransparency default 128;
|
||
property Visible: Boolean read GetVisible;
|
||
end;
|
||
|
||
// tree columns implementation
|
||
TVirtualTreeColumns = class;
|
||
TVTHeader = class;
|
||
|
||
TVirtualTreeColumnStyle = (
|
||
vsText,
|
||
vsOwnerDraw
|
||
);
|
||
|
||
|
||
TVTHeaderColumnLayout = (
|
||
blGlyphLeft,
|
||
blGlyphRight,
|
||
blGlyphTop,
|
||
blGlyphBottom
|
||
);
|
||
|
||
TVirtualTreeColumn = class(TCollectionItem)
|
||
private
|
||
FText,
|
||
FHint: String;
|
||
FLeft,
|
||
FWidth: Integer;
|
||
FPosition: TColumnPosition;
|
||
FMinWidth: Integer;
|
||
FMaxWidth: Integer;
|
||
FStyle: TVirtualTreeColumnStyle;
|
||
FImageIndex: TImageIndex;
|
||
FBiDiMode: TBiDiMode;
|
||
FLayout: TVTHeaderColumnLayout;
|
||
FMargin,
|
||
FSpacing: Integer;
|
||
FOptions: TVTColumnOptions;
|
||
FTag: Integer;
|
||
FAlignment: TAlignment;
|
||
FCaptionAlignment: TAlignment; // Alignment of the caption.
|
||
FLastWidth: Integer;
|
||
FColor: TColor;
|
||
FBonusPixel: Boolean;
|
||
FSpringRest: Single; // Accumulator for width adjustment when auto spring option is enabled.
|
||
FCaptionText: String;
|
||
FCheckBox: Boolean;
|
||
FCheckType: TCheckType;
|
||
FCheckState: TCheckState;
|
||
FImageRect: TRect;
|
||
FHasImage: Boolean;
|
||
function GetCaptionAlignment: TAlignment;
|
||
function GetLeft: Integer;
|
||
function IsBiDiModeStored: Boolean;
|
||
function IsCaptionAlignmentStored: Boolean;
|
||
function IsColorStored: Boolean;
|
||
procedure SetAlignment(const Value: TAlignment);
|
||
procedure SetBiDiMode(Value: TBiDiMode);
|
||
procedure SetCaptionAlignment(const Value: TAlignment);
|
||
procedure SetCheckBox(Value: Boolean);
|
||
procedure SetCheckState(Value: TCheckState);
|
||
procedure SetCheckType(Value: TCheckType);
|
||
procedure SetColor(const Value: TColor);
|
||
procedure SetImageIndex(Value: TImageIndex);
|
||
procedure SetLayout(Value: TVTHeaderColumnLayout);
|
||
procedure SetMargin(Value: Integer);
|
||
procedure SetMaxWidth(Value: Integer);
|
||
procedure SetMinWidth(Value: Integer);
|
||
procedure SetOptions(Value: TVTColumnOptions);
|
||
procedure SetPosition(Value: TColumnPosition);
|
||
procedure SetSpacing(Value: Integer);
|
||
procedure SetStyle(Value: TVirtualTreeColumnStyle);
|
||
procedure SetText(const Value: String);
|
||
procedure SetWidth(Value: Integer);
|
||
protected
|
||
procedure ComputeHeaderLayout(DC: HDC; const Client: TRect; UseHeaderGlyph, UseSortGlyph: Boolean;
|
||
var HeaderGlyphPos, SortGlyphPos: TPoint; var TextBounds: TRect; DrawFormat: Cardinal;
|
||
CalculateTextRect: Boolean = False);
|
||
procedure GetAbsoluteBounds(var Left, Right: Integer);
|
||
function GetDisplayName: string; override;
|
||
function GetOwner: TVirtualTreeColumns; reintroduce;
|
||
public
|
||
constructor Create(Collection: TCollection); override;
|
||
destructor Destroy; override;
|
||
|
||
procedure Assign(Source: TPersistent); override;
|
||
function Equals(OtherColumnObj: TObject): Boolean; virtual;
|
||
function GetRect: TRect; virtual;
|
||
procedure LoadFromStream(const Stream: TStream; Version: Integer);
|
||
procedure ParentBiDiModeChanged;
|
||
procedure ParentColorChanged;
|
||
procedure RestoreLastWidth;
|
||
procedure SaveToStream(const Stream: TStream);
|
||
function UseRightToLeftReading: Boolean;
|
||
|
||
property Left: Integer read GetLeft;
|
||
property Owner: TVirtualTreeColumns read GetOwner;
|
||
published
|
||
property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify;
|
||
property BiDiMode: TBiDiMode read FBiDiMode write SetBiDiMode stored IsBiDiModeStored default bdLeftToRight;
|
||
property CaptionAlignment: TAlignment read GetCaptionAlignment write SetCaptionAlignment
|
||
stored IsCaptionAlignmentStored default taLeftJustify;
|
||
property CaptionText: String read FCaptionText stored False;
|
||
property CheckType: TCheckType read FCheckType write SetCheckType default ctCheckBox;
|
||
property CheckState: TCheckState read FCheckState write SetCheckState default csUncheckedNormal;
|
||
property CheckBox: Boolean read FCheckBox write SetCheckBox default False;
|
||
property Color: TColor read FColor write SetColor stored IsColorStored;
|
||
property Hint: String read FHint write FHint;
|
||
property ImageIndex: TImageIndex read FImageIndex write SetImageIndex default -1;
|
||
property Layout: TVTHeaderColumnLayout read FLayout write SetLayout default blGlyphLeft;
|
||
property Margin: Integer read FMargin write SetMargin default 4;
|
||
property MaxWidth: Integer read FMaxWidth write SetMaxWidth default 10000;
|
||
property MinWidth: Integer read FMinWidth write SetMinWidth default 10;
|
||
property Options: TVTColumnOptions read FOptions write SetOptions default DefaultColumnOptions;
|
||
property Position: TColumnPosition read FPosition write SetPosition;
|
||
property Spacing: Integer read FSpacing write SetSpacing default 4;
|
||
property Style: TVirtualTreeColumnStyle read FStyle write SetStyle default vsText;
|
||
property Tag: Integer read FTag write FTag default 0;
|
||
property Text: String read FText write SetText;
|
||
property Width: Integer read FWidth write SetWidth default 50;
|
||
end;
|
||
|
||
TVirtualTreeColumnClass = class of TVirtualTreeColumn;
|
||
|
||
TColumnsArray = array of TVirtualTreeColumn;
|
||
TCardinalArray = array of Cardinal;
|
||
TIndexArray = array of TColumnIndex;
|
||
|
||
TVirtualTreeColumns = class(TCollection)
|
||
private
|
||
FHeader: TVTHeader;
|
||
FHeaderBitmap: TBitmap; // backbuffer for drawing
|
||
FHoverIndex, // currently "hot" column
|
||
FDownIndex, // Column on which a mouse button is held down.
|
||
FTrackIndex: TColumnIndex; // Index of column which is currently being resized
|
||
FClickIndex: TColumnIndex; // last clicked column
|
||
FPositionToIndex: TIndexArray;
|
||
FDefaultWidth: Integer; // the width columns are created with
|
||
FNeedPositionsFix: Boolean; // True if FixPositions must still be called after DFM loading or Bidi mode change.
|
||
FClearing: Boolean; // True if columns are being deleted entirely.
|
||
|
||
// drag support
|
||
FDragIndex: TColumnIndex; // index of column currently being dragged
|
||
FDropTarget: TColumnIndex; // current target column (index) while dragging
|
||
FDropBefore: Boolean; // True if drop position is in the left half of a column, False for the right
|
||
// side to drop the dragged column to
|
||
function GetCount: Integer;
|
||
function GetItem(Index: TColumnIndex): TVirtualTreeColumn;
|
||
function GetNewIndex(P: TPoint; var OldIndex: TColumnIndex): Boolean;
|
||
procedure SetDefaultWidth(Value: Integer);
|
||
procedure SetItem(Index: TColumnIndex; Value: TVirtualTreeColumn);
|
||
protected
|
||
procedure AdjustAutoSize(CurrentIndex: TColumnIndex; Force: Boolean = False);
|
||
function AdjustDownColumn(P: TPoint): TColumnIndex;
|
||
function AdjustHoverColumn(const P: TPoint): Boolean;
|
||
procedure AdjustPosition(Column: TVirtualTreeColumn; Position: Cardinal);
|
||
function CanSplitterResize(P: TPoint; Column: TColumnIndex): Boolean;
|
||
procedure DoCanSplitterResize(P: TPoint; Column: TColumnIndex; var Allowed: Boolean);
|
||
procedure DrawButtonText(DC: HDC; Caption: String; Bounds: TRect; Enabled, Hot: Boolean; DrawFormat: Cardinal;
|
||
WrapCaption: Boolean);
|
||
procedure DrawXPButton(DC: HDC; const ButtonR: TRect; DrawSplitter, Down, Hover: Boolean);
|
||
procedure FixPositions;
|
||
function GetColumnAndBounds(const P: TPoint; var ColumnLeft, ColumnRight: Integer; Relative: Boolean = True): Integer;
|
||
function GetOwner: TPersistent; override;
|
||
procedure HandleClick(P: TPoint; Button: TMouseButton; Force, DblClick: Boolean); virtual;
|
||
procedure IndexChanged(OldIndex, NewIndex: Integer);
|
||
procedure InitializePositionArray;
|
||
procedure ReorderColumns(RTL: Boolean);
|
||
procedure Update(Item: TCollectionItem); override;
|
||
procedure UpdatePositions(Force: Boolean = False);
|
||
|
||
property HeaderBitmap: TBitmap read FHeaderBitmap;
|
||
property PositionToIndex: TIndexArray read FPositionToIndex;
|
||
public
|
||
constructor Create(AOwner: TVTHeader);
|
||
destructor Destroy; override;
|
||
|
||
function Add: TVirtualTreeColumn; virtual;
|
||
procedure AnimatedResize(Column: TColumnIndex; NewWidth: Integer);
|
||
procedure Assign(Source: TPersistent); override;
|
||
procedure Clear; virtual;
|
||
function ColumnFromPosition(const P: TPoint; Relative: Boolean = True): TColumnIndex; overload; virtual;
|
||
function ColumnFromPosition(PositionIndex: TColumnPosition): TColumnIndex; overload; virtual;
|
||
function Equals(OtherColumnsObj: TObject): Boolean;
|
||
procedure GetColumnBounds(Column: TColumnIndex; out Left, Right: Integer);
|
||
function GetFirstVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
function GetLastVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
function GetNextColumn(Column: TColumnIndex): TColumnIndex;
|
||
function GetNextVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
function GetPreviousColumn(Column: TColumnIndex): TColumnIndex;
|
||
function GetPreviousVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
function GetScrollWidth: Integer;
|
||
function GetVisibleColumns: TColumnsArray;
|
||
function GetVisibleFixedWidth: Integer;
|
||
function IsValidColumn(Column: TColumnIndex): Boolean;
|
||
procedure LoadFromStream(const Stream: TStream; Version: Integer);
|
||
procedure PaintHeader(DC: HDC; const R: TRect; HOffset: Integer); virtual;
|
||
procedure SaveToStream(const Stream: TStream);
|
||
function TotalWidth: Integer;
|
||
|
||
property Count: Integer read GetCount;
|
||
property ClickIndex: TColumnIndex read FClickIndex;
|
||
property DefaultWidth: Integer read FDefaultWidth write SetDefaultWidth default 50;
|
||
property Items[Index: TColumnIndex]: TVirtualTreeColumn read GetItem write SetItem; default;
|
||
property Header: TVTHeader read FHeader;
|
||
property TrackIndex: TColumnIndex read FTrackIndex;
|
||
end;
|
||
|
||
TVirtualTreeColumnsClass = class of TVirtualTreeColumns;
|
||
|
||
TVTConstraintPercent = 0..100;
|
||
TVTFixedAreaConstraints = class(TPersistent)
|
||
private
|
||
FHeader: TVTHeader;
|
||
FMaxHeightPercent,
|
||
FMaxWidthPercent,
|
||
FMinHeightPercent,
|
||
FMinWidthPercent: TVTConstraintPercent;
|
||
FOnChange: TNotifyEvent;
|
||
procedure SetConstraints(Index: Integer; Value: TVTConstraintPercent);
|
||
protected
|
||
procedure Change;
|
||
property Header: TVTHeader read FHeader;
|
||
public
|
||
constructor Create(AOwner: TVTHeader);
|
||
|
||
procedure Assign(Source: TPersistent); override;
|
||
property OnChange: TNotifyEvent read FOnChange write FOnChange;
|
||
published
|
||
property MaxHeightPercent: TVTConstraintPercent index 0 read FMaxHeightPercent write SetConstraints default 0;
|
||
property MaxWidthPercent: TVTConstraintPercent index 1 read FMaxWidthPercent write SetConstraints default 0;
|
||
property MinHeightPercent: TVTConstraintPercent index 2 read FMinHeightPercent write SetConstraints default 0;
|
||
property MinWidthPercent: TVTConstraintPercent index 3 read FMinWidthPercent write SetConstraints default 0;
|
||
end;
|
||
|
||
TVTHeaderStyle = (
|
||
hsThickButtons, // TButton look and feel
|
||
hsFlatButtons, // flatter look than hsThickButton, like an always raised flat TToolButton
|
||
hsPlates, // flat TToolButton look and feel (raise on hover etc.)
|
||
hsXPStyle // Windows XP style
|
||
);
|
||
|
||
TVTHeaderOption = (
|
||
hoAutoResize, // Adjust a column so that the header never exceeds the client width of the owner control.
|
||
hoColumnResize, // Resizing columns with the mouse is allowed.
|
||
hoDblClickResize, // Allows a column to resize itself to its largest entry.
|
||
hoDrag, // Dragging columns is allowed.
|
||
hoHotTrack, // Header captions are highlighted when mouse is over a particular column.
|
||
hoOwnerDraw, // Header items with the owner draw style can be drawn by the application via event.
|
||
hoRestrictDrag, // Header can only be dragged horizontally.
|
||
hoShowHint, // Show application defined header hint.
|
||
hoShowImages, // Show header images.
|
||
hoShowSortGlyphs, // Allow visible sort glyphs.
|
||
hoVisible, // Header is visible.
|
||
hoAutoSpring, // Distribute size changes of the header to all columns, which are sizable and have the
|
||
// coAutoSpring option enabled. hoAutoResize must be enabled too.
|
||
hoFullRepaintOnResize, // Fully invalidate the header (instead of subsequent columns only) when a column is resized.
|
||
hoDisableAnimatedResize, // Disable animated resize for all columns.
|
||
hoHeightResize, // Allow resizing header height via mouse.
|
||
hoHeightDblClickResize // Allow the header to resize itself to its default height.
|
||
);
|
||
TVTHeaderOptions = set of TVTHeaderOption;
|
||
|
||
THeaderState = (
|
||
hsAutoSizing, // auto size chain is in progess, do not trigger again on WM_SIZE
|
||
hsDragging, // header dragging is in progress (only if enabled)
|
||
hsDragPending, // left button is down, user might want to start dragging a column
|
||
hsLoading, // The header currently loads from stream, so updates are not necessary.
|
||
hsColumnWidthTracking, // column resizing is in progress
|
||
hsColumnWidthTrackPending, // left button is down, user might want to start resize a column
|
||
hsHeightTracking, // height resizing is in progress
|
||
hsHeightTrackPending, // left button is down, user might want to start changing height
|
||
hsResizing, // multi column resizing in progress
|
||
hsScaling, // the header is scaled after a change of FixedAreaConstraints or client size
|
||
hsNeedScaling // the header needs to be scaled
|
||
);
|
||
THeaderStates = set of THeaderState;
|
||
|
||
TSortDirection = (
|
||
sdAscending,
|
||
sdDescending
|
||
);
|
||
|
||
// describes the used column resize behaviour for AutoFitColumns
|
||
TSmartAutoFitType = (
|
||
smaAllColumns, // consider nodes in view only for all columns
|
||
smaNoColumn, // consider nodes in view only for no column
|
||
smaUseColumnOption // use coSmartResize of the corresponding column
|
||
);
|
||
|
||
// desribes what made a structure change event happen
|
||
TChangeReason = (
|
||
crIgnore, // used as placeholder
|
||
crAccumulated, // used for delayed changes
|
||
crChildAdded, // one or more child nodes have been added
|
||
crChildDeleted, // one or more child nodes have been deleted
|
||
crNodeAdded, // a node has been added
|
||
crNodeCopied, // a node has been duplicated
|
||
crNodeMoved // a node has been moved to a new place
|
||
);
|
||
|
||
TVTHeader = class(TPersistent)
|
||
private
|
||
FOwner: TBaseVirtualTree;
|
||
FColumns: TVirtualTreeColumns;
|
||
FHeight: Integer;
|
||
FFont: TFont;
|
||
FParentFont: Boolean;
|
||
FOptions: TVTHeaderOptions;
|
||
FStates: THeaderStates; // Used to keep track of internal states the header can enter.
|
||
FTrackPoint: TPoint; // Client coordinate where the tracking started.
|
||
FStyle: TVTHeaderStyle; // button style
|
||
FBackground: TColor;
|
||
FAutoSizeIndex: TColumnIndex;
|
||
FPopupMenu: TPopupMenu;
|
||
FMainColumn: TColumnIndex; // the column which holds the tree
|
||
FMaxHeight: Integer;
|
||
FMinHeight: Integer;
|
||
FDefaultHeight: Integer;
|
||
FFixedAreaConstraints: TVTFixedAreaConstraints; // Percentages for the fixed area (header, fixed columns).
|
||
FImages: TCustomImageList;
|
||
FImageChangeLink: TChangeLink; // connections to the image list to get notified about changes
|
||
FSortColumn: TColumnIndex;
|
||
FSortDirection: TSortDirection;
|
||
FTrackStart: TPoint; // client coordinates of the tracking start point
|
||
FDragStart: TPoint; // initial mouse drag position
|
||
FDragImage: TVTDragImage; // drag image management during header drag
|
||
FLastWidth: Integer; // Used to adjust spring columns. This is the width of all visible columns,
|
||
// not the header rectangle.
|
||
procedure FontChanged(Sender: TObject);
|
||
function GetMainColumn: TColumnIndex;
|
||
function GetUseColumns: Boolean;
|
||
function IsFontStored: Boolean;
|
||
procedure SetAutoSizeIndex(Value: TColumnIndex);
|
||
procedure SetBackground(Value: TColor);
|
||
procedure SetColumns(Value: TVirtualTreeColumns);
|
||
procedure SetDefaultHeight(Value: Integer);
|
||
procedure SetFont(const Value: TFont);
|
||
procedure SetHeight(Value: Integer);
|
||
procedure SetImages(const Value: TCustomImageList);
|
||
procedure SetMainColumn(Value: TColumnIndex);
|
||
procedure SetMaxHeight(Value: Integer);
|
||
procedure SetMinHeight(Value: Integer);
|
||
procedure SetOptions(Value: TVTHeaderOptions);
|
||
procedure SetParentFont(Value: Boolean);
|
||
procedure SetSortColumn(Value: TColumnIndex);
|
||
procedure SetSortDirection(const Value: TSortDirection);
|
||
procedure SetStyle(Value: TVTHeaderStyle);
|
||
protected
|
||
procedure ChangeScale(M, D: Integer); virtual;
|
||
function DetermineSplitterIndex(const P: TPoint): Boolean; virtual;
|
||
procedure DoAfterAutoFitColumn(Column: TColumnIndex); virtual;
|
||
procedure DoAfterColumnWidthTracking(Column: TColumnIndex); virtual;
|
||
procedure DoAfterHeightTracking; virtual;
|
||
function DoBeforeAutoFitColumn(Column: TColumnIndex; SmartAutoFitType: TSmartAutoFitType): Boolean; virtual;
|
||
procedure DoBeforeColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState); virtual;
|
||
procedure DoBeforeHeightTracking(Shift: TShiftState); virtual;
|
||
function DoColumnWidthDblClickResize(Column: TColumnIndex; P: TPoint; Shift: TShiftState): Boolean; virtual;
|
||
function DoColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint): Boolean; virtual;
|
||
function DoGetPopupMenu(Column: TColumnIndex; Position: TPoint): TPopupMenu; virtual;
|
||
function DoHeightTracking(var P: TPoint; Shift: TShiftState): Boolean; virtual;
|
||
function DoHeightDblClickResize(var P: TPoint; Shift: TShiftState): Boolean; virtual;
|
||
procedure DoSetSortColumn(Value: TColumnIndex); virtual;
|
||
procedure DragTo(const P: TPoint);
|
||
procedure FixedAreaConstraintsChanged(Sender: TObject);
|
||
function GetColumnsClass: TVirtualTreeColumnsClass; virtual;
|
||
function GetOwner: TPersistent; override;
|
||
function GetShiftState: TShiftState;
|
||
function HandleHeaderMouseMove(var Message: TLMMouseMove): Boolean;
|
||
function HandleMessage(var Message: TLMessage): Boolean; virtual;
|
||
procedure ImageListChange(Sender: TObject);
|
||
procedure PrepareDrag(P, Start: TPoint);
|
||
procedure RecalculateHeader; virtual;
|
||
procedure RescaleHeader;
|
||
procedure UpdateMainColumn;
|
||
procedure UpdateSpringColumns;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree); virtual;
|
||
destructor Destroy; override;
|
||
|
||
function AllowFocus(ColumnIndex: TColumnIndex): Boolean;
|
||
procedure Assign(Source: TPersistent); override;
|
||
procedure AutoFitColumns(Animated: Boolean = True; SmartAutoFitType: TSmartAutoFitType = smaUseColumnOption;
|
||
RangeStartCol: Integer = NoColumn; RangeEndCol: Integer = NoColumn);
|
||
function InHeader(const P: TPoint): Boolean; virtual;
|
||
function InHeaderSplitterArea(P: TPoint): Boolean; virtual;
|
||
procedure Invalidate(Column: TVirtualTreeColumn; ExpandToBorder: Boolean = False);
|
||
procedure LoadFromStream(const Stream: TStream); virtual;
|
||
function ResizeColumns(ChangeBy: Integer; RangeStartCol: TColumnIndex; RangeEndCol: TColumnIndex;
|
||
Options: TVTColumnOptions = [coVisible]): Integer;
|
||
procedure RestoreColumns;
|
||
procedure SaveToStream(const Stream: TStream); virtual;
|
||
|
||
property DragImage: TVTDragImage read FDragImage;
|
||
property States: THeaderStates read FStates;
|
||
property Treeview: TBaseVirtualTree read FOwner;
|
||
property UseColumns: Boolean read GetUseColumns;
|
||
published
|
||
property AutoSizeIndex: TColumnIndex read FAutoSizeIndex write SetAutoSizeIndex;
|
||
property Background: TColor read FBackground write SetBackground default clBtnFace;
|
||
property Columns: TVirtualTreeColumns read FColumns write SetColumns;
|
||
property DefaultHeight: Integer read FDefaultHeight write SetDefaultHeight;
|
||
property Font: TFont read FFont write SetFont stored IsFontStored;
|
||
property FixedAreaConstraints: TVTFixedAreaConstraints read FFixedAreaConstraints write FFixedAreaConstraints;
|
||
property Height: Integer read FHeight write SetHeight default 17;
|
||
property Images: TCustomImageList read FImages write SetImages;
|
||
property MainColumn: TColumnIndex read GetMainColumn write SetMainColumn default 0;
|
||
property MaxHeight: Integer read FMaxHeight write SetMaxHeight default 10000;
|
||
property MinHeight: Integer read FMinHeight write SetMinHeight default 10;
|
||
property Options: TVTHeaderOptions read FOptions write SetOptions default [hoColumnResize, hoDrag, hoShowSortGlyphs];
|
||
property ParentFont: Boolean read FParentFont write SetParentFont default False;
|
||
property PopupMenu: TPopupMenu read FPopupMenu write FPopUpMenu;
|
||
property SortColumn: TColumnIndex read FSortColumn write SetSortColumn default NoColumn;
|
||
property SortDirection: TSortDirection read FSortDirection write SetSortDirection default sdAscending;
|
||
property Style: TVTHeaderStyle read FStyle write SetStyle default hsThickButtons;
|
||
end;
|
||
|
||
TVTHeaderClass = class of TVTHeader;
|
||
|
||
// Communication interface between a tree editor and the tree itself (declared as using stdcall in case it
|
||
// is implemented in a (C/C++) DLL). The GUID is not nessecary in Delphi but important for BCB users
|
||
// to allow QueryInterface and _uuidof calls.
|
||
IVTEditLink = interface
|
||
['{2BE3EAFA-5ACB-45B4-9D9A-B58BCC496E17}']
|
||
function BeginEdit: Boolean; stdcall; // Called when editing actually starts.
|
||
function CancelEdit: Boolean; stdcall; // Called when editing has been cancelled by the tree.
|
||
function EndEdit: Boolean; stdcall; // Called when editing has been finished by the tree.
|
||
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
|
||
// Called after creation to allow a setup.
|
||
function GetBounds: TRect; stdcall; // Called to get the current size of the edit window
|
||
// (only important if the edit resizes itself).
|
||
procedure ProcessMessage(var Message: TLMessage); stdcall;
|
||
// Used to forward messages to the edit window(s)-
|
||
procedure SetBounds(R: TRect); stdcall; // Called to place the editor.
|
||
end;
|
||
|
||
// Indicates in the OnUpdating event what state the tree is currently in.
|
||
TVTUpdateState = (
|
||
usBegin, // The tree just entered the update state (BeginUpdate call for the first time).
|
||
usBeginSynch, // The tree just entered the synch update state (BeginSynch call for the first time).
|
||
usSynch, // Begin/EndSynch has been called but the tree did not change the update state.
|
||
usUpdate, // Begin/EndUpdate has been called but the tree did not change the update state.
|
||
usEnd, // The tree just left the update state (EndUpdate called for the last level).
|
||
usEndSynch // The tree just left the synch update state (EndSynch called for the last level).
|
||
);
|
||
|
||
// Used during owner draw of the header to indicate which drop mark for the column must be drawn.
|
||
TVTDropMarkMode = (
|
||
dmmNone,
|
||
dmmLeft,
|
||
dmmRight
|
||
);
|
||
|
||
// This structure carries all important information about header painting and is used in the advanced header painting.
|
||
THeaderPaintInfo = record
|
||
TargetCanvas: TCanvas;
|
||
Column: TVirtualTreeColumn;
|
||
PaintRectangle: TRect;
|
||
TextRectangle: TRect;
|
||
IsHoverIndex,
|
||
IsDownIndex,
|
||
IsEnabled,
|
||
ShowHeaderGlyph,
|
||
ShowSortGlyph,
|
||
ShowRightBorder: Boolean;
|
||
DropMark: TVTDropMarkMode;
|
||
GlyphPos,
|
||
SortGlyphPos: TPoint;
|
||
end;
|
||
|
||
// These elements are used both to query the application, which of them it wants to draw itself and to tell it during
|
||
// painting, which elements must be drawn during the advanced custom draw events.
|
||
THeaderPaintElements = set of (
|
||
hpeBackground,
|
||
hpeDropMark,
|
||
hpeHeaderGlyph,
|
||
hpeSortGlyph,
|
||
hpeText
|
||
);
|
||
|
||
// Various events must be handled at different places than they were initiated or need
|
||
// a persistent storage until they are reset.
|
||
TVirtualTreeStates = set of (
|
||
tsCancelHintAnimation, // Set when a new hint is about to show but an old hint is still being animated.
|
||
tsChangePending, // A selection change is pending.
|
||
tsCheckPropagation, // Set during automatic check state propagation.
|
||
tsCollapsing, // A full collapse operation is in progress.
|
||
tsToggleFocusedSelection, // Node selection was modifed using Ctrl-click. Change selection state on next mouse up.
|
||
tsClearPending, // Need to clear the current selection on next mouse move.
|
||
tsClipboardFlushing, // Set during flushing the clipboard to avoid freeing the content.
|
||
tsCopyPending, // Indicates a pending copy operation which needs to be finished.
|
||
tsCutPending, // Indicates a pending cut operation which needs to be finished.
|
||
tsDrawSelPending, // Multiselection only. User held down the left mouse button on a free
|
||
// area and might want to start draw selection.
|
||
tsDrawSelecting, // Multiselection only. Draw selection has actually started.
|
||
tsEditing, // Indicates that an edit operation is currently in progress.
|
||
tsEditPending, // An mouse up start edit if dragging has not started.
|
||
tsExpanding, // A full expand operation is in progress.
|
||
tsNodeHeightTracking, // A node height changing operation is in progress.
|
||
tsNodeHeightTrackPending, // left button is down, user might want to start changing a node's height.
|
||
tsHint, // Set when our hint is visible or soon will be.
|
||
tsInAnimation, // Set if the tree is currently in an animation loop.
|
||
tsIncrementalSearching, // Set when the user starts incremental search.
|
||
tsIncrementalSearchPending, // Set in WM_KEYDOWN to tell to use the char in WM_CHAR for incremental search.
|
||
tsIterating, // Set when IterateSubtree is currently in progress.
|
||
tsKeyCheckPending, // A check operation is under way, initiated by a key press (space key). Ignore mouse.
|
||
tsLeftButtonDown, // Set when the left mouse button is down.
|
||
tsLeftDblClick, // Set when the left mouse button was doubly clicked.
|
||
tsMouseCheckPending, // A check operation is under way, initiated by a mouse click. Ignore space key.
|
||
tsMiddleButtonDown, // Set when the middle mouse button is down.
|
||
tsMiddleDblClick, // Set when the middle mouse button was doubly clicked.
|
||
tsNeedScale, // On next ChangeScale scale the default node height.
|
||
tsNeedRootCountUpdate, // Set if while loading a root node count is set.
|
||
tsOLEDragging, // OLE dragging in progress.
|
||
tsOLEDragPending, // User has requested to start delayed dragging.
|
||
tsPainting, // The tree is currently painting itself.
|
||
tsRightButtonDown, // Set when the right mouse button is down.
|
||
tsRightDblClick, // Set when the right mouse button was doubly clicked.
|
||
tsPopupMenuShown, // The user clicked the right mouse button, which might cause a popup menu to appear.
|
||
tsScrolling, // Set when autoscrolling is active.
|
||
tsScrollPending, // Set when waiting for the scroll delay time to elapse.
|
||
tsSizing, // Set when the tree window is being resized. This is used to prevent recursive calls
|
||
// due to setting the scrollbars when sizing.
|
||
tsStopValidation, // Cache validation can be stopped (usually because a change has occured meanwhile).
|
||
tsStructureChangePending, // The structure of the tree has been changed while the update was locked.
|
||
tsSynchMode, // Set when the tree is in synch mode, where no timer events are triggered.
|
||
tsThumbTracking, // Stop updating the horizontal scroll bar while dragging the vertical thumb and vice versa.
|
||
tsToggling, // A toggle operation (for some node) is in progress.
|
||
tsUpdateHiddenChildrenNeeded, // Pending update for the hidden children flag after massive visibility changes.
|
||
tsUpdating, // The tree does currently not update its window because a BeginUpdate has not yet ended.
|
||
tsUseCache, // The tree's node caches are validated and non-empty.
|
||
tsUserDragObject, // Signals that the application created an own drag object in OnStartDrag.
|
||
tsUseThemes, // The tree runs under WinXP+, is theme aware and themes are enabled.
|
||
tsValidating, // The tree's node caches are currently validated.
|
||
tsValidationNeeded, // Something in the structure of the tree has changed. The cache needs validation.
|
||
tsVCLDragging, // VCL drag'n drop in progress.
|
||
tsVCLDragPending, // One-shot flag to avoid clearing the current selection on implicit mouse up for VCL drag.
|
||
tsWheelPanning, // Wheel mouse panning is active or soon will be.
|
||
tsWheelScrolling, // Wheel mouse scrolling is active or soon will be.
|
||
tsWindowCreating // Set during window handle creation to avoid frequent unnecessary updates.
|
||
);
|
||
|
||
TChangeStates = set of (
|
||
csStopValidation, // Cache validation can be stopped (usually because a change has occured meanwhile).
|
||
csUseCache, // The tree's node caches are validated and non-empty.
|
||
csValidating, // The tree's node caches are currently validated.
|
||
csValidationNeeded // Something in the structure of the tree has changed. The cache needs validation.
|
||
);
|
||
|
||
// determines whether and how the drag image is to show
|
||
TVTDragImageKind = (
|
||
diComplete, // show a complete drag image with all columns, only visible columns are shown
|
||
diMainColumnOnly, // show only the main column (the tree column)
|
||
diNoImage // don't show a drag image at all
|
||
);
|
||
|
||
// Switch for OLE and VCL drag'n drop. Because it is not possible to have both simultanously.
|
||
TVTDragType = (
|
||
dtOLE,
|
||
dtVCL
|
||
);
|
||
|
||
// options which determine what to draw in PaintTree
|
||
TVTInternalPaintOption = (
|
||
poBackground, // draw background image if there is any and it is enabled
|
||
poColumnColor, // erase node's background with the column's color
|
||
poDrawFocusRect, // draw focus rectangle around the focused node
|
||
poDrawSelection, // draw selected nodes with the normal selection color
|
||
poDrawDropMark, // draw drop mark if a node is currently the drop target
|
||
poGridLines, // draw grid lines if enabled
|
||
poMainOnly, // draw only the main column
|
||
poSelectedOnly // draw only selected nodes
|
||
);
|
||
TVTInternalPaintOptions = set of TVTInternalPaintOption;
|
||
|
||
// Determines the look of a tree's lines.
|
||
TVTLineStyle = (
|
||
lsCustomStyle, // application provides a line pattern
|
||
lsDotted, // usual dotted lines (default)
|
||
lsSolid // simple solid lines
|
||
);
|
||
|
||
// TVTLineType is used during painting a tree
|
||
TVTLineType = (
|
||
ltNone, // no line at all
|
||
ltBottomRight, // a line from bottom to the center and from there to the right
|
||
ltTopDown, // a line from top to bottom
|
||
ltTopDownRight, // a line from top to bottom and from center to the right
|
||
ltRight, // a line from center to the right
|
||
ltTopRight, // a line from bottom to center and from there to the right
|
||
// special styles for alternative drawings of tree lines
|
||
ltLeft, // a line from top to bottom at the left
|
||
ltLeftBottom // a combination of ltLeft and a line at the bottom from left to right
|
||
);
|
||
|
||
// Determines how to draw tree lines.
|
||
TVTLineMode = (
|
||
lmNormal, // usual tree lines (as in TTreeview)
|
||
lmBands // looks similar to a Nassi-Schneidermann diagram
|
||
);
|
||
|
||
// A collection of line type IDs which is used while painting a node.
|
||
TLineImage = array of TVTLineType;
|
||
|
||
TVTScrollIncrement = 1..10000;
|
||
|
||
// Export type
|
||
TVTExportType = (
|
||
etRTF, // contentToRTF
|
||
etHTML, // contentToHTML
|
||
etText, // contentToText
|
||
etExcel, // supported by external tools
|
||
etWord, // supported by external tools
|
||
etCustom // supported by external tools
|
||
);
|
||
|
||
TVTNodeExportEvent = function (Sender: TBaseVirtualTree; aExportType: TVTExportType; Node: PVirtualNode): Boolean of object;
|
||
TVTColumnExportEvent = procedure (Sender: TBaseVirtualTree; aExportType: TVTExportType; Column: TVirtualTreeColumn) of object;
|
||
TVTTreeExportEvent = procedure(Sender: TBaseVirtualTree; aExportType: TVTExportType) of object;
|
||
|
||
// A class to manage scroll bar aspects.
|
||
TScrollBarOptions = class(TPersistent)
|
||
private
|
||
FAlwaysVisible: Boolean;
|
||
FOwner: TBaseVirtualTree;
|
||
FScrollBars: TScrollStyle; // used to hide or show vertical and/or horizontal scrollbar
|
||
FScrollBarStyle: TVTScrollBarStyle; // kind of scrollbars to use
|
||
FIncrementX,
|
||
FIncrementY: TVTScrollIncrement; // number of pixels to scroll in one step (when auto scrolling)
|
||
procedure SetAlwaysVisible(Value: Boolean);
|
||
procedure SetScrollBars(Value: TScrollStyle);
|
||
procedure SetScrollBarStyle(Value: TVTScrollBarStyle);
|
||
protected
|
||
function GetOwner: TPersistent; override;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree);
|
||
|
||
procedure Assign(Source: TPersistent); override;
|
||
published
|
||
property AlwaysVisible: Boolean read FAlwaysVisible write SetAlwaysVisible default False;
|
||
property HorizontalIncrement: TVTScrollIncrement read FIncrementX write FIncrementX default 20;
|
||
property ScrollBars: TScrollStyle read FScrollbars write SetScrollBars default ssBoth;
|
||
property ScrollBarStyle: TVTScrollBarStyle read FScrollBarStyle write SetScrollBarStyle default sbmRegular;
|
||
property VerticalIncrement: TVTScrollIncrement read FIncrementY write FIncrementY default 20;
|
||
end;
|
||
|
||
// class to collect all switchable colors into one place
|
||
TVTColors = class(TPersistent)
|
||
private
|
||
FOwner: TBaseVirtualTree;
|
||
FColors: array[0..14] of TColor;
|
||
function GetColor(const Index: Integer): TColor;
|
||
procedure SetColor(const Index: Integer; const Value: TColor);
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree);
|
||
|
||
procedure Assign(Source: TPersistent); override;
|
||
published
|
||
property BorderColor: TColor index 7 read GetColor write SetColor default clBtnFace;
|
||
property DisabledColor: TColor index 0 read GetColor write SetColor default clBtnShadow;
|
||
property DropMarkColor: TColor index 1 read GetColor write SetColor default clHighlight;
|
||
property DropTargetColor: TColor index 2 read GetColor write SetColor default clHighLight;
|
||
property DropTargetBorderColor: TColor index 11 read GetColor write SetColor default clHighLight;
|
||
property FocusedSelectionColor: TColor index 3 read GetColor write SetColor default clHighLight;
|
||
property FocusedSelectionBorderColor: TColor index 9 read GetColor write SetColor default clHighLight;
|
||
property GridLineColor: TColor index 4 read GetColor write SetColor default clBtnFace;
|
||
property HeaderHotColor: TColor index 14 read GetColor write SetColor default clBtnShadow;
|
||
property HotColor: TColor index 8 read GetColor write SetColor default clWindowText;
|
||
property SelectionRectangleBlendColor: TColor index 12 read GetColor write SetColor default clHighlight;
|
||
property SelectionRectangleBorderColor: TColor index 13 read GetColor write SetColor default clHighlight;
|
||
property TreeLineColor: TColor index 5 read GetColor write SetColor default clBtnShadow;
|
||
property UnfocusedSelectionColor: TColor index 6 read GetColor write SetColor default clBtnFace;
|
||
property UnfocusedSelectionBorderColor: TColor index 10 read GetColor write SetColor default clBtnFace;
|
||
end;
|
||
|
||
// For painting a node and its columns/cells a lot of information must be passed frequently around.
|
||
TVTImageInfo = record
|
||
Index: Integer; // Index in the associated image list.
|
||
XPos, // Horizontal position in the current target canvas.
|
||
YPos: Integer; // Vertical position in the current target canvas.
|
||
Ghosted: Boolean; // Flag to indicate that the image must be drawn slightly lighter.
|
||
Images: TCustomImageList; // The image list to be used for painting.
|
||
end;
|
||
|
||
TVTImageInfoIndex = (
|
||
iiNormal,
|
||
iiState,
|
||
iiCheck,
|
||
iiOverlay
|
||
);
|
||
|
||
// Options which are used when modifying the scroll offsets.
|
||
TScrollUpdateOptions = set of (
|
||
suoRepaintHeader, // if suoUpdateNCArea is also set then invalidate the header
|
||
suoRepaintScrollbars, // if suoUpdateNCArea is also set then repaint both scrollbars after updating them
|
||
suoScrollClientArea, // scroll and invalidate the proper part of the client area
|
||
suoUpdateNCArea // update non-client area (scrollbars, header)
|
||
);
|
||
|
||
// Determines the look of a tree's buttons.
|
||
TVTButtonStyle = (
|
||
bsRectangle, // traditional Windows look (plus/minus buttons)
|
||
bsTriangle // traditional Macintosh look
|
||
);
|
||
|
||
// TButtonFillMode is only used when the button style is bsRectangle and determines how to fill the interior.
|
||
TVTButtonFillMode = (
|
||
fmTreeColor, // solid color, uses the tree's background color
|
||
fmWindowColor, // solid color, uses clWindow
|
||
fmShaded, // color gradient, Windows XP style (legacy code, use toThemeAware on Windows XP instead)
|
||
fmTransparent // transparent color, use the item's background color
|
||
);
|
||
|
||
TVTPaintInfo = record
|
||
Canvas: TCanvas; // the canvas to paint on
|
||
PaintOptions: TVTInternalPaintOptions; // a copy of the paint options passed to PaintTree
|
||
Node: PVirtualNode; // the node to paint
|
||
Column: TColumnIndex; // the node's column index to paint
|
||
Position: TColumnPosition; // the column position of the node
|
||
CellRect, // the node cell
|
||
ContentRect: TRect; // the area of the cell used for the node's content
|
||
NodeWidth: Integer; // the actual node width
|
||
Alignment: TAlignment; // how to align within the node rectangle
|
||
CaptionAlignment: TAlignment; // how to align text within the caption rectangle
|
||
BidiMode: TBidiMode; // directionality to be used for painting
|
||
BrushOrigin: TPoint; // the alignment for the brush used to draw dotted lines
|
||
ImageInfo: array[TVTImageInfoIndex] of TVTImageInfo; // info about each possible node image
|
||
end;
|
||
|
||
// Method called by the Animate routine for each animation step.
|
||
TVTAnimationCallback = function(Step, StepSize: Integer; Data: Pointer): Boolean of object;
|
||
|
||
TVTIncrementalSearch = (
|
||
isAll, // search every node in tree, initialize if necessary
|
||
isNone, // disable incremental search
|
||
isInitializedOnly, // search only initialized nodes, skip others
|
||
isVisibleOnly // search only visible nodes, initialize if necessary
|
||
);
|
||
|
||
// Determines which direction to use when advancing nodes during an incremental search.
|
||
TVTSearchDirection = (
|
||
sdForward,
|
||
sdBackward
|
||
);
|
||
|
||
// Determines where to start incremental searching for each key press.
|
||
TVTSearchStart = (
|
||
ssAlwaysStartOver, // always use the first/last node (depending on direction) to search from
|
||
ssLastHit, // use the last found node
|
||
ssFocusedNode // use the currently focused node
|
||
);
|
||
|
||
// Determines how to use the align member of a node.
|
||
TVTNodeAlignment = (
|
||
naFromBottom, // the align member specifies amount of units (usually pixels) from top border of the node
|
||
naFromTop, // align is to be measured from bottom
|
||
naProportional // align is to be measure in percent of the entire node height and relative to top
|
||
);
|
||
|
||
// Determines how to draw the selection rectangle used for draw selection.
|
||
TVTDrawSelectionMode = (
|
||
smDottedRectangle, // same as DrawFocusRect
|
||
smBlendedRectangle // alpha blending, uses special colors (see TVTColors)
|
||
);
|
||
|
||
// Determines for which purpose the cell paint event is called.
|
||
TVTCellPaintMode = (
|
||
cpmPaint, // painting the cell
|
||
cpmGetContentMargin // getting cell content margin
|
||
);
|
||
|
||
// Determines which sides of the cell content margin should be considered.
|
||
TVTCellContentMarginType = (
|
||
ccmtAllSides, // consider all sides
|
||
ccmtTopLeftOnly, // consider top margin and left margin only
|
||
ccmtBottomRightOnly // consider bottom margin and right margin only
|
||
);
|
||
|
||
TClipboardFormats = class(TStringList)
|
||
private
|
||
FOwner: TBaseVirtualTree;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree); virtual;
|
||
|
||
function Add(const S: string): Integer; override;
|
||
procedure Insert(Index: Integer; const S: string); override;
|
||
property Owner: TBaseVirtualTree read FOwner;
|
||
end;
|
||
|
||
// ----- Event prototypes:
|
||
|
||
// node enumeration
|
||
TVTGetNodeProc = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean) of object;
|
||
|
||
// node events
|
||
TVTChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean) of object;
|
||
TVTCheckChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var NewState: TCheckState;
|
||
var Allowed: Boolean) of object;
|
||
TVTChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object;
|
||
TVTStructureChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Reason: TChangeReason) of object;
|
||
TVTEditCancelEvent = procedure(Sender: TBaseVirtualTree; Column: TColumnIndex) of object;
|
||
TVTEditChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
var Allowed: Boolean) of object;
|
||
TVTEditChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object;
|
||
TVTFreeNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object;
|
||
TVTFocusChangingEvent = procedure(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn,
|
||
NewColumn: TColumnIndex; var Allowed: Boolean) of object;
|
||
TVTFocusChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object;
|
||
TVTGetImageEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
||
var Ghosted: Boolean; var ImageIndex: Integer) of object;
|
||
TVTGetImageExEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
||
var Ghosted: Boolean; var ImageIndex: Integer; var ImageList: TCustomImageList) of object;
|
||
TVTGetImageTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
||
var ImageText: String) of object;
|
||
TVTHotNodeChangeEvent = procedure(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode) of object;
|
||
TVTInitChildrenEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal) of object;
|
||
TVTInitNodeEvent = procedure(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
|
||
var InitialStates: TVirtualNodeInitStates) of object;
|
||
TVTPopupEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; const P: TPoint;
|
||
var AskParent: Boolean; var PopupMenu: TPopupMenu) of object;
|
||
TVTHelpContextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
var HelpContext: Integer) of object;
|
||
TVTCreateEditorEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
out EditLink: IVTEditLink) of object;
|
||
TVTSaveNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Stream: TStream) of object;
|
||
|
||
// header/column events
|
||
TVTHeaderClickEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X,
|
||
Y: Integer) of object;
|
||
TVTHeaderMouseEvent = procedure(Sender: TVTHeader; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of object;
|
||
TVTHeaderMouseMoveEvent = procedure(Sender: TVTHeader; Shift: TShiftState; X, Y: Integer) of object;
|
||
TVTBeforeHeaderHeightTrackingEvent = procedure(Sender: TVTHeader; Shift: TShiftState) of object;
|
||
TVTAfterHeaderHeightTrackingEvent = procedure(Sender: TVTHeader) of object;
|
||
TVTHeaderHeightTrackingEvent = procedure(Sender: TVTHeader; var P: TPoint; Shift: TShiftState; var Allowed: Boolean) of object;
|
||
TVTHeaderHeightDblClickResizeEvent = procedure(Sender: TVTHeader; var P: TPoint; Shift: TShiftState; var Allowed: Boolean) of object;
|
||
TVTHeaderNotifyEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object;
|
||
TVTHeaderDraggingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var Allowed: Boolean) of object;
|
||
TVTHeaderDraggedEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; OldPosition: Integer) of object;
|
||
TVTHeaderDraggedOutEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; const DropPosition: TPoint) of object;
|
||
TVTHeaderPaintEvent = procedure(Sender: TVTHeader; HeaderCanvas: TCanvas; Column: TVirtualTreeColumn; const R: TRect; Hover,
|
||
Pressed: Boolean; DropMark: TVTDropMarkMode) of object;
|
||
TVTHeaderPaintQueryElementsEvent = procedure(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo;
|
||
var Elements: THeaderPaintElements) of object;
|
||
TVTAdvancedHeaderPaintEvent = procedure(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo;
|
||
const Elements: THeaderPaintElements) of object;
|
||
TVTBeforeAutoFitColumnsEvent = procedure(Sender: TVTHeader; var SmartAutoFitType: TSmartAutoFitType) of object;
|
||
TVTBeforeAutoFitColumnEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var SmartAutoFitType: TSmartAutoFitType;
|
||
var Allowed: Boolean) of object;
|
||
TVTAfterAutoFitColumnEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object;
|
||
TVTAfterAutoFitColumnsEvent = procedure(Sender: TVTHeader) of object;
|
||
TVTColumnClickEvent = procedure (Sender: TBaseVirtualTree; Column: TColumnIndex; Shift: TShiftState) of object;
|
||
TVTColumnDblClickEvent = procedure (Sender: TBaseVirtualTree; Column: TColumnIndex; Shift: TShiftState) of object;
|
||
TVTColumnWidthDblClickResizeEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState; P: TPoint;
|
||
var Allowed: Boolean) of object;
|
||
TVTBeforeColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState) of object;
|
||
TVTAfterColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object;
|
||
TVTColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint;
|
||
var Allowed: Boolean) of object;
|
||
TVTGetHeaderCursorEvent = procedure(Sender: TVTHeader; var Cursor: HCURSOR) of object;
|
||
TVTBeforeGetMaxColumnWidthEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var UseSmartColumnWidth: Boolean) of object;
|
||
TVTAfterGetMaxColumnWidthEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var MaxWidth: Integer) of object;
|
||
TVTCanSplitterResizeColumnEvent = procedure(Sender: TVTHeader; P: TPoint; Column: TColumnIndex; var Allowed: Boolean) of object;
|
||
|
||
// move, copy and node tracking events
|
||
TVTNodeMovedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object;
|
||
TVTNodeMovingEvent = procedure(Sender: TBaseVirtualTree; Node, Target: PVirtualNode;
|
||
var Allowed: Boolean) of object;
|
||
TVTNodeCopiedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object;
|
||
TVTNodeCopyingEvent = procedure(Sender: TBaseVirtualTree; Node, Target: PVirtualNode;
|
||
var Allowed: Boolean) of object;
|
||
TVTNodeHeightTrackingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState;
|
||
var TrackPoint: TPoint; P: TPoint; var Allowed: Boolean) of object;
|
||
TVTNodeHeightDblClickResizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
Shift: TShiftState; P: TPoint; var Allowed: Boolean) of object;
|
||
|
||
// drag'n drop/OLE events
|
||
TVTCreateDragManagerEvent = procedure(Sender: TBaseVirtualTree; out DragManager: IVTDragManager) of object;
|
||
TVTCreateDataObjectEvent = procedure(Sender: TBaseVirtualTree; out IDataObject: IDataObject) of object;
|
||
TVTDragAllowedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
var Allowed: Boolean) of object;
|
||
TVTDragOverEvent = procedure(Sender: TBaseVirtualTree; Source: TObject; Shift: TShiftState; State: TDragState;
|
||
const Pt: TPoint; Mode: TDropMode; var Effect: LongWord; var Accept: Boolean) of object;
|
||
TVTDragDropEvent = procedure(Sender: TBaseVirtualTree; Source: TObject; DataObject: IDataObject;
|
||
Formats: TFormatArray; Shift: TShiftState; const Pt: TPoint; var Effect: LongWord; Mode: TDropMode) of object;
|
||
TVTRenderOLEDataEvent = procedure(Sender: TBaseVirtualTree; const FormatEtcIn: TFormatEtc; out Medium: TStgMedium;
|
||
ForClipboard: Boolean; var Result: HRESULT) of object;
|
||
TVTGetUserClipboardFormatsEvent = procedure(Sender: TBaseVirtualTree; var Formats: TFormatEtcArray) of object;
|
||
|
||
// paint events
|
||
TVTBeforeItemEraseEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect;
|
||
var ItemColor: TColor; var EraseAction: TItemEraseAction) of object;
|
||
TVTAfterItemEraseEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
const ItemRect: TRect) of object;
|
||
TVTBeforeItemPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
const ItemRect: TRect; var CustomDraw: Boolean) of object;
|
||
TVTAfterItemPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
const ItemRect: TRect) of object;
|
||
TVTBeforeCellPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect) of object;
|
||
TVTAfterCellPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; const CellRect: TRect) of object;
|
||
TVTPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas) of object;
|
||
TVTBackgroundPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; const R: TRect;
|
||
var Handled: Boolean) of object;
|
||
TVTGetLineStyleEvent = procedure(Sender: TBaseVirtualTree; var Bits: Pointer) of object;
|
||
TVTMeasureItemEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
var NodeHeight: Integer) of object;
|
||
|
||
// search, sort
|
||
TVTCompareEvent = procedure(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex;
|
||
var Result: Integer) of object;
|
||
TVTIncrementalSearchEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: String;
|
||
var Result: Integer) of object;
|
||
|
||
// miscellaneous
|
||
TVTGetNodeDataSizeEvent = procedure(Sender: TBaseVirtualTree; var NodeDataSize: Integer) of object;
|
||
TVTKeyActionEvent = procedure(Sender: TBaseVirtualTree; var CharCode: Word; var Shift: TShiftState;
|
||
var DoDefault: Boolean) of object;
|
||
TVTScrollEvent = procedure(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer) of object;
|
||
TVTUpdatingEvent = procedure(Sender: TBaseVirtualTree; State: TVTUpdateState) of object;
|
||
TVTGetCursorEvent = procedure(Sender: TBaseVirtualTree; var Cursor: TCursor) of object;
|
||
TVTStateChangeEvent = procedure(Sender: TBaseVirtualTree; Enter, Leave: TVirtualTreeStates) of object;
|
||
TVTGetCellIsEmptyEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
var IsEmpty: Boolean) of object;
|
||
TVTScrollbarShowEvent = procedure(Sender: TBaseVirtualTree; Bar: Integer; Show: Boolean) of object;
|
||
|
||
// Helper types for node iterations.
|
||
TGetFirstNodeProc = function: PVirtualNode of object;
|
||
TGetNextNodeProc = function(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode of object;
|
||
|
||
// ----- TBaseVirtualTree
|
||
TBaseVirtualTree = class(TCustomControl)
|
||
private
|
||
//FBorderStyle: TBorderStyle;
|
||
FHeader: TVTHeader;
|
||
FRoot: PVirtualNode;
|
||
FDefaultNodeHeight,
|
||
FIndent: Cardinal;
|
||
FOptions: TCustomVirtualTreeOptions;
|
||
FUpdateCount: Cardinal; // update stopper, updates of the tree control are only done if = 0
|
||
FSynchUpdateCount: Cardinal; // synchronizer, causes all events which are usually done via timers
|
||
// to happen immediately, regardless of the normal update state
|
||
FNodeDataSize: Integer; // number of bytes to allocate with each node (in addition to its base
|
||
// structure and the internal data), if -1 then do callback
|
||
FStates: TVirtualTreeStates; // various active/pending states the tree needs to consider
|
||
FLastSelected,
|
||
FFocusedNode: PVirtualNode;
|
||
FEditColumn, // column to be edited (focused node)
|
||
FFocusedColumn: TColumnIndex; // NoColumn if no columns are active otherwise the last hit column of
|
||
// the currently focused node
|
||
FHeightTrackPoint: TPoint; // Starting point of a node's height changing operation.
|
||
FHeightTrackNode: PVirtualNode; // Node which height is being changed.
|
||
FHeightTrackColumn: TColumnIndex; // Initial column where the height changing operation takes place.
|
||
FScrollDirections: TScrollDirections; // directions to scroll client area into depending on mouse position
|
||
FLastStructureChangeReason: TChangeReason; // Used for delayed structure change event.
|
||
FLastStructureChangeNode, // dito
|
||
FLastChangedNode, // used for delayed change event
|
||
FCurrentHotNode: PVirtualNode; // Node over which the mouse is hovering.
|
||
FCurrentHotColumn: TColumnIndex; // Column over which the mouse is hovering.
|
||
FHotNodeButtonHit: Boolean; // Indicates wether the mouse is hovering over the hot node's button.
|
||
FLastSelRect,
|
||
FNewSelRect: TRect; // used while doing draw selection
|
||
FHotCursor: TCursor; // can be set to additionally indicate the current hot node
|
||
FHintMode: TVTHintMode; // determines the kind of the hint window
|
||
FHintData: TVTHintData; // used while preparing the hint window
|
||
FChangeDelay: Cardinal; // used to delay OnChange event
|
||
FEditDelay: Cardinal; // determines time to elapse before a node goes into edit mode
|
||
FPositionCache: TCache; // array which stores node references ordered by vertical positions
|
||
// (see also DoValidateCache for more information)
|
||
FVisibleCount: Cardinal; // number of currently visible nodes
|
||
FStartIndex: Cardinal; // index to start validating cache from
|
||
FSelection: TNodeArray; // list of currently selected nodes
|
||
FSelectionCount: Integer; // number of currently selected nodes (size of FSelection might differ)
|
||
FSelectionLocked: Boolean; // prevents the tree from changing the selection
|
||
FRangeAnchor: PVirtualNode; // anchor node for selection with the keyboard, determines start of a
|
||
// selection range
|
||
FCheckNode: PVirtualNode; // node which "captures" a check event
|
||
FPendingCheckState: TCheckState; // the new state the check node will get if all went fine
|
||
FCheckPropagationCount: Cardinal; // nesting level of check propagation (WL, 05.02.2004)
|
||
FLastSelectionLevel: Integer; // keeps the last node level for constrained multiselection
|
||
FDrawSelShiftState: TShiftState; // keeps the initial shift state when the user starts selection with
|
||
// the mouse
|
||
FEditLink: IVTEditLink; // used to comunicate with an application defined editor
|
||
FTempNodeCache: TNodeArray; // used at various places to hold temporarily a bunch of node refs.
|
||
FTempNodeCount: Cardinal; // number of nodes in FTempNodeCache
|
||
FBackground: TPicture; // A background image loadable at design and runtime.
|
||
FMargin: Integer; // horizontal border distance
|
||
FTextMargin: Integer; // space between the node's text and its horizontal bounds
|
||
FBackgroundOffsetX,
|
||
FBackgroundOffsetY: Integer; // used to fine tune the position of the background image
|
||
FAnimationDuration: Cardinal; // specifies how long an animation shall take (expanding, hint)
|
||
FWantTabs: Boolean; // If True then the tree also consumes the tab key.
|
||
FNodeAlignment: TVTNodeAlignment; // determines how to interpret the align member of a node
|
||
FHeaderRect: TRect; // Space which the header currently uses in the control (window coords).
|
||
FLastHintRect: TRect; // Area which the mouse must leave to reshow a hint.
|
||
FUpdateRect: TRect;
|
||
|
||
// paint support and images
|
||
FPlusBM,
|
||
FMinusBM, // small bitmaps used for tree buttons
|
||
FHotPlusBM,
|
||
FHotMinusBM: TBitmap; // small bitmaps used for hot tree buttons
|
||
FImages, // normal images in the tree
|
||
FStateImages: TCustomImageList; // state images in the tree
|
||
FCustomCheckImages: TBitmap; // application defined check images
|
||
FCheckImageKind: TCheckImageKind; // light or dark, cross marks or tick marks
|
||
FCheckImages: TBitmap; // Reference to global image list to be used for the check images.
|
||
FImageChangeLink,
|
||
FStateChangeLink,
|
||
FCustomCheckChangeLink: TChangeLink; // connections to the image lists
|
||
FOldFontChange: TNotifyEvent; // helper method pointer for tracking font changes in the off screen buffer
|
||
FFontChanged: Boolean; // flag for keeping informed about font changes in the off screen buffer
|
||
FColors: TVTColors; // class comprising all customizable colors in the tree
|
||
FButtonStyle: TVTButtonStyle; // style of the tree buttons
|
||
FButtonFillMode: TVTButtonFillMode; // for rectangular tree buttons only: how to fill them
|
||
FLineStyle: TVTLineStyle; // style of the tree lines
|
||
FLineMode: TVTLineMode; // tree lines or bands etc.
|
||
FDottedBrush: HBRUSH; // used to paint dotted lines without special pens
|
||
FSelectionCurveRadius: Cardinal; // radius for rounded selection rectangles
|
||
FSelectionBlendFactor: Byte; // Determines the factor by which the selection rectangle is to be
|
||
// faded if enabled.
|
||
FDrawSelectionMode: TVTDrawSelectionMode; // determines the paint mode for draw selection
|
||
|
||
// alignment and directionality support
|
||
FAlignment: TAlignment; // default alignment of the tree if no columns are shown
|
||
|
||
// drag'n drop and clipboard support
|
||
FDragImageKind: TVTDragImageKind; // determines whether or not and what to show in the drag image
|
||
FDragOperations: TDragOperations; // determines which operations are allowed during drag'n drop
|
||
FDragThreshold: Integer; // used to determine when to actually start a drag'n drop operation
|
||
FDragManager: IVTDragManager; // drag'n drop, cut'n paste
|
||
FDropTargetNode: PVirtualNode; // node currently selected as drop target
|
||
FLastDropMode: TDropMode; // set while dragging and used to track changes
|
||
FDragSelection: TNodeArray; // temporary copy of FSelection used during drag'n drop
|
||
FDragType: TVTDragType; // used to switch between OLE and VCL drag'n drop
|
||
FDragImage: TVTDragImage; // drag image management
|
||
FDragWidth,
|
||
FDragHeight: Integer; // size of the drag image, the larger the more CPU power is needed
|
||
FClipboardFormats: TClipboardFormats; // a list of clipboard format descriptions enabled for this tree
|
||
FLastVCLDragTarget: PVirtualNode; // A node cache for VCL drag'n drop (keywords: DragLeave on DragDrop).
|
||
FVCLDragEffect: LongWord; // A cache for VCL drag'n drop to keep the current drop effect.
|
||
|
||
// scroll support
|
||
FScrollBarOptions: TScrollBarOptions; // common properties of horizontal and vertical scrollbar
|
||
FAutoScrollInterval: TAutoScrollInterval; // determines speed of auto scrolling
|
||
FAutoScrollDelay: Cardinal; // amount of milliseconds to wait until autoscrolling becomes active
|
||
FAutoExpandDelay: Cardinal; // amount of milliseconds to wait until a node is expanded if it is the
|
||
// drop target
|
||
FOffsetX: Integer;
|
||
FOffsetY: Integer; // Determines left and top scroll offset.
|
||
FEffectiveOffsetX: Integer; // Actual position of the horizontal scroll bar (varies depending on bidi mode).
|
||
FRangeX,
|
||
FRangeY: Cardinal; // current virtual width and height of the tree
|
||
FBottomSpace: Cardinal; // Extra space below the last node.
|
||
|
||
FDefaultPasteMode: TVTNodeAttachMode; // Used to determine where to add pasted nodes to.
|
||
FSingletonNodeArray: TNodeArray; // Contains only one element for quick addition of single nodes
|
||
// to the selection.
|
||
FDragScrollStart: Cardinal; // Contains the start time when a tree does auto scrolling as drop target.
|
||
|
||
// search
|
||
FIncrementalSearch: TVTIncrementalSearch; // Used to determine whether and how incremental search is to be used.
|
||
FSearchTimeout: Cardinal; // Number of milliseconds after which to stop incremental searching.
|
||
FSearchBuffer: String; // Collects a sequence of keypresses used to do incremental searching.
|
||
FLastSearchNode: PVirtualNode; // Reference to node which was last found as search fit.
|
||
FSearchDirection: TVTSearchDirection; // Direction to incrementally search the tree.
|
||
FSearchStart: TVTSearchStart; // Where to start iteration on each key press.
|
||
|
||
// miscellanous
|
||
FTotalInternalDataSize: Cardinal; // Cache of the sum of the necessary internal data size for all tree
|
||
// classes derived from this base class.
|
||
FPanningWindow: TVirtualPanningWindow; // Helper window for wheel panning
|
||
FLastClickPos: TPoint; // Used for retained drag start and wheel mouse scrolling.
|
||
FOperationCount: Cardinal; // Counts how many nested long-running operations are in progress.
|
||
FOperationCanceled: Boolean; // Used to indicate that a long-running operation should be canceled.
|
||
|
||
{$ifdef EnableAccessible}
|
||
// MSAA support
|
||
FAccessible: IAccessible; // The IAccessible interface to the window itself.
|
||
FAccessibleItem: IAccessible; // The IAccessible to the item that currently has focus.
|
||
FAccessibleName: string; // The name the window is given for screen readers.
|
||
{$endif}
|
||
// export
|
||
FOnBeforeNodeExport: TVTNodeExportEvent; // called before exporting a node
|
||
FOnNodeExport: TVTNodeExportEvent;
|
||
FOnAfterNodeExport: TVTNodeExportEvent; // called after exporting a node
|
||
FOnBeforeColumnExport: TVTColumnExportEvent; // called before exporting a column
|
||
FOnColumnExport: TVTColumnExportEvent;
|
||
FOnAfterColumnExport: TVTColumnExportEvent; // called after exporting a column
|
||
FOnBeforeTreeExport: TVTTreeExportEvent; // called before starting the export
|
||
FOnAfterTreeExport: TVTTreeExportEvent; // called after finishing the export
|
||
FOnBeforeHeaderExport: TVTTreeExportEvent; // called before exporting the header
|
||
FOnAfterHeaderExport: TVTTreeExportEvent; // called after exporting the header
|
||
|
||
// common events
|
||
FOnChange: TVTChangeEvent; // selection change
|
||
FOnStructureChange: TVTStructureChangeEvent; // structural change like adding nodes etc.
|
||
FOnInitChildren: TVTInitChildrenEvent; // called when a node's children are needed (expanding etc.)
|
||
FOnInitNode: TVTInitNodeEvent; // called when a node needs to be initialized (child count etc.)
|
||
FOnFreeNode: TVTFreeNodeEvent; // called when a node is about to be destroyed, user data can and should
|
||
// be freed in this event
|
||
FOnGetImage: TVTGetImageEvent; // Used to retrieve the image index of a given node.
|
||
FOnGetImageEx: TVTGetImageExEvent; // Used to retrieve the image index of a given node along with a custom
|
||
// image list.
|
||
FOnGetImageText: TVTGetImageTextEvent; // Used to retrieve the image alternative text of a given node.
|
||
// Used by the accessibility interface to provide useful text for status images.
|
||
FOnHotChange: TVTHotNodeChangeEvent; // called when the current "hot" node (that is, the node under the mouse)
|
||
// changes and hot tracking is enabled
|
||
FOnExpanding, // called just before a node is expanded
|
||
FOnCollapsing: TVTChangingEvent; // called just before a node is collapsed
|
||
FOnChecking: TVTCheckChangingEvent; // called just before a node's check state is changed
|
||
FOnExpanded, // called after a node has been expanded
|
||
FOnCollapsed, // called after a node has been collapsed
|
||
FOnChecked: TVTChangeEvent; // called after a node's check state has been changed
|
||
FOnResetNode: TVTChangeEvent; // called when a node is set to be uninitialized
|
||
FOnNodeMoving: TVTNodeMovingEvent; // called just before a node is moved from one parent node to another
|
||
// (this can be cancelled)
|
||
FOnNodeMoved: TVTNodeMovedEvent; // called after a node and its children have been moved to another
|
||
// parent node (probably another tree, but within the same application)
|
||
FOnNodeCopying: TVTNodeCopyingEvent; // called when an node is copied to another parent node (probably in
|
||
// another tree, but within the same application, can be cancelled)
|
||
FOnNodeHeightTracking: TVTNodeHeightTrackingEvent; // called when a node's height is being changed via mouse
|
||
FOnNodeHeightDblClickResize: TVTNodeHeightDblClickResizeEvent; // called when a node's vertical splitter is double clicked
|
||
FOnNodeCopied: TVTNodeCopiedEvent; // call after a node has been copied
|
||
FOnEditing: TVTEditChangingEvent; // called just before a node goes into edit mode
|
||
FOnEditCancelled: TVTEditCancelEvent; // called when editing has been cancelled
|
||
FOnEdited: TVTEditChangeEvent; // called when editing has successfully been finished
|
||
FOnFocusChanging: TVTFocusChangingEvent; // called when the focus is about to go to a new node and/or column
|
||
// (can be cancelled)
|
||
FOnFocusChanged: TVTFocusChangeEvent; // called when the focus goes to a new node and/or column
|
||
FOnGetPopupMenu: TVTPopupEvent; // called when the popup for a node or the header needs to be shown
|
||
FOnGetHelpContext: TVTHelpContextEvent; // called when a node specific help theme should be called
|
||
FOnCreateEditor: TVTCreateEditorEvent; // called when a node goes into edit mode, this allows applications
|
||
// to supply their own editor
|
||
FOnLoadNode, // called after a node has been loaded from a stream (file, clipboard,
|
||
// OLE drag'n drop) to allow an application to load their own data
|
||
// saved in OnSaveNode
|
||
FOnSaveNode: TVTSaveNodeEvent; // called when a node needs to be serialized into a stream
|
||
// (see OnLoadNode) to give the application the opportunity to save
|
||
// their node specific, persistent data (note: never save memory
|
||
// references)
|
||
|
||
// header/column mouse events
|
||
FOnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent;
|
||
FOnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent;
|
||
FOnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent;
|
||
FOnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent;
|
||
FOnHeaderClick, // mouse events for the header, just like those for a control
|
||
FOnHeaderImageClick,
|
||
FOnHeaderCheckBoxClick: TVTHeaderClickEvent;
|
||
FOnHeaderDblClick: TVTHeaderClickEvent;
|
||
FOnAfterHeaderHeightTracking: TVTAfterHeaderHeightTrackingEvent;
|
||
FOnBeforeHeaderHeightTracking: TVTBeforeHeaderHeightTrackingEvent;
|
||
FOnHeaderHeightTracking: TVTHeaderHeightTrackingEvent;
|
||
FOnHeaderHeightDblClickResize: TVTHeaderHeightDblClickResizeEvent;
|
||
FOnHeaderMouseDown,
|
||
FOnHeaderMouseUp: TVTHeaderMouseEvent;
|
||
FOnHeaderMouseMove: TVTHeaderMouseMoveEvent;
|
||
FOnAfterGetMaxColumnWidth: TVTAfterGetMaxColumnWidthEvent;
|
||
FOnBeforeGetMaxColumnWidth: TVTBeforeGetMaxColumnWidthEvent;
|
||
FOnColumnClick: TVTColumnClickEvent;
|
||
FOnColumnDblClick: TVTColumnDblClickEvent;
|
||
FOnColumnResize: TVTHeaderNotifyEvent;
|
||
FOnColumnWidthDblClickResize: TVTColumnWidthDblClickResizeEvent;
|
||
FOnAfterColumnWidthTracking: TVTAfterColumnWidthTrackingEvent;
|
||
FOnBeforeColumnWidthTracking: TVTBeforeColumnWidthTrackingEvent;
|
||
FOnColumnWidthTracking: TVTColumnWidthTrackingEvent;
|
||
FOnGetHeaderCursor: TVTGetHeaderCursorEvent; // triggered to allow the app. to use customized cursors for the header
|
||
FOnCanSplitterResizeColumn: TVTCanSplitterResizeColumnEvent;
|
||
|
||
// paint events
|
||
FOnAfterPaint, // triggered when the tree has entirely been painted
|
||
FOnBeforePaint: TVTPaintEvent; // triggered when the tree is about to be painted
|
||
FOnAfterItemPaint: TVTAfterItemPaintEvent; // triggered after an item has been painted
|
||
FOnBeforeItemPaint: TVTBeforeItemPaintEvent; // triggered when an item is about to be painted
|
||
FOnBeforeItemErase: TVTBeforeItemEraseEvent; // triggered when an item's background is about to be erased
|
||
FOnAfterItemErase: TVTAfterItemEraseEvent; // triggered after an item's background has been erased
|
||
FOnAfterCellPaint: TVTAfterCellPaintEvent; // triggered after a column of an item has been painted
|
||
FOnBeforeCellPaint: TVTBeforeCellPaintEvent; // triggered when a column of an item is about to be painted
|
||
FOnHeaderDraw: TVTHeaderPaintEvent; // Used when owner draw is enabled for the header and a column is set
|
||
// to owner draw mode.
|
||
FOnHeaderDrawQueryElements: TVTHeaderPaintQueryElementsEvent; // Used for advanced header painting to query the
|
||
// application for the elements, which are drawn by it and which should
|
||
// be drawn by the tree.
|
||
FOnAdvancedHeaderDraw: TVTAdvancedHeaderPaintEvent; // Used when owner draw is enabled for the header and a column
|
||
// is set to owner draw mode. But only if OnHeaderDrawQueryElements
|
||
// returns at least one element to be drawn by the application.
|
||
// In this case OnHeaderDraw is not used.
|
||
FOnGetLineStyle: TVTGetLineStyleEvent; // triggered when a custom line style is used and the pattern brush
|
||
// needs to be build
|
||
FOnPaintBackground: TVTBackgroundPaintEvent; // triggered if a part of the tree's background must be erased which is
|
||
// not covered by any node
|
||
FOnMeasureItem: TVTMeasureItemEvent; // Triggered when a node is about to be drawn and its height was not yet
|
||
// determined by the application.
|
||
|
||
// drag'n drop events
|
||
FOnCreateDragManager: TVTCreateDragManagerEvent; // called to allow for app./descendant defined drag managers
|
||
FOnCreateDataObject: TVTCreateDataObjectEvent; // called to allow for app./descendant defined data objects
|
||
FOnDragAllowed: TVTDragAllowedEvent; // used to get permission for manual drag in mouse down
|
||
FOnDragOver: TVTDragOverEvent; // called for every mouse move
|
||
FOnDragDrop: TVTDragDropEvent; // called on release of mouse button (if drop was allowed)
|
||
FOnHeaderDragged: TVTHeaderDraggedEvent; // header (column) drag'n drop
|
||
FOnHeaderDraggedOut: TVTHeaderDraggedOutEvent; // header (column) drag'n drop, which did not result in a valid drop.
|
||
FOnHeaderDragging: TVTHeaderDraggingEvent; // header (column) drag'n drop
|
||
FOnRenderOLEData: TVTRenderOLEDataEvent; // application/descendant defined clipboard formats
|
||
FOnGetUserClipboardFormats: TVTGetUserClipboardFormatsEvent; // gives application/descendants the opportunity to
|
||
// add own clipboard formats on the fly
|
||
|
||
// miscellanous events
|
||
FOnGetNodeDataSize: TVTGetNodeDataSizeEvent; // Called if NodeDataSize is -1.
|
||
FOnKeyAction: TVTKeyActionEvent; // Used to selectively prevent key actions (full expand on Ctrl+'+' etc.).
|
||
FOnScroll: TVTScrollEvent; // Called when one or both paint offsets changed.
|
||
FOnUpdating: TVTUpdatingEvent; // Called from BeginUpdate, EndUpdate, BeginSynch and EndSynch.
|
||
FOnGetCursor: TVTGetCursorEvent; // Called to allow the app. to set individual cursors.
|
||
FOnStateChange: TVTStateChangeEvent; // Called whenever a state in the tree changes.
|
||
FOnGetCellIsEmpty: TVTGetCellIsEmptyEvent; // Called when the tree needs to know if a cell is empty.
|
||
FOnShowScrollbar: TVTScrollbarShowEvent; // Called when a scrollbar is changed in its visibility.
|
||
|
||
// search, sort
|
||
FOnCompareNodes: TVTCompareEvent; // used during sort
|
||
FOnIncrementalSearch: TVTIncrementalSearchEvent; // triggered on every key press (not key down)
|
||
|
||
procedure AdjustCoordinatesByIndent(var PaintInfo: TVTPaintInfo; Indent: Integer);
|
||
procedure AdjustImageBorder(ImageWidth, ImageHeight: Integer; BidiMode: TBidiMode; VAlign: Integer; var R: TRect;
|
||
var ImageInfo: TVTImageInfo);
|
||
procedure AdjustTotalCount(Node: PVirtualNode; Value: Integer; Relative: Boolean = False);
|
||
procedure AdjustTotalHeight(Node: PVirtualNode; Value: Integer; Relative: Boolean = False);
|
||
function CalculateCacheEntryCount: Integer;
|
||
procedure CalculateVerticalAlignments(ShowImages, ShowStateImages: Boolean; Node: PVirtualNode; out VAlign,
|
||
VButtonAlign: Integer);
|
||
function ChangeCheckState(Node: PVirtualNode; Value: TCheckState): Boolean;
|
||
function CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment; OldRect: TRect;
|
||
const NewRect: TRect): Boolean;
|
||
function CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment; OldRect: TRect;
|
||
const NewRect: TRect): Boolean;
|
||
procedure ClearNodeBackground(const PaintInfo: TVTPaintInfo; UseBackground, Floating: Boolean; R: TRect);
|
||
function CompareNodePositions(Node1, Node2: PVirtualNode; ConsiderChildrenAbove: Boolean = False): Integer;
|
||
function DetermineLineImageAndSelectLevel(Node: PVirtualNode; out LineImage: TLineImage): Integer;
|
||
procedure DrawLineImage(const PaintInfo: TVTPaintInfo; X, Y, H, VAlign: Integer; Style: TVTLineType; Reverse: Boolean);
|
||
function FindInPositionCache(Node: PVirtualNode; var CurrentPos: Cardinal): PVirtualNode; overload;
|
||
function FindInPositionCache(Position: Cardinal; var CurrentPos: Cardinal): PVirtualNode; overload;
|
||
procedure FixupTotalCount(Node: PVirtualNode);
|
||
procedure FixupTotalHeight(Node: PVirtualNode);
|
||
function GetBottomNode: PVirtualNode;
|
||
function GetCheckedCount: Integer;
|
||
function GetCheckState(Node: PVirtualNode): TCheckState;
|
||
function GetCheckType(Node: PVirtualNode): TCheckType;
|
||
function GetChildCount(Node: PVirtualNode): Cardinal;
|
||
function GetChildrenInitialized(Node: PVirtualNode): Boolean;
|
||
function GetCutCopyCount: Integer;
|
||
function GetDisabled(Node: PVirtualNode): Boolean;
|
||
function GetDragManager: IVTDragManager;
|
||
function GetExpanded(Node: PVirtualNode): Boolean;
|
||
function GetFullyVisible(Node: PVirtualNode): Boolean;
|
||
function GetHasChildren(Node: PVirtualNode): Boolean;
|
||
function GetMultiline(Node: PVirtualNode): Boolean;
|
||
function GetNodeHeight(Node: PVirtualNode): Cardinal;
|
||
function GetNodeParent(Node: PVirtualNode): PVirtualNode;
|
||
function GetOffsetXY: TPoint;
|
||
function GetRootNodeCount: Cardinal;
|
||
function GetSelected(Node: PVirtualNode): Boolean;
|
||
function GetTopNode: PVirtualNode;
|
||
function GetTotalCount: Cardinal;
|
||
function GetVerticalAlignment(Node: PVirtualNode): Byte;
|
||
function GetVisible(Node: PVirtualNode): Boolean;
|
||
function GetVisiblePath(Node: PVirtualNode): Boolean;
|
||
procedure HandleClickSelection(LastFocused, NewNode: PVirtualNode; Shift: TShiftState; DragPending: Boolean);
|
||
function HandleDrawSelection(X, Y: Integer): Boolean;
|
||
function HasVisibleNextSibling(Node: PVirtualNode): Boolean;
|
||
function HasVisiblePreviousSibling(Node: PVirtualNode): Boolean;
|
||
procedure ImageListChange(Sender: TObject);
|
||
procedure InitializeFirstColumnValues(var PaintInfo: TVTPaintInfo);
|
||
procedure InitRootNode(OldSize: Cardinal = 0);
|
||
procedure InterruptValidation;
|
||
function IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean;
|
||
function IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean;
|
||
procedure LimitPaintingToArea(Canvas: TCanvas; ClipRect: TRect; VisibleRegion: HRGN = 0);
|
||
//lcl
|
||
procedure LoadPanningCursors;
|
||
function MakeNewNode: PVirtualNode;
|
||
{$ifdef PACKARRAYPASCAL}
|
||
function PackArray(const TheArray: TNodeArray; Count: Integer): Integer;
|
||
{$else}
|
||
function PackArray(TheArray: TNodeArray; Count: Integer): Integer;
|
||
{$endif}
|
||
procedure PrepareBitmaps(NeedButtons, NeedLines: Boolean);
|
||
procedure SetAlignment(const Value: TAlignment);
|
||
procedure SetAnimationDuration(const Value: Cardinal);
|
||
procedure SetBackground(const Value: TPicture);
|
||
procedure SetBackgroundOffset(const Index, Value: Integer);
|
||
procedure SetBottomNode(Node: PVirtualNode);
|
||
procedure SetBottomSpace(const Value: Cardinal);
|
||
procedure SetButtonFillMode(const Value: TVTButtonFillMode);
|
||
procedure SetButtonStyle(const Value: TVTButtonStyle);
|
||
procedure SetCheckImageKind(Value: TCheckImageKind);
|
||
procedure SetCheckState(Node: PVirtualNode; Value: TCheckState);
|
||
procedure SetCheckType(Node: PVirtualNode; Value: TCheckType);
|
||
procedure SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal);
|
||
procedure SetClipboardFormats(const Value: TClipboardFormats);
|
||
procedure SetColors(const Value: TVTColors);
|
||
procedure SetCustomCheckImages(const Value: TBitmap);
|
||
procedure SetDefaultNodeHeight(Value: Cardinal);
|
||
procedure SetDisabled(Node: PVirtualNode; Value: Boolean);
|
||
procedure SetExpanded(Node: PVirtualNode; Value: Boolean);
|
||
procedure SetFocusedColumn(Value: TColumnIndex);
|
||
procedure SetFocusedNode(Value: PVirtualNode);
|
||
procedure SetFullyVisible(Node: PVirtualNode; Value: Boolean);
|
||
procedure SetHasChildren(Node: PVirtualNode; Value: Boolean);
|
||
procedure SetHeader(const Value: TVTHeader);
|
||
procedure SetImages(const Value: TCustomImageList);
|
||
procedure SetIndent(Value: Cardinal);
|
||
procedure SetLineMode(const Value: TVTLineMode);
|
||
procedure SetLineStyle(const Value: TVTLineStyle);
|
||
procedure SetMargin(Value: Integer);
|
||
procedure SetMultiline(Node: PVirtualNode; const Value: Boolean);
|
||
procedure SetNodeAlignment(const Value: TVTNodeAlignment);
|
||
procedure SetNodeDataSize(Value: Integer);
|
||
procedure SetNodeHeight(Node: PVirtualNode; Value: Cardinal);
|
||
procedure SetNodeParent(Node: PVirtualNode; const Value: PVirtualNode);
|
||
procedure SetOffsetX(const Value: Integer);
|
||
procedure SetOffsetXY(const Value: TPoint);
|
||
procedure SetOffsetY(const Value: Integer);
|
||
procedure SetOptions(const Value: TCustomVirtualTreeOptions);
|
||
procedure SetRootNodeCount(Value: Cardinal);
|
||
procedure SetScrollBarOptions(Value: TScrollBarOptions);
|
||
procedure SetSearchOption(const Value: TVTIncrementalSearch);
|
||
procedure SetSelected(Node: PVirtualNode; Value: Boolean);
|
||
procedure SetSelectionCurveRadius(const Value: Cardinal);
|
||
procedure SetStateImages(const Value: TCustomImageList);
|
||
procedure SetTextMargin(Value: Integer);
|
||
procedure SetTopNode(Node: PVirtualNode);
|
||
procedure SetUpdateState(Updating: Boolean);
|
||
procedure SetVerticalAlignment(Node: PVirtualNode; Value: Byte);
|
||
procedure SetVisible(Node: PVirtualNode; Value: Boolean);
|
||
procedure SetVisiblePath(Node: PVirtualNode; Value: Boolean);
|
||
procedure StaticBackground(Source: TBitmap; Target: TCanvas; const Offset: TPoint; const R: TRect);
|
||
procedure TileBackground(Source: TBitmap; Target: TCanvas; const Offset: TPoint; R: TRect);
|
||
function ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean;
|
||
protected
|
||
procedure CMColorChange(var Message: TLMessage); message CM_COLORCHANGED;
|
||
procedure CMBiDiModeChanged(var Message: TLMessage); message CM_BIDIMODECHANGED;
|
||
procedure CMDenySubclassing(var Message: TLMessage); message CM_DENYSUBCLASSING;
|
||
//procedure CMDrag(var Message: TCMDrag); message CM_DRAG;
|
||
procedure CMFontChanged(var Message: TLMessage); message CM_FONTCHANGED;
|
||
procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
|
||
procedure CMMouseLeave(var Message: TLMessage); message CM_MOUSELEAVE;
|
||
procedure CMMouseWheel(var Message: TLMMouseEvent); message LM_MOUSEWHEEL;
|
||
{$ifdef EnableNativeTVM}
|
||
procedure TVMGetItem(var Message: TLMessage); message TVM_GETITEM;
|
||
procedure TVMGetItemRect(var Message: TLMessage); message TVM_GETITEMRECT;
|
||
procedure TVMGetNextItem(var Message: TLMessage); message TVM_GETNEXTITEM;
|
||
{$endif}
|
||
procedure WMCancelMode(var Message: TLMNoParams); message LM_CANCELMODE;
|
||
procedure WMChangeState(var Message: TLMessage); message WM_CHANGESTATE;
|
||
procedure WMChar(var Message: TLMChar); message LM_CHAR;
|
||
procedure WMContextMenu(var Message: TLMContextMenu); message LM_CONTEXTMENU;
|
||
procedure WMCopy(var Message: TLMNoParams); message LM_COPY;
|
||
procedure WMCut(var Message: TLMNoParams); message LM_CUT;
|
||
procedure WMEnable(var Message: TLMNoParams); message LM_ENABLE;
|
||
procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
|
||
procedure WMGetDlgCode(var Message: TLMNoParams); message LM_GETDLGCODE;
|
||
{$ifdef EnableAccessible}
|
||
procedure WMGetObject(var Message: TLMessage);{ message WM_GETOBJECT;}
|
||
{$endif}
|
||
procedure WMHScroll(var Message: TLMHScroll); message LM_HSCROLL;
|
||
procedure WMKeyDown(var Message: TLMKeyDown); message LM_KEYDOWN;
|
||
procedure WMKeyUp(var Message: TLMKeyUp); message LM_KEYUP;
|
||
procedure WMKillFocus(var Msg: TLMKillFocus); message LM_KILLFOCUS;
|
||
procedure WMLButtonDblClk(var Message: TLMLButtonDblClk); message LM_LBUTTONDBLCLK;
|
||
procedure WMLButtonDown(var Message: TLMLButtonDown); message LM_LBUTTONDOWN;
|
||
procedure WMLButtonUp(var Message: TLMLButtonUp); message LM_LBUTTONUP;
|
||
procedure WMMButtonDblClk(var Message: TLMMButtonDblClk); message LM_MBUTTONDBLCLK;
|
||
procedure WMMButtonDown(var Message: TLMMButtonDown); message LM_MBUTTONDOWN;
|
||
procedure WMMButtonUp(var Message: TLMMButtonUp); message LM_MBUTTONUP;
|
||
{$ifdef EnableNCFunctions}
|
||
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
|
||
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
|
||
procedure WMNCPaint(var Message: TRealWMNCPaint); message WM_NCPAINT;
|
||
{$endif}
|
||
procedure WMPaint(var Message: TLMPaint); message LM_PAINT;
|
||
procedure WMPaste(var Message: TLMNoParams); message LM_PASTE;
|
||
{$ifdef EnablePrintFunctions}
|
||
procedure WMPrint(var Message: TWMPrint); message WM_PRINT;
|
||
procedure WMPrintClient(var Message: TWMPrintClient); message WM_PRINTCLIENT;
|
||
{$endif}
|
||
procedure WMRButtonDblClk(var Message: TLMRButtonDblClk); message LM_RBUTTONDBLCLK;
|
||
procedure WMRButtonDown(var Message: TLMRButtonDown); message LM_RBUTTONDOWN;
|
||
procedure WMRButtonUp(var Message: TLMRButtonUp); message LM_RBUTTONUP;
|
||
procedure WMSetFocus(var Msg: TLMSetFocus); message LM_SETFOCUS;
|
||
procedure WMSize(var Message: TLMSize); message LM_SIZE;
|
||
procedure WMTimer(var Message: TLMTimer); message LM_TIMER;
|
||
{$ifdef ThemeSupport}
|
||
{$ifdef Windows}
|
||
procedure WMThemeChanged(var Message: TLMessage); message WM_THEMECHANGED;
|
||
{$endif}
|
||
{$endif ThemeSupport}
|
||
procedure WMVScroll(var Message: TLMVScroll); message LM_VSCROLL;
|
||
procedure AddToSelection(Node: PVirtualNode); overload; virtual;
|
||
procedure AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False); overload; virtual;
|
||
procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; out NextNonEmpty: TColumnIndex); virtual;
|
||
procedure AdjustPanningCursor(X, Y: Integer); virtual;
|
||
procedure AdviseChangeEvent(StructureChange: Boolean; Node: PVirtualNode; Reason: TChangeReason); virtual;
|
||
function AllocateInternalDataArea(Size: Cardinal): Cardinal; virtual;
|
||
procedure Animate(Steps, Duration: Cardinal; Callback: TVTAnimationCallback; Data: Pointer); virtual;
|
||
procedure BeginOperation;
|
||
function CalculateSelectionRect(X, Y: Integer): Boolean; virtual;
|
||
function CanAutoScroll: Boolean; virtual;
|
||
function CanShowDragImage: Boolean; virtual;
|
||
procedure Change(Node: PVirtualNode); virtual;
|
||
procedure ChangeScale(M, D: Integer); override;
|
||
//lcl
|
||
procedure CheckImageListNeeded;
|
||
function CheckParentCheckState(Node: PVirtualNode; NewCheckState: TCheckState): Boolean; virtual;
|
||
procedure ClearTempCache; virtual;
|
||
function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual;
|
||
function ComputeRTLOffset(ExcludeScrollbar: Boolean = False): Integer; virtual;
|
||
function CountLevelDifference(Node1, Node2: PVirtualNode): Integer; virtual;
|
||
function CountVisibleChildren(Node: PVirtualNode): Cardinal; virtual;
|
||
procedure CreateParams(var Params: TCreateParams); override;
|
||
procedure CreateWnd; override;
|
||
procedure DestroyHandle; override;
|
||
procedure DetermineHiddenChildrenFlag(Node: PVirtualNode); virtual;
|
||
procedure DetermineHiddenChildrenFlagAllNodes; virtual;
|
||
procedure DetermineHitPositionLTR(var HitInfo: THitInfo; Offset, Right: Integer; Alignment: TAlignment); virtual;
|
||
procedure DetermineHitPositionRTL(var HitInfo: THitInfo; Offset, Right: Integer; Alignment: TAlignment); virtual;
|
||
function DetermineNextCheckState(CheckType: TCheckType; CheckState: TCheckState): TCheckState; virtual;
|
||
function DetermineScrollDirections(X, Y: Integer): TScrollDirections; virtual;
|
||
procedure DoAdvancedHeaderDraw(var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); virtual;
|
||
procedure DoAfterCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const CellRect: TRect); virtual;
|
||
procedure DoAfterItemErase(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect); virtual;
|
||
procedure DoAfterItemPaint(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect); virtual;
|
||
procedure DoAfterPaint(Canvas: TCanvas); virtual;
|
||
procedure DoAutoScroll(X, Y: Integer); virtual;
|
||
procedure DoAutoSize; override;
|
||
function DoBeforeDrag(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual;
|
||
procedure DoBeforeCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
||
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); virtual;
|
||
procedure DoBeforeItemErase(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect; var Color: TColor;
|
||
var EraseAction: TItemEraseAction); virtual;
|
||
function DoBeforeItemPaint(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect): Boolean; virtual;
|
||
procedure DoBeforePaint(Canvas: TCanvas); virtual;
|
||
function DoCancelEdit: Boolean; virtual;
|
||
procedure DoCanEdit(Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); virtual;
|
||
procedure DoChange(Node: PVirtualNode); virtual;
|
||
procedure DoCheckClick(Node: PVirtualNode; NewCheckState: TCheckState); virtual;
|
||
procedure DoChecked(Node: PVirtualNode); virtual;
|
||
function DoChecking(Node: PVirtualNode; var NewCheckState: TCheckState): Boolean; virtual;
|
||
procedure DoCollapsed(Node: PVirtualNode); virtual;
|
||
function DoCollapsing(Node: PVirtualNode): Boolean; virtual;
|
||
procedure DoColumnClick(Column: TColumnIndex; Shift: TShiftState); virtual;
|
||
procedure DoColumnDblClick(Column: TColumnIndex; Shift: TShiftState); virtual;
|
||
procedure DoColumnResize(Column: TColumnIndex); virtual;
|
||
function DoCompare(Node1, Node2: PVirtualNode; Column: TColumnIndex): Integer; virtual;
|
||
function DoCreateDataObject: IDataObject; virtual;
|
||
function DoCreateDragManager: IVTDragManager; virtual;
|
||
function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; virtual;
|
||
procedure DoDragging(P: TPoint); virtual;
|
||
procedure DoDragExpand; virtual;
|
||
function DoDragMsg(ADragMessage: TDragMessage; APosition: TPoint;
|
||
ADragObject: TDragObject; ATarget: TControl; ADocking: Boolean): LRESULT; override;
|
||
function DoDragOver(Source: TObject; Shift: TShiftState; State: TDragState; const Pt: TPoint; Mode: TDropMode;
|
||
var Effect: LongWord): Boolean; virtual;
|
||
procedure DoDragDrop(Source: TObject; DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState; const Pt: TPoint;
|
||
var Effect: LongWord; Mode: TDropMode); virtual;
|
||
procedure DoEdit; virtual;
|
||
procedure DoEndDrag(Target: TObject; X, Y: Integer); override;
|
||
function DoEndEdit: Boolean; virtual;
|
||
procedure DoExpanded(Node: PVirtualNode); virtual;
|
||
function DoExpanding(Node: PVirtualNode): Boolean; virtual;
|
||
procedure DoFocusChange(Node: PVirtualNode; Column: TColumnIndex); virtual;
|
||
function DoFocusChanging(OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex): Boolean; virtual;
|
||
procedure DoFocusNode(Node: PVirtualNode; Ask: Boolean); virtual;
|
||
procedure DoFreeNode(Node: PVirtualNode); virtual;
|
||
function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex;
|
||
CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; virtual;
|
||
procedure DoGetCursor(var Cursor: TCursor); virtual;
|
||
procedure DoGetHeaderCursor(var Cursor: HCURSOR); virtual;
|
||
function DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
||
var Ghosted: Boolean; var Index: Integer): TCustomImageList; virtual;
|
||
procedure DoGetImageText(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
||
var ImageText: String); virtual;
|
||
procedure DoGetLineStyle(var Bits: Pointer); virtual;
|
||
function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; virtual;
|
||
function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; virtual;
|
||
function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; virtual;
|
||
function DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Position: TPoint): TPopupMenu; virtual;
|
||
procedure DoGetUserClipboardFormats(var Formats: TFormatEtcArray); virtual;
|
||
procedure DoHeaderClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHeaderDblClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHeaderImageClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHeaderCheckBoxClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHeaderDragged(Column: TColumnIndex; OldPosition: TColumnPosition); virtual;
|
||
procedure DoHeaderDraggedOut(Column: TColumnIndex; const DropPosition: TPoint); virtual;
|
||
function DoHeaderDragging(Column: TColumnIndex): Boolean; virtual;
|
||
procedure DoHeaderDraw(Canvas: TCanvas; Column: TVirtualTreeColumn; const R: TRect; Hover, Pressed: Boolean;
|
||
DropMark: TVTDropMarkMode); virtual;
|
||
procedure DoHeaderDrawQueryElements(var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); virtual;
|
||
procedure DoHeaderMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHeaderMouseMove(Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHeaderMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
|
||
procedure DoHotChange(Old, New: PVirtualNode); virtual;
|
||
function DoIncrementalSearch(Node: PVirtualNode; const Text: String): Integer; virtual;
|
||
procedure DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal); virtual;
|
||
procedure DoInitNode(Parent, Node: PVirtualNode; var InitStates: TVirtualNodeInitStates); virtual;
|
||
function DoKeyAction(var CharCode: Word; var Shift: TShiftState): Boolean; virtual;
|
||
procedure DoLoadUserData(Node: PVirtualNode; Stream: TStream); virtual;
|
||
procedure DoMeasureItem(TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer); virtual;
|
||
procedure DoNodeCopied(Node: PVirtualNode); virtual;
|
||
function DoNodeCopying(Node, NewParent: PVirtualNode): Boolean; virtual;
|
||
function DoNodeHeightDblClickResize(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState;
|
||
P: TPoint): Boolean; virtual;
|
||
function DoNodeHeightTracking(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState;
|
||
var TrackPoint: TPoint; P: TPoint): Boolean; virtual;
|
||
procedure DoNodeMoved(Node: PVirtualNode); virtual;
|
||
function DoNodeMoving(Node, NewParent: PVirtualNode): Boolean; virtual;
|
||
function DoPaintBackground(Canvas: TCanvas; const R: TRect): Boolean; virtual;
|
||
procedure DoPaintDropMark(Canvas: TCanvas; Node: PVirtualNode; const R: TRect); virtual;
|
||
procedure DoPaintNode(var PaintInfo: TVTPaintInfo); virtual;
|
||
procedure DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Position: TPoint); virtual;
|
||
function DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium;
|
||
ForClipboard: Boolean): HRESULT; virtual;
|
||
procedure DoReset(Node: PVirtualNode); virtual;
|
||
procedure DoSaveUserData(Node: PVirtualNode; Stream: TStream); virtual;
|
||
procedure DoScroll(DeltaX, DeltaY: Integer); virtual;
|
||
function DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean; virtual;
|
||
procedure DoShowScrollbar(Bar: Integer; Show: Boolean); virtual;
|
||
procedure DoStartDrag(var DragObject: TDragObject); override;
|
||
procedure DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); virtual;
|
||
procedure DoStructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual;
|
||
procedure DoTimerScroll; virtual;
|
||
procedure DoUpdating(State: TVTUpdateState); virtual;
|
||
function DoValidateCache: Boolean; virtual;
|
||
procedure DragAndDrop(AllowedEffects: LongWord; DataObject: IDataObject;
|
||
DragEffect: LongWord); virtual;
|
||
procedure DragCanceled; override;
|
||
function DragDrop(const DataObject: IDataObject; KeyState: LongWord; Pt: TPoint;
|
||
var Effect: LongWord): HResult; reintroduce; virtual;
|
||
function DragEnter(KeyState: LongWord; Pt: TPoint; var Effect: LongWord): HResult; virtual;
|
||
procedure DragFinished; virtual;
|
||
procedure DragLeave; virtual;
|
||
function DragOver(Source: TObject; KeyState: LongWord; DragState: TDragState; Pt: TPoint;
|
||
var Effect: LongWord): HResult; reintroduce; virtual;
|
||
procedure DrawDottedHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: Integer); virtual;
|
||
procedure DrawDottedVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: Integer); virtual;
|
||
procedure EndOperation;
|
||
function FindNodeInSelection(P: PVirtualNode; out Index: Integer; LowBound, HighBound: Integer): Boolean; virtual;
|
||
procedure FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer); virtual;
|
||
procedure FontChanged(AFont: TObject); virtual;
|
||
function GetBorderDimensions: TSize; virtual;
|
||
function GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone;
|
||
ImgCheckState: TCheckState = csUncheckedNormal; ImgEnabled: Boolean = False): Integer; virtual;
|
||
function GetClientRect: TRect; override;
|
||
function GetColumnClass: TVirtualTreeColumnClass; virtual;
|
||
function GetHeaderClass: TVTHeaderClass; virtual;
|
||
function GetHintWindowClass: THintWindowClass; virtual;
|
||
procedure GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex;
|
||
DefaultImages: TCustomImageList); virtual;
|
||
function GetMaxRightExtend: Cardinal; virtual;
|
||
procedure GetNativeClipboardFormats(var Formats: TFormatEtcArray); virtual;
|
||
function GetOperationCanceled: Boolean;
|
||
function GetOptionsClass: TTreeOptionsClass; virtual;
|
||
function GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree; virtual;
|
||
procedure HandleHotTrack(X, Y: Integer); virtual;
|
||
procedure HandleIncrementalSearch(CharCode: Word); virtual;
|
||
procedure HandleMouseDblClick(var Message: TLMMouse; const HitInfo: THitInfo); virtual;
|
||
procedure HandleMouseDown(var Message: TLMMouse; var HitInfo: THitInfo); virtual;
|
||
procedure HandleMouseUp(Keys: PtrUInt; const HitInfo: THitInfo); virtual;
|
||
function HasImage(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex): Boolean; virtual;
|
||
function HasPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Pos: TPoint): Boolean; virtual;
|
||
procedure InitChildren(Node: PVirtualNode); virtual;
|
||
procedure InitNode(Node: PVirtualNode); virtual;
|
||
procedure InternalAddFromStream(Stream: TStream; Version: Integer; Node: PVirtualNode); virtual;
|
||
function InternalAddToSelection(Node: PVirtualNode; ForceInsert: Boolean): Boolean; overload;
|
||
function InternalAddToSelection(const NewItems: TNodeArray; NewLength: Integer;
|
||
ForceInsert: Boolean): Boolean; overload;
|
||
procedure InternalCacheNode(Node: PVirtualNode); virtual;
|
||
procedure InternalClearSelection; virtual;
|
||
procedure InternalConnectNode(Node, Destination: PVirtualNode; Target: TBaseVirtualTree; Mode: TVTNodeAttachMode); virtual;
|
||
function InternalData(Node: PVirtualNode): Pointer;
|
||
procedure InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True); virtual;
|
||
function InternalGetNodeAt(X, Y: Integer): PVirtualNode; overload;
|
||
function InternalGetNodeAt(X, Y: Integer; Relative: Boolean; var NodeTop: Integer): PVirtualNode; overload;
|
||
procedure InternalRemoveFromSelection(Node: PVirtualNode); virtual;
|
||
procedure InvalidateCache;
|
||
procedure Loaded; override;
|
||
procedure MainColumnChanged; virtual;
|
||
procedure MarkCutCopyNodes; virtual;
|
||
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
|
||
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
||
{$ifdef EnableNCFunctions}
|
||
procedure OriginalWMNCPaint(DC: HDC); virtual;
|
||
{$endif}
|
||
procedure Paint; override;
|
||
procedure PaintCheckImage(const PaintInfo: TVTPaintInfo); virtual;
|
||
procedure PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean); virtual;
|
||
procedure PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; ButtonX,
|
||
ButtonY: Integer; BidiMode: TBiDiMode); virtual;
|
||
procedure PaintTreeLines(const PaintInfo: TVTPaintInfo; VAlignment, IndentSize: Integer;
|
||
LineImage: TLineImage); virtual;
|
||
procedure PaintSelectionRectangle(Target: TCanvas; WindowOrgX: Integer; const SelectionRect: TRect;
|
||
TargetRect: TRect); virtual;
|
||
procedure PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer); virtual;
|
||
function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType,
|
||
ChunkSize: Integer): Boolean; virtual;
|
||
procedure ReadNode(Stream: TStream; Version: Integer; Node: PVirtualNode); virtual;
|
||
procedure RedirectFontChangeEvent(Canvas: TCanvas); virtual;
|
||
procedure RemoveFromSelection(Node: PVirtualNode); virtual;
|
||
function RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; virtual;
|
||
procedure ResetRangeAnchor; virtual;
|
||
procedure RestoreFontChangeEvent(Canvas: TCanvas); virtual;
|
||
procedure SelectNodes(StartNode, EndNode: PVirtualNode; AddOnly: Boolean); virtual;
|
||
procedure SetFocusedNodeAndColumn(Node: PVirtualNode; Column: TColumnIndex); virtual;
|
||
procedure SkipNode(Stream: TStream); virtual;
|
||
procedure StartWheelPanning(const Position: TPoint); virtual;
|
||
procedure StopWheelPanning; virtual;
|
||
procedure StructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual;
|
||
function SuggestDropEffect(Source: TObject; Shift: TShiftState; const Pt: TPoint; AllowedEffects: LongWord): LongWord; virtual;
|
||
procedure ToggleSelection(StartNode, EndNode: PVirtualNode); virtual;
|
||
procedure UnselectNodes(StartNode, EndNode: PVirtualNode); virtual;
|
||
//lcl
|
||
procedure UpdateCheckImageList;
|
||
procedure UpdateColumnCheckState(Col: TVirtualTreeColumn);
|
||
procedure UpdateDesigner; virtual;
|
||
procedure UpdateEditBounds; virtual;
|
||
procedure UpdateHeaderRect; virtual;
|
||
procedure UpdateWindowAndDragImage(const Tree: TBaseVirtualTree; TreeRect: TRect; UpdateNCArea,
|
||
ReshowDragImage: Boolean); virtual;
|
||
procedure ValidateCache; virtual;
|
||
procedure ValidateNodeDataSize(var Size: Integer); virtual;
|
||
procedure WndProc(var Message: TLMessage); override;
|
||
procedure WriteChunks(Stream: TStream; Node: PVirtualNode); virtual;
|
||
procedure WriteNode(Stream: TStream; Node: PVirtualNode); virtual;
|
||
|
||
property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify;
|
||
property AnimationDuration: Cardinal read FAnimationDuration write SetAnimationDuration default 200;
|
||
property AutoExpandDelay: Cardinal read FAutoExpandDelay write FAutoExpandDelay default 1000;
|
||
property AutoScrollDelay: Cardinal read FAutoScrollDelay write FAutoScrollDelay default 1000;
|
||
property AutoScrollInterval: TAutoScrollInterval read FAutoScrollInterval write FAutoScrollInterval default 1;
|
||
property Background: TPicture read FBackground write SetBackground;
|
||
property BackgroundOffsetX: Integer index 0 read FBackgroundOffsetX write SetBackgroundOffset default 0;
|
||
property BackgroundOffsetY: Integer index 1 read FBackgroundOffsetY write SetBackgroundOffset default 0;
|
||
//property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle;
|
||
property BottomSpace: Cardinal read FBottomSpace write SetBottomSpace default 0;
|
||
property ButtonFillMode: TVTButtonFillMode read FButtonFillMode write SetButtonFillMode default fmTreeColor;
|
||
property ButtonStyle: TVTButtonStyle read FButtonStyle write SetButtonStyle default bsRectangle;
|
||
property ChangeDelay: Cardinal read FChangeDelay write FChangeDelay default 0;
|
||
property CheckImageKind: TCheckImageKind read FCheckImageKind write SetCheckImageKind default ckSystemDefault;
|
||
property ClipboardFormats: TClipboardFormats read FClipboardFormats write SetClipboardFormats;
|
||
property Colors: TVTColors read FColors write SetColors;
|
||
property CustomCheckImages: TBitmap read FCustomCheckImages write SetCustomCheckImages;
|
||
property DefaultNodeHeight: Cardinal read FDefaultNodeHeight write SetDefaultNodeHeight default 18;
|
||
property DefaultPasteMode: TVTNodeAttachMode read FDefaultPasteMode write FDefaultPasteMode default amAddChildLast;
|
||
property DragHeight: Integer read FDragHeight write FDragHeight default 350;
|
||
property DragImageKind: TVTDragImageKind read FDragImageKind write FDragImageKind default diComplete;
|
||
property DragOperations: TDragOperations read FDragOperations write FDragOperations default [doCopy, doMove];
|
||
property DragSelection: TNodeArray read FDragSelection;
|
||
property DragType: TVTDragType read FDragType write FDragType default dtOLE;
|
||
property DragWidth: Integer read FDragWidth write FDragWidth default 200;
|
||
property DrawSelectionMode: TVTDrawSelectionMode read FDrawSelectionMode write FDrawSelectionMode
|
||
default smDottedRectangle;
|
||
property EditColumn: TColumnIndex read FEditColumn write FEditColumn;
|
||
property EditDelay: Cardinal read FEditDelay write FEditDelay default 1000;
|
||
property Header: TVTHeader read FHeader write SetHeader;
|
||
property HeaderRect: TRect read FHeaderRect;
|
||
property HintMode: TVTHintMode read FHintMode write FHintMode default hmDefault;
|
||
property HotCursor: TCursor read FHotCursor write FHotCursor default crDefault;
|
||
property Images: TCustomImageList read FImages write SetImages;
|
||
property IncrementalSearch: TVTIncrementalSearch read FIncrementalSearch write SetSearchOption default isNone;
|
||
property IncrementalSearchDirection: TVTSearchDirection read FSearchDirection write FSearchDirection default sdForward;
|
||
property IncrementalSearchStart: TVTSearchStart read FSearchStart write FSearchStart default ssFocusedNode;
|
||
property IncrementalSearchTimeout: Cardinal read FSearchTimeout write FSearchTimeout default 1000;
|
||
property Indent: Cardinal read FIndent write SetIndent default 18;
|
||
property LastClickPos: TPoint read FLastClickPos write FLastClickPos;
|
||
property LastDropMode: TDropMode read FLastDropMode write FlastDropMode;
|
||
property LineMode: TVTLineMode read FLineMode write SetLineMode default lmNormal;
|
||
property LineStyle: TVTLineStyle read FLineStyle write SetLineStyle default lsDotted;
|
||
property Margin: Integer read FMargin write SetMargin default 4;
|
||
property NodeAlignment: TVTNodeAlignment read FNodeAlignment write SetNodeAlignment default naProportional;
|
||
property NodeDataSize: Integer read FNodeDataSize write SetNodeDataSize default -1;
|
||
property OperationCanceled: Boolean read GetOperationCanceled;
|
||
property RootNodeCount: Cardinal read GetRootNodeCount write SetRootNodeCount default 0;
|
||
property ScrollBarOptions: TScrollBarOptions read FScrollBarOptions write SetScrollBarOptions;
|
||
property SelectionBlendFactor: Byte read FSelectionBlendFactor write FSelectionBlendFactor default 128;
|
||
property SelectionCurveRadius: Cardinal read FSelectionCurveRadius write SetSelectionCurveRadius default 0;
|
||
property StateImages: TCustomImageList read FStateImages write SetStateImages;
|
||
property TextMargin: Integer read FTextMargin write SetTextMargin default 4;
|
||
property TotalInternalDataSize: Cardinal read FTotalInternalDataSize;
|
||
property TreeOptions: TCustomVirtualTreeOptions read FOptions write SetOptions;
|
||
property WantTabs: Boolean read FWantTabs write FWantTabs default False;
|
||
|
||
property OnAdvancedHeaderDraw: TVTAdvancedHeaderPaintEvent read FOnAdvancedHeaderDraw write FOnAdvancedHeaderDraw;
|
||
property OnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent read FOnAfterAutoFitColumn write FOnAfterAutoFitColumn;
|
||
property OnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent read FOnAfterAutoFitColumns write FOnAfterAutoFitColumns;
|
||
property OnAfterCellPaint: TVTAfterCellPaintEvent read FOnAfterCellPaint write FOnAfterCellPaint;
|
||
property OnAfterColumnExport : TVTColumnExportEvent read FOnAfterColumnExport write FOnAfterColumnExport;
|
||
property OnAfterColumnWidthTracking: TVTAfterColumnWidthTrackingEvent read FOnAfterColumnWidthTracking write FOnAfterColumnWidthTracking;
|
||
property OnAfterGetMaxColumnWidth: TVTAfterGetMaxColumnWidthEvent read FOnAfterGetMaxColumnWidth write FOnAfterGetMaxColumnWidth;
|
||
property OnAfterHeaderExport: TVTTreeExportEvent read FOnBeforeHeaderExport write FOnBeforeHeaderExport;
|
||
property OnAfterHeaderHeightTracking: TVTAfterHeaderHeightTrackingEvent read FOnAfterHeaderHeightTracking
|
||
write FOnAfterHeaderHeightTracking;
|
||
property OnAfterItemErase: TVTAfterItemEraseEvent read FOnAfterItemErase write FOnAfterItemErase;
|
||
property OnAfterItemPaint: TVTAfterItemPaintEvent read FOnAfterItemPaint write FOnAfterItemPaint;
|
||
property OnAfterNodeExport: TVTNodeExportEvent read FOnAfterNodeExport write FOnAfterNodeExport;
|
||
property OnAfterPaint: TVTPaintEvent read FOnAfterPaint write FOnAfterPaint;
|
||
property OnAfterTreeExport: TVTTreeExportEvent read FOnAfterTreeExport write FOnAfterTreeExport;
|
||
property OnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent read FOnBeforeAutoFitColumn write FOnBeforeAutoFitColumn;
|
||
property OnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent read FOnBeforeAutoFitColumns write FOnBeforeAutoFitColumns;
|
||
property OnBeforeCellPaint: TVTBeforeCellPaintEvent read FOnBeforeCellPaint write FOnBeforeCellPaint;
|
||
property OnBeforeColumnExport: TVTColumnExportEvent read FOnBeforeColumnExport write FOnBeforeColumnExport;
|
||
property OnBeforeColumnWidthTracking: TVTBeforeColumnWidthTrackingEvent read FOnBeforeColumnWidthTracking
|
||
write FOnBeforeColumnWidthTracking;
|
||
property OnBeforeGetMaxColumnWidth: TVTBeforeGetMaxColumnWidthEvent read FOnBeforeGetMaxColumnWidth write FOnBeforeGetMaxColumnWidth;
|
||
property OnBeforeHeaderExport: TVTTreeExportEvent read FOnBeforeHeaderExport write FOnBeforeHeaderExport;
|
||
property OnBeforeHeaderHeightTracking: TVTBeforeHeaderHeightTrackingEvent read FOnBeforeHeaderHeightTracking
|
||
write FOnBeforeHeaderHeightTracking;
|
||
property OnBeforeItemErase: TVTBeforeItemEraseEvent read FOnBeforeItemErase write FOnBeforeItemErase;
|
||
property OnBeforeItemPaint: TVTBeforeItemPaintEvent read FOnBeforeItemPaint write FOnBeforeItemPaint;
|
||
property OnBeforeNodeExport: TVTNodeExportEvent read FOnBeforeNodeExport write FOnBeforeNodeExport;
|
||
property OnBeforePaint: TVTPaintEvent read FOnBeforePaint write FOnBeforePaint;
|
||
property OnBeforeTreeExport: TVTTreeExportEvent read FOnBeforeTreeExport write FOnBeforeTreeExport;
|
||
property OnCanSplitterResizeColumn: TVTCanSplitterResizeColumnEvent read FOnCanSplitterResizeColumn write FOnCanSplitterResizeColumn;
|
||
property OnChange: TVTChangeEvent read FOnChange write FOnChange;
|
||
property OnChecked: TVTChangeEvent read FOnChecked write FOnChecked;
|
||
property OnChecking: TVTCheckChangingEvent read FOnChecking write FOnChecking;
|
||
property OnCollapsed: TVTChangeEvent read FOnCollapsed write FOnCollapsed;
|
||
property OnCollapsing: TVTChangingEvent read FOnCollapsing write FOnCollapsing;
|
||
property OnColumnClick: TVTColumnClickEvent read FOnColumnClick write FOnColumnClick;
|
||
property OnColumnDblClick: TVTColumnDblClickEvent read FOnColumnDblClick write FOnColumnDblClick;
|
||
property OnColumnExport : TVTColumnExportEvent read FOnColumnExport write FOnColumnExport;
|
||
property OnColumnResize: TVTHeaderNotifyEvent read FOnColumnResize write FOnColumnResize;
|
||
property OnColumnWidthDblClickResize: TVTColumnWidthDblClickResizeEvent read FOnColumnWidthDblClickResize
|
||
write FOnColumnWidthDblClickResize;
|
||
property OnColumnWidthTracking: TVTColumnWidthTrackingEvent read FOnColumnWidthTracking write FOnColumnWidthTracking;
|
||
property OnCompareNodes: TVTCompareEvent read FOnCompareNodes write FOnCompareNodes;
|
||
property OnCreateDataObject: TVTCreateDataObjectEvent read FOnCreateDataObject write FOnCreateDataObject;
|
||
property OnCreateDragManager: TVTCreateDragManagerEvent read FOnCreateDragManager write FOnCreateDragManager;
|
||
property OnCreateEditor: TVTCreateEditorEvent read FOnCreateEditor write FOnCreateEditor;
|
||
property OnDragAllowed: TVTDragAllowedEvent read FOnDragAllowed write FOnDragAllowed;
|
||
property OnDragOver: TVTDragOverEvent read FOnDragOver write FOnDragOver;
|
||
property OnDragDrop: TVTDragDropEvent read FOnDragDrop write FOnDragDrop;
|
||
property OnEditCancelled: TVTEditCancelEvent read FOnEditCancelled write FOnEditCancelled;
|
||
property OnEditing: TVTEditChangingEvent read FOnEditing write FOnEditing;
|
||
property OnEdited: TVTEditChangeEvent read FOnEdited write FOnEdited;
|
||
property OnExpanded: TVTChangeEvent read FOnExpanded write FOnExpanded;
|
||
property OnExpanding: TVTChangingEvent read FOnExpanding write FOnExpanding;
|
||
property OnFocusChanged: TVTFocusChangeEvent read FOnFocusChanged write FOnFocusChanged;
|
||
property OnFocusChanging: TVTFocusChangingEvent read FOnFocusChanging write FOnFocusChanging;
|
||
property OnFreeNode: TVTFreeNodeEvent read FOnFreeNode write FOnFreeNode;
|
||
property OnGetCellIsEmpty: TVTGetCellIsEmptyEvent read FOnGetCellIsEmpty write FOnGetCellIsEmpty;
|
||
property OnGetCursor: TVTGetCursorEvent read FOnGetCursor write FOnGetCursor;
|
||
property OnGetHeaderCursor: TVTGetHeaderCursorEvent read FOnGetHeaderCursor write FOnGetHeaderCursor;
|
||
property OnGetHelpContext: TVTHelpContextEvent read FOnGetHelpContext write FOnGetHelpContext;
|
||
property OnGetImageIndex: TVTGetImageEvent read FOnGetImage write FOnGetImage;
|
||
property OnGetImageIndexEx: TVTGetImageExEvent read FOnGetImageEx write FOnGetImageEx;
|
||
property OnGetImageText: TVTGetImageTextEvent read FOnGetImageText write FOnGetImageText;
|
||
property OnGetLineStyle: TVTGetLineStyleEvent read FOnGetLineStyle write FOnGetLineStyle;
|
||
property OnGetNodeDataSize: TVTGetNodeDataSizeEvent read FOnGetNodeDataSize write FOnGetNodeDataSize;
|
||
property OnGetPopupMenu: TVTPopupEvent read FOnGetPopupMenu write FOnGetPopupMenu;
|
||
property OnGetUserClipboardFormats: TVTGetUserClipboardFormatsEvent read FOnGetUserClipboardFormats
|
||
write FOnGetUserClipboardFormats;
|
||
property OnHeaderCheckBoxClick: TVTHeaderClickEvent read FOnHeaderCheckBoxClick write FOnHeaderCheckBoxClick;
|
||
property OnHeaderClick: TVTHeaderClickEvent read FOnHeaderClick write FOnHeaderClick;
|
||
property OnHeaderDblClick: TVTHeaderClickEvent read FOnHeaderDblClick write FOnHeaderDblClick;
|
||
property OnHeaderDragged: TVTHeaderDraggedEvent read FOnHeaderDragged write FOnHeaderDragged;
|
||
property OnHeaderDraggedOut: TVTHeaderDraggedOutEvent read FOnHeaderDraggedOut write FOnHeaderDraggedOut;
|
||
property OnHeaderDragging: TVTHeaderDraggingEvent read FOnHeaderDragging write FOnHeaderDragging;
|
||
property OnHeaderDraw: TVTHeaderPaintEvent read FOnHeaderDraw write FOnHeaderDraw;
|
||
property OnHeaderDrawQueryElements: TVTHeaderPaintQueryElementsEvent read FOnHeaderDrawQueryElements
|
||
write FOnHeaderDrawQueryElements;
|
||
property OnHeaderHeightTracking: TVTHeaderHeightTrackingEvent read FOnHeaderHeightTracking
|
||
write FOnHeaderHeightTracking;
|
||
property OnHeaderHeightDblClickResize: TVTHeaderHeightDblClickResizeEvent read FOnHeaderHeightDblClickResize
|
||
write FOnHeaderHeightDblClickResize;
|
||
property OnHeaderImageClick: TVTHeaderClickEvent read FOnHeaderImageClick write FOnHeaderImageClick;
|
||
property OnHeaderMouseDown: TVTHeaderMouseEvent read FOnHeaderMouseDown write FOnHeaderMouseDown;
|
||
property OnHeaderMouseMove: TVTHeaderMouseMoveEvent read FOnHeaderMouseMove write FOnHeaderMouseMove;
|
||
property OnHeaderMouseUp: TVTHeaderMouseEvent read FOnHeaderMouseUp write FOnHeaderMouseUp;
|
||
property OnHotChange: TVTHotNodeChangeEvent read FOnHotChange write FOnHotChange;
|
||
property OnIncrementalSearch: TVTIncrementalSearchEvent read FOnIncrementalSearch write FOnIncrementalSearch;
|
||
property OnInitChildren: TVTInitChildrenEvent read FOnInitChildren write FOnInitChildren;
|
||
property OnInitNode: TVTInitNodeEvent read FOnInitNode write FOnInitNode;
|
||
property OnKeyAction: TVTKeyActionEvent read FOnKeyAction write FOnKeyAction;
|
||
property OnLoadNode: TVTSaveNodeEvent read FOnLoadNode write FOnLoadNode;
|
||
property OnMeasureItem: TVTMeasureItemEvent read FOnMeasureItem write FOnMeasureItem;
|
||
property OnNodeCopied: TVTNodeCopiedEvent read FOnNodeCopied write FOnNodeCopied;
|
||
property OnNodeCopying: TVTNodeCopyingEvent read FOnNodeCopying write FOnNodeCopying;
|
||
property OnNodeExport: TVTNodeExportEvent read FOnNodeExport write FOnNodeExport;
|
||
property OnNodeHeightTracking: TVTNodeHeightTrackingEvent read FOnNodeHeightTracking write FOnNodeHeightTracking;
|
||
property OnNodeHeightDblClickResize: TVTNodeHeightDblClickResizeEvent read FOnNodeHeightDblClickResize
|
||
write FOnNodeHeightDblClickResize;
|
||
property OnNodeMoved: TVTNodeMovedEvent read FOnNodeMoved write FOnNodeMoved;
|
||
property OnNodeMoving: TVTNodeMovingEvent read FOnNodeMoving write FOnNodeMoving;
|
||
property OnPaintBackground: TVTBackgroundPaintEvent read FOnPaintBackground write FOnPaintBackground;
|
||
property OnRenderOLEData: TVTRenderOLEDataEvent read FOnRenderOLEData write FOnRenderOLEData;
|
||
property OnResetNode: TVTChangeEvent read FOnResetNode write FOnResetNode;
|
||
property OnSaveNode: TVTSaveNodeEvent read FOnSaveNode write FOnSaveNode;
|
||
property OnScroll: TVTScrollEvent read FOnScroll write FOnScroll;
|
||
property OnShowScrollbar: TVTScrollbarShowEvent read FOnShowScrollbar write FOnShowScrollbar;
|
||
property OnStateChange: TVTStateChangeEvent read FOnStateChange write FOnStateChange;
|
||
property OnStructureChange: TVTStructureChangeEvent read FOnStructureChange write FOnStructureChange;
|
||
property OnUpdating: TVTUpdatingEvent read FOnUpdating write FOnUpdating;
|
||
public
|
||
constructor Create(AOwner: TComponent); override;
|
||
destructor Destroy; override;
|
||
|
||
function AbsoluteIndex(Node: PVirtualNode): Cardinal;
|
||
function AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; virtual;
|
||
procedure AddFromStream(Stream: TStream; TargetNode: PVirtualNode);
|
||
procedure AfterConstruction; override;
|
||
procedure Assign(Source: TPersistent); override;
|
||
procedure BeginDrag(Immediate: Boolean; Threshold: Integer = -1);
|
||
procedure BeginSynch;
|
||
procedure BeginUpdate; virtual;
|
||
procedure CancelCutOrCopy;
|
||
function CancelEditNode: Boolean;
|
||
procedure CancelOperation;
|
||
function CanEdit(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual;
|
||
procedure Clear; virtual;
|
||
procedure ClearChecked;
|
||
procedure ClearSelection;
|
||
function CopyTo(Source: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode;
|
||
ChildrenOnly: Boolean): PVirtualNode; overload;
|
||
function CopyTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode;
|
||
ChildrenOnly: Boolean): PVirtualNode; overload;
|
||
procedure CopyToClipBoard; virtual;
|
||
procedure CutToClipBoard; virtual;
|
||
procedure DefaultHandler(var AMessage); override;
|
||
procedure DeleteChildren(Node: PVirtualNode; ResetHasChildren: Boolean = False);
|
||
procedure DeleteNode(Node: PVirtualNode; Reindex: Boolean = True);
|
||
procedure DeleteSelectedNodes; virtual;
|
||
function Dragging: Boolean;
|
||
function EditNode(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual;
|
||
function EndEditNode: Boolean;
|
||
procedure EndSynch;
|
||
procedure EndUpdate; virtual;
|
||
function ExecuteAction(Action: TBasicAction): Boolean; override;
|
||
procedure FinishCutOrCopy;
|
||
procedure FlushClipboard;
|
||
procedure FullCollapse(Node: PVirtualNode = nil); virtual;
|
||
procedure FullExpand(Node: PVirtualNode = nil); virtual;
|
||
{$ifndef fpc}
|
||
function GetControlsAlignment: TAlignment; override;
|
||
{$endif}
|
||
function GetDisplayRect(Node: PVirtualNode; Column: TColumnIndex; TextOnly: Boolean; Unclipped: Boolean = False;
|
||
ApplyCellContentMargin: Boolean = False): TRect;
|
||
function GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetFirstChecked(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetFirstChild(Node: PVirtualNode): PVirtualNode;
|
||
function GetFirstCutCopy(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetFirstInitialized(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetFirstLeaf: PVirtualNode;
|
||
function GetFirstLevel(NodeLevel: Cardinal): PVirtualNode;
|
||
function GetFirstNoInit(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetFirstSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetFirstVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetFirstVisibleChild(Node: PVirtualNode): PVirtualNode;
|
||
function GetFirstVisibleChildNoInit(Node: PVirtualNode): PVirtualNode;
|
||
function GetFirstVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
procedure GetHitTestInfoAt(X, Y: Integer; Relative: Boolean; var HitInfo: THitInfo); virtual;
|
||
function GetLast(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetLastInitialized(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetLastNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetLastChild(Node: PVirtualNode): PVirtualNode;
|
||
function GetLastChildNoInit(Node: PVirtualNode): PVirtualNode;
|
||
function GetLastVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetLastVisibleChild(Node: PVirtualNode): PVirtualNode;
|
||
function GetLastVisibleChildNoInit(Node: PVirtualNode): PVirtualNode;
|
||
function GetLastVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): Integer;
|
||
function GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal;
|
||
ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetNextCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetNextInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetNextLeaf(Node: PVirtualNode): PVirtualNode;
|
||
function GetNextLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode;
|
||
function GetNextNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetNextSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetNextSibling(Node: PVirtualNode): PVirtualNode;
|
||
function GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetNextVisibleSibling(Node: PVirtualNode): PVirtualNode;
|
||
function GetNextVisibleSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
||
function GetNodeAt(X, Y: Integer): PVirtualNode; overload;
|
||
function GetNodeAt(X, Y: Integer; Relative: Boolean; var NodeTop: Integer): PVirtualNode; overload;
|
||
function GetNodeData(Node: PVirtualNode): Pointer;
|
||
function GetNodeLevel(Node: PVirtualNode): Cardinal;
|
||
function GetPrevious(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetPreviousChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal;
|
||
ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetPreviousCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetPreviousInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetPreviousLeaf(Node: PVirtualNode): PVirtualNode;
|
||
function GetPreviousLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode;
|
||
function GetPreviousNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetPreviousSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
function GetPreviousSibling(Node: PVirtualNode): PVirtualNode;
|
||
function GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetPreviousVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
function GetPreviousVisibleSibling(Node: PVirtualNode): PVirtualNode;
|
||
function GetPreviousVisibleSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
||
function GetSortedCutCopySet(Resolve: Boolean): TNodeArray;
|
||
function GetSortedSelection(Resolve: Boolean): TNodeArray;
|
||
procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect;
|
||
out Text: String); virtual;
|
||
function GetTreeRect: TRect;
|
||
function GetVisibleParent(Node: PVirtualNode): PVirtualNode;
|
||
function HasAsParent(Node, PotentialParent: PVirtualNode): Boolean;
|
||
function InsertNode(Node: PVirtualNode; Mode: TVTNodeAttachMode; UserData: Pointer = nil): PVirtualNode;
|
||
procedure InvalidateChildren(Node: PVirtualNode; Recursive: Boolean);
|
||
procedure InvalidateColumn(Column: TColumnIndex);
|
||
function InvalidateNode(Node: PVirtualNode): TRect; virtual;
|
||
procedure InvalidateToBottom(Node: PVirtualNode);
|
||
procedure InvertSelection(VisibleOnly: Boolean);
|
||
function IsEditing: Boolean;
|
||
function IsMouseSelecting: Boolean;
|
||
function IterateSubtree(Node: PVirtualNode; Callback: TVTGetNodeProc; Data: Pointer; Filter: TVirtualNodeStates = [];
|
||
DoInit: Boolean = False; ChildNodesOnly: Boolean = False): PVirtualNode;
|
||
procedure LoadFromFile(const FileName: TFileName); virtual;
|
||
procedure LoadFromStream(Stream: TStream); virtual;
|
||
procedure MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode);
|
||
procedure MoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean); overload;
|
||
procedure MoveTo(Node: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode;
|
||
ChildrenOnly: Boolean); overload;
|
||
procedure PaintTree(TargetCanvas: TCanvas; const Window: TRect; Target: TPoint; PaintOptions: TVTInternalPaintOptions;
|
||
PixelFormat: TPixelFormat = pfDevice);
|
||
function PasteFromClipboard: Boolean; virtual;
|
||
procedure PrepareDragImage(HotSpot: TPoint; const DataObject: IDataObject);
|
||
{$ifdef EnablePrint}
|
||
procedure Print(Printer: TPrinter; PrintHeader: Boolean);
|
||
{$endif}
|
||
function ProcessDrop(DataObject: IDataObject; TargetNode: PVirtualNode; var Effect: LongWord; Mode:
|
||
TVTNodeAttachMode): Boolean;
|
||
function ProcessOLEData(Source: TBaseVirtualTree; DataObject: IDataObject; TargetNode: PVirtualNode;
|
||
Mode: TVTNodeAttachMode; Optimized: Boolean): Boolean;
|
||
procedure RepaintNode(Node: PVirtualNode);
|
||
procedure ReinitChildren(Node: PVirtualNode; Recursive: Boolean); virtual;
|
||
procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean); virtual;
|
||
procedure ResetNode(Node: PVirtualNode); virtual;
|
||
procedure SaveToFile(const FileName: TFileName);
|
||
procedure SaveToStream(Stream: TStream; Node: PVirtualNode = nil); virtual;
|
||
function ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean; overload;
|
||
function ScrollIntoView(Column: TColumnIndex; Center: Boolean): Boolean; overload;
|
||
procedure SelectAll(VisibleOnly: Boolean);
|
||
procedure Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual;
|
||
procedure SortTree(Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual;
|
||
procedure ToggleNode(Node: PVirtualNode);
|
||
function UpdateAction(Action: TBasicAction): Boolean; override;
|
||
procedure UpdateHorizontalScrollBar(DoRepaint: Boolean);
|
||
procedure UpdateScrollBars(DoRepaint: Boolean); virtual;
|
||
procedure UpdateVerticalScrollBar(DoRepaint: Boolean);
|
||
//lcl: reenable in case TControl implementation change to match Delphi
|
||
// function UseRightToLeftReading: Boolean;
|
||
procedure ValidateChildren(Node: PVirtualNode; Recursive: Boolean);
|
||
procedure ValidateNode(Node: PVirtualNode; Recursive: Boolean);
|
||
{$ifdef EnableAccessible}
|
||
property Accessible: IAccessible read FAccessible write FAccessible;
|
||
property AccessibleItem: IAccessible read FAccessibleItem write FAccessibleItem;
|
||
property AccessibleName: string read FAccessibleName write FAccessibleName;
|
||
{$endif}
|
||
property BottomNode: PVirtualNode read GetBottomNode write SetBottomNode;
|
||
property CheckedCount: Integer read GetCheckedCount;
|
||
property CheckImages: TBitmap read FCheckImages;
|
||
property CheckState[Node: PVirtualNode]: TCheckState read GetCheckState write SetCheckState;
|
||
property CheckType[Node: PVirtualNode]: TCheckType read GetCheckType write SetCheckType;
|
||
property ChildCount[Node: PVirtualNode]: Cardinal read GetChildCount write SetChildCount;
|
||
property ChildrenInitialized[Node: PVirtualNode]: Boolean read GetChildrenInitialized;
|
||
property CutCopyCount: Integer read GetCutCopyCount;
|
||
property DragImage: TVTDragImage read FDragImage;
|
||
property VTVDragManager: IVTDragManager read GetDragManager;
|
||
property DropTargetNode: PVirtualNode read FDropTargetNode;
|
||
property EditLink: IVTEditLink read FEditLink;
|
||
property Expanded[Node: PVirtualNode]: Boolean read GetExpanded write SetExpanded;
|
||
property FocusedColumn: TColumnIndex read FFocusedColumn write SetFocusedColumn default InvalidColumn;
|
||
property FocusedNode: PVirtualNode read FFocusedNode write SetFocusedNode;
|
||
property Font;
|
||
property FullyVisible[Node: PVirtualNode]: Boolean read GetFullyVisible write SetFullyVisible;
|
||
property HasChildren[Node: PVirtualNode]: Boolean read GetHasChildren write SetHasChildren;
|
||
property HotNode: PVirtualNode read FCurrentHotNode;
|
||
property IsDisabled[Node: PVirtualNode]: Boolean read GetDisabled write SetDisabled;
|
||
property IsVisible[Node: PVirtualNode]: Boolean read GetVisible write SetVisible;
|
||
property MultiLine[Node: PVirtualNode]: Boolean read GetMultiline write SetMultiline;
|
||
property NodeHeight[Node: PVirtualNode]: Cardinal read GetNodeHeight write SetNodeHeight;
|
||
property NodeParent[Node: PVirtualNode]: PVirtualNode read GetNodeParent write SetNodeParent;
|
||
property OffsetX: Integer read FOffsetX write SetOffsetX;
|
||
property OffsetXY: TPoint read GetOffsetXY write SetOffsetXY;
|
||
property OffsetY: Integer read FOffsetY write SetOffsetY;
|
||
property RootNode: PVirtualNode read FRoot;
|
||
property SearchBuffer: String read FSearchBuffer;
|
||
property Selected[Node: PVirtualNode]: Boolean read GetSelected write SetSelected;
|
||
property SelectionLocked: Boolean read FSelectionLocked write FSelectionLocked;
|
||
property TotalCount: Cardinal read GetTotalCount;
|
||
property TreeStates: TVirtualTreeStates read FStates write FStates;
|
||
property SelectedCount: Integer read FSelectionCount;
|
||
property TopNode: PVirtualNode read GetTopNode write SetTopNode;
|
||
property VerticalAlignment[Node: PVirtualNode]: Byte read GetVerticalAlignment write SetVerticalAlignment;
|
||
property VisibleCount: Cardinal read FVisibleCount;
|
||
property VisiblePath[Node: PVirtualNode]: Boolean read GetVisiblePath write SetVisiblePath;
|
||
property UpdateCount: Cardinal read FUpdateCount;
|
||
end;
|
||
|
||
|
||
// --------- TCustomVirtualStringTree
|
||
|
||
// Options regarding strings (useful only for the string tree and descendants):
|
||
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.
|
||
);
|
||
TVTStringOptions = set of TVTStringOption;
|
||
|
||
const
|
||
DefaultStringOptions = [toSaveCaptions, toAutoAcceptEditChange];
|
||
|
||
type
|
||
TCustomStringTreeOptions = class(TCustomVirtualTreeOptions)
|
||
private
|
||
FStringOptions: TVTStringOptions;
|
||
procedure SetStringOptions(const Value: TVTStringOptions);
|
||
protected
|
||
property StringOptions: TVTStringOptions read FStringOptions write SetStringOptions default DefaultStringOptions;
|
||
public
|
||
constructor Create(AOwner: TBaseVirtualTree); override;
|
||
|
||
procedure AssignTo(Dest: TPersistent); override;
|
||
end;
|
||
|
||
TStringTreeOptions = class(TCustomStringTreeOptions)
|
||
published
|
||
property AnimationOptions;
|
||
property AutoOptions;
|
||
property ExportMode;
|
||
property MiscOptions;
|
||
property PaintOptions;
|
||
property SelectionOptions;
|
||
property StringOptions;
|
||
end;
|
||
|
||
TCustomVirtualStringTree = class;
|
||
|
||
// Edit support classes.
|
||
TStringEditLink = class;
|
||
|
||
{ TVTEdit }
|
||
|
||
TVTEdit = class(TCustomEdit)
|
||
private
|
||
procedure CMAutoAdjust(var Message: TLMessage); message CM_AUTOADJUST;
|
||
procedure CMExit(var Message: TLMessage); message CM_EXIT;
|
||
procedure CNCommand(var Message: TLMCommand); message CN_COMMAND;
|
||
procedure DoRelease(Data: PtrInt);
|
||
procedure WMChar(var Message: TLMChar); message LM_CHAR;
|
||
procedure WMDestroy(var Message: TLMDestroy); message LM_DESTROY;
|
||
procedure WMGetDlgCode(var Message: TLMNoParams); message LM_GETDLGCODE;
|
||
procedure WMKeyDown(var Message: TLMKeyDown); message LM_KEYDOWN;
|
||
protected
|
||
FRefLink: IVTEditLink;
|
||
FLink: TStringEditLink;
|
||
procedure AutoAdjustSize; virtual;
|
||
procedure CreateParams(var Params: TCreateParams); override;
|
||
public
|
||
constructor Create(Link: TStringEditLink); reintroduce;
|
||
|
||
procedure Release;
|
||
|
||
property AutoSelect;
|
||
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.
|
||
procedure SetEdit(const Value: TVTEdit);
|
||
protected
|
||
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.
|
||
public
|
||
constructor Create; virtual;
|
||
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: String) of object;
|
||
TVSTGetHintEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: String) of object;
|
||
// New text can only be set for variable caption.
|
||
TVSTNewTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
const NewText: String) of object;
|
||
TVSTShortenStringEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; const S: String; TextSpace: Integer; var Result: String;
|
||
var Done: Boolean) of object;
|
||
TVTMeasureTextWidthEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; const Text: String; var Width: Integer) of object;
|
||
TVTDrawTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; const Text: String; const CellRect: TRect; var DefaultDraw: Boolean) of object;
|
||
|
||
// Helper class to speed up rendering text formats for clipboard and drag'n drop transfers.
|
||
|
||
{ TBufferedUTF8String }
|
||
|
||
TBufferedUTF8String = class
|
||
private
|
||
FStart,
|
||
FPosition,
|
||
FEnd: PChar;
|
||
function GetAsAnsiString: AnsiString;
|
||
function GetAsUTF16String: UnicodeString;
|
||
function GetAsUTF8String: String;
|
||
public
|
||
destructor Destroy; override;
|
||
|
||
procedure Add(const S: String);
|
||
procedure AddNewLine;
|
||
|
||
property AsAnsiString: AnsiString read GetAsAnsiString;
|
||
property AsUTF8String: String read GetAsUTF8String;
|
||
property AsUTF16String: UnicodeString read GetAsUTF16String;
|
||
end;
|
||
|
||
{ TCustomVirtualStringTree }
|
||
|
||
TCustomVirtualStringTree = class(TBaseVirtualTree)
|
||
private
|
||
FDefaultText: String; // 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: TVSTGetTextEvent; // used to retrieve the string to be displayed for a specific node
|
||
FOnGetHint: TVSTGetHintEvent; // 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
|
||
FOnMeasureTextWidth: TVTMeasureTextWidthEvent; // used to adjust the width of the cells
|
||
FOnDrawText: TVTDrawTextEvent; // used to custom draw the node text
|
||
|
||
procedure AddContentToBuffer(Buffer: TBufferedUTF8String; Source: TVSTTextSourceType; const Separator: String);
|
||
function GetImageText(Node: PVirtualNode; Kind: TVTImageKind;
|
||
Column: TColumnIndex): String;
|
||
procedure GetRenderStartValues(Source: TVSTTextSourceType; out Node: PVirtualNode;
|
||
out NextNodeProc: TGetNextNodeProc);
|
||
function GetOptions: TCustomStringTreeOptions;
|
||
function GetText(Node: PVirtualNode; Column: TColumnIndex): String;
|
||
procedure InitializeTextProperties(var PaintInfo: TVTPaintInfo);
|
||
procedure PaintNormalText(var PaintInfo: TVTPaintInfo; TextOutFlags: Integer; Text: String);
|
||
procedure PaintStaticText(const PaintInfo: TVTPaintInfo; TextOutFlags: Integer; const Text: String);
|
||
procedure SetDefaultText(const Value: String);
|
||
procedure SetOptions(const Value: TCustomStringTreeOptions);
|
||
procedure SetText(Node: PVirtualNode; Column: TColumnIndex; const Value: String);
|
||
procedure CMFontChanged(var Msg: TLMessage); message CM_FONTCHANGED;
|
||
protected
|
||
procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; out NextNonEmpty: TColumnIndex); override;
|
||
function CanExportNode(Node: PVirtualNode): Boolean;
|
||
function CalculateTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: String): Integer; virtual;
|
||
function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
||
function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; override;
|
||
function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; override;
|
||
function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; override;
|
||
function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; override;
|
||
procedure DoGetText(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
||
var Text: String); virtual;
|
||
function DoIncrementalSearch(Node: PVirtualNode; const Text: String): Integer; override;
|
||
procedure DoNewText(Node: PVirtualNode; Column: TColumnIndex; const Text: String); virtual;
|
||
procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override;
|
||
procedure DoPaintText(Node: PVirtualNode; const Canvas: TCanvas; Column: TColumnIndex;
|
||
TextType: TVSTTextType); virtual;
|
||
function DoShortenString(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const S: String; Width: Integer;
|
||
EllipsisWidth: Integer = 0): String; virtual;
|
||
procedure DoTextDrawing(var PaintInfo: TVTPaintInfo; const Text: String; CellRect: TRect; DrawFormat: Cardinal); virtual;
|
||
function DoTextMeasuring(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: String): Integer; virtual;
|
||
function GetOptionsClass: TTreeOptionsClass; override;
|
||
function InternalData(Node: PVirtualNode): Pointer;
|
||
procedure MainColumnChanged; override;
|
||
function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType,
|
||
ChunkSize: Integer): Boolean; override;
|
||
function RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; override;
|
||
procedure WriteChunks(Stream: TStream; Node: PVirtualNode); override;
|
||
|
||
property DefaultText: String read FDefaultText write SetDefaultText;
|
||
property EllipsisWidth: Integer read FEllipsisWidth;
|
||
property TreeOptions: TCustomStringTreeOptions read GetOptions write SetOptions;
|
||
|
||
property OnGetHint: TVSTGetHintEvent 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;
|
||
property OnMeasureTextWidth: TVTMeasureTextWidthEvent read FOnMeasureTextWidth write FOnMeasureTextWidth;
|
||
property OnDrawText: TVTDrawTextEvent read FOnDrawText write FOnDrawText;
|
||
public
|
||
constructor Create(AOwner: TComponent); override;
|
||
function ComputeNodeHeight(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; S: String = ''): Integer; virtual;
|
||
function ContentToClipboard(Format: Word; Source: TVSTTextSourceType): HGLOBAL;
|
||
procedure ContentToCustom(Source: TVSTTextSourceType);
|
||
function ContentToHTML(Source: TVSTTextSourceType; const Caption: String = ''): String;
|
||
function ContentToRTF(Source: TVSTTextSourceType): AnsiString;
|
||
function ContentToAnsi(Source: TVSTTextSourceType; const Separator: String): AnsiString;
|
||
function ContentToText(Source: TVSTTextSourceType; const Separator: String): AnsiString; inline;
|
||
function ContentToUnicode(Source: TVSTTextSourceType; const Separator: String): UnicodeString; inline;
|
||
function ContentToUTF16(Source: TVSTTextSourceType; const Separator: String): UnicodeString;
|
||
function ContentToUTF8(Source: TVSTTextSourceType; const Separator: String): String;
|
||
{$ifndef LCLWin32}
|
||
procedure CopyToClipBoard; override;
|
||
procedure CutToClipBoard; override;
|
||
{$endif}
|
||
procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect;
|
||
out Text: String); override;
|
||
function InvalidateNode(Node: PVirtualNode): TRect; override;
|
||
function Path(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; Delimiter: Char): String;
|
||
procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean); override;
|
||
|
||
property ImageText[Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex]: String read GetImageText;
|
||
property Text[Node: PVirtualNode; Column: TColumnIndex]: String 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
|
||
{$ifdef EnableAccessible}
|
||
property AccessibleName;
|
||
{$endif}
|
||
property Action;
|
||
property Align;
|
||
property Alignment;
|
||
property Anchors;
|
||
property AnimationDuration;
|
||
property AutoExpandDelay;
|
||
property AutoScrollDelay;
|
||
property AutoScrollInterval;
|
||
property Background;
|
||
property BackgroundOffsetX;
|
||
property BackgroundOffsetY;
|
||
property BiDiMode;
|
||
//property BevelEdges;
|
||
//property BevelInner;
|
||
//property BevelOuter;
|
||
//property BevelKind;
|
||
//property BevelWidth;
|
||
property BorderSpacing;
|
||
property BorderStyle default bsSingle;
|
||
property BottomSpace;
|
||
property ButtonFillMode;
|
||
property ButtonStyle;
|
||
property BorderWidth;
|
||
property ChangeDelay;
|
||
property CheckImageKind;
|
||
property ClipboardFormats;
|
||
property Color;
|
||
property Colors;
|
||
property Constraints;
|
||
//todo: see a way to set CustomCheckImages at design time
|
||
//property CustomCheckImages;
|
||
property DefaultNodeHeight;
|
||
property DefaultPasteMode;
|
||
property DefaultText;
|
||
property DragCursor;
|
||
property DragHeight;
|
||
property DragKind;
|
||
property DragImageKind;
|
||
property DragMode;
|
||
property DragOperations;
|
||
property DragType;
|
||
property DragWidth;
|
||
property DrawSelectionMode;
|
||
property EditDelay;
|
||
property Enabled;
|
||
property Font;
|
||
property Header;
|
||
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;
|
||
property OperationCanceled;
|
||
property ParentBiDiMode;
|
||
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 OnAfterAutoFitColumn;
|
||
property OnAfterAutoFitColumns;
|
||
property OnAfterCellPaint;
|
||
property OnAfterColumnExport;
|
||
property OnAfterColumnWidthTracking;
|
||
property OnAfterGetMaxColumnWidth;
|
||
property OnAfterHeaderExport;
|
||
property OnAfterHeaderHeightTracking;
|
||
property OnAfterItemErase;
|
||
property OnAfterItemPaint;
|
||
property OnAfterNodeExport;
|
||
property OnAfterPaint;
|
||
property OnAfterTreeExport;
|
||
property OnBeforeAutoFitColumn;
|
||
property OnBeforeAutoFitColumns;
|
||
property OnBeforeCellPaint;
|
||
property OnBeforeColumnExport;
|
||
property OnBeforeColumnWidthTracking;
|
||
property OnBeforeGetMaxColumnWidth;
|
||
property OnBeforeHeaderExport;
|
||
property OnBeforeHeaderHeightTracking;
|
||
property OnBeforeItemErase;
|
||
property OnBeforeItemPaint;
|
||
property OnBeforeNodeExport;
|
||
property OnBeforePaint;
|
||
property OnBeforeTreeExport;
|
||
property OnCanSplitterResizeColumn;
|
||
property OnChange;
|
||
property OnChecked;
|
||
property OnChecking;
|
||
property OnClick;
|
||
property OnCollapsed;
|
||
property OnCollapsing;
|
||
property OnColumnClick;
|
||
property OnColumnDblClick;
|
||
property OnColumnExport;
|
||
property OnColumnResize;
|
||
property OnColumnWidthDblClickResize;
|
||
property OnColumnWidthTracking;
|
||
property OnCompareNodes;
|
||
property OnContextPopup;
|
||
property OnCreateDataObject;
|
||
property OnCreateDragManager;
|
||
property OnCreateEditor;
|
||
property OnDblClick;
|
||
property OnDragAllowed;
|
||
property OnDragOver;
|
||
property OnDragDrop;
|
||
property OnDrawText;
|
||
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 OnGetImageIndexEx;
|
||
property OnGetImageText;
|
||
property OnGetHint;
|
||
property OnGetLineStyle;
|
||
property OnGetNodeDataSize;
|
||
property OnGetPopupMenu;
|
||
property OnGetUserClipboardFormats;
|
||
property OnHeaderCheckBoxClick;
|
||
property OnHeaderClick;
|
||
property OnHeaderDblClick;
|
||
property OnHeaderDragged;
|
||
property OnHeaderDraggedOut;
|
||
property OnHeaderDragging;
|
||
property OnHeaderDraw;
|
||
property OnHeaderDrawQueryElements;
|
||
property OnHeaderHeightDblClickResize;
|
||
property OnHeaderHeightTracking;
|
||
property OnHeaderImageClick;
|
||
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 OnMeasureTextWidth;
|
||
property OnMouseDown;
|
||
property OnMouseMove;
|
||
property OnMouseUp;
|
||
property OnMouseWheel;
|
||
property OnNewText;
|
||
property OnNodeCopied;
|
||
property OnNodeCopying;
|
||
property OnNodeExport;
|
||
property OnNodeHeightDblClickResize;
|
||
property OnNodeHeightTracking;
|
||
property OnNodeMoved;
|
||
property OnNodeMoving;
|
||
property OnPaintBackground;
|
||
property OnRenderOLEData;
|
||
property OnResetNode;
|
||
property OnResize;
|
||
property OnSaveNode;
|
||
property OnScroll;
|
||
property OnShortenString;
|
||
property OnShowScrollbar;
|
||
property OnStartDock;
|
||
property OnStartDrag;
|
||
property OnStateChange;
|
||
property OnStructureChange;
|
||
property OnUpdating;
|
||
property OnUTF8KeyPress;
|
||
end;
|
||
|
||
TVTDrawHintEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; const R: TRect;
|
||
Column: TColumnIndex) of object;
|
||
TVTDrawNodeEvent = procedure(Sender: TBaseVirtualTree; const PaintInfo: TVTPaintInfo) of object;
|
||
TVTGetCellContentMarginEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; CellContentMarginType: TVTCellContentMarginType; var CellContentMargin: TPoint) of object;
|
||
TVTGetNodeWidthEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode;
|
||
Column: TColumnIndex; var NodeWidth: Integer) of object;
|
||
TVTGetHintSizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
||
var R: TRect) of object;
|
||
|
||
// Tree descendant to let an application draw its stuff itself.
|
||
TCustomVirtualDrawTree = class(TBaseVirtualTree)
|
||
private
|
||
FOnDrawNode: TVTDrawNodeEvent;
|
||
FOnGetCellContentMargin: TVTGetCellContentMarginEvent;
|
||
FOnGetNodeWidth: TVTGetNodeWidthEvent;
|
||
FOnGetHintSize: TVTGetHintSizeEvent;
|
||
FOnDrawHint: TVTDrawHintEvent;
|
||
protected
|
||
procedure DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; const R: TRect; Column: TColumnIndex);
|
||
function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex;
|
||
CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; override;
|
||
procedure DoGetHintSize(Node: PVirtualNode; Column: TColumnIndex; var R: TRect); virtual;
|
||
function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; override;
|
||
procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override;
|
||
|
||
property OnDrawHint: TVTDrawHintEvent read FOnDrawHint write FOnDrawHint;
|
||
property OnDrawNode: TVTDrawNodeEvent read FOnDrawNode write FOnDrawNode;
|
||
property OnGetCellContentMargin: TVTGetCellContentMarginEvent read FOnGetCellContentMargin write FOnGetCellContentMargin;
|
||
property OnGetHintSize: TVTGetHintSizeEvent read FOnGetHintSize write FOnGetHintSize;
|
||
property OnGetNodeWidth: TVTGetNodeWidthEvent read FOnGetNodeWidth write FOnGetNodeWidth;
|
||
end;
|
||
|
||
TVirtualDrawTree = class(TCustomVirtualDrawTree)
|
||
private
|
||
function GetOptions: TVirtualTreeOptions;
|
||
procedure SetOptions(const Value: TVirtualTreeOptions);
|
||
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 BiDiMode;
|
||
//property BevelEdges;
|
||
//property BevelInner;
|
||
//property BevelOuter;
|
||
//property BevelKind;
|
||
// property BevelWidth;
|
||
property BorderSpacing;
|
||
property BorderStyle default bsSingle;
|
||
property BottomSpace;
|
||
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 DragCursor;
|
||
property DragHeight;
|
||
property DragKind;
|
||
property DragImageKind;
|
||
property DragMode;
|
||
property DragOperations;
|
||
property DragType;
|
||
property DragWidth;
|
||
property DrawSelectionMode;
|
||
property EditDelay;
|
||
property Enabled;
|
||
property Font;
|
||
property Header;
|
||
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;
|
||
property OperationCanceled;
|
||
property ParentBiDiMode;
|
||
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: TVirtualTreeOptions read GetOptions write SetOptions;
|
||
property Visible;
|
||
property WantTabs;
|
||
|
||
property OnAdvancedHeaderDraw;
|
||
property OnAfterAutoFitColumn;
|
||
property OnAfterAutoFitColumns;
|
||
property OnAfterCellPaint;
|
||
property OnAfterColumnExport;
|
||
property OnAfterColumnWidthTracking;
|
||
property OnAfterGetMaxColumnWidth;
|
||
property OnAfterHeaderExport;
|
||
property OnAfterHeaderHeightTracking;
|
||
property OnAfterItemErase;
|
||
property OnAfterItemPaint;
|
||
property OnAfterNodeExport;
|
||
property OnAfterPaint;
|
||
property OnAfterTreeExport;
|
||
property OnBeforeAutoFitColumn;
|
||
property OnBeforeAutoFitColumns;
|
||
property OnBeforeCellPaint;
|
||
property OnBeforeColumnExport;
|
||
property OnBeforeColumnWidthTracking;
|
||
property OnBeforeGetMaxColumnWidth;
|
||
property OnBeforeHeaderExport;
|
||
property OnBeforeHeaderHeightTracking;
|
||
property OnBeforeItemErase;
|
||
property OnBeforeItemPaint;
|
||
property OnBeforeNodeExport;
|
||
property OnBeforePaint;
|
||
property OnBeforeTreeExport;
|
||
property OnCanSplitterResizeColumn;
|
||
property OnChange;
|
||
property OnChecked;
|
||
property OnChecking;
|
||
property OnClick;
|
||
property OnCollapsed;
|
||
property OnCollapsing;
|
||
property OnColumnClick;
|
||
property OnColumnDblClick;
|
||
property OnColumnExport;
|
||
property OnColumnResize;
|
||
property OnColumnWidthDblClickResize;
|
||
property OnColumnWidthTracking;
|
||
property OnCompareNodes;
|
||
property OnContextPopup;
|
||
property OnCreateDataObject;
|
||
property OnCreateDragManager;
|
||
property OnCreateEditor;
|
||
property OnDblClick;
|
||
property OnDragAllowed;
|
||
property OnDragOver;
|
||
property OnDragDrop;
|
||
property OnDrawHint;
|
||
property OnDrawNode;
|
||
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 OnGetHelpContext;
|
||
property OnGetHintSize;
|
||
property OnGetImageIndex;
|
||
property OnGetImageIndexEx;
|
||
property OnGetLineStyle;
|
||
property OnGetNodeDataSize;
|
||
property OnGetNodeWidth;
|
||
property OnGetPopupMenu;
|
||
property OnGetUserClipboardFormats;
|
||
property OnHeaderCheckBoxClick;
|
||
property OnHeaderClick;
|
||
property OnHeaderDblClick;
|
||
property OnHeaderDragged;
|
||
property OnHeaderDraggedOut;
|
||
property OnHeaderDragging;
|
||
property OnHeaderDraw;
|
||
property OnHeaderDrawQueryElements;
|
||
property OnHeaderHeightTracking;
|
||
property OnHeaderHeightDblClickResize;
|
||
property OnHeaderImageClick;
|
||
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 OnMouseWheel;
|
||
property OnNodeCopied;
|
||
property OnNodeCopying;
|
||
property OnNodeExport;
|
||
property OnNodeHeightTracking;
|
||
property OnNodeHeightDblClickResize;
|
||
property OnNodeMoved;
|
||
property OnNodeMoving;
|
||
property OnPaintBackground;
|
||
property OnRenderOLEData;
|
||
property OnResetNode;
|
||
property OnResize;
|
||
property OnSaveNode;
|
||
property OnScroll;
|
||
property OnShowScrollbar;
|
||
property OnStartDock;
|
||
property OnStartDrag;
|
||
property OnStateChange;
|
||
property OnStructureChange;
|
||
property OnUpdating;
|
||
property OnUTF8KeyPress;
|
||
end;
|
||
|
||
// OLE Clipboard and drag'n drop helper
|
||
procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; const List: TStrings); overload;
|
||
procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray); overload;
|
||
function GetVTClipboardFormatDescription(AFormat: Word): string;
|
||
procedure RegisterVTClipboardFormat(AFormat: Word; TreeClass: TVirtualTreeClass; Priority: Cardinal); overload;
|
||
function RegisterVTClipboardFormat(Description: string; TreeClass: TVirtualTreeClass; Priority: Cardinal;
|
||
tymed: Integer = TYMED_HGLOBAL; ptd: PDVTargetDevice = nil; dwAspect: Integer = DVASPECT_CONTENT;
|
||
lindex: Integer = -1): Word; overload;
|
||
|
||
// utility routines
|
||
{$ifdef EnablePrint}
|
||
procedure PrtStretchDrawDIB(Canvas: TCanvas; DestRect: TRect; ABitmap: TBitmap);
|
||
{$endif}
|
||
function ShortenString(DC: HDC; const S: String; Width: Integer; EllipsisWidth: Integer = 0): String;
|
||
function TreeFromNode(Node: PVirtualNode): TBaseVirtualTree;
|
||
procedure GetStringDrawRect(DC: HDC; const S: String; var Bounds: TRect; DrawFormat: Cardinal);
|
||
function WrapString(DC: HDC; const S: String; const Bounds: TRect; RTL: Boolean;
|
||
DrawFormat: Cardinal): String;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
implementation
|
||
|
||
uses
|
||
StrUtils, Math,
|
||
{$ifdef EnableOLE}
|
||
//AxCtrls, // TOLEStream
|
||
{$endif}
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB, // wrapper for systems without flat SB support
|
||
{$endif UseFlatScrollbars}
|
||
{$ifdef Windows}
|
||
MMSystem, // for animation timer (does not include further resources)
|
||
{$else}
|
||
FakeMMSystem,
|
||
{$endif}
|
||
TypInfo, // for migration stuff
|
||
ActnList,
|
||
StdActns, // for standard action support
|
||
GraphType,
|
||
LCLProc
|
||
{$ifdef EnableAccessible}
|
||
,VTAccessibilityFactory
|
||
{$endif}; // accessibility helper class
|
||
|
||
resourcestring
|
||
// Localizable strings.
|
||
SEditLinkIsNil = 'Edit link must not be nil.';
|
||
SWrongMoveError = 'Target node cannot be a child node of the node to be moved.';
|
||
SWrongStreamFormat = 'Unable to load tree structure, the format is wrong.';
|
||
SWrongStreamVersion = 'Unable to load tree structure, the version is unknown.';
|
||
SStreamTooSmall = 'Unable to load tree structure, not enough data available.';
|
||
SCorruptStream1 = 'Stream data corrupt. A node''s anchor chunk is missing.';
|
||
SCorruptStream2 = 'Stream data corrupt. Unexpected data after node''s end position.';
|
||
SClipboardFailed = 'Clipboard operation failed.';
|
||
SCannotSetUserData = 'Cannot set initial user data because there is not enough user data space allocated.';
|
||
|
||
const
|
||
ClipboardStates = [tsCopyPending, tsCutPending];
|
||
DefaultScrollUpdateFlags = [suoRepaintHeader, suoRepaintScrollbars, suoScrollClientArea, suoUpdateNCArea];
|
||
MinimumTimerInterval = 1; // minimum resolution for timeGetTime
|
||
TreeNodeSize = (SizeOf(TVirtualNode) + 3) and not 3; // used for node allocation and access to internal data
|
||
|
||
// Lookup to quickly convert a specific check state into its pressed counterpart and vice versa.
|
||
PressedState: array[TCheckState] of TCheckState = (
|
||
csUncheckedPressed, csUncheckedPressed, csCheckedPressed, csCheckedPressed, csMixedPressed, csMixedPressed
|
||
);
|
||
UnpressedState: array[TCheckState] of TCheckState = (
|
||
csUncheckedNormal, csUncheckedNormal, csCheckedNormal, csCheckedNormal, csMixedNormal, csMixedNormal
|
||
);
|
||
MouseButtonDown = [tsLeftButtonDown, tsMiddleButtonDown, tsRightButtonDown];
|
||
|
||
// Do not modify the copyright in any way! Usage of this unit is prohibited without the copyright notice
|
||
// in the compiled binary file.
|
||
Copyright: string = 'Virtual Treeview <20> 1999, 2010 Mike Lischke';
|
||
|
||
var
|
||
//Workaround to LCL bug 8553
|
||
{$ifndef LCLWin32}
|
||
pf32bit: TPixelFormat = pfDevice;
|
||
{$endif}
|
||
|
||
StandardOLEFormat: TFormatEtc = (
|
||
// Format must later be set.
|
||
cfFormat: 0;
|
||
// No specific target device to render on.
|
||
ptd: nil;
|
||
// Normal content to render.
|
||
dwAspect: DVASPECT_CONTENT;
|
||
// No specific page of multipage data (we don't use multipage data by default).
|
||
lindex: -1;
|
||
// Acceptable storage formats are IStream and global memory. The first is preferred.
|
||
tymed: TYMED_ISTREAM or TYMED_HGLOBAL;
|
||
);
|
||
|
||
|
||
type // streaming support
|
||
TMagicID = array[0..5] of Char;
|
||
|
||
TChunkHeader = record
|
||
ChunkType,
|
||
ChunkSize: Integer; // contains the size of the chunk excluding the header
|
||
end;
|
||
|
||
// base information about a node
|
||
TBaseChunkBody = packed record
|
||
ChildCount,
|
||
NodeHeight: Cardinal;
|
||
States: TVirtualNodeStates;
|
||
Align: Byte;
|
||
CheckState: TCheckState;
|
||
CheckType: TCheckType;
|
||
Reserved: Cardinal;
|
||
end;
|
||
|
||
TBaseChunk = packed record
|
||
Header: TChunkHeader;
|
||
Body: TBaseChunkBody;
|
||
end;
|
||
|
||
// Toggle animation modes.
|
||
TToggleAnimationMode = (
|
||
tamScrollUp,
|
||
tamScrollDown,
|
||
tamNoScroll
|
||
);
|
||
|
||
// Internally used data for animations.
|
||
TToggleAnimationData = record
|
||
Window: HWND; // copy of the tree's window handle
|
||
DC: HDC; // the DC of the window to erase uncovered parts
|
||
Brush: HBRUSH; // the brush to be used to erase uncovered parts
|
||
R1,
|
||
R2: TRect; // animation rectangles
|
||
Mode1,
|
||
Mode2: TToggleAnimationMode; // animation modes
|
||
ScaleFactor: Double; // the factor between the missing step size when doing two animations
|
||
MissedSteps: Double;
|
||
end;
|
||
|
||
const
|
||
CheckImagesStrings: array [TCheckImageKind] of String =
|
||
('VT_CHECK_LIGHT',
|
||
'VT_CHECK_DARK',
|
||
'VT_TICK_LIGHT',
|
||
'VT_TICK_DARK',
|
||
'VT_FLAT',
|
||
'VT_XP',
|
||
'',//ckCustom,
|
||
// Only the button images are used for ckSystem *
|
||
// The check buttons are draw at fly as requested
|
||
'VT_FLAT',//ckSystemFlat
|
||
'VT_CHECK_DARK' //ckSystemDefault
|
||
);
|
||
MagicID: TMagicID = (#$45, 'V', 'T', Char(VTTreeStreamVersion), ' ', #$46);
|
||
|
||
// chunk IDs
|
||
NodeChunk = 1;
|
||
BaseChunk = 2; // chunk containing node state, check state, child node count etc.
|
||
// this chunk is immediately followed by all child nodes
|
||
CaptionChunk = 3; // used by the string tree to store a node's caption
|
||
UserChunk = 4; // used for data supplied by the application
|
||
|
||
{$ifdef UseFlatScrollbars}
|
||
ScrollBarProp: array[TScrollBarStyle] of Integer = (
|
||
FSB_REGULAR_MODE,
|
||
FSB_FLAT_MODE,
|
||
FSB_ENCARTA_MODE
|
||
);
|
||
{$endif}
|
||
|
||
RTLFlag: array[Boolean] of Integer = (0, ETO_RTLREADING);
|
||
AlignmentToDrawFlag: array[TAlignment] of Cardinal = (DT_LEFT, DT_RIGHT, DT_CENTER);
|
||
|
||
WideCR = WideChar(#13);
|
||
WideLF = WideChar(#10);
|
||
|
||
type
|
||
// internal worker thread
|
||
TWorkerThread = class(TThread)
|
||
private
|
||
FCurrentTree: TBaseVirtualTree;
|
||
FWaiterList: TThreadList;
|
||
FRefCount: Cardinal;
|
||
protected
|
||
procedure CancelValidation(Tree: TBaseVirtualTree);
|
||
procedure ChangeTreeStates(EnterStates, LeaveStates: TChangeStates);
|
||
procedure Execute; override;
|
||
public
|
||
constructor Create(CreateSuspended: Boolean);
|
||
destructor Destroy; override;
|
||
|
||
procedure AddTree(Tree: TBaseVirtualTree);
|
||
procedure RemoveTree(Tree: TBaseVirtualTree);
|
||
|
||
property CurrentTree: TBaseVirtualTree read FCurrentTree;
|
||
end;
|
||
|
||
var
|
||
WorkerThread: TWorkerThread;
|
||
WorkEvent: TEvent;
|
||
UtilityImages: TBitmap; // some small additional images (e.g for header dragging)
|
||
Initialized: Boolean; // True if global structures have been initialized.
|
||
NeedToUnitialize: Boolean; // True if the OLE subsystem could be initialized successfully.
|
||
|
||
//----------------- TClipboardFormats ----------------------------------------------------------------------------------
|
||
|
||
type
|
||
PClipboardFormatListEntry = ^TClipboardFormatListEntry;
|
||
TClipboardFormatListEntry = record
|
||
Description: string; // The string used to register the format with Windows.
|
||
TreeClass: TVirtualTreeClass; // The tree class which supports rendering this format.
|
||
Priority: Cardinal; // Number which determines the order of formats used in IDataObject.
|
||
FormatEtc: TFormatEtc; // The definition of the format in the IDataObject.
|
||
end;
|
||
|
||
TClipboardFormatList = class
|
||
private
|
||
FList: TFpList;
|
||
procedure Sort;
|
||
public
|
||
constructor Create;
|
||
destructor Destroy; override;
|
||
|
||
procedure Add(FormatString: string; AClass: TVirtualTreeClass; Priority: Cardinal; AFormatEtc: TFormatEtc);
|
||
procedure Clear;
|
||
procedure EnumerateFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray;
|
||
const AllowedFormats: TClipboardFormats = nil); overload;
|
||
procedure EnumerateFormats(TreeClass: TVirtualTreeClass; const Formats: TStrings); overload;
|
||
function FindFormat(FormatString: string): PClipboardFormatListEntry; overload;
|
||
function FindFormat(FormatString: string; var Fmt: Word): TVirtualTreeClass; overload;
|
||
function FindFormat(Fmt: Word; out Description: string): TVirtualTreeClass; overload;
|
||
end;
|
||
|
||
var
|
||
InternalClipboardFormats: TClipboardFormatList;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
constructor TClipboardFormatList.Create;
|
||
|
||
begin
|
||
FList := TFpList.Create;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TClipboardFormatList.Destroy;
|
||
|
||
begin
|
||
Clear;
|
||
FList.Free;
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TClipboardFormatList.Sort;
|
||
|
||
// Sorts all entry for priority (increasing priority value).
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
procedure QuickSort(L, R: Integer);
|
||
|
||
var
|
||
I, J: Integer;
|
||
P, T: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
repeat
|
||
I := L;
|
||
J := R;
|
||
P := FList[(L + R) shr 1];
|
||
repeat
|
||
while PClipboardFormatListEntry(FList[I]).Priority < P.Priority do
|
||
Inc(I);
|
||
while PClipboardFormatListEntry(Flist[J]).Priority > P.Priority do
|
||
Dec(J);
|
||
if I <= J then
|
||
begin
|
||
T := Flist[I];
|
||
FList[I] := FList[J];
|
||
FList[J] := T;
|
||
Inc(I);
|
||
Dec(J);
|
||
end;
|
||
until I > J;
|
||
if L < J then
|
||
QuickSort(L, J);
|
||
L := I;
|
||
until I >= R;
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
begin
|
||
if FList.Count > 1 then
|
||
QuickSort(0, FList.Count - 1);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TClipboardFormatList.Add(FormatString: string; AClass: TVirtualTreeClass; Priority: Cardinal;
|
||
AFormatEtc: TFormatEtc);
|
||
|
||
// Adds the given data to the internal list. The priority value is used to sort formats for importance. Larger priority
|
||
// values mean less priority.
|
||
|
||
var
|
||
Entry: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
New(Entry);
|
||
Entry.Description := FormatString;
|
||
Entry.TreeClass := AClass;
|
||
Entry.Priority := Priority;
|
||
Entry.FormatEtc := AFormatEtc;
|
||
FList.Add(Entry);
|
||
|
||
Sort;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TClipboardFormatList.Clear;
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
for I := 0 to FList.Count - 1 do
|
||
Dispose(PClipboardFormatListEntry(FList[I]));
|
||
FList.Clear;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TClipboardFormatList.EnumerateFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray;
|
||
const AllowedFormats: TClipboardFormats = nil);
|
||
|
||
// Returns a list of format records for the given class. If assigned the AllowedFormats is used to limit the
|
||
// enumerated formats to those described in the list.
|
||
|
||
var
|
||
I, Count: Integer;
|
||
Entry: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
SetLength(Formats, FList.Count);
|
||
Count := 0;
|
||
for I := 0 to FList.Count - 1 do
|
||
begin
|
||
Entry := FList[I];
|
||
// Does the tree class support this clipboard format?
|
||
if TreeClass.InheritsFrom(Entry.TreeClass) then
|
||
begin
|
||
// Is this format allowed to be included?
|
||
if (AllowedFormats = nil) or (AllowedFormats.IndexOf(Entry.Description) > -1) then
|
||
begin
|
||
// The list could change before we use the FormatEtc so it is best not to pass a pointer to the true FormatEtc
|
||
// structure. Instead make a copy and send that.
|
||
Formats[Count] := Entry.FormatEtc;
|
||
Inc(Count);
|
||
end;
|
||
end;
|
||
end;
|
||
SetLength(Formats, Count);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TClipboardFormatList.EnumerateFormats(TreeClass: TVirtualTreeClass; const Formats: TStrings);
|
||
|
||
// Returns a list of format descriptions for the given class.
|
||
|
||
var
|
||
I: Integer;
|
||
Entry: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
for I := 0 to FList.Count - 1 do
|
||
begin
|
||
Entry := FList[I];
|
||
if TreeClass.InheritsFrom(Entry.TreeClass) then
|
||
Formats.Add(Entry.Description);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TClipboardFormatList.FindFormat(FormatString: string): PClipboardFormatListEntry;
|
||
|
||
var
|
||
I: Integer;
|
||
Entry: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
Result := nil;
|
||
for I := FList.Count - 1 downto 0 do
|
||
begin
|
||
Entry := FList[I];
|
||
if CompareText(Entry.Description, FormatString) = 0 then
|
||
begin
|
||
Result := Entry;
|
||
Break;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TClipboardFormatList.FindFormat(FormatString: string; var Fmt: Word): TVirtualTreeClass;
|
||
|
||
var
|
||
I: Integer;
|
||
Entry: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
Result := nil;
|
||
for I := FList.Count - 1 downto 0 do
|
||
begin
|
||
Entry := FList[I];
|
||
if CompareText(Entry.Description, FormatString) = 0 then
|
||
begin
|
||
Result := Entry.TreeClass;
|
||
Fmt := Entry.FormatEtc.cfFormat;
|
||
Break;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TClipboardFormatList.FindFormat(Fmt: Word; out Description: string): TVirtualTreeClass;
|
||
|
||
var
|
||
I: Integer;
|
||
Entry: PClipboardFormatListEntry;
|
||
|
||
begin
|
||
Result := nil;
|
||
for I := FList.Count - 1 downto 0 do
|
||
begin
|
||
Entry := FList[I];
|
||
if Entry.FormatEtc.cfFormat = Fmt then
|
||
begin
|
||
Result := Entry.TreeClass;
|
||
Description := Entry.Description;
|
||
Break;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
type
|
||
TClipboardFormatEntry = record
|
||
ID: Word;
|
||
Description: string;
|
||
end;
|
||
|
||
var
|
||
ClipboardDescriptions: array [1..CF_MAX - 1] of TClipboardFormatEntry = (
|
||
(ID: CF_TEXT; Description: 'Plain text'), // Do not localize
|
||
(ID: CF_BITMAP; Description: 'Windows bitmap'), // Do not localize
|
||
(ID: CF_METAFILEPICT; Description: 'Windows metafile'), // Do not localize
|
||
(ID: CF_SYLK; Description: 'Symbolic link'), // Do not localize
|
||
(ID: CF_DIF; Description: 'Data interchange format'), // Do not localize
|
||
(ID: CF_TIFF; Description: 'Tiff image'), // Do not localize
|
||
(ID: CF_OEMTEXT; Description: 'OEM text'), // Do not localize
|
||
(ID: CF_DIB; Description: 'DIB image'), // Do not localize
|
||
(ID: CF_PALETTE; Description: 'Palette data'), // Do not localize
|
||
(ID: CF_PENDATA; Description: 'Pen data'), // Do not localize
|
||
(ID: CF_RIFF; Description: 'Riff audio data'), // Do not localize
|
||
(ID: CF_WAVE; Description: 'Wav audio data'), // Do not localize
|
||
(ID: CF_UNICODETEXT; Description: 'Unicode text'), // Do not localize
|
||
(ID: CF_ENHMETAFILE; Description: 'Enhanced metafile image'), // Do not localize
|
||
(ID: CF_HDROP; Description: 'File name(s)'), // Do not localize
|
||
(ID: CF_LOCALE; Description: 'Locale descriptor') // Do not localize
|
||
);
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; const List: TStrings);
|
||
|
||
begin
|
||
if InternalClipboardFormats = nil then
|
||
InternalClipboardFormats := TClipboardFormatList.Create;
|
||
InternalClipboardFormats.EnumerateFormats(TreeClass, List);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure EnumerateVTClipboardFormats(TreeClass: TVirtualTreeClass; var Formats: TFormatEtcArray);
|
||
|
||
begin
|
||
if InternalClipboardFormats = nil then
|
||
InternalClipboardFormats := TClipboardFormatList.Create;
|
||
InternalClipboardFormats.EnumerateFormats(TreeClass, Formats);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function GetVTClipboardFormatDescription(AFormat: Word): string;
|
||
|
||
begin
|
||
if InternalClipboardFormats = nil then
|
||
InternalClipboardFormats := TClipboardFormatList.Create;
|
||
if InternalClipboardFormats.FindFormat(AFormat, Result) = nil then
|
||
Result := '';
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure RegisterVTClipboardFormat(AFormat: Word; TreeClass: TVirtualTreeClass; Priority: Cardinal);
|
||
|
||
// Registers the given clipboard format for the given TreeClass.
|
||
|
||
var
|
||
I: Integer;
|
||
FormatEtc: TFormatEtc;
|
||
|
||
begin
|
||
if InternalClipboardFormats = nil then
|
||
InternalClipboardFormats := TClipboardFormatList.Create;
|
||
|
||
// Assumes a HGlobal format.
|
||
FormatEtc.cfFormat := AFormat;
|
||
FormatEtc.ptd := nil;
|
||
FormatEtc.dwAspect := DVASPECT_CONTENT;
|
||
FormatEtc.lindex := -1;
|
||
FormatEtc.tymed := TYMED_HGLOBAL;
|
||
|
||
// Determine description string of the given format. For predefined formats we need the lookup table because they
|
||
// don't have a description string. For registered formats the description string is the string which was used
|
||
// to register them.
|
||
if AFormat < CF_MAX then
|
||
begin
|
||
for I := 1 to High(ClipboardDescriptions) do
|
||
if ClipboardDescriptions[I].ID = AFormat then
|
||
begin
|
||
InternalClipboardFormats.Add(ClipboardDescriptions[I].Description, TreeClass, Priority, FormatEtc);
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
InternalClipboardFormats.Add(ClipboardFormatToMimeType(AFormat), TreeClass, Priority, FormatEtc);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function RegisterVTClipboardFormat(Description: string; TreeClass: TVirtualTreeClass; Priority: Cardinal;
|
||
tymed: Integer = TYMED_HGLOBAL; ptd: PDVTargetDevice = nil; dwAspect: Integer = DVASPECT_CONTENT;
|
||
lindex: Integer = -1): Word;
|
||
|
||
// Alternative method to register a certain clipboard format for a given tree class. Registration with the
|
||
// clipboard is done here too and the assigned ID returned by the function.
|
||
// tymed may contain or'ed TYMED constants which allows to register several storage formats for one clipboard format.
|
||
|
||
var
|
||
FormatEtc: TFormatEtc;
|
||
|
||
begin
|
||
if InternalClipboardFormats = nil then
|
||
InternalClipboardFormats := TClipboardFormatList.Create;
|
||
Result := ClipboardRegisterFormat(Description);
|
||
FormatEtc.cfFormat := Result;
|
||
FormatEtc.ptd := ptd;
|
||
FormatEtc.dwAspect := dwAspect;
|
||
FormatEtc.lindex := lindex;
|
||
FormatEtc.tymed := tymed;
|
||
InternalClipboardFormats.Add(Description, TreeClass, Priority, FormatEtc);
|
||
end;
|
||
|
||
//----------------- utility functions ----------------------------------------------------------------------------------
|
||
|
||
procedure ShowError(Msg: String; HelpContext: Integer);
|
||
|
||
begin
|
||
raise EVirtualTreeError.CreateHelp(Msg, HelpContext);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TreeFromNode(Node: PVirtualNode): TBaseVirtualTree;
|
||
|
||
// Returns the tree the node currently belongs to or nil if the node is not attached to a tree.
|
||
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil.');
|
||
|
||
// The root node is marked by having its NextSibling (and PrevSibling) pointing to itself.
|
||
while Assigned(Node) and (Node.NextSibling <> Node) do
|
||
Node := Node.Parent;
|
||
if Assigned(Node) then
|
||
Result := TBaseVirtualTree(Node.Parent)
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function OrderRect(const R: TRect): TRect;
|
||
|
||
// Converts the incoming rectangle so that left and top are always less than or equal to right and bottom.
|
||
|
||
begin
|
||
if R.Left < R.Right then
|
||
begin
|
||
Result.Left := R.Left;
|
||
Result.Right := R.Right;
|
||
end
|
||
else
|
||
begin
|
||
Result.Left := R.Right;
|
||
Result.Right := R.Left;
|
||
end;
|
||
if R.Top < R.Bottom then
|
||
begin
|
||
Result.Top := R.Top;
|
||
Result.Bottom := R.Bottom;
|
||
end
|
||
else
|
||
begin
|
||
Result.Top := R.Bottom;
|
||
Result.Bottom := R.Top;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure QuickSort(const TheArray: TNodeArray; L, R: Integer);
|
||
|
||
var
|
||
I, J: Integer;
|
||
P, T: Pointer;
|
||
|
||
begin
|
||
repeat
|
||
I := L;
|
||
J := R;
|
||
P := TheArray[(L + R) shr 1];
|
||
repeat
|
||
while TheArray[I] < P do
|
||
Inc(I);
|
||
while TheArray[J] > P do
|
||
Dec(J);
|
||
if I <= J then
|
||
begin
|
||
T := TheArray[I];
|
||
TheArray[I] := TheArray[J];
|
||
TheArray[J] := T;
|
||
Inc(I);
|
||
Dec(J);
|
||
end;
|
||
until I > J;
|
||
if L < J then
|
||
QuickSort(TheArray, L, J);
|
||
L := I;
|
||
until I >= R;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
//todo: Unify the procedure or change to widgetset specific
|
||
// Currently the UTF-8 version is broken.
|
||
// the unicode version is used when all winapi is available
|
||
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
function ShortenString(DC: HDC; const S: String; Width: Integer; EllipsisWidth: Integer = 0): String;
|
||
|
||
// Adjusts the given string S so that it fits into the given width. EllipsisWidth gives the width of
|
||
// the three points to be added to the shorted string. If this value is 0 then it will be determined implicitely.
|
||
// For higher speed (and multiple entries to be shorted) specify this value explicitely.
|
||
// Note: It is assumed that the string really needs shortage. Check this in advance.
|
||
|
||
var
|
||
Size: TSize;
|
||
Len: Integer;
|
||
L, H, N, W: Integer;
|
||
WideStr: UnicodeString;
|
||
begin
|
||
WideStr := UTF8Decode(S);
|
||
Len := Length(WideStr);
|
||
if (Len = 0) or (Width <= 0) then
|
||
Result := ''
|
||
else
|
||
begin
|
||
// Determine width of triple point using the current DC settings (if not already done).
|
||
if EllipsisWidth = 0 then
|
||
begin
|
||
GetTextExtentPoint32W(DC, '...', 3, Size);
|
||
EllipsisWidth := Size.cx;
|
||
end;
|
||
|
||
if Width <= EllipsisWidth then
|
||
Result := ''
|
||
else
|
||
begin
|
||
// Do a binary search for the optimal string length which fits into the given width.
|
||
L := 0;
|
||
H := Len - 1;
|
||
while L < H do
|
||
begin
|
||
N := (L + H + 1) shr 1;
|
||
GetTextExtentPoint32W(DC, PWideChar(WideStr), N, Size);
|
||
W := Size.cx + EllipsisWidth;
|
||
if W <= Width then
|
||
L := N
|
||
else
|
||
H := N - 1;
|
||
end;
|
||
Result := UTF8Encode(Copy(WideStr, 1, L) + '...');
|
||
end;
|
||
end;
|
||
end;
|
||
{$else}
|
||
function ShortenString(DC: HDC; const S: String; Width: Integer; EllipsisWidth: Integer = 0): String;
|
||
|
||
// Adjusts the given string S so that it fits into the given width. EllipsisWidth gives the width of
|
||
// the three points to be added to the shorted string. If this value is 0 then it will be determined implicitely.
|
||
// For higher speed (and multiple entries to be shorted) specify this value explicitely.
|
||
// Note: It is assumed that the string really needs shortage. Check this in advance.
|
||
|
||
var
|
||
Size: TSize;
|
||
Len: Integer;
|
||
L, H, N, W: Integer;
|
||
begin
|
||
Len := Length(S);
|
||
if (Len = 0) or (Width <= 0) then
|
||
Result := ''
|
||
else
|
||
begin
|
||
// Determine width of triple point using the current DC settings (if not already done).
|
||
if EllipsisWidth = 0 then
|
||
begin
|
||
GetTextExtentPoint32(DC, '...', 3, Size);
|
||
EllipsisWidth := Size.cx;
|
||
end;
|
||
|
||
if Width <= EllipsisWidth then
|
||
Result := ''
|
||
else
|
||
begin
|
||
// Do a binary search for the optimal string length which fits into the given width.
|
||
L := 0;
|
||
H := Len - 1;
|
||
while L < H do
|
||
begin
|
||
N := (L + H + 1) shr 1;
|
||
GetTextExtentPoint32(DC, PAnsiChar(S), N, Size);
|
||
W := Size.cx + EllipsisWidth;
|
||
if W <= Width then
|
||
L := N
|
||
else
|
||
H := N - 1;
|
||
end;
|
||
Result := Copy(S, 1, L) + '...';
|
||
end;
|
||
end;
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function WrapString(DC: HDC; const S: String; const Bounds: TRect; RTL: Boolean;
|
||
DrawFormat: Cardinal): String;
|
||
|
||
// Wrap the given string S so that it fits into a space of given width.
|
||
// RTL determines if right-to-left reading is active.
|
||
|
||
var
|
||
Width,
|
||
Len,
|
||
WordCounter,
|
||
WordsInLine,
|
||
I, W: Integer;
|
||
Buffer,
|
||
Line: String;
|
||
Words: Array of String;
|
||
R: TRect;
|
||
|
||
begin
|
||
Result := '';
|
||
Width := Bounds.Right - Bounds.Left;
|
||
R := Rect(0, 0, 0, 0);
|
||
|
||
// Leading and trailing are ignored.
|
||
Buffer := Trim(S);
|
||
Len := Length(Buffer);
|
||
if Len < 1 then
|
||
Exit;
|
||
|
||
// Count the words in the string.
|
||
WordCounter := 1;
|
||
for I := 1 to Len do
|
||
if Buffer[I] = ' ' then
|
||
Inc(WordCounter);
|
||
SetLength(Words, WordCounter);
|
||
|
||
if RTL then
|
||
begin
|
||
// At first we split the string into words with the last word being the
|
||
// first element in Words.
|
||
W := 0;
|
||
for I := 1 to Len do
|
||
if Buffer[I] = ' ' then
|
||
Inc(W)
|
||
else
|
||
Words[W] := Words[W] + Buffer[I];
|
||
|
||
// Compose Result.
|
||
while WordCounter > 0 do
|
||
begin
|
||
WordsInLine := 0;
|
||
Line := '';
|
||
|
||
while WordCounter > 0 do
|
||
begin
|
||
GetStringDrawRect(DC, Line + IfThen(WordsInLine > 0, ' ', '') + Words[WordCounter - 1], R, DrawFormat);
|
||
if R.Right > Width then
|
||
begin
|
||
// If at least one word fits into this line then continue with the next line.
|
||
if WordsInLine > 0 then
|
||
Break;
|
||
|
||
Buffer := Words[WordCounter - 1];
|
||
if Len > 1 then
|
||
begin
|
||
for Len := Length(Buffer) - 1 downto 2 do
|
||
begin
|
||
GetStringDrawRect(DC, RightStr(Buffer, Len), R, DrawFormat);
|
||
if R.Right <= Width then
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
Len := Length(Buffer);
|
||
|
||
Line := Line + RightStr(Buffer, Max(Len, 1));
|
||
Words[WordCounter - 1] := LeftStr(Buffer, Length(Buffer) - Max(Len, 1));
|
||
if Words[WordCounter - 1] = '' then
|
||
Dec(WordCounter);
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
Dec(WordCounter);
|
||
Line := Words[WordCounter] + IfThen(WordsInLine > 0, ' ', '') + Line;
|
||
Inc(WordsInLine);
|
||
end;
|
||
end;
|
||
|
||
Result := Result + Line + LineEnding;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// At first we split the string into words with the last word being the
|
||
// first element in Words.
|
||
W := WordCounter - 1;
|
||
for I := 1 to Len do
|
||
if Buffer[I] = ' ' then
|
||
Dec(W)
|
||
else
|
||
Words[W] := Words[W] + Buffer[I];
|
||
|
||
// Compose Result.
|
||
while WordCounter > 0 do
|
||
begin
|
||
WordsInLine := 0;
|
||
Line := '';
|
||
|
||
while WordCounter > 0 do
|
||
begin
|
||
GetStringDrawRect(DC, Line + IfThen(WordsInLine > 0, ' ', '') + Words[WordCounter - 1], R, DrawFormat);
|
||
if R.Right > Width then
|
||
begin
|
||
// If at least one word fits into this line then continue with the next line.
|
||
if WordsInLine > 0 then
|
||
Break;
|
||
|
||
Buffer := Words[WordCounter - 1];
|
||
if Len > 1 then
|
||
begin
|
||
for Len := Length(Buffer) - 1 downto 2 do
|
||
begin
|
||
GetStringDrawRect(DC, LeftStr(Buffer, Len), R, DrawFormat);
|
||
if R.Right <= Width then
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
Len := Length(Buffer);
|
||
|
||
Line := Line + LeftStr(Buffer, Max(Len, 1));
|
||
Words[WordCounter - 1] := RightStr(Buffer, Length(Buffer) - Max(Len, 1));
|
||
if Words[WordCounter - 1] = '' then
|
||
Dec(WordCounter);
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
Dec(WordCounter);
|
||
Line := Line + IfThen(WordsInLine > 0, ' ', '') + Words[WordCounter];
|
||
Inc(WordsInLine);
|
||
end;
|
||
end;
|
||
|
||
Result := Result + Line + LineEnding;
|
||
end;
|
||
end;
|
||
|
||
Len := Length(Result) - Length(LineEnding);
|
||
if CompareByte(Result[Len + 1], String(LineEnding)[1], Length(LineEnding)) = 0 then
|
||
SetLength(Result, Len);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
// Calculates bounds of a drawing rectangle for the given string
|
||
|
||
procedure GetStringDrawRect(DC: HDC; const S: String; var Bounds: TRect; DrawFormat: Cardinal);
|
||
begin
|
||
Bounds.Right := Bounds.Left + 1;
|
||
Bounds.Bottom := Bounds.Top + 1;
|
||
|
||
DrawText(DC, PChar(S), Length(S), Bounds, DrawFormat or DT_CALCRECT);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure FillDragRectangles(DragWidth, DragHeight, DeltaX, DeltaY: Integer; out RClip, RScroll, RSamp1, RSamp2, RDraw1,
|
||
RDraw2: TRect);
|
||
|
||
// Fills the given rectangles with values which can be used while dragging around an image
|
||
// (used in DragMove of the drag manager and DragTo of the header columns).
|
||
|
||
begin
|
||
// ScrollDC limits
|
||
RClip := Rect(0, 0, DragWidth, DragHeight);
|
||
if DeltaX > 0 then
|
||
begin
|
||
// move to the left
|
||
if DeltaY = 0 then
|
||
begin
|
||
// move only to the left
|
||
// background movement
|
||
RScroll := Rect(0, 0, DragWidth - DeltaX, DragHeight);
|
||
RSamp1 := Rect(0, 0, DeltaX, DragHeight);
|
||
RDraw1 := Rect(DragWidth - DeltaX, 0, DeltaX, DragHeight);
|
||
end
|
||
else
|
||
if DeltaY < 0 then
|
||
begin
|
||
// move to bottom left
|
||
RScroll := Rect(0, -DeltaY, DragWidth - DeltaX, DragHeight);
|
||
RSamp1 := Rect(0, 0, DeltaX, DragHeight);
|
||
RSamp2 := Rect(DeltaX, DragHeight + DeltaY, DragWidth - DeltaX, -DeltaY);
|
||
RDraw1 := Rect(0, 0, DragWidth - DeltaX, -DeltaY);
|
||
RDraw2 := Rect(DragWidth - DeltaX, 0, DeltaX, DragHeight);
|
||
end
|
||
else
|
||
begin
|
||
// move to upper left
|
||
RScroll := Rect(0, 0, DragWidth - DeltaX, DragHeight - DeltaY);
|
||
RSamp1 := Rect(0, 0, DeltaX, DragHeight);
|
||
RSamp2 := Rect(DeltaX, 0, DragWidth - DeltaX, DeltaY);
|
||
RDraw1 := Rect(0, DragHeight - DeltaY, DragWidth - DeltaX, DeltaY);
|
||
RDraw2 := Rect(DragWidth - DeltaX, 0, DeltaX, DragHeight);
|
||
end;
|
||
end
|
||
else
|
||
if DeltaX = 0 then
|
||
begin
|
||
// vertical movement only
|
||
if DeltaY < 0 then
|
||
begin
|
||
// move downwards
|
||
RScroll := Rect(0, -DeltaY, DragWidth, DragHeight);
|
||
RSamp2 := Rect(0, DragHeight + DeltaY, DragWidth, -DeltaY);
|
||
RDraw2 := Rect(0, 0, DragWidth, -DeltaY);
|
||
end
|
||
else
|
||
begin
|
||
// move upwards
|
||
RScroll := Rect(0, 0, DragWidth, DragHeight - DeltaY);
|
||
RSamp2 := Rect(0, 0, DragWidth, DeltaY);
|
||
RDraw2 := Rect(0, DragHeight - DeltaY, DragWidth, DeltaY);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// move to the right
|
||
if DeltaY > 0 then
|
||
begin
|
||
// move up right
|
||
RScroll := Rect(-DeltaX, 0, DragWidth, DragHeight);
|
||
RSamp1 := Rect(0, 0, DragWidth + DeltaX, DeltaY);
|
||
RSamp2 := Rect(DragWidth + DeltaX, 0, -DeltaX, DragHeight);
|
||
RDraw1 := Rect(0, 0, -DeltaX, DragHeight);
|
||
RDraw2 := Rect(-DeltaX, DragHeight - DeltaY, DragWidth + DeltaX, DeltaY);
|
||
end
|
||
else
|
||
if DeltaY = 0 then
|
||
begin
|
||
// to the right only
|
||
RScroll := Rect(-DeltaX, 0, DragWidth, DragHeight);
|
||
RSamp1 := Rect(DragWidth + DeltaX, 0, -DeltaX, DragHeight);
|
||
RDraw1 := Rect(0, 0, -DeltaX, DragHeight);
|
||
end
|
||
else
|
||
begin
|
||
// move down right
|
||
RScroll := Rect(-DeltaX, -DeltaY, DragWidth, DragHeight);
|
||
RSamp1 := Rect(0, DragHeight + DeltaY, DragWidth + DeltaX, -DeltaY);
|
||
RSamp2 := Rect(DragWidth + DeltaX, 0, -DeltaX, DragHeight);
|
||
RDraw1 := Rect(0, 0, -DeltaX, DragHeight);
|
||
RDraw2 := Rect(-DeltaX, 0, DragWidth + DeltaX, -DeltaY);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function GetRGBColor(Value: TColor): DWORD;
|
||
|
||
// Little helper to convert a Delphi color to an image list color.
|
||
|
||
begin
|
||
Result := ColorToRGB(Value);
|
||
case Result of
|
||
clNone:
|
||
Result := CLR_NONE;
|
||
clDefault:
|
||
Result := CLR_DEFAULT;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{$ifdef CPU64}
|
||
|
||
function HasMMX: Boolean;
|
||
begin
|
||
Result := True;
|
||
end;
|
||
|
||
{$else}
|
||
|
||
function HasMMX: Boolean;
|
||
|
||
// Helper method to determine whether the current processor supports MMX.
|
||
|
||
asm
|
||
PUSH EBX
|
||
XOR EAX, EAX // Result := False
|
||
PUSHFD // determine if the processor supports the CPUID command
|
||
POP EDX
|
||
MOV ECX, EDX
|
||
XOR EDX, $200000
|
||
PUSH EDX
|
||
POPFD
|
||
PUSHFD
|
||
POP EDX
|
||
XOR ECX, EDX
|
||
JZ @1 // no CPUID support so we can't even get to the feature information
|
||
PUSH EDX
|
||
POPFD
|
||
|
||
MOV EAX, 1
|
||
DW $A20F // CPUID, EAX contains now version info and EDX feature information
|
||
MOV EBX, EAX // free EAX to get the result value
|
||
XOR EAX, EAX // Result := False
|
||
CMP EBX, $50
|
||
JB @1 // if processor family is < 5 then it is not a Pentium class processor
|
||
TEST EDX, $800000
|
||
JZ @1 // if the MMX bit is not set then we don't have MMX
|
||
INC EAX // Result := True
|
||
@1:
|
||
POP EBX
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{$ifdef EnablePrint}
|
||
procedure PrtStretchDrawDIB(Canvas: TCanvas; DestRect: TRect; ABitmap: TBitmap);
|
||
|
||
// Stretch draw on to the new canvas.
|
||
|
||
var
|
||
Header,
|
||
Bits: Pointer;
|
||
HeaderSize,
|
||
BitsSize: Cardinal;
|
||
|
||
begin
|
||
GetDIBSizes(ABitmap.Handle, HeaderSize, BitsSize);
|
||
|
||
GetMem(Header, HeaderSize);
|
||
GetMem(Bits, BitsSize);
|
||
try
|
||
GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^);
|
||
StretchDIBits(Canvas.Handle, DestRect.Left, DestRect.Top, DestRect.Right - DestRect.Left, DestRect.Bottom -
|
||
DestRect.Top, 0, 0, ABitmap.Width, ABitmap.Height, Bits, TBitmapInfo(Header^), DIB_RGB_COLORS, SRCCOPY);
|
||
finally
|
||
FreeMem(Header);
|
||
FreeMem(Bits);
|
||
end;
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{$ifdef EnableAccessible}
|
||
procedure GetAccessibilityFactory;
|
||
|
||
// Accessibility helper function to create a singleton class that will create or return
|
||
// the IAccessible interface for the tree and the focused node.
|
||
|
||
begin
|
||
// Check to see if the class has already been created.
|
||
if VTAccessibleFactory = nil then
|
||
VTAccessibleFactory := TVTAccessibilityFactory.Create;
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure InitializeGlobalStructures;
|
||
|
||
// initialization of stuff global to the unit
|
||
|
||
begin
|
||
Initialized := True;
|
||
|
||
// For the drag image a fast MMX blend routine is used. We have to make sure MMX is available.
|
||
MMXAvailable := HasMMX;
|
||
|
||
// There is a bug in Win95 and WinME (and potentially in Win98 too) regarding GetDCEx which causes sometimes
|
||
// serious trouble within GDI (see method WMNCPaint).
|
||
|
||
//IsWinNT := (Win32Platform and VER_PLATFORM_WIN32_NT) <> 0;
|
||
IsWinNT := True;
|
||
|
||
{$ifdef EnableOLE}
|
||
// Initialize OLE subsystem for drag'n drop and clipboard operations.
|
||
//todo: replace by Suceeded (see in windows unit)
|
||
NeedToUnitialize := OleInitialize(nil) in [S_FALSE,S_OK];
|
||
{$endif}
|
||
// Register the tree reference clipboard format. Others will be handled in InternalClipboarFormats.
|
||
CF_VTREFERENCE := ClipboardRegisterFormat(CFSTR_VTREFERENCE);
|
||
|
||
UtilityImages := TBitmap.Create;
|
||
UtilityImages.Transparent := True;
|
||
UtilityImages.LoadFromLazarusResource('VT_UTILITIES');
|
||
|
||
// Specify an useful timer resolution for timeGetTime.
|
||
timeBeginPeriod(MinimumTimerInterval);
|
||
|
||
// Delphi (at least version 6 and lower) does not provide a standard split cursor.
|
||
// Hence we have to load our own.
|
||
Screen.Cursors[crHeaderSplit] := LoadCursorFromLazarusResource('VT_HEADERSPLIT');
|
||
Screen.Cursors[crVertSplit] := LoadCursorFromLazarusResource('VT_VERTSPLIT');
|
||
// Clipboard format registration.
|
||
// Native clipboard format. Needs a new identifier and has an average priority to allow other formats to take over.
|
||
// This format is supposed to use the IStream storage format but unfortunately this does not work when
|
||
// OLEFlushClipboard is used. Hence it is disabled until somebody finds a solution.
|
||
CF_VIRTUALTREE := RegisterVTClipboardFormat(CFSTR_VIRTUALTREE, TBaseVirtualTree, 50, TYMED_HGLOBAL {or TYMED_ISTREAM});
|
||
// Specialized string tree formats.
|
||
CF_HTML := RegisterVTClipboardFormat(CFSTR_HTML, TCustomVirtualStringTree, 80);
|
||
CF_VRTFNOOBJS := RegisterVTClipboardFormat(CFSTR_RTFNOOBJS, TCustomVirtualStringTree, 84);
|
||
CF_VRTF := RegisterVTClipboardFormat(CFSTR_RTF, TCustomVirtualStringTree, 85);
|
||
CF_CSV := RegisterVTClipboardFormat(CFSTR_CSV, TCustomVirtualStringTree, 90);
|
||
// Predefined clipboard formats. Just add them to the internal list.
|
||
RegisterVTClipboardFormat(CF_TEXT, TCustomVirtualStringTree, 100);
|
||
RegisterVTClipboardFormat(CF_UNICODETEXT, TCustomVirtualStringTree, 95);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure FinalizeGlobalStructures;
|
||
|
||
begin
|
||
timeEndPeriod(MinimumTimerInterval);
|
||
|
||
FreeAndNil(UtilityImages);
|
||
|
||
if NeedToUnitialize then
|
||
OleUninitialize;
|
||
end;
|
||
|
||
//----------------- TWorkerThread --------------------------------------------------------------------------------------
|
||
|
||
procedure AddThreadReference;
|
||
|
||
begin
|
||
if WorkerThread = nil then
|
||
begin
|
||
// Create an event used to trigger our worker thread when something is to do.
|
||
WorkEvent := TEvent.Create(nil, False, False, '');
|
||
//todo: see how to check if a event was succesfully created under linux since handle is allways 0
|
||
{$ifdef Windows}
|
||
if WorkEvent.Handle = TEventHandle(0) then
|
||
Raise Exception.Create('VirtualTreeView - Error creating TEvent instance');
|
||
{$endif}
|
||
// Create worker thread, initialize it and send it to its wait loop.
|
||
WorkerThread := TWorkerThread.Create(False);
|
||
end;
|
||
Inc(WorkerThread.FRefCount);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure ReleaseThreadReference(Tree: TBaseVirtualTree);
|
||
|
||
begin
|
||
if Assigned(WorkerThread) then
|
||
begin
|
||
Dec(WorkerThread.FRefCount);
|
||
|
||
// Make sure there is no reference remaining to the releasing tree.
|
||
Tree.InterruptValidation;
|
||
|
||
if WorkerThread.FRefCount = 0 then
|
||
begin
|
||
with WorkerThread do
|
||
begin
|
||
Terminate;
|
||
WorkEvent.SetEvent;
|
||
|
||
//lcl: probably not necessary under fpc. Remove later
|
||
// The following work around is no longer necessary with Delphi 6 and up.
|
||
{$ifndef fpc}
|
||
// There is a problem when the thread is freed in the exit code of a DLL. This can happen when a tree is
|
||
// destroyed on unload of a DLL (e.g. control panel applet). In this case only the main thread will get
|
||
// CPU time, other threads will never awake again. The VCL however waits for a thread when freeing it
|
||
// which will result in a deadlock (the WaitFor call does not return because the thread does not get CPU time).
|
||
// If a thread is however suspended then the VCL does not wait and all is fine.
|
||
if IsLibrary then
|
||
Suspend;
|
||
{$endif}
|
||
|
||
WorkerThread.Free;
|
||
end;
|
||
WorkerThread := nil;
|
||
WorkEvent.Free;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
constructor TWorkerThread.Create(CreateSuspended: Boolean);
|
||
|
||
begin
|
||
inherited Create(CreateSuspended);
|
||
FWaiterList := TThreadList.Create;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TWorkerThread.Destroy;
|
||
|
||
begin
|
||
// First let the ancestor stop the thread before freeing our resources.
|
||
inherited;
|
||
|
||
FWaiterList.Free;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TWorkerThread.CancelValidation(Tree: TBaseVirtualTree);
|
||
|
||
var
|
||
Msg: TMsg;
|
||
|
||
begin
|
||
// Wait for any references to this tree to be released.
|
||
// Pump WM_CHANGESTATE messages so the thread doesn't block on SendMessage calls.
|
||
while FCurrentTree = Tree do
|
||
begin
|
||
if Tree.HandleAllocated and PeekMessage(Msg, Tree.Handle, WM_CHANGESTATE, WM_CHANGESTATE, PM_REMOVE) then
|
||
begin
|
||
//todo: see if is correct / will work
|
||
Application.ProcessMessages;
|
||
//TranslateMessage(Msg);
|
||
//DispatchMessage(Msg);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TWorkerThread.ChangeTreeStates(EnterStates, LeaveStates: TChangeStates);
|
||
|
||
begin
|
||
if Assigned(FCurrentTree) and (FCurrentTree.HandleAllocated) then
|
||
SendMessage(FCurrentTree.Handle, WM_CHANGESTATE, Byte(EnterStates), Byte(LeaveStates));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TWorkerThread.Execute;
|
||
|
||
// Does some background tasks, like validating tree caches.
|
||
|
||
var
|
||
EnterStates,
|
||
LeaveStates: TChangeStates;
|
||
|
||
begin
|
||
while not Terminated do
|
||
begin
|
||
WorkEvent.WaitFor(INFINITE);
|
||
if not Terminated then
|
||
begin
|
||
// Get the next waiting tree.
|
||
with FWaiterList.LockList do
|
||
try
|
||
if Count > 0 then
|
||
begin
|
||
FCurrentTree := Items[0];
|
||
// Remove this tree from waiter list.
|
||
Delete(0);
|
||
// If there is yet another tree to work on then set the work event to keep looping.
|
||
if Count > 0 then
|
||
WorkEvent.SetEvent;
|
||
end
|
||
else
|
||
FCurrentTree := nil;
|
||
finally
|
||
FWaiterList.UnlockList;
|
||
end;
|
||
|
||
// Something to do?
|
||
if Assigned(FCurrentTree) then
|
||
begin
|
||
try
|
||
ChangeTreeStates([csValidating], [csUseCache]);
|
||
EnterStates := [];
|
||
if not (tsStopValidation in FCurrentTree.FStates) and FCurrentTree.DoValidateCache then
|
||
EnterStates := [csUseCache];
|
||
|
||
finally
|
||
LeaveStates := [csValidating, csStopValidation];
|
||
if csUseCache in EnterStates then
|
||
Include(LeaveStates, csValidationNeeded);
|
||
ChangeTreeStates(EnterStates, LeaveStates);
|
||
FCurrentTree := nil;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TWorkerThread.AddTree(Tree: TBaseVirtualTree);
|
||
|
||
begin
|
||
Assert(Assigned(Tree), 'Tree must not be nil.');
|
||
|
||
// Remove validation stop flag, just in case it is still set.
|
||
Tree.DoStateChange([], [tsStopValidation]);
|
||
with FWaiterList.LockList do
|
||
try
|
||
if IndexOf(Tree) = -1 then
|
||
Add(Tree);
|
||
finally
|
||
FWaiterList.UnlockList;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TWorkerThread.RemoveTree(Tree: TBaseVirtualTree);
|
||
|
||
begin
|
||
Assert(Assigned(Tree), 'Tree must not be nil.');
|
||
|
||
with FWaiterList.LockList do
|
||
try
|
||
Remove(Tree);
|
||
finally
|
||
FWaiterList.UnlockList;
|
||
end;
|
||
CancelValidation(Tree);
|
||
end;
|
||
|
||
//----------------- TBufferedUTF8String --------------------------------------------------------------------------------
|
||
const
|
||
AllocIncrement = 2 shl 11; // Must be a power of 2.
|
||
|
||
destructor TBufferedUTF8String.Destroy;
|
||
|
||
begin
|
||
FreeMem(FStart);
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBufferedUTF8String.GetAsAnsiString: AnsiString;
|
||
begin
|
||
//an implicit conversion is done
|
||
Result := AsUTF16String;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBufferedUTF8String.GetAsUTF16String: UnicodeString;
|
||
begin
|
||
//todo: optimize
|
||
Result := UTF8Decode(AsUTF8String);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBufferedUTF8String.GetAsUTF8String: String;
|
||
begin
|
||
SetString(Result, FStart, FPosition - FStart);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBufferedUTF8String.Add(const S: String);
|
||
|
||
var
|
||
NewLen,
|
||
LastOffset,
|
||
Len: Integer;
|
||
|
||
begin
|
||
Len := Length(S);
|
||
// Make room for the new string.
|
||
if FEnd - FPosition <= Len then
|
||
begin
|
||
// Round up NewLen so it is always a multiple of AllocIncrement.
|
||
NewLen := FEnd - FStart + (Len + AllocIncrement - 1) and not (AllocIncrement - 1);
|
||
// Keep last offset to restore it correctly in the case that FStart gets a new memory block assigned.
|
||
LastOffset := FPosition - FStart;
|
||
ReallocMem(FStart, NewLen);
|
||
FPosition := FStart + LastOffset;
|
||
FEnd := FStart + NewLen;
|
||
end;
|
||
Move(PChar(S)^, FPosition^, Len);
|
||
Inc(FPosition, Len);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBufferedUTF8String.AddNewLine;
|
||
|
||
var
|
||
NewLen,
|
||
LastOffset: Integer;
|
||
|
||
begin
|
||
// Make room for the CR/LF characters.
|
||
if FEnd - FPosition <= 4 then
|
||
begin
|
||
//todo: see in calculation of NewLen is correct for String
|
||
// Round up NewLen so it is always a multiple of AllocIncrement.
|
||
NewLen := FEnd - FStart + (2 + AllocIncrement - 1) and not (AllocIncrement - 1);
|
||
// Keep last offset to restore it correctly in the case that FStart gets a new memory block assigned.
|
||
LastOffset := FPosition - FStart;
|
||
ReallocMem(FStart, NewLen);
|
||
FPosition := FStart + LastOffset;
|
||
FEnd := FStart + NewLen;
|
||
end;
|
||
FPosition^ := #13;
|
||
Inc(FPosition);
|
||
FPosition^ := #10;
|
||
Inc(FPosition);
|
||
end;
|
||
|
||
//----------------- TCustomVirtualTreeOptions --------------------------------------------------------------------------
|
||
|
||
constructor TCustomVirtualTreeOptions.Create(AOwner: TBaseVirtualTree);
|
||
|
||
begin
|
||
FOwner := AOwner;
|
||
|
||
FPaintOptions := DefaultPaintOptions;
|
||
FAnimationOptions := DefaultAnimationOptions;
|
||
FAutoOptions := DefaultAutoOptions;
|
||
FSelectionOptions := DefaultSelectionOptions;
|
||
FMiscOptions := DefaultMiscOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualTreeOptions.SetAnimationOptions(const Value: TVTAnimationOptions);
|
||
|
||
begin
|
||
FAnimationOptions := Value;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualTreeOptions.SetAutoOptions(const Value: TVTAutoOptions);
|
||
|
||
var
|
||
ChangedOptions: TVTAutoOptions;
|
||
|
||
begin
|
||
if FAutoOptions <> Value then
|
||
begin
|
||
// Exclusive ORing to get all entries wich are in either set but not in both.
|
||
ChangedOptions := FAutoOptions + Value - (FAutoOptions * Value);
|
||
FAutoOptions := Value;
|
||
with FOwner do
|
||
if (toAutoSpanColumns in ChangedOptions) and not (csLoading in ComponentState) and HandleAllocated then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualTreeOptions.SetMiscOptions(const Value: TVTMiscOptions);
|
||
|
||
var
|
||
ToBeSet,
|
||
ToBeCleared: TVTMiscOptions;
|
||
|
||
begin
|
||
if FMiscOptions <> Value then
|
||
begin
|
||
ToBeSet := Value - FMiscOptions;
|
||
ToBeCleared := FMiscOptions - Value;
|
||
FMiscOptions := Value;
|
||
{$ifndef Windows}
|
||
Exclude(FMiscOptions,toAcceptOLEDrop);
|
||
Exclude(ToBeCleared,toAcceptOLEDrop);
|
||
Exclude(ToBeSet,toAcceptOLEDrop);
|
||
{$endif}
|
||
with FOwner do
|
||
if not (csLoading in ComponentState) and HandleAllocated then
|
||
begin
|
||
if toCheckSupport in ToBeSet + ToBeCleared then
|
||
begin
|
||
CheckImageListNeeded;
|
||
Invalidate;
|
||
end;
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
if toFullRepaintOnResize in (TobeSet + ToBeCleared) then
|
||
//todo_lcl_check
|
||
RecreateWnd(FOwner);
|
||
if toAcceptOLEDrop in ToBeSet then
|
||
RegisterDragDrop(Handle, DragManager as IDropTarget);
|
||
if toAcceptOLEDrop in ToBeCleared then
|
||
RevokeDragDrop(Handle);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualTreeOptions.SetPaintOptions(const Value: TVTPaintOptions);
|
||
|
||
var
|
||
ToBeSet,
|
||
ToBeCleared: TVTPaintOptions;
|
||
|
||
begin
|
||
if FPaintOptions <> Value then
|
||
begin
|
||
ToBeSet := Value - FPaintOptions;
|
||
ToBeCleared := FPaintOptions - Value;
|
||
FPaintOptions := Value;
|
||
with FOwner do
|
||
if HandleAllocated then
|
||
begin
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
// if (tsUseThemes in FStates) or (toThemeAware in ToBeSet) then
|
||
// if (toUseExplorerTheme in ToBeSet) and IsWinVistaOrAbove then
|
||
// SetWindowTheme(Handle, 'explorer', nil)
|
||
// else
|
||
// SetWindowTheme(Handle, '', nil);
|
||
{$endif ThemeSupport}
|
||
|
||
if not (csLoading in ComponentState) then
|
||
begin
|
||
{$ifdef ThemeSupport}
|
||
if (toThemeAware in ToBeSet + ToBeCleared) or (toUseExplorerTheme in ToBeSet + ToBeCleared) then
|
||
begin
|
||
if (toThemeAware in ToBeSet) and ThemeServices.ThemesEnabled then
|
||
DoStateChange([tsUseThemes])
|
||
else
|
||
if (toThemeAware in ToBeCleared) then
|
||
DoStateChange([], [tsUseThemes]);
|
||
|
||
PrepareBitmaps(True, False);
|
||
RedrawWindow(Handle, nil, 0, RDW_INVALIDATE or RDW_VALIDATE or RDW_FRAME);
|
||
end
|
||
else
|
||
{$endif ThemeSupport}
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualTreeOptions.SetSelectionOptions(const Value: TVTSelectionOptions);
|
||
|
||
var
|
||
ToBeSet,
|
||
ToBeCleared: TVTSelectionOptions;
|
||
|
||
begin
|
||
if FSelectionOptions <> Value then
|
||
begin
|
||
ToBeSet := Value - FSelectionOptions;
|
||
ToBeCleared := FSelectionOptions - Value;
|
||
FSelectionOptions := Value;
|
||
|
||
with FOwner do
|
||
begin
|
||
if (toMultiSelect in (ToBeCleared + ToBeSet)) or
|
||
([toLevelSelectConstraint, toSiblingSelectConstraint] * ToBeSet <> []) then
|
||
ClearSelection;
|
||
|
||
if (toExtendedFocus in ToBeCleared) and (FFocusedColumn > 0) and HandleAllocated then
|
||
begin
|
||
FFocusedColumn := FHeader.MainColumn;
|
||
Invalidate;
|
||
end;
|
||
|
||
if not (toExtendedFocus in FSelectionOptions) then
|
||
FFocusedColumn := FHeader.MainColumn;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualTreeOptions.AssignTo(Dest: TPersistent);
|
||
|
||
begin
|
||
if Dest is TCustomVirtualTreeOptions then
|
||
begin
|
||
with TCustomVirtualTreeOptions(Dest) do
|
||
begin
|
||
PaintOptions := Self.PaintOptions;
|
||
AnimationOptions := Self.AnimationOptions;
|
||
AutoOptions := Self.AutoOptions;
|
||
SelectionOptions := Self.SelectionOptions;
|
||
MiscOptions := Self.MiscOptions;
|
||
end;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
{$i vtvdragmanager.inc}
|
||
|
||
//----------------- TVirtualTreeHintWindow -----------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeHintWindow.WMShowWindow(var Message: TLMShowWindow);
|
||
|
||
// Clear hint data when the window becomes hidden.
|
||
|
||
begin
|
||
if not Message.Show then
|
||
begin
|
||
// Don't touch the last hint rectangle stored in the associated tree to avoid flickering in certain situations.
|
||
Finalize(FHintData);
|
||
FillChar(FHintData, SizeOf(FHintData), 0);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeHintWindow.Paint;
|
||
|
||
begin
|
||
with FHintData do
|
||
begin
|
||
if Tree is TCustomVirtualDrawTree and Assigned(Node) then
|
||
begin
|
||
// The draw tree has by default no hint text so let it draw the hint itself.
|
||
// HintBorderWidth is a private constant in hint code and is set to two
|
||
TCustomVirtualDrawTree(Tree).DoDrawHint(Canvas, Node,
|
||
Rect(0, 0, Width - 2, Height - 2), Column);
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect;
|
||
var
|
||
P: TPoint;
|
||
begin
|
||
if AData = nil then
|
||
// Defensive approach, it *can* happen that AData is nil. Maybe when several user defined hint classes are used.
|
||
Result := Rect(0, 0, 0, 0)
|
||
else
|
||
begin
|
||
FHintData := PVTHintData(AData)^;
|
||
with FHintData do
|
||
begin
|
||
// The draw tree gets its hint size by the application (but only if not a header hint is about to show).
|
||
// This size has already been determined in CMHintShow.
|
||
if (Tree is TCustomVirtualDrawTree) and Assigned(Node) then
|
||
Result := HintRect
|
||
else
|
||
begin
|
||
//todo remove this define as soon as 0.9.30 is released to avoid future problems
|
||
if Column <= NoColumn then
|
||
begin
|
||
BidiMode := Tree.BidiMode;
|
||
Alignment := Tree.Alignment;
|
||
end
|
||
else
|
||
begin
|
||
BidiMode := Tree.Header.Columns[Column].BidiMode;
|
||
Alignment := Tree.Header.Columns[Column].Alignment;
|
||
end;
|
||
//select font according to the type of hint
|
||
if (Node = nil) or (Tree.FHintMode <> hmToolTip) then
|
||
Canvas.Font := Screen.HintFont
|
||
else
|
||
begin
|
||
Canvas.Font := Tree.Font;
|
||
//necessary to set customized fonts
|
||
if Tree is TCustomVirtualStringTree then
|
||
with TCustomVirtualStringTree(Tree) do
|
||
DoPaintText(Node, Self.Canvas, Column, ttNormal);
|
||
//force the default hint font color
|
||
Canvas.Font.Color := Screen.HintFont.Color;
|
||
end;
|
||
|
||
//let THintWindow do the job
|
||
Result := inherited CalcHintRect(MaxWidth, AHint, AData);
|
||
//fix position taking into account bidimode and control bounds
|
||
if (Tree.HintMode <> hmTooltip) or ((Result.Right - Result.Left) < Tree.Width) then
|
||
begin
|
||
if BiDiMode = bdLeftToRight then
|
||
begin
|
||
P := Tree.ClientToScreen(Point(0, 0));
|
||
HintInfo^.HintPos.X := Max(P.X, HintInfo^.HintPos.X);
|
||
end
|
||
else
|
||
begin
|
||
if (Tree.HintMode = hmTooltip) and (Node <> nil) then
|
||
begin
|
||
P := Tree.ClientToScreen(Point(Min(Tree.ClientWidth, HintInfo^.CursorRect.Right), 0));
|
||
Dec(P.X, Result.Right);
|
||
HintInfo^.HintPos.X := Max(P.X, HintInfo^.HintPos.X);
|
||
end
|
||
else
|
||
Dec(HintInfo^.HintPos.X, Result.Right - 20);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------- TVTDragImage ---------------------------------------------------------------------------------------
|
||
|
||
constructor TVTDragImage.Create(AOwner: TBaseVirtualTree);
|
||
|
||
begin
|
||
FOwner := AOwner;
|
||
FTransparency := 128;
|
||
FPreBlendBias := 0;
|
||
FPostBlendBias := 0;
|
||
FFade := False;
|
||
FRestriction := dmrNone;
|
||
FColorKey := clNone;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TVTDragImage.Destroy;
|
||
|
||
begin
|
||
EndDrag;
|
||
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTDragImage.GetVisible: Boolean;
|
||
|
||
// Returns True if the internal drag image is used (i.e. the system does not natively support drag images) and
|
||
// the internal image is currently visible on screen.
|
||
|
||
begin
|
||
Result := FStates * [disHidden, disInDrag, disPrepared, disSystemSupport] = [disInDrag, disPrepared];
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.InternalShowDragImage(ScreenDC: HDC);
|
||
|
||
// Frequently called helper routine to actually do the blend and put it onto the screen.
|
||
// Only used if the system does not support drag images.
|
||
|
||
var
|
||
BlendMode: TBlendMode;
|
||
|
||
begin
|
||
with FAlphaImage do
|
||
BitBlt(Canvas.Handle, 0, 0, Width, Height, FBackImage.Canvas.Handle, 0, 0, SRCCOPY);
|
||
if not FFade and (FColorKey = clNone) then
|
||
BlendMode := bmConstantAlpha
|
||
else
|
||
BlendMode := bmMasterAlpha;
|
||
with FDragImage do
|
||
AlphaBlend(Canvas.Handle, FAlphaImage.Canvas.Handle, Rect(0, 0, Width, Height), Point(0, 0), BlendMode,
|
||
FTransparency, FPostBlendBias);
|
||
|
||
with FAlphaImage do
|
||
BitBlt(ScreenDC, FImagePosition.X, FImagePosition.Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.MakeAlphaChannel(Source, Target: TBitmap);
|
||
|
||
// Helper method to create a proper alpha channel in Target (which must be in 32 bit pixel format), depending
|
||
// on the settings for the drag image and the color values in Source.
|
||
// Only used if the system does not support drag images.
|
||
|
||
type
|
||
PBGRA = ^TBGRA;
|
||
TBGRA = packed record
|
||
case Boolean of
|
||
False:
|
||
(Color: Cardinal);
|
||
True:
|
||
(BGR: array[0..2] of Byte;
|
||
Alpha: Byte);
|
||
end;
|
||
|
||
var
|
||
Color,
|
||
ColorKeyRef: COLORREF;
|
||
UseColorKey: Boolean;
|
||
SourceRun,
|
||
TargetRun: PBGRA;
|
||
X, Y,
|
||
MaxDimension,
|
||
HalfWidth,
|
||
HalfHeight: Integer;
|
||
T: Extended;
|
||
SourceBits, TargetBits: Pointer;
|
||
|
||
begin
|
||
{$ifdef EnableAdvancedGraphics}
|
||
SourceBits := GetBitmapBitsFromBitmap(Source.Handle);
|
||
TargetBits := GetBitmapBitsFromBitmap(Target.Handle);
|
||
|
||
if (SourceBits = nil) or (TargetBits = nil) then
|
||
Exit;
|
||
|
||
UseColorKey := ColorKey <> clNone;
|
||
ColorKeyRef := ColorToRGB(ColorKey) and $FFFFFF;
|
||
// Color values are in the form BGR (red on LSB) while bitmap colors are in the form ARGB (blue on LSB)
|
||
// hence we have to swap red and blue in the color key.
|
||
with TBGRA(ColorKeyRef) do
|
||
begin
|
||
X := BGR[0];
|
||
BGR[0] := BGR[2];
|
||
BGR[2] := X;
|
||
end;
|
||
|
||
with Target do
|
||
begin
|
||
MaxDimension := Max(Width, Height);
|
||
|
||
HalfWidth := Width div 2;
|
||
HalfHeight := Height div 2;
|
||
for Y := 0 to Height - 1 do
|
||
begin
|
||
TargetRun := CalculateScanline(TargetBits, Width, Height, Y);
|
||
SourceRun := CalculateScanline(SourceBits, Source.Width, Source.Height, Y);
|
||
for X := 0 to Width - 1 do
|
||
begin
|
||
Color := SourceRun.Color and $FFFFFF;
|
||
if UseColorKey and (Color = ColorKeyRef) then
|
||
TargetRun.Alpha := 0
|
||
else
|
||
begin
|
||
// If the color is not the given color key (or none is used) then do full calculation of a bell curve.
|
||
T := exp(-8 * Sqrt(Sqr((X - HalfWidth) / MaxDimension) + Sqr((Y - HalfHeight) / MaxDimension)));
|
||
TargetRun.Alpha := Round(255 * T);
|
||
end;
|
||
Inc(SourceRun);
|
||
Inc(TargetRun);
|
||
end;
|
||
end;
|
||
end;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTDragImage.DragTo(const P: TPoint; ForceRepaint: Boolean): Boolean;
|
||
|
||
// Moves the drag image to a new position, which is determined from the passed point P and the previous
|
||
// mouse position.
|
||
// ForceRepaint is True if something on the screen changed and the back image must be refreshed.
|
||
|
||
var
|
||
ScreenDC: HDC;
|
||
DeltaX,
|
||
DeltaY: Integer;
|
||
|
||
// optimized drag image move support
|
||
RSamp1,
|
||
RSamp2, // newly added parts from screen which will be overwritten
|
||
RDraw1,
|
||
RDraw2, // parts to be restored to screen
|
||
RScroll,
|
||
RClip: TRect; // ScrollDC of the existent background
|
||
|
||
begin
|
||
// Determine distances to move the drag image. Take care for restrictions.
|
||
case FRestriction of
|
||
dmrHorizontalOnly:
|
||
begin
|
||
DeltaX := FLastPosition.X - P.X;
|
||
DeltaY := 0;
|
||
end;
|
||
dmrVerticalOnly:
|
||
begin
|
||
DeltaX := 0;
|
||
DeltaY := FLastPosition.Y - P.Y;
|
||
end;
|
||
else // dmrNone
|
||
DeltaX := FLastPosition.X - P.X;
|
||
DeltaY := FLastPosition.Y - P.Y;
|
||
end;
|
||
|
||
Result := (DeltaX <> 0) or (DeltaY <> 0) or ForceRepaint;
|
||
if Result then
|
||
begin
|
||
if Visible then
|
||
begin
|
||
// All this stuff is only called if we have to handle the drag image ourselves. If the system supports
|
||
// drag image then this is all never executed.
|
||
ScreenDC := GetDC(0);
|
||
try
|
||
if (Abs(DeltaX) >= FDragImage.Width) or (Abs(DeltaY) >= FDragImage.Height) or ForceRepaint then
|
||
begin
|
||
// If moved more than image size then just restore old screen and blit image to new position.
|
||
BitBlt(ScreenDC, FImagePosition.X, FImagePosition.Y, FBackImage.Width, FBackImage.Height,
|
||
FBackImage.Canvas.Handle, 0, 0, SRCCOPY);
|
||
|
||
if ForceRepaint then
|
||
UpdateWindow(FOwner.Handle);
|
||
|
||
Inc(FImagePosition.X, -DeltaX);
|
||
Inc(FImagePosition.Y, -DeltaY);
|
||
|
||
BitBlt(FBackImage.Canvas.Handle, 0, 0, FBackImage.Width, FBackImage.Height, ScreenDC, FImagePosition.X,
|
||
FImagePosition.Y, SRCCOPY);
|
||
end
|
||
else
|
||
begin
|
||
// overlapping copy
|
||
FillDragRectangles(FDragImage.Width, FDragImage.Height, DeltaX, DeltaY, RClip, RScroll, RSamp1, RSamp2, RDraw1,
|
||
RDraw2);
|
||
|
||
with FBackImage.Canvas do
|
||
begin
|
||
// restore uncovered areas of the screen
|
||
if DeltaX = 0 then
|
||
begin
|
||
with RDraw2 do
|
||
BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top,
|
||
SRCCOPY);
|
||
end
|
||
else
|
||
begin
|
||
if DeltaY = 0 then
|
||
begin
|
||
with RDraw1 do
|
||
BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top,
|
||
SRCCOPY);
|
||
end
|
||
else
|
||
begin
|
||
with RDraw1 do
|
||
BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top,
|
||
SRCCOPY);
|
||
with RDraw2 do
|
||
BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top,
|
||
SRCCOPY);
|
||
end;
|
||
end;
|
||
|
||
//todo: implement ScrollDC. Alternatively reimplement drag operations
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
// move existent background
|
||
ScrollDC(Handle, DeltaX, DeltaY, RScroll, RClip, 0, nil);
|
||
{$endif}
|
||
|
||
Inc(FImagePosition.X, -DeltaX);
|
||
Inc(FImagePosition.Y, -DeltaY);
|
||
|
||
// Get first and second additional rectangle from screen.
|
||
if DeltaX = 0 then
|
||
begin
|
||
with RSamp2 do
|
||
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
||
SRCCOPY);
|
||
end
|
||
else
|
||
if DeltaY = 0 then
|
||
begin
|
||
with RSamp1 do
|
||
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
||
SRCCOPY);
|
||
end
|
||
else
|
||
begin
|
||
with RSamp1 do
|
||
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
||
SRCCOPY);
|
||
with RSamp2 do
|
||
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
||
SRCCOPY);
|
||
end;
|
||
end;
|
||
end;
|
||
InternalShowDragImage(ScreenDC);
|
||
finally
|
||
ReleaseDC(0, ScreenDC);
|
||
end;
|
||
end;
|
||
FLastPosition.X := P.X;
|
||
FLastPosition.Y := P.Y;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.EndDrag;
|
||
|
||
begin
|
||
HideDragImage;
|
||
FStates := FStates - [disInDrag, disPrepared];
|
||
|
||
FBackImage.Free;
|
||
FBackImage := nil;
|
||
FDragImage.Free;
|
||
FDragImage := nil;
|
||
FAlphaImage.Free;
|
||
FAlphaImage := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTDragImage.GetDragImageRect: TRect;
|
||
|
||
// Returns the current size and position of the drag image (screen coordinates).
|
||
|
||
begin
|
||
if Visible then
|
||
begin
|
||
with FBackImage do
|
||
Result := Rect(FImagePosition.X, FImagePosition.Y, FImagePosition.X + Width, FImagePosition.Y + Height);
|
||
end
|
||
else
|
||
Result := Rect(0, 0, 0, 0);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.HideDragImage;
|
||
|
||
var
|
||
ScreenDC: HDC;
|
||
|
||
begin
|
||
if Visible then
|
||
begin
|
||
Include(FStates, disHidden);
|
||
ScreenDC := GetDC(0);
|
||
try
|
||
// restore screen
|
||
with FBackImage do
|
||
BitBlt(ScreenDC, FImagePosition.X, FImagePosition.Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY);
|
||
finally
|
||
ReleaseDC(0, ScreenDC);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.PrepareDrag(DragImage: TBitmap; const ImagePosition, HotSpot: TPoint; const DataObject: IDataObject);
|
||
|
||
// Creates all necessary structures to do alpha blended dragging using the given image.
|
||
// ImagePostion and Hotspot are given in screen coordinates. The first determines where to place the drag image while
|
||
// the second is the initial mouse position.
|
||
// This method also determines whether the system supports drag images natively. If so then only minimal structures
|
||
// are created.
|
||
|
||
var
|
||
Width,
|
||
Height: Integer;
|
||
DragSourceHelper: IDragSourceHelper;
|
||
DragInfo: TSHDragImage;
|
||
|
||
begin
|
||
Width := DragImage.Width;
|
||
Height := DragImage.Height;
|
||
|
||
// Determine whether the system supports the drag helper interfaces.
|
||
if Assigned(DataObject) and Succeeded(CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER,
|
||
IID_IDragSourceHelper, DragSourceHelper)) then
|
||
begin
|
||
Include(FStates, disSystemSupport);
|
||
|
||
// Supply the drag source helper with our drag image.
|
||
DragInfo.sizeDragImage.cx := Width;
|
||
DragInfo.sizeDragImage.cy := Height;
|
||
DragInfo.ptOffset.x := Width div 2;
|
||
DragInfo.ptOffset.y := Height div 2;
|
||
//todo: replace CopyImage. Alternatively reimplement Drag support
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
DragInfo.hbmpDragImage := CopyImage(DragImage.Handle, IMAGE_BITMAP, Width, Height, LR_COPYRETURNORG);
|
||
{$else}
|
||
DragInfo.hbmpDragImage := 0;
|
||
{$endif}
|
||
DragInfo.ColorRef := ColorToRGB(FColorKey);
|
||
if not Succeeded(DragSourceHelper.InitializeFromBitmap(DragInfo, DataObject)) then
|
||
begin
|
||
DeleteObject(DragInfo.hbmpDragImage);
|
||
Exclude(FStates, disSystemSupport);
|
||
end;
|
||
end
|
||
else
|
||
Exclude(FStates, disSystemSupport);
|
||
|
||
if MMXAvailable and not (disSystemSupport in FStates) then
|
||
begin
|
||
FLastPosition := HotSpot;
|
||
|
||
FDragImage := TBitmap.Create;
|
||
FDragImage.PixelFormat := pf32Bit;
|
||
FDragImage.Width := Width;
|
||
FDragImage.Height := Height;
|
||
|
||
FAlphaImage := TBitmap.Create;
|
||
FAlphaImage.PixelFormat := pf32Bit;
|
||
FAlphaImage.Width := Width;
|
||
FAlphaImage.Height := Height;
|
||
|
||
FBackImage := TBitmap.Create;
|
||
FBackImage.PixelFormat := pf32Bit;
|
||
FBackImage.Width := Width;
|
||
FBackImage.Height := Height;
|
||
|
||
// Copy the given drag image and apply pre blend bias if required.
|
||
if FPreBlendBias = 0 then
|
||
with FDragImage do
|
||
BitBlt(Canvas.Handle, 0, 0, Width, Height, DragImage.Canvas.Handle, 0, 0, SRCCOPY)
|
||
else
|
||
AlphaBlend(DragImage.Canvas.Handle, FDragImage.Canvas.Handle, Rect(0, 0, Width, Height), Point(0, 0),
|
||
bmConstantAlpha, 255, FPreBlendBias);
|
||
|
||
// Create a proper alpha channel also if no fading is required (transparent parts).
|
||
MakeAlphaChannel(DragImage, FDragImage);
|
||
|
||
FImagePosition := ImagePosition;
|
||
|
||
// Initially the drag image is hidden and will be shown during the immediately following DragEnter event.
|
||
FStates := FStates + [disInDrag, disHidden, disPrepared];
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.RecaptureBackground(Tree: TBaseVirtualTree; R: TRect; VisibleRegion: HRGN;
|
||
CaptureNCArea, ReshowDragImage: Boolean);
|
||
|
||
// Notification by the drop target tree to update the background image because something in the tree has changed.
|
||
// Note: The passed rectangle is given in client coordinates of the current drop target tree (given in Tree).
|
||
// The caller does not check if the given rectangle is actually within the drag image. Hence this method must do
|
||
// all the checks.
|
||
// This method does nothing if the system manages the drag image.
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
var
|
||
DragRect,
|
||
ClipRect: TRect;
|
||
PaintTarget: TPoint;
|
||
PaintOptions: TVTInternalPaintOptions;
|
||
ScreenDC: HDC;
|
||
{$endif}
|
||
|
||
begin
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
// Recapturing means we want the tree to paint the new part into our back bitmap instead to the screen.
|
||
if Visible then
|
||
begin
|
||
// Create the minimum rectangle to be recaptured.
|
||
MapWindowPoints(Tree.Handle, 0, R, 2);
|
||
DragRect := GetDragImageRect;
|
||
IntersectRect(R, R, DragRect);
|
||
|
||
OffsetRgn(VisibleRegion, -DragRect.Left, -DragRect.Top);
|
||
|
||
// The target position for painting in the drag image is relative and can be determined from screen coordinates too.
|
||
PaintTarget.X := R.Left - DragRect.Left;
|
||
PaintTarget.Y := R.Top - DragRect.Top;
|
||
|
||
// The source rectangle is determined by the offsets in the tree.
|
||
MapWindowPoints(0, Tree.Handle, R, 2);
|
||
OffsetRect(R, -Tree.FOffsetX, -Tree.FOffsetY);
|
||
|
||
// Finally let the tree paint the relevant part and upate the drag image on screen.
|
||
PaintOptions := [poBackground, poColumnColor, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines];
|
||
with FBackImage do
|
||
begin
|
||
ClipRect.TopLeft := PaintTarget;
|
||
ClipRect.Right := ClipRect.Left + R.Right - R.Left;
|
||
ClipRect.Bottom := ClipRect.Top + R.Bottom - R.Top;
|
||
Tree.LimitPaintingToArea(Canvas, ClipRect, VisibleRegion);
|
||
Tree.PaintTree(Canvas, R, PaintTarget, PaintOptions);
|
||
|
||
if CaptureNCArea then
|
||
begin
|
||
// For the non-client area we only need the visible region of the window as limit for painting.
|
||
SelectClipRgn(Canvas.Handle, VisibleRegion);
|
||
// Since WM_PRINT cannot be given a position where to draw we simply move the window origin and
|
||
// get the same effect.
|
||
GetWindowRect(Tree.Handle, ClipRect);
|
||
SetWindowOrgEx(Canvas.Handle, DragRect.Left - ClipRect.Left, DragRect.Top - ClipRect.Top, nil);
|
||
//todo: see what todo here
|
||
//Tree.Perform(WM_PRINT, Integer(Canvas.Handle), PRF_NONCLIENT);
|
||
SetWindowOrgEx(Canvas.Handle, 0, 0, nil);
|
||
end;
|
||
SelectClipRgn(Canvas.Handle, 0);
|
||
|
||
if ReshowDragImage then
|
||
begin
|
||
GDIFlush;
|
||
ScreenDC := GetDC(0);
|
||
try
|
||
InternalShowDragImage(ScreenDC);
|
||
finally
|
||
ReleaseDC(0, ScreenDC);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTDragImage.ShowDragImage;
|
||
|
||
// Shows the drag image after it has been hidden by HideDragImage.
|
||
// Note: there might be a new background now.
|
||
// Also this method does nothing if the system manages the drag image.
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
var
|
||
ScreenDC: HDC;
|
||
{$endif}
|
||
|
||
begin
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if FStates * [disInDrag, disHidden, disPrepared, disSystemSupport] = [disInDrag, disHidden, disPrepared] then
|
||
begin
|
||
Exclude(FStates, disHidden);
|
||
|
||
GDIFlush;
|
||
ScreenDC := GetDC(0);
|
||
try
|
||
BitBlt(FBackImage.Canvas.Handle, 0, 0, FBackImage.Width, FBackImage.Height, ScreenDC, FImagePosition.X,
|
||
FImagePosition.Y, SRCCOPY);
|
||
|
||
InternalShowDragImage(ScreenDC);
|
||
finally
|
||
ReleaseDC(0, ScreenDC);
|
||
end;
|
||
end;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTDragImage.WillMove(const P: TPoint): Boolean;
|
||
|
||
// This method determines whether the drag image would "physically" move when DragTo would be called with the same
|
||
// target point.
|
||
// Always returns False if the system drag image support is available.
|
||
|
||
var
|
||
DeltaX,
|
||
DeltaY: Integer;
|
||
|
||
begin
|
||
Result := Visible;
|
||
if Result then
|
||
begin
|
||
// Determine distances to move the drag image. Take care for restrictions.
|
||
case FRestriction of
|
||
dmrHorizontalOnly:
|
||
begin
|
||
DeltaX := FLastPosition.X - P.X;
|
||
DeltaY := 0;
|
||
end;
|
||
dmrVerticalOnly:
|
||
begin
|
||
DeltaX := 0;
|
||
DeltaY := FLastPosition.Y - P.Y;
|
||
end;
|
||
else // dmrNone
|
||
DeltaX := FLastPosition.X - P.X;
|
||
DeltaY := FLastPosition.Y - P.Y;
|
||
end;
|
||
|
||
Result := (DeltaX <> 0) or (DeltaY <> 0);
|
||
end;
|
||
end;
|
||
|
||
//----------------- TVirtualTreeColumn ---------------------------------------------------------------------------------
|
||
|
||
constructor TVirtualTreeColumn.Create(Collection: TCollection);
|
||
|
||
begin
|
||
FMinWidth := 10;
|
||
FMaxWidth := 10000;
|
||
FImageIndex := -1;
|
||
FMargin := 4;
|
||
FSpacing := 4;
|
||
FText := '';
|
||
FOptions := DefaultColumnOptions;
|
||
FAlignment := taLeftJustify;
|
||
FBidiMode := bdLeftToRight;
|
||
FColor := clWindow;
|
||
FLayout := blGlyphLeft;
|
||
FBonusPixel := False;
|
||
FCaptionAlignment := taLeftJustify;
|
||
FCheckType := ctCheckBox;
|
||
FCheckState := csUncheckedNormal;
|
||
FCheckBox := False;
|
||
FHasImage := False;
|
||
|
||
inherited Create(Collection);
|
||
|
||
FWidth := Owner.FDefaultWidth;
|
||
FLastWidth := Owner.FDefaultWidth;
|
||
//lcl: setting FPosition here will override the Design time value
|
||
//FPosition := Owner.Count - 1;
|
||
// Read parent bidi mode and color values as default values.
|
||
ParentBiDiModeChanged;
|
||
ParentColorChanged;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TVirtualTreeColumn.Destroy;
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
//--------------- local function ---------------------------------------------
|
||
|
||
procedure AdjustColumnIndex(var ColumnIndex: TColumnIndex);
|
||
|
||
begin
|
||
if Index = ColumnIndex then
|
||
ColumnIndex := NoColumn
|
||
else
|
||
if Index < ColumnIndex then
|
||
Dec(ColumnIndex);
|
||
end;
|
||
|
||
//--------------- end local function -----------------------------------------
|
||
|
||
begin
|
||
// Check if this column is somehow referenced by its collection parent or the header.
|
||
with Owner do
|
||
begin
|
||
// If the columns collection object is currently deleting all columns
|
||
// then we don't need to check the various cached indices individually.
|
||
if not FClearing then
|
||
begin
|
||
IndexChanged(Index, -1);
|
||
|
||
AdjustColumnIndex(FHoverIndex);
|
||
AdjustColumnIndex(FDownIndex);
|
||
AdjustColumnIndex(FTrackIndex);
|
||
AdjustColumnIndex(FClickIndex);
|
||
|
||
with Header do
|
||
begin
|
||
AdjustColumnIndex(FAutoSizeIndex);
|
||
if Index = FMainColumn then
|
||
begin
|
||
// If the current main column is about to be destroyed then we have to find a new main column.
|
||
FMainColumn := NoColumn;
|
||
for I := 0 to Count - 1 do
|
||
if I <> Index then
|
||
begin
|
||
FMainColumn := I;
|
||
Break;
|
||
end;
|
||
end;
|
||
AdjustColumnIndex(FSortColumn);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.GetCaptionAlignment: TAlignment;
|
||
|
||
begin
|
||
if coUseCaptionAlignment in FOptions then
|
||
Result := FCaptionAlignment
|
||
else
|
||
Result := FAlignment;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.GetLeft: Integer;
|
||
|
||
begin
|
||
Result := FLeft;
|
||
if [coVisible, coFixed] * FOptions <> [coVisible, coFixed] then
|
||
Dec(Result, Owner.Header.Treeview.FEffectiveOffsetX);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.IsBiDiModeStored: Boolean;
|
||
|
||
begin
|
||
Result := not (coParentBiDiMode in FOptions);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.IsCaptionAlignmentStored: Boolean;
|
||
|
||
begin
|
||
Result := coUseCaptionAlignment in FOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.IsColorStored: Boolean;
|
||
|
||
begin
|
||
Result := not (coParentColor in FOptions);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetAlignment(const Value: TAlignment);
|
||
|
||
begin
|
||
if FAlignment <> Value then
|
||
begin
|
||
FAlignment := Value;
|
||
Changed(False);
|
||
// Setting the alignment affects also the tree, hence invalidate it too.
|
||
Owner.Header.TreeView.Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetBiDiMode(Value: TBiDiMode);
|
||
|
||
begin
|
||
if Value <> FBiDiMode then
|
||
begin
|
||
FBiDiMode := Value;
|
||
Exclude(FOptions, coParentBiDiMode);
|
||
Changed(False);
|
||
// Setting the alignment affects also the tree, hence invalidate it too.
|
||
Owner.Header.TreeView.Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetCaptionAlignment(const Value: TAlignment);
|
||
|
||
begin
|
||
if not (coUseCaptionAlignment in FOptions) or (FCaptionAlignment <> Value) then
|
||
begin
|
||
FCaptionAlignment := Value;
|
||
Exclude(FOptions, coUseCaptionAlignment);
|
||
// Setting the alignment affects also the tree, hence invalidate it too.
|
||
Owner.Header.Invalidate(Self);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetColor(const Value: TColor);
|
||
|
||
begin
|
||
if FColor <> Value then
|
||
begin
|
||
FColor := Value;
|
||
Exclude(FOptions, coParentColor);
|
||
Changed(False);
|
||
Owner.Header.TreeView.Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetCheckBox(Value: boolean);
|
||
|
||
begin
|
||
if Value <> FCheckBox then
|
||
begin
|
||
FCheckBox := Value;
|
||
//lcl
|
||
if FCheckBox then
|
||
Owner.Header.Treeview.CheckImageListNeeded;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetCheckState(Value: TCheckState);
|
||
|
||
begin
|
||
if Value <> FCheckState then
|
||
begin
|
||
FCheckState := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetCheckType(Value: TCheckType);
|
||
|
||
begin
|
||
if Value <> FCheckType then
|
||
begin
|
||
FCheckType := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetImageIndex(Value: TImageIndex);
|
||
|
||
begin
|
||
if Value <> FImageIndex then
|
||
begin
|
||
FImageIndex := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetLayout(Value: TVTHeaderColumnLayout);
|
||
|
||
begin
|
||
if FLayout <> Value then
|
||
begin
|
||
FLayout := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetMargin(Value: Integer);
|
||
|
||
begin
|
||
// Compatibility setting for -1.
|
||
if Value < 0 then
|
||
Value := 4;
|
||
if FMargin <> Value then
|
||
begin
|
||
FMargin := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetMaxWidth(Value: Integer);
|
||
|
||
begin
|
||
if Value < FMinWidth then
|
||
Value := FMinWidth;
|
||
if not IsWinNT and (Value > 10000) then
|
||
Value := 10000;
|
||
FMaxWidth := Value;
|
||
SetWidth(FWidth);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetMinWidth(Value: Integer);
|
||
|
||
begin
|
||
if Value < 0 then
|
||
Value := 0;
|
||
if Value > FMaxWidth then
|
||
Value := FMaxWidth;
|
||
FMinWidth := Value;
|
||
SetWidth(FWidth);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetOptions(Value: TVTColumnOptions);
|
||
|
||
var
|
||
ToBeSet,
|
||
ToBeCleared: TVTColumnOptions;
|
||
AVisibleChanged,
|
||
ColorChanged: Boolean;
|
||
|
||
begin
|
||
if FOptions <> Value then
|
||
begin
|
||
ToBeCleared := FOptions - Value;
|
||
ToBeSet := Value - FOptions;
|
||
|
||
FOptions := Value;
|
||
|
||
AVisibleChanged := coVisible in (ToBeSet + ToBeCleared);
|
||
ColorChanged := coParentColor in ToBeSet;
|
||
|
||
if coParentBidiMode in ToBeSet then
|
||
ParentBiDiModeChanged;
|
||
if ColorChanged then
|
||
ParentColorChanged;
|
||
|
||
if coAutoSpring in ToBeSet then
|
||
FSpringRest := 0;
|
||
|
||
if ((coFixed in ToBeSet) or (coFixed in ToBeCleared)) and (coVisible in FOptions) then
|
||
Owner.Header.RescaleHeader;
|
||
|
||
Changed(False);
|
||
// Need to repaint and adjust the owner tree too.
|
||
|
||
//lcl: fpc refuses to compile the original code by no aparent reason.
|
||
//Found: Was confounding TControl.VisibleChanged
|
||
with Owner, Header.Treeview do
|
||
if not (csLoading in ComponentState) and (AVisibleChanged or ColorChanged) and (UpdateCount = 0) and
|
||
HandleAllocated then
|
||
begin
|
||
Invalidate;
|
||
if AVisibleChanged then
|
||
UpdateHorizontalScrollBar(False);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetPosition(Value: TColumnPosition);
|
||
|
||
var
|
||
Temp: TColumnIndex;
|
||
|
||
begin
|
||
if csLoading in Owner.Header.Treeview.ComponentState then
|
||
// Only cache the position for final fixup when loading from DFM.
|
||
FPosition := Value
|
||
else
|
||
begin
|
||
if Value >= TColumnPosition(Collection.Count) then
|
||
Value := Collection.Count - 1;
|
||
if FPosition <> Value then
|
||
begin
|
||
with Owner do
|
||
begin
|
||
InitializePositionArray;
|
||
Header.Treeview.CancelEditNode;
|
||
AdjustPosition(Self, Value);
|
||
Self.Changed(False);
|
||
|
||
// Need to repaint.
|
||
with Header do
|
||
begin
|
||
if (UpdateCount = 0) and Treeview.HandleAllocated then
|
||
begin
|
||
Invalidate(Self);
|
||
Treeview.Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
// If the moved column is now within the fixed columns then we make it fixed as well. If it's not
|
||
// we clear the fixed state (in case that fixed column is moved outside fixed area).
|
||
if (coFixed in FOptions) and (FPosition > 0) then
|
||
Temp := Owner.ColumnFromPosition(FPosition - 1)
|
||
else
|
||
Temp := Owner.ColumnFromPosition(FPosition + 1);
|
||
|
||
if Temp <> NoColumn then
|
||
begin
|
||
if coFixed in Owner[Temp].Options then
|
||
Options := Options + [coFixed]
|
||
else
|
||
Options := Options - [coFixed]
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetSpacing(Value: Integer);
|
||
|
||
begin
|
||
if FSpacing <> Value then
|
||
begin
|
||
FSpacing := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetStyle(Value: TVirtualTreeColumnStyle);
|
||
|
||
begin
|
||
if FStyle <> Value then
|
||
begin
|
||
FStyle := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetText(const Value: String);
|
||
|
||
begin
|
||
if FText <> Value then
|
||
begin
|
||
FText := Value;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SetWidth(Value: Integer);
|
||
|
||
var
|
||
EffectiveMaxWidth,
|
||
EffectiveMinWidth,
|
||
TotalFixedMaxWidth,
|
||
TotalFixedMinWidth: Integer;
|
||
I: TColumnIndex;
|
||
|
||
begin
|
||
if not (hsScaling in Owner.FHeader.FStates) then
|
||
if ([coVisible, coFixed] * FOptions = [coVisible, coFixed]) then
|
||
begin
|
||
with Owner, FHeader, FFixedAreaConstraints, TreeView do
|
||
begin
|
||
TotalFixedMinWidth := 0;
|
||
TotalFixedMaxWidth := 0;
|
||
for I := 0 to FColumns.Count - 1 do
|
||
if ([coVisible, coFixed] * FColumns[I].FOptions = [coVisible, coFixed]) then
|
||
begin
|
||
Inc(TotalFixedMaxWidth, FColumns[I].FMaxWidth);
|
||
Inc(TotalFixedMinWidth, FColumns[I].FMinWidth);
|
||
end;
|
||
|
||
// The percentage values have precedence over the pixel values.
|
||
TotalFixedMinWidth := IfThen(FMaxWidthPercent > 0,
|
||
Min((ClientWidth * FMaxWidthPercent) div 100, TotalFixedMinWidth),
|
||
TotalFixedMinWidth);
|
||
TotalFixedMaxWidth := IfThen(FMinWidthPercent > 0,
|
||
Max((ClientWidth * FMinWidthPercent) div 100, TotalFixedMaxWidth),
|
||
TotalFixedMaxWidth);
|
||
|
||
EffectiveMaxWidth := Min(TotalFixedMaxWidth - (GetVisibleFixedWidth - Self.FWidth), FMaxWidth);
|
||
EffectiveMinWidth := Max(TotalFixedMinWidth - (GetVisibleFixedWidth - Self.FWidth), FMinWidth);
|
||
Value := Min(Max(Value, EffectiveMinWidth), EffectiveMaxWidth);
|
||
|
||
if FMinWidthPercent > 0 then
|
||
Value := Max((ClientWidth * FMinWidthPercent) div 100 - GetVisibleFixedWidth + Self.FWidth, Value);
|
||
if FMaxWidthPercent > 0 then
|
||
Value := Min((ClientWidth * FMaxWidthPercent) div 100 - GetVisibleFixedWidth + Self.FWidth, Value);
|
||
end;
|
||
end
|
||
else
|
||
Value := Min(Max(Value, FMinWidth), FMaxWidth);
|
||
|
||
if FWidth <> Value then
|
||
begin
|
||
FLastWidth := FWidth;
|
||
if not (hsResizing in Owner.Header.States) then
|
||
FBonusPixel := False;
|
||
with Owner, Header do
|
||
begin
|
||
if not (hoAutoResize in FOptions) or (Index <> FAutoSizeIndex) then
|
||
begin
|
||
FWidth := Value;
|
||
UpdatePositions;
|
||
end;
|
||
if not (csLoading in Treeview.ComponentState) and (UpdateCount = 0) then
|
||
begin
|
||
if hoAutoResize in FOptions then
|
||
AdjustAutoSize(Index);
|
||
Treeview.DoColumnResize(Index);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.ComputeHeaderLayout(DC: HDC; const Client: TRect; UseHeaderGlyph, UseSortGlyph: Boolean;
|
||
var HeaderGlyphPos, SortGlyphPos: TPoint; var TextBounds: TRect; DrawFormat: Cardinal;
|
||
CalculateTextRect: Boolean = False);
|
||
|
||
// The layout of a column header is determined by a lot of factors. This method takes them all into account and
|
||
// determines all necessary positions and bounds:
|
||
// - for the header text
|
||
// - the header glyph
|
||
// - the sort glyph
|
||
|
||
var
|
||
TextSize: TSize;
|
||
TextPos,
|
||
ClientSize,
|
||
HeaderGlyphSize,
|
||
SortGlyphSize: TPoint;
|
||
CurrentAlignment: TAlignment;
|
||
MinLeft,
|
||
MaxRight,
|
||
TextSpacing: Integer;
|
||
UseText: Boolean;
|
||
R: TRect;
|
||
|
||
begin
|
||
UseText := Length(FText) > 0;
|
||
// If nothing is to show then don't waste time with useless preparation.
|
||
if not (UseText or UseHeaderGlyph or UseSortGlyph) then
|
||
Exit;
|
||
|
||
CurrentAlignment := CaptionAlignment;
|
||
if FBidiMode <> bdLeftToRight then
|
||
ChangeBiDiModeAlignment(CurrentAlignment);
|
||
|
||
// Calculate sizes of the involved items.
|
||
ClientSize := Point(Client.Right - Client.Left, Client.Bottom - Client.Top);
|
||
with Owner, Header do
|
||
begin
|
||
if UseHeaderGlyph then
|
||
if not FCheckBox then
|
||
HeaderGlyphSize := Point(FImages.Width, FImages.Height)
|
||
else
|
||
HeaderGlyphSize := Point(Treeview.CheckImages.Width, Treeview.CheckImages.Height)
|
||
else
|
||
HeaderGlyphSize := Point(0, 0);
|
||
if UseSortGlyph then
|
||
begin
|
||
SortGlyphSize := Point(UtilityImages.Height, UtilityImages.Height);
|
||
// In any case, the sort glyph is vertically centered.
|
||
SortGlyphPos.Y := (ClientSize.Y - SortGlyphSize.Y) div 2;
|
||
end
|
||
else
|
||
SortGlyphSize := Point(0, 0);
|
||
end;
|
||
|
||
if UseText then
|
||
begin
|
||
if not (coWrapCaption in FOptions) then
|
||
begin
|
||
FCaptionText := FText;
|
||
GetTextExtentPoint32(DC, PChar(FText), Length(FText), TextSize);
|
||
Inc(TextSize.cx, 2);
|
||
TextBounds := Rect(0, 0, TextSize.cx, TextSize.cy);
|
||
end
|
||
else
|
||
begin
|
||
R := Client;
|
||
if FCaptionText = '' then
|
||
FCaptionText := FText;
|
||
|
||
GetStringDrawRect(DC, FCaptionText, R, DrawFormat);
|
||
TextSize.cx := Client.Right - Client.Left;
|
||
TextSize.cy := R.Bottom - R.Top;
|
||
TextBounds := Rect(0, 0, TextSize.cx, TextSize.cy);
|
||
end;
|
||
TextSpacing := FSpacing;
|
||
end
|
||
else
|
||
begin
|
||
TextSpacing := 0;
|
||
TextSize.cx := 0;
|
||
TextSize.cy := 0;
|
||
end;
|
||
|
||
// Check first for the special case where nothing is shown except the sort glyph.
|
||
if UseSortGlyph and not (UseText or UseHeaderGlyph) then
|
||
begin
|
||
// Center the sort glyph in the available area if nothing else is there.
|
||
SortGlyphPos := Point((ClientSize.X - SortGlyphSize.X) div 2, (ClientSize.Y - SortGlyphSize.Y) div 2);
|
||
end
|
||
else
|
||
begin
|
||
// Determine extents of text and glyph and calculate positions which are clear from the layout.
|
||
if (Layout in [blGlyphLeft, blGlyphRight]) or not UseHeaderGlyph then
|
||
begin
|
||
HeaderGlyphPos.Y := (ClientSize.Y - HeaderGlyphSize.Y) div 2;
|
||
// If the text is taller than the given height, perform no vertical centration as this
|
||
// would make the text even less readable.
|
||
if TextSize.cy >= ClientSize.Y then
|
||
TextPos.Y := 0
|
||
else
|
||
TextPos.Y := (ClientSize.Y - TextSize.cy) div 2;
|
||
end
|
||
else
|
||
begin
|
||
if Layout = blGlyphTop then
|
||
begin
|
||
HeaderGlyphPos.Y := (ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing) div 2;
|
||
TextPos.Y := HeaderGlyphPos.Y + HeaderGlyphSize.Y + TextSpacing;
|
||
end
|
||
else
|
||
begin
|
||
TextPos.Y := (ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing) div 2;
|
||
HeaderGlyphPos.Y := TextPos.Y + TextSize.cy + TextSpacing;
|
||
end;
|
||
end;
|
||
|
||
// Each alignment needs special consideration.
|
||
case CurrentAlignment of
|
||
taLeftJustify:
|
||
begin
|
||
MinLeft := FMargin;
|
||
if UseSortGlyph and (FBidiMode <> bdLeftToRight) then
|
||
begin
|
||
// In RTL context is the sort glyph placed on the left hand side.
|
||
SortGlyphPos.X := MinLeft;
|
||
Inc(MinLeft, SortGlyphSize.X + FSpacing);
|
||
end;
|
||
if Layout in [blGlyphTop, blGlyphBottom] then
|
||
begin
|
||
// Header glyph is above or below text, so both must be considered when calculating
|
||
// the left positition of the sort glyph (if it is on the right hand side).
|
||
TextPos.X := MinLeft;
|
||
if UseHeaderGlyph then
|
||
begin
|
||
HeaderGlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2;
|
||
if HeaderGlyphPos.X < MinLeft then
|
||
HeaderGlyphPos.X := MinLeft;
|
||
MinLeft := Max(TextPos.X + TextSize.cx + TextSpacing, HeaderGlyphPos.X + HeaderGlyphSize.X + FSpacing);
|
||
end
|
||
else
|
||
MinLeft := TextPos.X + TextSize.cx + TextSpacing;
|
||
end
|
||
else
|
||
begin
|
||
// Everything is lined up. TextSpacing might be 0 if there is no text.
|
||
// This simplifies the calculation because no extra tests are necessary.
|
||
if UseHeaderGlyph and (Layout = blGlyphLeft) then
|
||
begin
|
||
HeaderGlyphPos.X := MinLeft;
|
||
Inc(MinLeft, HeaderGlyphSize.X + FSpacing);
|
||
end;
|
||
TextPos.X := MinLeft;
|
||
Inc(MinLeft, TextSize.cx + TextSpacing);
|
||
if UseHeaderGlyph and (Layout = blGlyphRight) then
|
||
begin
|
||
HeaderGlyphPos.X := MinLeft;
|
||
Inc(MinLeft, HeaderGlyphSize.X + FSpacing);
|
||
end;
|
||
end;
|
||
if UseSortGlyph and (FBidiMode = bdLeftToRight) then
|
||
SortGlyphPos.X := MinLeft;
|
||
end;
|
||
taCenter:
|
||
begin
|
||
if Layout in [blGlyphTop, blGlyphBottom] then
|
||
begin
|
||
HeaderGlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2;
|
||
TextPos.X := (ClientSize.X - TextSize.cx) div 2;
|
||
if UseSortGlyph then
|
||
Dec(TextPos.X, SortGlyphSize.X div 2);
|
||
end
|
||
else
|
||
begin
|
||
MinLeft := (ClientSize.X - HeaderGlyphSize.X - TextSpacing - TextSize.cx) div 2;
|
||
if UseHeaderGlyph and (Layout = blGlyphLeft) then
|
||
begin
|
||
HeaderGlyphPos.X := MinLeft;
|
||
Inc(MinLeft, HeaderGlyphSize.X + TextSpacing);
|
||
end;
|
||
TextPos.X := MinLeft;
|
||
Inc(MinLeft, TextSize.cx + TextSpacing);
|
||
if UseHeaderGlyph and (Layout = blGlyphRight) then
|
||
HeaderGlyphPos.X := MinLeft;
|
||
end;
|
||
if UseHeaderGlyph then
|
||
begin
|
||
MinLeft := Min(HeaderGlyphPos.X, TextPos.X);
|
||
MaxRight := Max(HeaderGlyphPos.X + HeaderGlyphSize.X, TextPos.X + TextSize.cx);
|
||
end
|
||
else
|
||
begin
|
||
MinLeft := TextPos.X;
|
||
MaxRight := TextPos.X + TextSize.cx;
|
||
end;
|
||
// Place the sort glyph directly to the left or right of the larger item.
|
||
if UseSortGlyph then
|
||
if FBidiMode = bdLeftToRight then
|
||
begin
|
||
// Sort glyph on the right hand side.
|
||
SortGlyphPos.X := MaxRight + FSpacing;
|
||
end
|
||
else
|
||
begin
|
||
// Sort glyph on the left hand side.
|
||
SortGlyphPos.X := MinLeft - FSpacing - SortGlyphSize.X;
|
||
end;
|
||
end;
|
||
else
|
||
// taRightJustify
|
||
MaxRight := ClientSize.X - FMargin;
|
||
if UseSortGlyph and (FBidiMode = bdLeftToRight) then
|
||
begin
|
||
// In LTR context is the sort glyph placed on the right hand side.
|
||
Dec(MaxRight, SortGlyphSize.X);
|
||
SortGlyphPos.X := MaxRight;
|
||
Dec(MaxRight, FSpacing);
|
||
end;
|
||
if Layout in [blGlyphTop, blGlyphBottom] then
|
||
begin
|
||
TextPos.X := MaxRight - TextSize.cx;
|
||
if UseHeaderGlyph then
|
||
begin
|
||
HeaderGlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2;
|
||
if HeaderGlyphPos.X + HeaderGlyphSize.X + FSpacing > MaxRight then
|
||
HeaderGlyphPos.X := MaxRight - HeaderGlyphSize.X - FSpacing;
|
||
MaxRight := Min(TextPos.X - TextSpacing, HeaderGlyphPos.X - FSpacing);
|
||
end
|
||
else
|
||
MaxRight := TextPos.X - TextSpacing;
|
||
end
|
||
else
|
||
begin
|
||
// Everything is lined up. TextSpacing might be 0 if there is no text.
|
||
// This simplifies the calculation because no extra tests are necessary.
|
||
if UseHeaderGlyph and (Layout = blGlyphRight) then
|
||
begin
|
||
HeaderGlyphPos.X := MaxRight - HeaderGlyphSize.X;
|
||
MaxRight := HeaderGlyphPos.X - FSpacing;
|
||
end;
|
||
TextPos.X := MaxRight - TextSize.cx;
|
||
MaxRight := TextPos.X - TextSpacing;
|
||
if UseHeaderGlyph and (Layout = blGlyphLeft) then
|
||
begin
|
||
HeaderGlyphPos.X := MaxRight - HeaderGlyphSize.X;
|
||
MaxRight := HeaderGlyphPos.X - FSpacing;
|
||
end;
|
||
end;
|
||
if UseSortGlyph and (FBidiMode <> bdLeftToRight) then
|
||
SortGlyphPos.X := MaxRight - SortGlyphSize.X;
|
||
end;
|
||
end;
|
||
|
||
// Once the position of each element is determined there remains only one but important step.
|
||
// The horizontal positions of every element must be adjusted so that it always fits into the
|
||
// given header area. This is accomplished by shorten the text appropriately.
|
||
|
||
// These are the maximum bounds. Nothing goes beyond them.
|
||
MinLeft := FMargin;
|
||
MaxRight := ClientSize.X - FMargin;
|
||
if UseSortGlyph then
|
||
begin
|
||
if FBidiMode = bdLeftToRight then
|
||
begin
|
||
// Sort glyph on the right hand side.
|
||
if SortGlyphPos.X + SortGlyphSize.X > MaxRight then
|
||
SortGlyphPos.X := MaxRight - SortGlyphSize.X;
|
||
MaxRight := SortGlyphPos.X - FSpacing;
|
||
end;
|
||
|
||
// Consider also the left side of the sort glyph regardless of the bidi mode.
|
||
if SortGlyphPos.X < MinLeft then
|
||
SortGlyphPos.X := MinLeft;
|
||
// Left border needs only adjustment if the sort glyph marks the left border.
|
||
if FBidiMode <> bdLeftToRight then
|
||
MinLeft := SortGlyphPos.X + SortGlyphSize.X + FSpacing;
|
||
|
||
// Finally transform sort glyph to its actual position.
|
||
with SortGlyphPos do
|
||
begin
|
||
Inc(X, Client.Left);
|
||
Inc(Y, Client.Top);
|
||
end;
|
||
end;
|
||
if UseHeaderGlyph then
|
||
begin
|
||
if HeaderGlyphPos.X + HeaderGlyphSize.X > MaxRight then
|
||
HeaderGlyphPos.X := MaxRight - HeaderGlyphSize.X;
|
||
if Layout = blGlyphRight then
|
||
MaxRight := HeaderGlyphPos.X - FSpacing;
|
||
if HeaderGlyphPos.X < MinLeft then
|
||
HeaderGlyphPos.X := MinLeft;
|
||
if Layout = blGlyphLeft then
|
||
MinLeft := HeaderGlyphPos.X + HeaderGlyphSize.X + FSpacing;
|
||
// Finally transform header glyph to its actual position.
|
||
with HeaderGlyphPos do
|
||
begin
|
||
Inc(X, Client.Left);
|
||
Inc(Y, Client.Top);
|
||
end;
|
||
end;
|
||
if UseText then
|
||
begin
|
||
if TextPos.X < MinLeft then
|
||
TextPos.X := MinLeft;
|
||
OffsetRect(TextBounds, TextPos.X, TextPos.Y);
|
||
if TextBounds.Right > MaxRight then
|
||
TextBounds.Right := MaxRight;
|
||
OffsetRect(TextBounds, Client.Left, Client.Top);
|
||
|
||
if coWrapCaption in FOptions then
|
||
begin
|
||
// Wrap the column caption if necessary.
|
||
R := TextBounds;
|
||
FCaptionText := WrapString(DC, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat);
|
||
GetStringDrawRect(DC, FCaptionText, R, DrawFormat);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.GetAbsoluteBounds(var Left, Right: Integer);
|
||
|
||
// Returns the column's left and right bounds in header coordinates, that is, independant of the scrolling position.
|
||
|
||
begin
|
||
Left := FLeft;
|
||
Right := FLeft + FWidth;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.GetDisplayName: string;
|
||
|
||
// Returns the column text otherwise the column id is returned
|
||
|
||
begin
|
||
if Length(FText) > 0 then
|
||
Result := FText
|
||
else
|
||
Result := Format('Column %d', [Index]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.GetOwner: TVirtualTreeColumns;
|
||
|
||
begin
|
||
Result := Collection as TVirtualTreeColumns;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.Assign(Source: TPersistent);
|
||
|
||
var
|
||
OldOptions: TVTColumnOptions;
|
||
|
||
begin
|
||
if Source is TVirtualTreeColumn then
|
||
begin
|
||
OldOptions := FOptions;
|
||
FOptions := [];
|
||
|
||
BiDiMode := TVirtualTreeColumn(Source).BiDiMode;
|
||
ImageIndex := TVirtualTreeColumn(Source).ImageIndex;
|
||
Layout := TVirtualTreeColumn(Source).Layout;
|
||
Margin := TVirtualTreeColumn(Source).Margin;
|
||
MaxWidth := TVirtualTreeColumn(Source).MaxWidth;
|
||
MinWidth := TVirtualTreeColumn(Source).MinWidth;
|
||
Position := TVirtualTreeColumn(Source).Position;
|
||
Spacing := TVirtualTreeColumn(Source).Spacing;
|
||
Style := TVirtualTreeColumn(Source).Style;
|
||
Text := TVirtualTreeColumn(Source).Text;
|
||
Hint := TVirtualTreeColumn(Source).Hint;
|
||
Width := TVirtualTreeColumn(Source).Width;
|
||
Alignment := TVirtualTreeColumn(Source).Alignment;
|
||
CaptionAlignment := TVirtualTreeColumn(Source).CaptionAlignment;
|
||
Color := TVirtualTreeColumn(Source).Color;
|
||
Tag := TVirtualTreeColumn(Source).Tag;
|
||
|
||
// Order is important. Assign options last.
|
||
FOptions := OldOptions;
|
||
Options := TVirtualTreeColumn(Source).Options;
|
||
|
||
Changed(False);
|
||
end
|
||
else
|
||
inherited Assign(Source);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.Equals(OtherColumnObj: TObject): Boolean;
|
||
var
|
||
OtherColumn : TVirtualTreeColumn;
|
||
begin
|
||
if OtherColumnObj is TVirtualTreeColumn then
|
||
begin
|
||
OtherColumn := TVirtualTreeColumn (OtherColumnObj);
|
||
Result := (BiDiMode = OtherColumn.BiDiMode) and
|
||
(ImageIndex = OtherColumn.ImageIndex) and
|
||
(Layout = OtherColumn.Layout) and
|
||
(Margin = OtherColumn.Margin) and
|
||
(MaxWidth = OtherColumn.MaxWidth) and
|
||
(MinWidth = OtherColumn.MinWidth) and
|
||
(Position = OtherColumn.Position) and
|
||
(Spacing = OtherColumn.Spacing) and
|
||
(Style = OtherColumn.Style) and
|
||
(Text = OtherColumn.Text) and
|
||
(Hint = OtherColumn.Hint) and
|
||
(Width = OtherColumn.Width) and
|
||
(Alignment = OtherColumn.Alignment) and
|
||
(CaptionAlignment = OtherColumn.CaptionAlignment) and
|
||
(Color = OtherColumn.Color) and
|
||
(Tag = OtherColumn.Tag) and
|
||
(Options = OtherColumn.Options)
|
||
end
|
||
else
|
||
Result := False
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.GetRect: TRect;
|
||
|
||
// Returns the rectangle this column occupies in the header (relative to (0, 0) of the non-client area).
|
||
|
||
begin
|
||
with TVirtualTreeColumns(GetOwner).FHeader do
|
||
Result := Treeview.FHeaderRect;
|
||
Inc(Result.Left, FLeft);
|
||
Result.Right := Result.Left + FWidth;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.LoadFromStream(const Stream: TStream; Version: Integer);
|
||
|
||
var
|
||
Dummy: Integer;
|
||
S: String;
|
||
|
||
begin
|
||
with Stream do
|
||
begin
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
SetLength(S, Dummy);
|
||
ReadBuffer(PChar(S)^, Dummy);
|
||
Text := S;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
SetLength(FHint, Dummy);
|
||
ReadBuffer(PChar(FHint)^, Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Width := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
MinWidth := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
MaxWidth := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Style := TVirtualTreeColumnStyle(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
ImageIndex := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Layout := TVTHeaderColumnLayout(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Margin := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Spacing := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
BiDiMode := TBiDiMode(Dummy);
|
||
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Options := TVTColumnOptions(Word(Dummy and $FFFF));
|
||
|
||
// Parts which have been introduced/changed with header stream version 1+.
|
||
// LCL port started with header stream version 6 so no need to do the check here
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Tag := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Alignment := TAlignment(Dummy);
|
||
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Color := TColor(Dummy);
|
||
|
||
if coUseCaptionAlignment in FOptions then
|
||
begin
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
CaptionAlignment := TAlignment(Dummy);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.ParentBiDiModeChanged;
|
||
|
||
var
|
||
Columns: TVirtualTreeColumns;
|
||
|
||
begin
|
||
if coParentBiDiMode in FOptions then
|
||
begin
|
||
Columns := GetOwner as TVirtualTreeColumns;
|
||
if Assigned(Columns) and (FBidiMode <> Columns.FHeader.Treeview.BiDiMode) then
|
||
begin
|
||
FBiDiMode := Columns.FHeader.Treeview.BiDiMode;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.ParentColorChanged;
|
||
|
||
var
|
||
Columns: TVirtualTreeColumns;
|
||
TreeViewColor: TColor;
|
||
begin
|
||
if coParentColor in FOptions then
|
||
begin
|
||
Columns := GetOwner as TVirtualTreeColumns;
|
||
if Assigned(Columns) then
|
||
begin
|
||
TreeViewColor := Columns.FHeader.Treeview.Brush.Color;
|
||
if FColor <> TreeViewColor then
|
||
begin
|
||
FColor := TreeViewColor;
|
||
Changed(False);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.RestoreLastWidth;
|
||
|
||
begin
|
||
TVirtualTreeColumns(GetOwner).AnimatedResize(Index, FLastWidth);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumn.SaveToStream(const Stream: TStream);
|
||
|
||
var
|
||
Dummy: Integer;
|
||
|
||
begin
|
||
with Stream do
|
||
begin
|
||
Dummy := Length(FText);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
WriteBuffer(PChar(FText)^, Dummy);
|
||
Dummy := Length(FHint);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
WriteBuffer(PChar(FHint)^, Dummy);
|
||
WriteBuffer(FWidth, SizeOf(FWidth));
|
||
WriteBuffer(FMinWidth, SizeOf(FMinWidth));
|
||
WriteBuffer(FMaxWidth, SizeOf(FMaxWidth));
|
||
Dummy := Ord(FStyle);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := FImageIndex;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Ord(FLayout);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
WriteBuffer(FMargin, SizeOf(FMargin));
|
||
WriteBuffer(FSpacing, SizeOf(FSpacing));
|
||
Dummy := Ord(FBiDiMode);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Word(FOptions);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
|
||
// parts introduced with stream version 1
|
||
WriteBuffer(FTag, SizeOf(Dummy));
|
||
Dummy := Cardinal(FAlignment);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
|
||
// parts introduced with stream version 2
|
||
Dummy := Integer(FColor);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
|
||
// parts introduced with stream version 6
|
||
if coUseCaptionAlignment in FOptions then
|
||
begin
|
||
Dummy := Cardinal(FCaptionAlignment);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumn.UseRightToLeftReading: Boolean;
|
||
|
||
begin
|
||
Result := FBiDiMode <> bdLeftToRight;
|
||
end;
|
||
|
||
//----------------- TVirtualTreeColumns --------------------------------------------------------------------------------
|
||
|
||
constructor TVirtualTreeColumns.Create(AOwner: TVTHeader);
|
||
|
||
var
|
||
ColumnClass: TVirtualTreeColumnClass;
|
||
|
||
begin
|
||
FHeader := AOwner;
|
||
|
||
// Determine column class to be used in the header.
|
||
ColumnClass := AOwner.FOwner.GetColumnClass;
|
||
// The owner tree always returns the default tree column class if not changed by application/descendants.
|
||
inherited Create(ColumnClass);
|
||
|
||
FHeaderBitmap := TBitmap.Create;
|
||
FHeaderBitmap.PixelFormat := pf32Bit;
|
||
|
||
FHoverIndex := NoColumn;
|
||
FDownIndex := NoColumn;
|
||
FClickIndex := NoColumn;
|
||
FDropTarget := NoColumn;
|
||
FTrackIndex := NoColumn;
|
||
FDefaultWidth := 50;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TVirtualTreeColumns.Destroy;
|
||
|
||
begin
|
||
FHeaderBitmap.Free;
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetCount: Integer;
|
||
|
||
begin
|
||
Result := inherited Count;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetItem(Index: TColumnIndex): TVirtualTreeColumn;
|
||
|
||
begin
|
||
Result := TVirtualTreeColumn(inherited GetItem(Index));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetNewIndex(P: TPoint; var OldIndex: TColumnIndex): Boolean;
|
||
|
||
var
|
||
NewIndex: Integer;
|
||
|
||
begin
|
||
Result := False;
|
||
// convert to local coordinates
|
||
Inc(P.Y, FHeader.FHeight);
|
||
NewIndex := ColumnFromPosition(P);
|
||
if NewIndex <> OldIndex then
|
||
begin
|
||
if OldIndex > NoColumn then
|
||
FHeader.Invalidate(Items[OldIndex]);
|
||
OldIndex := NewIndex;
|
||
if OldIndex > NoColumn then
|
||
FHeader.Invalidate(Items[OldIndex]);
|
||
Result := True;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.SetDefaultWidth(Value: Integer);
|
||
|
||
begin
|
||
FDefaultWidth := Value;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.SetItem(Index: TColumnIndex; Value: TVirtualTreeColumn);
|
||
|
||
begin
|
||
inherited SetItem(Index, Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.AdjustAutoSize(CurrentIndex: TColumnIndex; Force: Boolean = False);
|
||
|
||
// Called only if the header is in auto-size mode which means a column needs to be so large
|
||
// that it fills all the horizontal space not occupied by the other columns.
|
||
// CurrentIndex (if not InvalidColumn) describes which column has just been resized.
|
||
|
||
var
|
||
NewValue,
|
||
AutoIndex,
|
||
Index,
|
||
RestWidth: Integer;
|
||
|
||
begin
|
||
if Count > 0 then
|
||
begin
|
||
// Determine index to be used for auto resizing. This is usually given by the owner's AutoSizeIndex, but
|
||
// could be different if the column whose resize caused the invokation here is either the auto column itself
|
||
// or visually to the right of the auto size column.
|
||
AutoIndex := FHeader.FAutoSizeIndex;
|
||
if (AutoIndex < 0) or (AutoIndex >= Count) then
|
||
AutoIndex := Count - 1;
|
||
|
||
if AutoIndex >= 0 then
|
||
begin
|
||
with FHeader.Treeview do
|
||
begin
|
||
if HandleAllocated then
|
||
RestWidth := ClientWidth
|
||
else
|
||
RestWidth := Width;
|
||
end;
|
||
|
||
// Go through all columns and calculate the rest space remaining.
|
||
for Index := 0 to Count - 1 do
|
||
if (Index <> AutoIndex) and (coVisible in Items[Index].FOptions) then
|
||
Dec(RestWidth, Items[Index].Width);
|
||
|
||
with Items[AutoIndex] do
|
||
begin
|
||
NewValue := Max(MinWidth, Min(MaxWidth, RestWidth));
|
||
if Force or (FWidth <> NewValue) then
|
||
begin
|
||
FWidth := NewValue;
|
||
UpdatePositions;
|
||
FHeader.Treeview.DoColumnResize(AutoIndex);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.AdjustDownColumn(P: TPoint): TColumnIndex;
|
||
|
||
// Determines the column from the given position and returns it. If this column is allowed to be clicked then
|
||
// it is also kept for later use.
|
||
|
||
begin
|
||
// Convert to local coordinates.
|
||
Inc(P.Y, FHeader.FHeight);
|
||
Result := ColumnFromPosition(P);
|
||
if (Result > NoColumn) and (Result <> FDownIndex) and (coAllowClick in Items[Result].FOptions) and
|
||
(coEnabled in Items[Result].FOptions) then
|
||
begin
|
||
if FDownIndex > NoColumn then
|
||
FHeader.Invalidate(Items[FDownIndex]);
|
||
FDownIndex := Result;
|
||
FHeader.Invalidate(Items[FDownIndex]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.AdjustHoverColumn(const P: TPoint): Boolean;
|
||
|
||
// Determines the new hover column index and returns True if the index actually changed else False.
|
||
|
||
begin
|
||
Result := GetNewIndex(P, FHoverIndex);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.AdjustPosition(Column: TVirtualTreeColumn; Position: Cardinal);
|
||
|
||
// Reorders the column position array so that the given column gets the given position.
|
||
|
||
var
|
||
OldPosition: Cardinal;
|
||
|
||
begin
|
||
OldPosition := Column.Position;
|
||
if OldPosition <> Position then
|
||
begin
|
||
if OldPosition < Position then
|
||
begin
|
||
// column will be moved up so move down other entries
|
||
Move(FPositionToIndex[OldPosition + 1], FPositionToIndex[OldPosition], (Position - OldPosition) * SizeOf(Cardinal));
|
||
end
|
||
else
|
||
begin
|
||
// column will be moved down so move up other entries
|
||
Move(FPositionToIndex[Position], FPositionToIndex[Position + 1], (OldPosition - Position) * SizeOf(Cardinal));
|
||
end;
|
||
FPositionToIndex[Position] := Column.Index;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.CanSplitterResize(P: TPoint; Column: TColumnIndex): Boolean;
|
||
begin
|
||
Result := (Column > NoColumn) and ([coResizable, coVisible] * Items[Column].FOptions = [coResizable, coVisible]);
|
||
DoCanSplitterResize(P, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.DoCanSplitterResize(P: TPoint; Column: TColumnIndex; var Allowed: Boolean);
|
||
begin
|
||
if Assigned(FHeader.Treeview.FOnCanSplitterResizeColumn) then
|
||
FHeader.Treeview.FOnCanSplitterResizeColumn(FHeader, P, Column, Allowed);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.DrawButtonText(DC: HDC; Caption: String; Bounds: TRect; Enabled, Hot: Boolean;
|
||
DrawFormat: Cardinal; WrapCaption: Boolean);
|
||
|
||
var
|
||
TextSpace: Integer;
|
||
Size: TSize;
|
||
|
||
begin
|
||
if not WrapCaption then
|
||
begin
|
||
// Do we need to shorten the caption due to limited space?
|
||
GetTextExtentPoint32(DC, PChar(Caption), Length(Caption), Size);
|
||
TextSpace := Bounds.Right - Bounds.Left;
|
||
if TextSpace < Size.cx then
|
||
Caption := ShortenString(DC, Caption, TextSpace);
|
||
end;
|
||
|
||
SetBkMode(DC, TRANSPARENT);
|
||
if not Enabled then
|
||
begin
|
||
OffsetRect(Bounds, 1, 1);
|
||
SetTextColor(DC, ColorToRGB(clBtnHighlight));
|
||
DrawText(DC, PChar(Caption), Length(Caption), Bounds, DrawFormat);
|
||
OffsetRect(Bounds, -1, -1);
|
||
SetTextColor(DC, ColorToRGB(clBtnShadow));
|
||
DrawText(DC, PChar(Caption), Length(Caption), Bounds, DrawFormat);
|
||
end
|
||
else
|
||
begin
|
||
if Hot then
|
||
SetTextColor(DC, ColorToRGB(FHeader.Treeview.FColors.HeaderHotColor))
|
||
else
|
||
SetTextColor(DC, ColorToRGB(FHeader.FFont.Color));
|
||
DrawText(DC, PChar(Caption), Length(Caption), Bounds, DrawFormat);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
// XP style header button legacy code. This procedure is only used on non-XP systems to simulate the themed
|
||
// header style.
|
||
// Note: the theme elements displayed here only correspond to the standard themes of Windows XP
|
||
|
||
const
|
||
XPMainHeaderColorUp = $DBEAEB; // Main background color of the header if drawn as being not pressed.
|
||
XPMainHeaderColorDown = $D8DFDE; // Main background color of the header if drawn as being pressed.
|
||
XPMainHeaderColorHover = $F3F8FA; // Main background color of the header if drawn as being under the mouse pointer.
|
||
XPDarkSplitBarColor = $B2C5C7; // Dark color of the splitter bar.
|
||
XPLightSplitBarColor = $FFFFFF; // Light color of the splitter bar.
|
||
XPDarkGradientColor = $B8C7CB; // Darkest color in the bottom gradient. Other colors will be interpolated.
|
||
XPDownOuterLineColor = $97A5A5; // Down state border color.
|
||
XPDownMiddleLineColor = $B8C2C1; // Down state border color.
|
||
XPDownInnerLineColor = $C9D1D0; // Down state border color.
|
||
|
||
procedure TVirtualTreeColumns.DrawXPButton(DC: HDC; const ButtonR: TRect; DrawSplitter, Down, Hover: Boolean);
|
||
|
||
// Helper procedure to draw an Windows XP like header button.
|
||
|
||
var
|
||
PaintBrush: HBRUSH;
|
||
Pen,
|
||
OldPen: HPEN;
|
||
PenColor,
|
||
FillColor: COLORREF;
|
||
dRed, dGreen, dBlue: Single;
|
||
Width,
|
||
XPos: Integer;
|
||
|
||
begin
|
||
if Down then
|
||
FillColor := XPMainHeaderColorDown
|
||
else
|
||
if Hover then
|
||
FillColor := XPMainHeaderColorHover
|
||
else
|
||
FillColor := XPMainHeaderColorUp;
|
||
PaintBrush := CreateSolidBrush(FillColor);
|
||
FillRect(DC, ButtonR, PaintBrush);
|
||
DeleteObject(PaintBrush);
|
||
|
||
if DrawSplitter and not (Down or Hover) then
|
||
begin
|
||
// One solid pen for the dark line...
|
||
Pen := CreatePen(PS_SOLID, 1, XPDarkSplitBarColor);
|
||
OldPen := SelectObject(DC, Pen);
|
||
MoveToEx(DC, ButtonR.Right - 2, ButtonR.Top + 3, nil);
|
||
LineTo(DC, ButtonR.Right - 2, ButtonR.Bottom - 5);
|
||
// ... and one solid pen for the light line.
|
||
Pen := CreatePen(PS_SOLID, 1, XPLightSplitBarColor);
|
||
DeleteObject(SelectObject(DC, Pen));
|
||
MoveToEx(DC, ButtonR.Right - 1, ButtonR.Top + 3, nil);
|
||
LineTo(DC, ButtonR.Right - 1, ButtonR.Bottom - 5);
|
||
SelectObject(DC, OldPen);
|
||
DeleteObject(Pen);
|
||
end;
|
||
|
||
if Down then
|
||
begin
|
||
// Down state. Three lines to draw.
|
||
// First one is the outer line, drawn at left, bottom and right.
|
||
Pen := CreatePen(PS_SOLID, 1, XPDownOuterLineColor);
|
||
OldPen := SelectObject(DC, Pen);
|
||
MoveToEx(DC, ButtonR.Left, ButtonR.Top, nil);
|
||
LineTo(DC, ButtonR.Left, ButtonR.Bottom - 1);
|
||
LineTo(DC, ButtonR.Right - 1, ButtonR.Bottom - 1);
|
||
LineTo(DC, ButtonR.Right - 1, ButtonR.Top - 1);
|
||
|
||
// Second one is the middle line, which is a bit lighter.
|
||
Pen := CreatePen(PS_SOLID, 1, XPDownMiddleLineColor);
|
||
DeleteObject(SelectObject(DC, Pen));
|
||
MoveToEx(DC, ButtonR.Left + 1, ButtonR.Bottom - 2, nil);
|
||
LineTo(DC, ButtonR.Left + 1, ButtonR.Top);
|
||
LineTo(DC, ButtonR.Right - 1, ButtonR.Top);
|
||
|
||
// Third line is the inner line, which is even lighter than the middle line.
|
||
Pen := CreatePen(PS_SOLID, 1, XPDownInnerLineColor);
|
||
DeleteObject(SelectObject(DC, Pen));
|
||
MoveToEx(DC, ButtonR.Left + 2, ButtonR.Bottom - 2, nil);
|
||
LineTo(DC, ButtonR.Left + 2, ButtonR.Top + 1);
|
||
LineTo(DC, ButtonR.Right - 1, ButtonR.Top + 1);
|
||
|
||
// Housekeeping:
|
||
SelectObject(DC, OldPen);
|
||
DeleteObject(Pen);
|
||
end
|
||
else
|
||
if Hover then
|
||
begin
|
||
// Hover state. There are three lines at the bottom border, but they are rendered in a way which
|
||
// requires expensive construction.
|
||
Width := ButtonR.Right - ButtonR.Left;
|
||
if Width <= 32 then
|
||
begin
|
||
BitBlt(DC, ButtonR.Right - 16, ButtonR.Bottom - 3, UtilityImageSize, 3, UtilityImages.Canvas.Handle,
|
||
8 * UtilityImageSize, 0, SRCCOPY);
|
||
//ImageList_DrawEx(UtilityImages.Handle, 8, DC, ButtonR.Right - 16, ButtonR.Bottom - 3, 16, 3, CLR_NONE, CLR_NONE,
|
||
// ILD_NORMAL);
|
||
BitBlt(DC, ButtonR.Left, ButtonR.Bottom - 3, Width div 2, 3, UtilityImages.Canvas.Handle,
|
||
6 * UtilityImageSize, 0, SRCCOPY);
|
||
//ImageList_DrawEx(UtilityImages.Handle, 6, DC, ButtonR.Left, ButtonR.Bottom - 3, Width div 2, 3, CLR_NONE,
|
||
// CLR_NONE, ILD_NORMAL);
|
||
end
|
||
else
|
||
begin
|
||
BitBlt(DC, ButtonR.Left, ButtonR.Bottom - 3, UtilityImageSize, 3, UtilityImages.Canvas.Handle,
|
||
6 * UtilityImageSize, 0, SRCCOPY);
|
||
//ImageList_DrawEx(UtilityImages.Handle, 6, DC, ButtonR.Left, ButtonR.Bottom - 3, 16, 3, CLR_NONE, CLR_NONE,
|
||
// ILD_NORMAL);
|
||
// Replicate inner part as many times as need to fill up the button rectangle.
|
||
XPos := ButtonR.Left + 16;
|
||
repeat
|
||
BitBlt(DC, XPos, ButtonR.Bottom - 3, UtilityImageSize, 3, UtilityImages.Canvas.Handle,
|
||
7 * UtilityImageSize, 0, SRCCOPY);
|
||
//ImageList_DrawEx(UtilityImages.Handle, 7, DC, XPos, ButtonR.Bottom - 3, 16, 3, CLR_NONE, CLR_NONE, ILD_NORMAL);
|
||
Inc(XPos, 16);
|
||
until XPos + 16 >= ButtonR.Right;
|
||
BitBlt(DC, ButtonR.Right - 16, ButtonR.Bottom - 3, UtilityImageSize, 3, UtilityImages.Canvas.Handle,
|
||
8 * UtilityImageSize, 0, SRCCOPY);
|
||
//ImageList_DrawEx(UtilityImages.Handle, 8, DC, ButtonR.Right - 16, ButtonR.Bottom - 3, 16, 3, CLR_NONE, CLR_NONE,
|
||
// ILD_NORMAL);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// There is a three line gradient near the bottom border which transforms from the button color to a dark,
|
||
// clBtnFace like color (here XPDarkGradientColor).
|
||
PenColor := XPMainHeaderColorUp;
|
||
dRed := ((PenColor and $FF) - (XPDarkGradientColor and $FF)) / 3;
|
||
dGreen := (((PenColor shr 8) and $FF) - ((XPDarkGradientColor shr 8) and $FF)) / 3;
|
||
dBlue := (((PenColor shr 16) and $FF) - ((XPDarkGradientColor shr 16) and $FF)) / 3;
|
||
|
||
// First line:
|
||
PenColor := PenColor - Round(dRed) - Round(dGreen) shl 8 - Round(dBlue) shl 16;
|
||
Pen := CreatePen(PS_SOLID, 1, PenColor);
|
||
OldPen := SelectObject(DC, Pen);
|
||
MoveToEx(DC, ButtonR.Left, ButtonR.Bottom - 3, nil);
|
||
LineTo(DC, ButtonR.Right, ButtonR.Bottom - 3);
|
||
|
||
// Second line:
|
||
PenColor := PenColor - Round(dRed) - Round(dGreen) shl 8 - Round(dBlue) shl 16;
|
||
Pen := CreatePen(PS_SOLID, 1, PenColor);
|
||
DeleteObject(SelectObject(DC, Pen));
|
||
MoveToEx(DC, ButtonR.Left, ButtonR.Bottom - 2, nil);
|
||
LineTo(DC, ButtonR.Right, ButtonR.Bottom - 2);
|
||
|
||
// Third line:
|
||
Pen := CreatePen(PS_SOLID, 1, XPDarkGradientColor);
|
||
DeleteObject(SelectObject(DC, Pen));
|
||
MoveToEx(DC, ButtonR.Left, ButtonR.Bottom - 1, nil);
|
||
LineTo(DC, ButtonR.Right, ButtonR.Bottom - 1);
|
||
|
||
// Housekeeping:
|
||
DeleteObject(SelectObject(DC, OldPen));
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.FixPositions;
|
||
|
||
// Fixes column positions after loading from DFM or Bidi mode change.
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
for I := 0 to Count - 1 do
|
||
FPositionToIndex[Items[I].Position] := I;
|
||
|
||
FNeedPositionsFix := False;
|
||
UpdatePositions(True);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetColumnAndBounds(const P: TPoint; var ColumnLeft, ColumnRight: Integer;
|
||
Relative: Boolean = True): Integer;
|
||
|
||
// Returns the column where the mouse is currently in as well as the left and right bound of
|
||
// this column (Left and Right are undetermined if no column is involved).
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
Result := InvalidColumn;
|
||
if Relative and (P.X > Header.Columns.GetVisibleFixedWidth) then
|
||
ColumnLeft := -FHeader.Treeview.FEffectiveOffsetX
|
||
else
|
||
ColumnLeft := 0;
|
||
|
||
if FHeader.Treeview.UseRightToLeftAlignment then
|
||
Inc(ColumnLeft, FHeader.Treeview.ComputeRTLOffset(True));
|
||
|
||
for I := 0 to Count - 1 do
|
||
with Items[FPositionToIndex[I]] do
|
||
if coVisible in FOptions then
|
||
begin
|
||
ColumnRight := ColumnLeft + FWidth;
|
||
if P.X < ColumnRight then
|
||
begin
|
||
Result := FPositionToIndex[I];
|
||
Exit;
|
||
end;
|
||
ColumnLeft := ColumnRight;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetOwner: TPersistent;
|
||
|
||
begin
|
||
Result := FHeader;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.HandleClick(P: TPoint; Button: TMouseButton; Force, DblClick: Boolean);
|
||
|
||
// Generates a click event if the mouse button has been released over the same column it was pressed first.
|
||
// Alternatively, Force might be set to True to indicate that the down index does not matter (right, middle and
|
||
// double click).
|
||
|
||
var
|
||
NewClickIndex: Integer;
|
||
Shift: TShiftState;
|
||
|
||
begin
|
||
// Convert vertical position to local coordinates.
|
||
Inc(P.Y, FHeader.FHeight);
|
||
NewClickIndex := ColumnFromPosition(P);
|
||
if (NewClickIndex > NoColumn) and (coAllowClick in Items[NewClickIndex].FOptions) and
|
||
((NewClickIndex = FDownIndex) or Force) then
|
||
begin
|
||
FClickIndex := NewClickIndex;
|
||
Shift := FHeader.GetShiftState;
|
||
if DblClick then
|
||
Shift := Shift + [ssDouble];
|
||
if Items[NewClickIndex].FHasImage and PtInRect(Items[NewClickIndex].FImageRect, P) then
|
||
begin
|
||
if Items[NewClickIndex].CheckBox then
|
||
begin
|
||
FHeader.Treeview.UpdateColumnCheckState(Items[NewClickIndex]);
|
||
FHeader.Treeview.DoHeaderCheckBoxClick(NewClickIndex, Button, Shift, P.X, P.Y);
|
||
end
|
||
else
|
||
FHeader.Treeview.DoHeaderImageClick(NewClickIndex, Button, Shift, P.X, P.Y)
|
||
end
|
||
else
|
||
FHeader.Treeview.DoHeaderClick(NewClickIndex, Button, Shift, P.X, P.Y);
|
||
FHeader.Invalidate(Items[NewClickIndex]);
|
||
end
|
||
else
|
||
FClickIndex := NoColumn;
|
||
|
||
if (FClickIndex > NoColumn) and (FClickIndex <> NewClickIndex) then
|
||
FHeader.Invalidate(Items[FClickIndex]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.IndexChanged(OldIndex, NewIndex: Integer);
|
||
|
||
// Called by a column when its index in the collection changes. If NewIndex is -1 then the column is
|
||
// about to be removed, otherwise it is moved to a new index.
|
||
// The method will then update the position array to reflect the change.
|
||
|
||
var
|
||
I: Integer;
|
||
Increment: Integer;
|
||
Lower,
|
||
Upper: Integer;
|
||
|
||
begin
|
||
if NewIndex = -1 then
|
||
begin
|
||
// Find position in the array with the old index.
|
||
Upper := High(FPositionToIndex);
|
||
for I := 0 to Upper do
|
||
begin
|
||
if FPositionToIndex[I] = OldIndex then
|
||
begin
|
||
// Index found. Move all higher entries one step down and remove the last entry.
|
||
if I < Upper then
|
||
Move(FPositionToIndex[I + 1], FPositionToIndex[I], (Upper - I) * SizeOf(Integer));
|
||
end;
|
||
// Decrease all indices, which are greater than the index to be deleted.
|
||
if FPositionToIndex[I] > OldIndex then
|
||
Dec(FPositionToIndex[I]);
|
||
end;
|
||
SetLength(FPositionToIndex, High(FPositionToIndex));
|
||
end
|
||
else
|
||
begin
|
||
if OldIndex < NewIndex then
|
||
Increment := -1
|
||
else
|
||
Increment := 1;
|
||
|
||
Lower := Min(OldIndex, NewIndex);
|
||
Upper := Max(OldIndex, NewIndex);
|
||
for I := 0 to High(FPositionToIndex) do
|
||
begin
|
||
if (FPositionToIndex[I] >= Lower) and (FPositionToIndex[I] < Upper) then
|
||
Inc(FPositionToIndex[I], Increment)
|
||
else
|
||
if FPositionToIndex[I] = OldIndex then
|
||
FPositionToIndex[I] := NewIndex;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.InitializePositionArray;
|
||
|
||
// Ensures that the column position array contains as many entries as columns are defined.
|
||
// The array is resized and initialized with default values if needed.
|
||
|
||
var
|
||
I, OldSize: Integer;
|
||
Changed: Boolean;
|
||
|
||
begin
|
||
if Count <> Length(FPositionToIndex) then
|
||
begin
|
||
OldSize := Length(FPositionToIndex);
|
||
SetLength(FPositionToIndex, Count);
|
||
if Count > OldSize then
|
||
begin
|
||
// New items have been added, just set their position to the same as their index.
|
||
for I := OldSize to Count - 1 do
|
||
FPositionToIndex[I] := I;
|
||
end
|
||
else
|
||
begin
|
||
// Items have been deleted, so reindex remaining entries by decrementing values larger than the highest
|
||
// possible index until no entry is higher than this limit.
|
||
repeat
|
||
Changed := False;
|
||
for I := 0 to Count - 1 do
|
||
if FPositionToIndex[I] >= Count then
|
||
begin
|
||
Dec(FPositionToIndex[I]);
|
||
Changed := True;
|
||
end;
|
||
until not Changed;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.ReorderColumns(RTL: Boolean);
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
if RTL then
|
||
begin
|
||
for I := 0 to Count - 1 do
|
||
FPositionToIndex[I] := Count - I - 1;
|
||
end
|
||
else
|
||
begin
|
||
for I := 0 to Count - 1 do
|
||
FPositionToIndex[I] := I;
|
||
end;
|
||
|
||
UpdatePositions(True);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.Update(Item: TCollectionItem);
|
||
|
||
begin
|
||
//lcl
|
||
// Skip while Destroying
|
||
if csDestroying in FHeader.TreeView.ComponentState then
|
||
Exit;
|
||
// This is the only place which gets notified when a new column has been added or removed
|
||
// and we need this event to adjust the column position array.
|
||
InitializePositionArray;
|
||
if csLoading in Header.Treeview.ComponentState then
|
||
FNeedPositionsFix := True
|
||
else
|
||
UpdatePositions;
|
||
|
||
// The first column which is created is by definition also the main column.
|
||
if (Count > 0) and (Header.FMainColumn < 0) then
|
||
FHeader.FMainColumn := 0;
|
||
|
||
if not (csLoading in Header.Treeview.ComponentState) and not (hsLoading in FHeader.FStates) then
|
||
begin
|
||
with FHeader do
|
||
begin
|
||
if hoAutoResize in FOptions then
|
||
AdjustAutoSize(InvalidColumn);
|
||
if Assigned(Item) then
|
||
Invalidate(Item as TVirtualTreeColumn)
|
||
else
|
||
if Treeview.HandleAllocated then
|
||
begin
|
||
Treeview.UpdateHorizontalScrollBar(False);
|
||
Invalidate(nil);
|
||
Treeview.Invalidate;
|
||
end;
|
||
|
||
if not (tsUpdating in Treeview.FStates) then
|
||
// This is mainly to let the designer know when a change occurs at design time which
|
||
// doesn't involve the object inspector (like column resizing with the mouse).
|
||
// This does NOT include design time code as the communication is done via an interface.
|
||
Treeview.UpdateDesigner;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.UpdatePositions(Force: Boolean = False);
|
||
|
||
// Recalculates the left border of every column and updates their position property according to the
|
||
// PostionToIndex array which primarily determines where each column is placed visually.
|
||
|
||
var
|
||
I, RunningPos: Integer;
|
||
|
||
begin
|
||
if not FNeedPositionsFix and (Force or (UpdateCount = 0)) then
|
||
begin
|
||
RunningPos := 0;
|
||
for I := 0 to High(FPositionToIndex) do
|
||
with Items[FPositionToIndex[I]] do
|
||
begin
|
||
FPosition := I;
|
||
FLeft := RunningPos;
|
||
if coVisible in FOptions then
|
||
Inc(RunningPos, FWidth);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.Add: TVirtualTreeColumn;
|
||
|
||
begin
|
||
Result := TVirtualTreeColumn(inherited Add);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.AnimatedResize(Column: TColumnIndex; NewWidth: Integer);
|
||
|
||
// Resizes the given column animated by scrolling the window DC.
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
var
|
||
OldWidth: Integer;
|
||
DC: HDC;
|
||
I,
|
||
Steps,
|
||
DX: Integer;
|
||
HeaderScrollRect,
|
||
ScrollRect,
|
||
R: TRect;
|
||
|
||
NewBrush,
|
||
LastBrush: HBRUSH;
|
||
{$endif}
|
||
begin
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if not IsValidColumn(Column) then exit; // Just in case.
|
||
|
||
// Make sure the width constrains are considered.
|
||
if NewWidth < Items[Column].FMinWidth then
|
||
NewWidth := Items[Column].FMinWidth;
|
||
if NewWidth > Items[Column].FMaxWidth then
|
||
NewWidth := Items[Column].FMaxWidth;
|
||
|
||
OldWidth := Items[Column].Width;
|
||
// Nothing to do if the width is the same.
|
||
if OldWidth <> NewWidth then
|
||
begin
|
||
if not ( (hoDisableAnimatedResize in FHeader.Options) or
|
||
(coDisableAnimatedResize in Items[Column].Options) ) then
|
||
begin
|
||
DC := GetWindowDC(FHeader.Treeview.Handle);
|
||
with FHeader.Treeview do
|
||
try
|
||
Steps := 32;
|
||
DX := (NewWidth - OldWidth) div Steps;
|
||
|
||
// Determination of the scroll rectangle is a bit complicated since we neither want
|
||
// to scroll the scrollbars nor the border of the treeview window.
|
||
HeaderScrollRect := FHeaderRect;
|
||
ScrollRect := HeaderScrollRect;
|
||
// Exclude the header itself from scrolling.
|
||
ScrollRect.Top := ScrollRect.Bottom;
|
||
ScrollRect.Bottom := ScrollRect.Top + ClientHeight;
|
||
ScrollRect.Right := ScrollRect.Left + ClientWidth;
|
||
with Items[Column] do
|
||
Inc(ScrollRect.Left, FLeft + FWidth);
|
||
HeaderScrollRect.Left := ScrollRect.Left;
|
||
HeaderScrollRect.Right := ScrollRect.Right;
|
||
|
||
// When the new width is larger then avoid artefacts on the left hand side
|
||
// by deleting a small stripe
|
||
if NewWidth > OldWidth then
|
||
begin
|
||
R := ScrollRect;
|
||
NewBrush := CreateSolidBrush(ColorToRGB(Brush.Color));
|
||
LastBrush := SelectObject(DC, NewBrush);
|
||
R.Right := R.Left + DX;
|
||
FillRect(DC, R, NewBrush);
|
||
SelectObject(DC, LastBrush);
|
||
DeleteObject(NewBrush);
|
||
end
|
||
else
|
||
begin
|
||
Inc(HeaderScrollRect.Left, DX);
|
||
Inc(ScrollRect.Left, DX);
|
||
end;
|
||
|
||
for I := 0 to Steps - 1 do
|
||
begin
|
||
ScrollDC(DC, DX, 0, HeaderScrollRect, HeaderScrollRect, 0, nil);
|
||
Inc(HeaderScrollRect.Left, DX);
|
||
ScrollDC(DC, DX, 0, ScrollRect, ScrollRect, 0, nil);
|
||
Inc(ScrollRect.Left, DX);
|
||
Sleep(1);
|
||
end;
|
||
finally
|
||
ReleaseDC(Handle, DC);
|
||
end;
|
||
end;
|
||
Items[Column].Width := NewWidth;
|
||
end;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.Assign(Source: TPersistent);
|
||
|
||
begin
|
||
// Let the collection class assign the items.
|
||
inherited;
|
||
|
||
if Source is TVirtualTreeColumns then
|
||
begin
|
||
// Copying the position array is the only needed task here.
|
||
FPositionToIndex := Copy(TVirtualTreeColumns(Source).FPositionToIndex, 0, MaxInt);
|
||
|
||
// Make sure the left edges are correct after assignment.
|
||
FNeedPositionsFix := False;
|
||
UpdatePositions(True);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.Clear;
|
||
|
||
begin
|
||
FClearing := True;
|
||
try
|
||
// Since we're freeing all columns, the following have to be true when we're done.
|
||
FHoverIndex := NoColumn;
|
||
FDownIndex := NoColumn;
|
||
FTrackIndex := NoColumn;
|
||
FClickIndex := NoColumn;
|
||
|
||
with Header do
|
||
if not (hsLoading in FStates) then
|
||
begin
|
||
FAutoSizeIndex := NoColumn;
|
||
FMainColumn := NoColumn;
|
||
FSortColumn := NoColumn;
|
||
end;
|
||
|
||
inherited Clear;
|
||
finally
|
||
FClearing := False;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.ColumnFromPosition(const P: TPoint; Relative: Boolean = True): TColumnIndex;
|
||
|
||
// Determines the current column based on the position passed in P.
|
||
|
||
var
|
||
I, Sum: Integer;
|
||
|
||
begin
|
||
Result := InvalidColumn;
|
||
|
||
// The position must be within the header area, but we extend the vertical bounds to the entire treeview area.
|
||
if (P.X >= 0) and (P.Y >= 0) and (P.Y <= FHeader.TreeView.Height) then
|
||
with FHeader, Treeview do
|
||
begin
|
||
if Relative and (P.X > GetVisibleFixedWidth) then
|
||
Sum := -FEffectiveOffsetX
|
||
else
|
||
Sum := 0;
|
||
|
||
if UseRightToLeftAlignment then
|
||
Inc(Sum, ComputeRTLOffset(True));
|
||
|
||
for I := 0 to Count - 1 do
|
||
if coVisible in Items[FPositionToIndex[I]].FOptions then
|
||
begin
|
||
Inc(Sum, Items[FPositionToIndex[I]].Width);
|
||
if P.X < Sum then
|
||
begin
|
||
Result := FPositionToIndex[I];
|
||
Break;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.ColumnFromPosition(PositionIndex: TColumnPosition): TColumnIndex;
|
||
|
||
// Returns the index of the column at the given position.
|
||
|
||
begin
|
||
if Integer(PositionIndex) < Length(FPositionToIndex) then
|
||
Result := FPositionToIndex[PositionIndex]
|
||
else
|
||
Result := NoColumn;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.Equals(OtherColumnsObj: TObject): Boolean;
|
||
|
||
// Compares itself with the given set of columns and returns True if all published properties are the same
|
||
// (including column order), otherwise False is returned.
|
||
|
||
var
|
||
I: Integer;
|
||
OtherColumns : TVirtualTreeColumns;
|
||
|
||
begin
|
||
if not (OtherColumnsObj is TVirtualTreeColumns) then
|
||
begin
|
||
Result := False;
|
||
Exit
|
||
end;
|
||
|
||
OtherColumns := TVirtualTreeColumns (OtherColumnsObj);
|
||
|
||
// Same number of columns?
|
||
Result := OtherColumns.Count = Count;
|
||
if Result then
|
||
begin
|
||
// Same order of columns?
|
||
Result := CompareMem(Pointer(FPositionToIndex), Pointer(OtherColumns.FPositionToIndex),
|
||
Length(FPositionToIndex) * SizeOf(TColumnIndex));
|
||
if Result then
|
||
begin
|
||
for I := 0 to Count - 1 do
|
||
if not Items[I].Equals(OtherColumns[I]) then
|
||
begin
|
||
Result := False;
|
||
Break;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.GetColumnBounds(Column: TColumnIndex; out Left, Right: Integer);
|
||
|
||
// Returns the left and right bound of the given column. If Column is NoColumn then the entire client width is returned.
|
||
|
||
begin
|
||
if Column <= NoColumn then
|
||
begin
|
||
Left := 0;
|
||
Right := FHeader.Treeview.ClientWidth;
|
||
end
|
||
else
|
||
begin
|
||
Left := Items[Column].Left;
|
||
Right := Left + Items[Column].Width;
|
||
if FHeader.Treeview.UseRightToLeftAlignment then
|
||
begin
|
||
Inc(Left, FHeader.Treeview.ComputeRTLOffset(True));
|
||
Inc(Right, FHeader.Treeview.ComputeRTLOffset(True));
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetScrollWidth: Integer;
|
||
|
||
// Returns the average width of all visible, non-fixed columns. If there is no such column the indent is returned.
|
||
|
||
var
|
||
I: Integer;
|
||
ScrollColumnCount: Integer;
|
||
|
||
begin
|
||
|
||
Result := 0;
|
||
|
||
ScrollColumnCount := 0;
|
||
for I := 0 to FHeader.Columns.Count - 1 do
|
||
begin
|
||
if ([coVisible, coFixed] * FHeader.Columns[I].Options = [coVisible]) then
|
||
begin
|
||
Inc(Result, FHeader.Columns[I].Width);
|
||
Inc(ScrollColumnCount);
|
||
end;
|
||
end;
|
||
|
||
if ScrollColumnCount > 0 then // use average width
|
||
Result := Round(Result / ScrollColumnCount)
|
||
else // use indent
|
||
Result := Integer(FHeader.Treeview.FIndent);
|
||
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetFirstVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
|
||
// Returns the index of the first visible column or "InvalidColumn" if either no columns are defined or
|
||
// all columns are hidden.
|
||
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
Result := InvalidColumn;
|
||
for I := 0 to Count - 1 do
|
||
if (coVisible in Items[FPositionToIndex[I]].FOptions) and
|
||
( (not ConsiderAllowFocus) or
|
||
(coAllowFocus in Items[FPositionToIndex[I]].FOptions)
|
||
) then
|
||
begin
|
||
Result := FPositionToIndex[I];
|
||
Break;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetLastVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
|
||
// Returns the index of the last visible column or "InvalidColumn" if either no columns are defined or
|
||
// all columns are hidden.
|
||
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
Result := InvalidColumn;
|
||
for I := Count - 1 downto 0 do
|
||
if (coVisible in Items[FPositionToIndex[I]].FOptions) and
|
||
( (not ConsiderAllowFocus) or
|
||
(coAllowFocus in Items[FPositionToIndex[I]].FOptions)
|
||
) then
|
||
begin
|
||
Result := FPositionToIndex[I];
|
||
Break;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetNextColumn(Column: TColumnIndex): TColumnIndex;
|
||
|
||
// Returns the next column in display order. Column is the index of an item in the collection (a column).
|
||
|
||
var
|
||
Position: Integer;
|
||
|
||
begin
|
||
if Column < 0 then
|
||
Result := InvalidColumn
|
||
else
|
||
begin
|
||
Position := Items[Column].Position;
|
||
if Position < Count - 1 then
|
||
Result := FPositionToIndex[Position + 1]
|
||
else
|
||
Result := InvalidColumn;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetNextVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
|
||
// Returns the next visible column in display order, Column is an index into the columns list.
|
||
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
|
||
|
||
begin
|
||
Result := Column;
|
||
repeat
|
||
Result := GetNextColumn(Result);
|
||
until (Result = InvalidColumn) or
|
||
( (coVisible in Items[Result].FOptions) and
|
||
( (not ConsiderAllowFocus) or
|
||
(coAllowFocus in Items[Result].FOptions)
|
||
)
|
||
);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetPreviousColumn(Column: TColumnIndex): TColumnIndex;
|
||
|
||
// Returns the previous column in display order, Column is an index into the columns list.
|
||
|
||
var
|
||
Position: Integer;
|
||
|
||
begin
|
||
if Column < 0 then
|
||
Result := InvalidColumn
|
||
else
|
||
begin
|
||
Position := Items[Column].Position;
|
||
if Position > 0 then
|
||
Result := FPositionToIndex[Position - 1]
|
||
else
|
||
Result := InvalidColumn;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetPreviousVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
||
|
||
// Returns the previous visible column in display order, Column is an index into the columns list.
|
||
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
|
||
|
||
begin
|
||
Result := Column;
|
||
repeat
|
||
Result := GetPreviousColumn(Result);
|
||
until (Result = InvalidColumn) or
|
||
( (coVisible in Items[Result].FOptions) and
|
||
( (not ConsiderAllowFocus) or
|
||
(coAllowFocus in Items[Result].FOptions)
|
||
)
|
||
);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetVisibleColumns: TColumnsArray;
|
||
|
||
// Returns a list of all currently visible columns in actual order.
|
||
|
||
var
|
||
I, Counter: Integer;
|
||
|
||
begin
|
||
SetLength(Result, Count);
|
||
Counter := 0;
|
||
|
||
for I := 0 to Count - 1 do
|
||
if coVisible in Items[FPositionToIndex[I]].FOptions then
|
||
begin
|
||
Result[Counter] := Items[FPositionToIndex[I]];
|
||
Inc(Counter);
|
||
end;
|
||
// Set result length to actual visible count.
|
||
SetLength(Result, Counter);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.GetVisibleFixedWidth: Integer;
|
||
|
||
// Determines the horizontal space all visible and fixed columns occupy.
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
Result := 0;
|
||
for I := 0 to Count - 1 do
|
||
begin
|
||
if Items[I].Options * [coVisible, coFixed] = [coVisible, coFixed] then
|
||
Inc(Result, Items[I].Width);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.IsValidColumn(Column: TColumnIndex): Boolean;
|
||
|
||
// Determines whether the given column is valid or not, that is, whether it is one of the current columns.
|
||
|
||
begin
|
||
Result := (Column > NoColumn) and (Column < Count);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.LoadFromStream(const Stream: TStream; Version: Integer);
|
||
|
||
var
|
||
I,
|
||
ItemCount: Integer;
|
||
|
||
begin
|
||
Clear;
|
||
Stream.ReadBuffer(ItemCount, SizeOf(ItemCount));
|
||
// number of columns
|
||
if ItemCount > 0 then
|
||
begin
|
||
BeginUpdate;
|
||
try
|
||
for I := 0 to ItemCount - 1 do
|
||
Add.LoadFromStream(Stream, Version);
|
||
SetLength(FPositionToIndex, ItemCount);
|
||
Stream.ReadBuffer(FPositionToIndex[0], ItemCount * SizeOf(Cardinal));
|
||
UpdatePositions(True);
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
|
||
// Data introduced with header stream version 5
|
||
// LCL port started with header stream version 6 so no need to do the check here
|
||
Stream.ReadBuffer(FDefaultWidth, SizeOf(FDefaultWidth));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.PaintHeader(DC: HDC; const R: TRect; HOffset: Integer);
|
||
|
||
// Main paint method to draw the header.
|
||
|
||
const
|
||
SortGlyphs: array[TSortDirection, Boolean] of Integer = ( // ascending/descending, normal/XP style
|
||
(3, 5) {ascending}, (2, 4) {descending}
|
||
);
|
||
|
||
var
|
||
I, Y,
|
||
SortIndex: Integer;
|
||
Run: TRect;
|
||
RightBorderFlag,
|
||
NormalButtonStyle,
|
||
NormalButtonFlags,
|
||
PressedButtonStyle,
|
||
PressedButtonFlags,
|
||
RaisedButtonStyle,
|
||
RaisedButtonFlags: Cardinal;
|
||
DrawFormat: Cardinal;
|
||
Images: TCustomImageList;
|
||
ButtonRgn: HRGN;
|
||
OwnerDraw,
|
||
WrapCaption,
|
||
AdvancedOwnerDraw: Boolean;
|
||
{$ifdef ThemeSupport}
|
||
Details: TThemedElementDetails;
|
||
{$endif ThemeSupport}
|
||
|
||
PaintInfo: THeaderPaintInfo;
|
||
RequestedElements,
|
||
ActualElements: THeaderPaintElements;
|
||
|
||
Temp: TRect;
|
||
ColCaptionText: String;
|
||
ColImages: TCustomImageList;
|
||
ColImageIndex: Integer;
|
||
|
||
begin
|
||
Run := FHeader.Treeview.FHeaderRect;
|
||
FHeaderBitmap.Width := Max(Run.Right, R.Right - R.Left);
|
||
FHeaderBitmap.Height := Run.Bottom;
|
||
OwnerDraw := (hoOwnerDraw in FHeader.FOptions) and Assigned(FHeader.Treeview.FOnHeaderDraw) and
|
||
not (csDesigning in FHeader.Treeview.ComponentState);
|
||
AdvancedOwnerDraw := (hoOwnerDraw in FHeader.FOptions) and Assigned(FHeader.Treeview.FOnAdvancedHeaderDraw) and
|
||
Assigned(FHeader.Treeview.FOnHeaderDrawQueryElements) and not (csDesigning in FHeader.Treeview.ComponentState);
|
||
// If both draw posibillities are specified then prefer the advanced way.
|
||
if AdvancedOwnerDraw then
|
||
OwnerDraw := False;
|
||
|
||
FillChar(PaintInfo, SizeOf(PaintInfo),#0);
|
||
PaintInfo.TargetCanvas := FHeaderBitmap.Canvas;
|
||
|
||
with PaintInfo, TargetCanvas do
|
||
begin
|
||
Font := FHeader.FFont;
|
||
|
||
RaisedButtonStyle := 0;
|
||
RaisedButtonFlags := 0;
|
||
case FHeader.Style of
|
||
hsThickButtons:
|
||
begin
|
||
NormalButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER;
|
||
NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_SOFT or BF_ADJUST;
|
||
PressedButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER;
|
||
PressedButtonFlags := NormalButtonFlags or BF_RIGHT or BF_FLAT or BF_ADJUST;
|
||
end;
|
||
hsFlatButtons:
|
||
begin
|
||
NormalButtonStyle := BDR_RAISEDINNER;
|
||
NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST;
|
||
PressedButtonStyle := BDR_SUNKENOUTER;
|
||
PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST;
|
||
end;
|
||
else
|
||
// hsPlates or hsXPStyle, values are not used in the latter case
|
||
begin
|
||
NormalButtonStyle := BDR_RAISEDINNER;
|
||
NormalButtonFlags := BF_RECT or BF_MIDDLE or BF_SOFT or BF_ADJUST;
|
||
PressedButtonStyle := BDR_SUNKENOUTER;
|
||
PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST;
|
||
RaisedButtonStyle := BDR_RAISEDINNER;
|
||
RaisedButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST;
|
||
end;
|
||
end;
|
||
|
||
// Use shortcut for the images.
|
||
Images := FHeader.FImages;
|
||
|
||
// Erase background of the header.
|
||
// See if the application wants to do that on its own.
|
||
RequestedElements := [];
|
||
if AdvancedOwnerDraw then
|
||
begin
|
||
PaintInfo.PaintRectangle := R;
|
||
PaintInfo.Column := nil;
|
||
FHeader.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
|
||
end;
|
||
|
||
if hpeBackground in RequestedElements then
|
||
begin
|
||
FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]);
|
||
end
|
||
else
|
||
begin
|
||
{$ifdef ThemeSupport}
|
||
if tsUseThemes in FHeader.Treeview.FStates then
|
||
begin
|
||
Details := ThemeServices.GetElementDetails(thHeaderItemRightNormal);
|
||
ThemeServices.DrawElement(Handle, Details, R, @R);
|
||
end
|
||
else
|
||
{$endif ThemeSupport}
|
||
if FHeader.Style = hsXPStyle then
|
||
DrawXPButton(Handle, Run, False, False, False)
|
||
else
|
||
begin
|
||
Brush.Color := FHeader.FBackground;
|
||
FillRect(R);
|
||
end;
|
||
end;
|
||
|
||
Run.Top := R.Top;
|
||
Run.Right := R.Left;
|
||
Run.Bottom := R.Bottom;
|
||
// Run.Left is set in the loop
|
||
|
||
// Consider right-to-left directionality.
|
||
with FHeader.Treeview do
|
||
if UseRightToLeftAlignment then
|
||
Inc(Run.Right, ComputeRTLOffset);
|
||
|
||
Temp := Run;
|
||
//todo_lcl_check
|
||
ShowRightBorder := (FHeader.Style = hsThickButtons) or not (hoAutoResize in FHeader.FOptions);// or
|
||
//(FHeader.Treeview.BevelKind = bkNone);
|
||
|
||
// Now go for each button.
|
||
for I := 0 to Count - 1 do
|
||
begin
|
||
with Items[FPositionToIndex[I]] do
|
||
if coVisible in FOptions then
|
||
begin
|
||
if not (coFixed in FOptions) then
|
||
begin
|
||
Inc(Run.Right, HOffset);
|
||
HOffset := 0;
|
||
end;
|
||
|
||
Temp := Rect(Temp.Right, Run.Top, Max(Temp.Right, Run.Right + Width), Run.Bottom);
|
||
|
||
Run.Left := Run.Right;
|
||
Inc(Run.Right, Width);
|
||
// Skip columns which are not visible at all.
|
||
if (Run.Right > R.Left) and (Run.Right > Temp.Left) then
|
||
begin
|
||
// Stop painting if the rectangle is filled.
|
||
if Run.Left > R.Right then
|
||
Break;
|
||
|
||
// Create a clip region to avoid overpainting any other area which does not belong to this column.
|
||
if Temp.Right > R.Right then
|
||
Temp.Right := R.Right;
|
||
if Temp.Left < R.Left then
|
||
Temp.Left := R.Left;
|
||
|
||
ButtonRgn := CreateRectRgnIndirect(Temp);
|
||
SelectClipRgn(Handle, ButtonRgn);
|
||
DeleteObject(ButtonRgn);
|
||
|
||
//lclheader
|
||
//Under Delphi/VCL, unlike LCL, the hover index is not changed while dragging.
|
||
//Here we check if dragging and not draw as hover
|
||
IsHoverIndex := (Integer(FPositionToIndex[I]) = FHoverIndex) and (hoHotTrack in FHeader.FOptions) and
|
||
(coEnabled in FOptions) and not (hsDragging in FHeader.States);
|
||
IsDownIndex := Integer(FPositionToIndex[I]) = FDownIndex;
|
||
if (coShowDropMark in FOptions) and (Integer(FPositionToIndex[I]) = FDropTarget) and
|
||
(Integer(FPositionToIndex[I]) <> FDragIndex) then
|
||
begin
|
||
if FDropBefore then
|
||
DropMark := dmmLeft
|
||
else
|
||
DropMark := dmmRight;
|
||
end
|
||
else
|
||
DropMark := dmmNone;
|
||
IsEnabled := (coEnabled in FOptions) and (FHeader.Treeview.Enabled);
|
||
ShowHeaderGlyph := (hoShowImages in FHeader.FOptions) and Assigned(Images) and (FImageIndex > -1);
|
||
ShowSortGlyph := (Integer(FPositionToIndex[I]) = FHeader.FSortColumn) and (hoShowSortGlyphs in FHeader.FOptions);
|
||
WrapCaption := coWrapCaption in FOptions;
|
||
|
||
PaintRectangle := Run;
|
||
|
||
// This path for text columns or advanced owner draw.
|
||
if (Style = vsText) or not OwnerDraw or AdvancedOwnerDraw then
|
||
begin
|
||
// See if the application wants to draw part of the header itself.
|
||
RequestedElements := [];
|
||
if AdvancedOwnerDraw then
|
||
begin
|
||
PaintInfo.Column := Items[FPositionToIndex[I]];
|
||
FHeader.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
|
||
end;
|
||
|
||
if ShowRightBorder or (I < Count - 1) then
|
||
RightBorderFlag := BF_RIGHT
|
||
else
|
||
RightBorderFlag := 0;
|
||
|
||
if hpeBackground in RequestedElements then
|
||
FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground])
|
||
else
|
||
begin
|
||
// Draw button first before setting the clip region.
|
||
{$ifdef ThemeSupport}
|
||
if tsUseThemes in FHeader.Treeview.FStates then
|
||
begin
|
||
if IsDownIndex then
|
||
Details := ThemeServices.GetElementDetails(thHeaderItemPressed)
|
||
else
|
||
if IsHoverIndex then
|
||
Details := ThemeServices.GetElementDetails(thHeaderItemHot)
|
||
else
|
||
Details := ThemeServices.GetElementDetails(thHeaderItemNormal);
|
||
ThemeServices.DrawElement(Handle, Details, PaintRectangle, @PaintRectangle);
|
||
end
|
||
else
|
||
{$endif ThemeSupport}
|
||
begin
|
||
if FHeader.Style = hsXPStyle then
|
||
DrawXPButton(Handle, PaintRectangle, RightBorderFlag <> 0, IsDownIndex, IsHoverIndex)
|
||
else
|
||
if IsDownIndex then
|
||
DrawEdge(Handle, PaintRectangle, PressedButtonStyle, PressedButtonFlags)
|
||
else
|
||
// Plates have the special case of raising on mouse over.
|
||
if (FHeader.Style = hsPlates) and IsHoverIndex and
|
||
(coAllowClick in FOptions) and (coEnabled in FOptions) then
|
||
DrawEdge(Handle, PaintRectangle, RaisedButtonStyle, RaisedButtonFlags or RightBorderFlag)
|
||
else
|
||
DrawEdge(Handle, PaintRectangle, NormalButtonStyle, NormalButtonFlags or RightBorderFlag);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
PaintRectangle := Run;
|
||
if (Style = vsText) or not OwnerDraw or AdvancedOwnerDraw then
|
||
begin
|
||
// calculate text and glyph position
|
||
InflateRect(PaintRectangle, -2, -2);
|
||
DrawFormat := DT_TOP or DT_NOPREFIX;
|
||
case CaptionAlignment of
|
||
taLeftJustify : DrawFormat := DrawFormat or DT_LEFT;
|
||
taRightJustify : DrawFormat := DrawFormat or DT_RIGHT;
|
||
taCenter : DrawFormat := DrawFormat or DT_CENTER;
|
||
end;
|
||
if UseRightToLeftReading then
|
||
DrawFormat := DrawFormat + DT_RTLREADING;
|
||
ComputeHeaderLayout(Handle, PaintRectangle, ShowHeaderGlyph, ShowSortGlyph, GlyphPos, SortGlyphPos,
|
||
TextRectangle, DrawFormat);
|
||
|
||
// Move glyph and text one pixel to the right and down to simulate a pressed button.
|
||
if IsDownIndex then
|
||
begin
|
||
OffsetRect(TextRectangle, 1, 1);
|
||
Inc(GlyphPos.X);
|
||
Inc(GlyphPos.Y);
|
||
Inc(SortGlyphPos.X);
|
||
Inc(SortGlyphPos.Y);
|
||
end;
|
||
|
||
// Advanced owner draw allows to paint elements, which would normally not be painted (because of space
|
||
// limitations, empty captions etc.).
|
||
ActualElements := RequestedElements * [hpeHeaderGlyph, hpeSortGlyph, hpeDropMark, hpeText];
|
||
|
||
// main glyph
|
||
FHasImage := False;
|
||
if not (hpeHeaderGlyph in ActualElements) and ShowHeaderGlyph and
|
||
(not ShowSortGlyph or (FBidiMode <> bdLeftToRight) or (GlyphPos.X + Images.Width <= SortGlyphPos.X) ) then
|
||
begin
|
||
if not FCheckBox then
|
||
begin
|
||
ColImages := Images;
|
||
ColImageIndex := FImageIndex;
|
||
ColImages.Draw(FHeaderBitmap.Canvas, GlyphPos.X, GlyphPos.Y, ColImageIndex, IsEnabled );
|
||
end
|
||
else
|
||
begin
|
||
with Header.Treeview do
|
||
begin
|
||
CheckImageListNeeded;
|
||
ColImageIndex := GetCheckImage(nil, FCheckType, FCheckState, IsEnabled);
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
with FCheckImages do
|
||
DirectMaskBlt(FHeaderBitmap.Canvas.Handle, GlyphPos.X, GlyphPos.Y,
|
||
Height, Height, Canvas.Handle, ColImageIndex * Height, 0, MaskHandle);
|
||
{$else}
|
||
with FCheckImages do
|
||
StretchMaskBlt(FHeaderBitmap.Canvas.Handle, GlyphPos.X, GlyphPos.Y,
|
||
Height, Height, Canvas.Handle, ColImageIndex * Height, 0,
|
||
Height, Height, MaskHandle, ColImageIndex * Height, 0, SRCCOPY);
|
||
{$endif}
|
||
end;
|
||
end;
|
||
|
||
FHasImage := True;
|
||
with FImageRect do
|
||
begin
|
||
Left := GlyphPos.X;
|
||
Top := GlyphPos.Y;
|
||
Right := Left + ColImages.Width;
|
||
Bottom := Top + ColImages.Height;
|
||
end;
|
||
end;
|
||
|
||
// caption
|
||
if WrapCaption then
|
||
ColCaptionText := FCaptionText
|
||
else
|
||
ColCaptionText := Text;
|
||
|
||
if not (hpeText in ActualElements) and (Length(Text) > 0) then
|
||
DrawButtonText(Handle, ColCaptionText, TextRectangle, IsEnabled, IsHoverIndex and (hoHotTrack in FHeader.FOptions) and
|
||
not (tsUseThemes in FHeader.Treeview.FStates), DrawFormat, WrapCaption );
|
||
|
||
// sort glyph
|
||
if not (hpeSortGlyph in ActualElements) and ShowSortGlyph then
|
||
begin
|
||
SortIndex := SortGlyphs[FHeader.FSortDirection, tsUseThemes in FHeader.Treeview.FStates];
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
DirectMaskBlt(FHeaderBitmap.Canvas.Handle, SortGlyphPos.X, SortGlyphPos.Y, UtilityImageSize, UtilityImageSize, UtilityImages.Canvas.Handle,
|
||
SortIndex * UtilityImageSize, 0, UtilityImages.MaskHandle);
|
||
{$else}
|
||
StretchMaskBlt(FHeaderBitmap.Canvas.Handle, SortGlyphPos.X, SortGlyphPos.Y, UtilityImageSize, UtilityImageSize, UtilityImages.Canvas.Handle,
|
||
SortIndex * UtilityImageSize, 0, UtilityImageSize, UtilityImageSize, UtilityImages.MaskHandle,SortIndex * UtilityImageSize, 0, SRCCOPY);
|
||
{$endif}
|
||
end;
|
||
|
||
// Show an indication if this column is the current drop target in a header drag operation.
|
||
if not (hpeDropMark in ActualElements) and (DropMark <> dmmNone) then
|
||
begin
|
||
Y := (PaintRectangle.Top + PaintRectangle.Bottom - UtilityImages.Height) div 2;
|
||
if DropMark = dmmLeft then
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
DirectMaskBlt(FHeaderBitmap.Canvas.Handle, PaintRectangle.Left, Y, UtilityImageSize, UtilityImageSize, UtilityImages.Canvas.Handle,
|
||
0, 0, UtilityImages.MaskHandle)
|
||
{$else}
|
||
StretchMaskBlt(FHeaderBitmap.Canvas.Handle, PaintRectangle.Left, Y, UtilityImageSize, UtilityImageSize, UtilityImages.Canvas.Handle,
|
||
0, 0, UtilityImageSize, UtilityImageSize, UtilityImages.MaskHandle, 0, 0, SRCCOPY)
|
||
{$endif}
|
||
else
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
DirectMaskBlt(FHeaderBitmap.Canvas.Handle, PaintRectangle.Right - 16, Y, UtilityImageSize, UtilityImageSize, UtilityImages.Canvas.Handle,
|
||
UtilityImageSize, 0, UtilityImages.MaskHandle);
|
||
{$else}
|
||
StretchMaskBlt(FHeaderBitmap.Canvas.Handle, PaintRectangle.Right - 16, Y, UtilityImageSize, UtilityImageSize, UtilityImages.Canvas.Handle,
|
||
UtilityImageSize, 0, UtilityImageSize, UtilityImageSize, UtilityImages.MaskHandle, UtilityImageSize, 0, SRCCOPY);
|
||
{$endif}
|
||
end;
|
||
|
||
if ActualElements <> [] then
|
||
begin
|
||
SaveHandleState;
|
||
FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, ActualElements);
|
||
RestoreHandleState;
|
||
end;
|
||
end
|
||
else // Let application draw the header.
|
||
FHeader.Treeview.DoHeaderDraw(FHeaderBitmap.Canvas, Items[FPositionToIndex[I]], PaintRectangle, IsHoverIndex,
|
||
IsDownIndex, DropMark);
|
||
SelectClipRgn(Handle, 0);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
// Blit the result to target.
|
||
with R do
|
||
BitBlt(DC, Left, Top, Right - Left, Bottom - Top, Handle, Left, Top, SRCCOPY);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualTreeColumns.SaveToStream(const Stream: TStream);
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
I := Count;
|
||
Stream.WriteBuffer(I, SizeOf(I));
|
||
if I > 0 then
|
||
begin
|
||
for I := 0 to Count - 1 do
|
||
TVirtualTreeColumn(Items[I]).SaveToStream(Stream);
|
||
|
||
Stream.WriteBuffer(FPositionToIndex[0], Count * SizeOf(Cardinal));
|
||
end;
|
||
|
||
// Data introduced with header stream version 5.
|
||
Stream.WriteBuffer(DefaultWidth, SizeOf(DefaultWidth));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualTreeColumns.TotalWidth: Integer;
|
||
|
||
var
|
||
LastColumn: TColumnIndex;
|
||
|
||
begin
|
||
Result := 0;
|
||
if (Count > 0) and (Length(FPositionToIndex) > 0) then
|
||
begin
|
||
LastColumn := FPositionToIndex[Count - 1];
|
||
if not (coVisible in Items[LastColumn].FOptions) then
|
||
LastColumn := GetPreviousVisibleColumn(LastColumn);
|
||
if LastColumn > NoColumn then
|
||
with Items[LastColumn] do
|
||
Result := FLeft + FWidth
|
||
end;
|
||
end;
|
||
|
||
//----------------- TVTFixedAreaConstraints ----------------------------------------------------------------------------
|
||
|
||
constructor TVTFixedAreaConstraints.Create(AOwner: TVTHeader);
|
||
|
||
begin
|
||
inherited Create;
|
||
|
||
FHeader := AOwner;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTFixedAreaConstraints.SetConstraints(Index: Integer; Value: TVTConstraintPercent);
|
||
|
||
begin
|
||
case Index of
|
||
0:
|
||
if Value <> FMaxHeightPercent then
|
||
begin
|
||
FMaxHeightPercent := Value;
|
||
if (Value > 0) and (Value < FMinHeightPercent) then
|
||
FMinHeightPercent := Value;
|
||
Change;
|
||
end;
|
||
1:
|
||
if Value <> FMaxWidthPercent then
|
||
begin
|
||
FMaxWidthPercent := Value;
|
||
if (Value > 0) and (Value < FMinWidthPercent) then
|
||
FMinWidthPercent := Value;
|
||
Change;
|
||
end;
|
||
2:
|
||
if Value <> FMinHeightPercent then
|
||
begin
|
||
FMinHeightPercent := Value;
|
||
if (FMaxHeightPercent > 0) and (Value > FMaxHeightPercent) then
|
||
FMaxHeightPercent := Value;
|
||
Change;
|
||
end;
|
||
3:
|
||
if Value <> FMinWidthPercent then
|
||
begin
|
||
FMinWidthPercent := Value;
|
||
if (FMaxWidthPercent > 0) and (Value > FMaxWidthPercent) then
|
||
FMaxWidthPercent := Value;
|
||
Change;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTFixedAreaConstraints.Change;
|
||
|
||
begin
|
||
if Assigned(FOnChange) then
|
||
FOnChange(Self);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTFixedAreaConstraints.Assign(Source: TPersistent);
|
||
|
||
begin
|
||
if Source is TVTFixedAreaConstraints then
|
||
begin
|
||
FMaxHeightPercent := TVTFixedAreaConstraints(Source).FMaxHeightPercent;
|
||
FMaxWidthPercent := TVTFixedAreaConstraints(Source).FMaxWidthPercent;
|
||
FMinHeightPercent := TVTFixedAreaConstraints(Source).FMinHeightPercent;
|
||
FMinWidthPercent := TVTFixedAreaConstraints(Source).FMinWidthPercent;
|
||
Change;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
//----------------- TVTHeader -----------------------------------------------------------------------------------------
|
||
|
||
constructor TVTHeader.Create(AOwner: TBaseVirtualTree);
|
||
|
||
begin
|
||
inherited Create;
|
||
FOwner := AOwner;
|
||
FColumns := GetColumnsClass.Create(Self);
|
||
FHeight := 17;
|
||
FDefaultHeight := 17;
|
||
FMinHeight := 10;
|
||
FMaxHeight := 10000;
|
||
FFont := TFont.Create;
|
||
FFont.OnChange := FontChanged;
|
||
FParentFont := False;
|
||
FBackground := clBtnFace;
|
||
FOptions := [hoColumnResize, hoDrag, hoShowSortGlyphs];
|
||
|
||
FImageChangeLink := TChangeLink.Create;
|
||
FImageChangeLink.OnChange := ImageListChange;
|
||
|
||
FSortColumn := NoColumn;
|
||
FSortDirection := sdAscending;
|
||
FMainColumn := NoColumn;
|
||
|
||
FDragImage := TVTDragImage.Create(AOwner);
|
||
with FDragImage do
|
||
begin
|
||
Fade := False;
|
||
PostBlendBias := 0;
|
||
PreBlendBias := -50;
|
||
Transparency := 140;
|
||
end;
|
||
|
||
FFixedAreaConstraints := TVTFixedAreaConstraints.Create(Self);
|
||
FFixedAreaConstraints.OnChange := FixedAreaConstraintsChanged;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TVTHeader.Destroy;
|
||
|
||
begin
|
||
FDragImage.Free;
|
||
FFixedAreaConstraints.Free;
|
||
FImageChangeLink.Free;
|
||
FFont.Free;
|
||
FColumns.Clear; // TCollection's Clear method is not virtual, so we have to call our own Clear method manually.
|
||
FColumns.Free;
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.FontChanged(Sender: TObject);
|
||
|
||
begin
|
||
Invalidate(nil);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.GetMainColumn: TColumnIndex;
|
||
|
||
begin
|
||
if FColumns.Count > 0 then
|
||
Result := FMainColumn
|
||
else
|
||
Result := NoColumn;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.GetUseColumns: Boolean;
|
||
|
||
begin
|
||
Result := FColumns.Count > 0;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.IsFontStored: Boolean;
|
||
|
||
begin
|
||
Result := not ParentFont;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetAutoSizeIndex(Value: TColumnIndex);
|
||
|
||
begin
|
||
if FAutoSizeIndex <> Value then
|
||
begin
|
||
FAutoSizeIndex := Value;
|
||
if hoAutoResize in FOptions then
|
||
Columns.AdjustAutoSize(InvalidColumn);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetBackground(Value: TColor);
|
||
|
||
begin
|
||
if FBackground <> Value then
|
||
begin
|
||
FBackground := Value;
|
||
Invalidate(nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetColumns(Value: TVirtualTreeColumns);
|
||
|
||
begin
|
||
FColumns.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetDefaultHeight(Value: Integer);
|
||
|
||
begin
|
||
if Value < FMinHeight then
|
||
Value := FMinHeight;
|
||
if Value > FMaxHeight then
|
||
Value := FMaxHeight;
|
||
|
||
if FHeight = FDefaultHeight then
|
||
SetHeight(Value);
|
||
FDefaultHeight := Value;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetFont(const Value: TFont);
|
||
|
||
begin
|
||
FFont.Assign(Value);
|
||
FParentFont := False;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetHeight(Value: Integer);
|
||
|
||
var
|
||
RelativeMaxHeight,
|
||
RelativeMinHeight,
|
||
EffectiveMaxHeight,
|
||
EffectiveMinHeight: Integer;
|
||
|
||
begin
|
||
if not TreeView.HandleAllocated then
|
||
begin
|
||
FHeight := Value;
|
||
Include(FStates, hsNeedScaling);
|
||
end
|
||
else
|
||
begin
|
||
with FFixedAreaConstraints do
|
||
begin
|
||
RelativeMaxHeight := ((Treeview.ClientHeight + FHeight) * FMaxHeightPercent) div 100;
|
||
RelativeMinHeight := ((Treeview.ClientHeight + FHeight) * FMinHeightPercent) div 100;
|
||
|
||
EffectiveMinHeight := IfThen(FMaxHeightPercent > 0, Min(RelativeMaxHeight, FMinHeight), FMinHeight);
|
||
EffectiveMaxHeight := IfThen(FMinHeightPercent > 0, Max(RelativeMinHeight, FMaxHeight), FMaxHeight);
|
||
|
||
Value := Min(Max(Value, EffectiveMinHeight), EffectiveMaxHeight);
|
||
if FMinHeightPercent > 0 then
|
||
Value := Max(RelativeMinHeight, Value);
|
||
if FMaxHeightPercent > 0 then
|
||
Value := Min(RelativeMaxHeight, Value);
|
||
end;
|
||
|
||
if FHeight <> Value then
|
||
begin
|
||
FHeight := Value;
|
||
if not (csLoading in Treeview.ComponentState) and not (hsScaling in FStates) then
|
||
RecalculateHeader;
|
||
Treeview.Invalidate;
|
||
UpdateWindow(Treeview.Handle);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetImages(const Value: TCustomImageList);
|
||
|
||
begin
|
||
if FImages <> Value then
|
||
begin
|
||
if Assigned(FImages) then
|
||
begin
|
||
FImages.UnRegisterChanges(FImageChangeLink);
|
||
FImages.RemoveFreeNotification(FOwner);
|
||
end;
|
||
FImages := Value;
|
||
if Assigned(FImages) then
|
||
begin
|
||
FImages.RegisterChanges(FImageChangeLink);
|
||
FImages.FreeNotification(FOwner);
|
||
end;
|
||
if not (csLoading in Treeview.ComponentState) then
|
||
Invalidate(nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetMainColumn(Value: TColumnIndex);
|
||
|
||
begin
|
||
if csLoading in Treeview.ComponentState then
|
||
FMainColumn := Value
|
||
else
|
||
begin
|
||
if Value < 0 then
|
||
Value := 0;
|
||
if Value > FColumns.Count - 1 then
|
||
Value := FColumns.Count - 1;
|
||
if Value <> FMainColumn then
|
||
begin
|
||
FMainColumn := Value;
|
||
if Treeview.HandleAllocated then
|
||
begin
|
||
Treeview.MainColumnChanged;
|
||
if not (toExtendedFocus in Treeview.FOptions.FSelectionOptions) then
|
||
Treeview.FocusedColumn := Value;
|
||
Treeview.Invalidate;
|
||
end
|
||
else
|
||
begin
|
||
if not (toExtendedFocus in Treeview.FOptions.FSelectionOptions) then
|
||
Treeview.FFocusedColumn := Value;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetMaxHeight(Value: Integer);
|
||
|
||
begin
|
||
if Value < FMinHeight then
|
||
Value := FMinHeight;
|
||
if not IsWinNT and (Value > 10000) then
|
||
Value := 10000;
|
||
FMaxHeight := Value;
|
||
SetHeight(FHeight);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetMinHeight(Value: Integer);
|
||
|
||
begin
|
||
if Value < 0 then
|
||
Value := 0;
|
||
if Value > FMaxHeight then
|
||
Value := FMaxHeight;
|
||
FMinHeight := Value;
|
||
SetHeight(FHeight);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetOptions(Value: TVTHeaderOptions);
|
||
|
||
var
|
||
ToBeSet,
|
||
ToBeCleared: TVTHeaderOptions;
|
||
|
||
begin
|
||
ToBeSet := Value - FOptions;
|
||
ToBeCleared := FOptions - Value;
|
||
FOptions := Value;
|
||
|
||
if (hoAutoResize in (ToBeSet + ToBeCleared)) and (FColumns.Count > 0) then
|
||
begin
|
||
FColumns.AdjustAutoSize(InvalidColumn);
|
||
if Treeview.HandleAllocated then
|
||
begin
|
||
Treeview.UpdateHorizontalScrollBar(False);
|
||
if hoAutoResize in ToBeSet then
|
||
Treeview.Invalidate;
|
||
end;
|
||
end;
|
||
|
||
if not (csLoading in Treeview.ComponentState) and Treeview.HandleAllocated then
|
||
begin
|
||
if hoVisible in (ToBeSet + ToBeCleared) then
|
||
RecalculateHeader;
|
||
Invalidate(nil);
|
||
Treeview.Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetParentFont(Value: Boolean);
|
||
|
||
begin
|
||
if FParentFont <> Value then
|
||
begin
|
||
FParentFont := Value;
|
||
if FParentFont then
|
||
FFont.Assign(FOwner.Font);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetSortColumn(Value: TColumnIndex);
|
||
|
||
begin
|
||
if csLoading in Treeview.ComponentState then
|
||
FSortColumn := Value
|
||
else
|
||
DoSetSortColumn(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetSortDirection(const Value: TSortDirection);
|
||
|
||
begin
|
||
if Value <> FSortDirection then
|
||
begin
|
||
FSortDirection := Value;
|
||
Invalidate(nil);
|
||
if (toAutoSort in Treeview.FOptions.FAutoOptions) and (Treeview.FUpdateCount = 0) then
|
||
Treeview.SortTree(FSortColumn, FSortDirection, True);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SetStyle(Value: TVTHeaderStyle);
|
||
|
||
begin
|
||
if FStyle <> Value then
|
||
begin
|
||
FStyle := Value;
|
||
if not (csLoading in Treeview.ComponentState) then
|
||
Invalidate(nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.ChangeScale(M, D: Integer);
|
||
|
||
begin
|
||
FFont.Size := MulDiv(FFont.Size, M, D);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DetermineSplitterIndex(const P: TPoint): Boolean;
|
||
|
||
// Tries to find the index of that column whose right border corresponds to P.
|
||
// Result is True if column border was hit (with -3..+5 pixels tolerance).
|
||
// For continuous resizing the current track index and the column's left/right border are set.
|
||
// Note: The hit test is checking from right to left (or left to right in RTL mode) to make enlarging of zero-sized
|
||
// columns possible.
|
||
|
||
var
|
||
I,
|
||
VisibleFixedWidth: Integer;
|
||
SplitPoint: Integer;
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
function IsNearBy(IsFixedCol: Boolean; LeftTolerance, RightTolerance: Integer): Boolean;
|
||
|
||
begin
|
||
if IsFixedCol then
|
||
Result := (P.X < SplitPoint + Treeview.FEffectiveOffsetX + RightTolerance) and (P.X > SplitPoint + Treeview.FEffectiveOffsetX - LeftTolerance)
|
||
else
|
||
Result := (P.X > VisibleFixedWidth) and (P.X < SplitPoint + RightTolerance) and (P.X > SplitPoint - LeftTolerance);
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
begin
|
||
Result := False;
|
||
FColumns.FTrackIndex := NoColumn;
|
||
|
||
VisibleFixedWidth := FColumns.GetVisibleFixedWidth;
|
||
|
||
if FColumns.Count > 0 then
|
||
begin
|
||
if Treeview.UseRightToLeftAlignment then
|
||
begin
|
||
SplitPoint := -Treeview.FEffectiveOffsetX;
|
||
if Integer(Treeview.FRangeX) < Treeview.ClientWidth then
|
||
Inc(SplitPoint, Treeview.ClientWidth - Integer(Treeview.FRangeX));
|
||
|
||
for I := 0 to FColumns.Count - 1 do
|
||
with FColumns, Items[FPositionToIndex[I]] do
|
||
if coVisible in FOptions then
|
||
begin
|
||
if IsNearBy(coFixed in FOptions, 5, 3) then
|
||
begin
|
||
if CanSplitterResize(P, FPositionToIndex[I]) then
|
||
begin
|
||
Result := True;
|
||
FTrackIndex := FPositionToIndex[I];
|
||
|
||
// Keep the right border of this column. This and the current mouse position
|
||
// directly determine the current column width.
|
||
FTrackPoint.X := SplitPoint + IfThen(coFixed in FOptions, Treeview.FEffectiveOffsetX) + FWidth;
|
||
FTrackPoint.Y := P.Y;
|
||
Break;
|
||
end;
|
||
end;
|
||
Inc(SplitPoint, FWidth);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
SplitPoint := -Treeview.FEffectiveOffsetX + Integer(Treeview.FRangeX);
|
||
|
||
for I := FColumns.Count - 1 downto 0 do
|
||
with FColumns, Items[FPositionToIndex[I]] do
|
||
if coVisible in FOptions then
|
||
begin
|
||
if IsNearBy(coFixed in FOptions, 3, 5) then
|
||
begin
|
||
if CanSplitterResize(P, FPositionToIndex[I]) then
|
||
begin
|
||
Result := True;
|
||
FTrackIndex := FPositionToIndex[I];
|
||
|
||
// Keep the left border of this column. This and the current mouse position
|
||
// directly determine the current column width.
|
||
FTrackPoint.X := SplitPoint + IfThen(coFixed in FOptions, Treeview.FEffectiveOffsetX) - FWidth;
|
||
FTrackPoint.Y := P.Y;
|
||
Break;
|
||
end;
|
||
end;
|
||
Dec(SplitPoint, FWidth);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DoAfterAutoFitColumn(Column: TColumnIndex);
|
||
|
||
begin
|
||
if Assigned(TreeView.FOnAfterAutoFitColumn) then
|
||
TreeView.FOnAfterAutoFitColumn(Self, Column);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DoAfterColumnWidthTracking(Column: TColumnIndex);
|
||
|
||
// Tell the application that a column width tracking operation has been finished.
|
||
|
||
begin
|
||
if Assigned(TreeView.FOnAfterColumnWidthTracking) then
|
||
TreeView.FOnAfterColumnWidthTracking(Self, Column);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DoAfterHeightTracking;
|
||
|
||
// Tell the application that a height tracking operation has been finished.
|
||
|
||
begin
|
||
if Assigned(TreeView.FOnAfterHeaderHeightTracking) then
|
||
TreeView.FOnAfterHeaderHeightTracking(Self);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DoBeforeAutoFitColumn(Column: TColumnIndex; SmartAutoFitType: TSmartAutoFitType): Boolean;
|
||
|
||
// Query the application if we may autofit a column.
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(TreeView.FOnBeforeAutoFitColumn) then
|
||
TreeView.FOnBeforeAutoFitColumn(Self, Column, SmartAutoFitType, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DoBeforeColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState);
|
||
|
||
// Tell the a application that a column width tracking operation may begin.
|
||
|
||
begin
|
||
if Assigned(TreeView.FOnBeforeColumnWidthTracking) then
|
||
TreeView.FOnBeforeColumnWidthTracking(Self, Column, Shift);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DoBeforeHeightTracking(Shift: TShiftState);
|
||
|
||
// Tell the application that a height tracking operation may begin.
|
||
|
||
begin
|
||
if Assigned(TreeView.FOnBeforeHeaderHeightTracking) then
|
||
TreeView.FOnBeforeHeaderHeightTracking(Self, Shift);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DoColumnWidthDblClickResize(Column: TColumnIndex; P: TPoint; Shift: TShiftState): Boolean;
|
||
|
||
// Queries the application whether a double click on the column splitter should resize the column.
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(TreeView.FOnColumnWidthDblClickResize) then
|
||
TreeView.FOnColumnWidthDblClickResize(Self, Column, Shift, P, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DoColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(TreeView.FOnColumnWidthTracking) then
|
||
TreeView.FOnColumnWidthTracking(Self, Column, Shift, TrackPoint, P, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DoGetPopupMenu(Column: TColumnIndex; Position: TPoint): TPopupMenu;
|
||
|
||
// Queries the application whether there is a column specific header popup menu.
|
||
|
||
var
|
||
AskParent: Boolean;
|
||
|
||
begin
|
||
Result := nil;
|
||
if Assigned(TreeView.FOnGetPopupMenu) then
|
||
TreeView.FOnGetPopupMenu(TreeView, nil, Column, Position, AskParent, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DoHeightTracking(var P: TPoint; Shift: TShiftState): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(TreeView.FOnHeaderHeightTracking) then
|
||
TreeView.FOnHeaderHeightTracking(Self, P, Shift, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.DoHeightDblClickResize(var P: TPoint; Shift: TShiftState): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(TreeView.FOnHeaderHeightDblClickResize) then
|
||
TreeView.FOnHeaderHeightDblClickResize(Self, P, Shift, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DoSetSortColumn(Value: TColumnIndex);
|
||
|
||
begin
|
||
if Value < NoColumn then
|
||
Value := NoColumn;
|
||
if Value > Columns.Count - 1 then
|
||
Value := Columns.Count - 1;
|
||
if FSortColumn <> Value then
|
||
begin
|
||
if FSortColumn > NoColumn then
|
||
Invalidate(Columns[FSortColumn]);
|
||
FSortColumn := Value;
|
||
if FSortColumn > NoColumn then
|
||
Invalidate(Columns[FSortColumn]);
|
||
if (toAutoSort in Treeview.FOptions.FAutoOptions) and (Treeview.FUpdateCount = 0) then
|
||
Treeview.SortTree(FSortColumn, FSortDirection, True);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.DragTo(const P: TPoint);
|
||
|
||
// Moves the drag image to a new position, which is determined from the passed point P and the previous
|
||
// mouse position.
|
||
|
||
var
|
||
I,
|
||
NewTarget: Integer;
|
||
// optimized drag image move support
|
||
ClientP: TPoint;
|
||
Left,
|
||
Right: Integer;
|
||
NeedRepaint: Boolean; // True if the screen needs an update (changed drop target or drop side)
|
||
|
||
begin
|
||
// Determine new drop target and which side of it is prefered.
|
||
ClientP := Treeview.ScreenToClient(P);
|
||
// Make coordinates relative to (0, 0) of the non-client area.
|
||
Inc(ClientP.Y, FHeight);
|
||
NewTarget := FColumns.ColumnFromPosition(ClientP);
|
||
NeedRepaint := (NewTarget <> InvalidColumn) and (NewTarget <> FColumns.FDropTarget);
|
||
if NewTarget >= 0 then
|
||
begin
|
||
FColumns.GetColumnBounds(NewTarget, Left, Right);
|
||
if (ClientP.X < ((Left + Right) div 2)) <> FColumns.FDropBefore then
|
||
begin
|
||
NeedRepaint := True;
|
||
FColumns.FDropBefore := not FColumns.FDropBefore;
|
||
end;
|
||
end;
|
||
|
||
if NeedRepaint then
|
||
begin
|
||
// Invalidate columns which need a repaint.
|
||
if FColumns.FDropTarget > NoColumn then
|
||
begin
|
||
I := FColumns.FDropTarget;
|
||
FColumns.FDropTarget := NoColumn;
|
||
Invalidate(FColumns.Items[I]);
|
||
end;
|
||
if (NewTarget > NoColumn) and (NewTarget <> FColumns.FDropTarget) then
|
||
begin
|
||
Invalidate(FColumns.Items[NewTarget]);
|
||
FColumns.FDropTarget := NewTarget;
|
||
end;
|
||
end;
|
||
|
||
FDragImage.DragTo(P, NeedRepaint);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.FixedAreaConstraintsChanged(Sender: TObject);
|
||
|
||
// This method gets called when FFixedAreaConstraints is changed.
|
||
|
||
begin
|
||
Include(FStates, hsNeedScaling);
|
||
if Treeview.HandleAllocated then
|
||
RescaleHeader;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.GetColumnsClass: TVirtualTreeColumnsClass;
|
||
|
||
// Returns the class to be used for the actual column implementation. descendants may optionally override this and
|
||
// return their own class.
|
||
|
||
begin
|
||
Result := TVirtualTreeColumns;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.GetOwner: TPersistent;
|
||
|
||
begin
|
||
Result := FOwner;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.GetShiftState: TShiftState;
|
||
|
||
begin
|
||
Result := [];
|
||
if GetKeyState(VK_SHIFT) < 0 then
|
||
Include(Result, ssShift);
|
||
if GetKeyState(VK_LWIN) < 0 then // Mac OS X substitute of ssCtrl
|
||
Include(Result, ssMeta);
|
||
if GetKeyState(VK_CONTROL) < 0 then
|
||
Include(Result, ssCtrl);
|
||
if GetKeyState(VK_MENU) < 0 then
|
||
Include(Result, ssAlt);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.HandleHeaderMouseMove(var Message: TLMMouseMove): Boolean;
|
||
|
||
var
|
||
P: TPoint;
|
||
I: TColumnIndex;
|
||
|
||
begin
|
||
Result := False;
|
||
with Message do
|
||
begin
|
||
P := Point(XPos, YPos);
|
||
if hsColumnWidthTrackPending in FStates then
|
||
begin
|
||
FStates := FStates - [hsColumnWidthTrackPending] + [hsColumnWidthTracking];
|
||
HandleHeaderMouseMove := True;
|
||
Result := 0;
|
||
end
|
||
else if hsHeightTrackPending in FStates then
|
||
begin
|
||
FStates := FStates - [hsHeightTrackPending] + [hsHeightTracking];
|
||
HandleHeaderMouseMove := True;
|
||
Result := 0;
|
||
end
|
||
else
|
||
if hsColumnWidthTracking in FStates then
|
||
begin
|
||
if DoColumnWidthTracking(FColumns.FTrackIndex, GetShiftState, FTrackPoint, P) then
|
||
if Treeview.UseRightToLeftAlignment then
|
||
FColumns[FColumns.FTrackIndex].Width := FTrackPoint.X - XPos
|
||
else
|
||
FColumns[FColumns.FTrackIndex].Width := XPos - FTrackPoint.X;
|
||
HandleHeaderMouseMove := True;
|
||
Result := 0;
|
||
end
|
||
else if hsHeightTracking in FStates then
|
||
begin
|
||
//lclheader
|
||
//fixes setting height
|
||
Dec(P.Y, FHeight);
|
||
if DoHeightTracking(P, GetShiftState) then
|
||
SetHeight(Integer(FHeight) + P.Y);
|
||
HandleHeaderMouseMove := True;
|
||
Result := 0;
|
||
end
|
||
else
|
||
begin
|
||
if hsDragPending in FStates then
|
||
begin
|
||
P := Treeview.ClientToScreen(P);
|
||
// start actual dragging if allowed
|
||
if (hoDrag in FOptions) and Treeview.DoHeaderDragging(FColumns.FDownIndex) then
|
||
begin
|
||
if ((Abs(FDragStart.X - P.X) > DragManager.DragThreshold) or
|
||
(Abs(FDragStart.Y - P.Y) > DragManager.DragThreshold)) then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag], 'HandleHeaderMouseMove - DragIndex: %d - DownIndex: %d',
|
||
[FColumns.FDragIndex, FColumns.FDownIndex]);{$endif}
|
||
I := FColumns.FDownIndex;
|
||
FColumns.FDownIndex := NoColumn;
|
||
FColumns.FHoverIndex := NoColumn;
|
||
if I > NoColumn then
|
||
Invalidate(FColumns[I]);
|
||
//todo: implement drag image under gtk
|
||
PrepareDrag(P, FDragStart);
|
||
FStates := FStates - [hsDragPending] + [hsDragging];
|
||
HandleHeaderMouseMove := True;
|
||
Result := 0;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
if hsDragging in FStates then
|
||
begin
|
||
DragTo(Treeview.ClientToScreen(P));
|
||
HandleHeaderMouseMove := True;
|
||
Result := 0;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.HandleMessage(var Message: TLMessage): Boolean;
|
||
|
||
// The header gets here the opportunity to handle certain messages before they reach the tree. This is important
|
||
// because the tree needs to handle various non-client area messages for the header as well as some dragging/tracking
|
||
// events.
|
||
// By returning True the message will not be handled further, otherwise the message is then dispatched
|
||
// to the proper message handlers.
|
||
|
||
var
|
||
P: TPoint;
|
||
R: TRect;
|
||
I: TColumnIndex;
|
||
OldPosition: Integer;
|
||
HitIndex: TColumnIndex;
|
||
NewCursor: HCURSOR;
|
||
Button: TMouseButton;
|
||
Menu: TPopupMenu;
|
||
IsInHeader,
|
||
IsHSplitterHit,
|
||
IsVSplitterHit: Boolean;
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
function HSPlitterHit: Boolean;
|
||
|
||
var
|
||
NextCol: TColumnIndex;
|
||
|
||
begin
|
||
Result := (hoColumnResize in FOptions) and DetermineSplitterIndex(P);
|
||
if Result and not InHeader(P) then
|
||
begin
|
||
NextCol := FColumns.GetNextVisibleColumn(FColumns.FTrackIndex);
|
||
if not (coFixed in FColumns[FColumns.FTrackIndex].Options) or (NextCol <= NoColumn) or
|
||
(coFixed in FColumns[NextCol].Options) or (P.Y > Integer(Treeview.FRangeY)) then
|
||
Result := False;
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
begin
|
||
Result := False;
|
||
case Message.Msg of
|
||
LM_SIZE:
|
||
begin
|
||
if not (tsWindowCreating in FOwner.FStates) then
|
||
if (hoAutoResize in FOptions) and not (hsAutoSizing in FStates) then
|
||
begin
|
||
FColumns.AdjustAutoSize(InvalidColumn);
|
||
Invalidate(nil);
|
||
end
|
||
else if not (hsScaling in FStates) then
|
||
begin
|
||
RescaleHeader;
|
||
Invalidate(nil);
|
||
end;
|
||
end;
|
||
CM_PARENTFONTCHANGED:
|
||
if FParentFont then
|
||
FFont.Assign(FOwner.Font);
|
||
CM_BIDIMODECHANGED:
|
||
for I := 0 to FColumns.Count - 1 do
|
||
if coParentBiDiMode in FColumns[I].FOptions then
|
||
FColumns[I].ParentBiDiModeChanged;
|
||
LM_MBUTTONDOWN:
|
||
begin
|
||
//lclheader: NCMessages are given in screen coordinates unlike the ordinary
|
||
with TLMMButtonDown(Message) do
|
||
P:= Point(XPos, YPos);
|
||
//P := Treeview.ScreenToClient(Point(XPos, YPos));
|
||
if InHeader(P) then
|
||
FOwner.DoHeaderMouseDown(mbMiddle, GetShiftState, P.X, P.Y + Integer(FHeight));
|
||
end;
|
||
LM_MBUTTONUP:
|
||
begin
|
||
with TLMMButtonUp(Message) do
|
||
P:= Point(XPos, YPos);
|
||
//P := FOwner.ScreenToClient(Point(XPos, YPos));
|
||
if InHeader(P) then
|
||
begin
|
||
FColumns.HandleClick(P, mbMiddle, True, False);
|
||
FOwner.DoHeaderMouseUp(mbMiddle, GetShiftState, P.X, P.Y + Integer(FHeight));
|
||
FColumns.FDownIndex := NoColumn;
|
||
end;
|
||
end;
|
||
LM_LBUTTONDBLCLK,
|
||
LM_MBUTTONDBLCLK,
|
||
LM_RBUTTONDBLCLK:
|
||
begin
|
||
with TLMLButtonDblClk(Message) do
|
||
P := Point(XPos, YPos);
|
||
|
||
if (hoHeightDblClickResize in FOptions) and InHeaderSplitterArea(P) and (FDefaultHeight > 0) then
|
||
begin
|
||
if DoHeightDblClickResize(P, GetShiftState) and (FDefaultHeight > 0) then
|
||
SetHeight(FMinHeight);
|
||
Result := True;
|
||
end
|
||
else if HSplitterHit and (Message.Msg = LM_LBUTTONDBLCLK) and
|
||
(hoDblClickResize in FOptions) and (FColumns.FTrackIndex > NoColumn) then
|
||
begin
|
||
// If the click was on a splitter then resize column to smallest width.
|
||
if DoColumnWidthDblClickResize(FColumns.FTrackIndex, P, GetShiftState) then
|
||
AutoFitColumns(True, smaUseColumnOption, Columns.FTrackIndex, Columns.FTrackIndex);
|
||
Message.Result := 0;
|
||
Result := True;
|
||
end
|
||
else if InHeader(P) and (Message.Msg <> LM_LBUTTONDBLCLK) then
|
||
begin
|
||
case Message.Msg of
|
||
LM_MBUTTONDBLCLK:
|
||
Button := mbMiddle;
|
||
LM_RBUTTONDBLCLK:
|
||
Button := mbRight;
|
||
else
|
||
// WM_NCLBUTTONDBLCLK
|
||
Button := mbLeft;
|
||
end;
|
||
FColumns.HandleClick(P, Button, True, True);
|
||
end;
|
||
end;
|
||
// The "hot" area of the headers horizontal splitter is partly within the client area of the the tree, so we need
|
||
// to handle WM_LBUTTONDOWN here, too.
|
||
LM_LBUTTONDOWN:
|
||
begin
|
||
if csDesigning in Treeview.ComponentState then
|
||
Exit;
|
||
|
||
Application.CancelHint;
|
||
|
||
// make sure no auto scrolling is active...
|
||
KillTimer(Treeview.Handle, ScrollTimer);
|
||
Treeview.DoStateChange([], [tsScrollPending, tsScrolling]);
|
||
// ... pending editing is cancelled (actual editing remains active)
|
||
KillTimer(Treeview.Handle, EditTimer);
|
||
Treeview.DoStateChange([], [tsEditPending]);
|
||
|
||
with TLMLButtonDown(Message) do
|
||
begin
|
||
// want the drag start point in screen coordinates
|
||
P := Point(XPos, YPos);
|
||
FDragStart := Treeview.ClientToScreen(P);
|
||
//FDragStart := Point(XPos, YPos);
|
||
//P := Treeview.ScreenToClient(FDragStart);
|
||
end;
|
||
|
||
IsInHeader := InHeader(P);
|
||
IsVSplitterHit := (hoHeightResize in FOptions) and InHeaderSplitterArea(P);
|
||
IsHSplitterHit := HSplitterHit;
|
||
|
||
if IsVSplitterHit or IsHSplitterHit then
|
||
begin
|
||
FTrackStart := P;
|
||
FColumns.FHoverIndex := NoColumn;
|
||
if IsVSplitterHit then
|
||
begin
|
||
DoBeforeHeightTracking(GetShiftState);
|
||
Include(FStates, hsHeightTrackPending)
|
||
end
|
||
else
|
||
begin
|
||
DoBeforeColumnWidthTracking(FColumns.FTrackIndex, GetShiftState);
|
||
Include(FStates, hsColumnWidthTrackPending);
|
||
end;
|
||
|
||
SetCapture(Treeview.Handle);
|
||
Result := True;
|
||
Message.Result := 0;
|
||
end
|
||
else if IsInHeader then
|
||
begin
|
||
HitIndex := Columns.AdjustDownColumn(P);
|
||
if (hoDrag in FOptions) and (HitIndex > NoColumn) and (coDraggable in FColumns[HitIndex].FOptions) then
|
||
begin
|
||
// Show potential drag operation.
|
||
// Disabled columns do not start a drag operation because they can't be clicked.
|
||
Include(FStates, hsDragPending);
|
||
SetCapture(Treeview.Handle);
|
||
Result := True;
|
||
Message.Result := 0;
|
||
end;
|
||
end;
|
||
|
||
// This is a good opportunity to notify the application.
|
||
if IsInHeader then
|
||
FOwner.DoHeaderMouseDown(mbLeft, GetShiftState, P.X, P.Y + Integer(FHeight));
|
||
end;
|
||
LM_RBUTTONDOWN:
|
||
begin
|
||
with TLMRButtonDown(Message) do
|
||
P:=Point(XPos,YPos);
|
||
//P := FOwner.ScreenToClient(Point(XPos, YPos));
|
||
if InHeader(P) then
|
||
FOwner.DoHeaderMouseDown(mbRight, GetShiftState, P.X, P.Y + Integer(FHeight));
|
||
end;
|
||
LM_RBUTTONUP:
|
||
if not (csDesigning in FOwner.ComponentState) then
|
||
with TLMRButtonUp(Message) do
|
||
begin
|
||
Application.CancelHint;
|
||
|
||
P := Point(XPos,YPos);
|
||
//P := FOwner.ScreenToClient(Point(XPos, YPos));
|
||
if InHeader(P) then
|
||
begin
|
||
FColumns.HandleClick(P, mbRight, True, False);
|
||
FOwner.DoHeaderMouseUp(mbRight, GetShiftState, P.X, P.Y + Integer(FHeight));
|
||
FColumns.FDownIndex := NoColumn;
|
||
FColumns.FTrackIndex := NoColumn;
|
||
|
||
Menu := FPopupMenu;
|
||
if not Assigned(Menu) then
|
||
Menu := DoGetPopupMenu(FColumns.ColumnFromPosition(Point(P.X, P.Y + Integer(FHeight))), P);
|
||
|
||
// Trigger header popup if there's one.
|
||
if Assigned(Menu) then
|
||
begin
|
||
KillTimer(Treeview.Handle, ScrollTimer);
|
||
FColumns.FHoverIndex := NoColumn;
|
||
Treeview.DoStateChange([], [tsScrollPending, tsScrolling]);
|
||
Menu.PopupComponent := Treeview;
|
||
P := Treeview.ClientToScreen(Point(XPos, YPos));
|
||
Menu.Popup(P.X, P.Y);
|
||
HandleMessage := True;
|
||
end;
|
||
end;
|
||
end;
|
||
// When the tree window has an active mouse capture then we only get "client-area" messages.
|
||
LM_LBUTTONUP:
|
||
begin
|
||
Application.CancelHint;
|
||
|
||
if FStates <> [] then
|
||
begin
|
||
ReleaseCapture;
|
||
//lcl
|
||
if hsColumnWidthTracking in FStates then
|
||
begin
|
||
if not InHeader(SmallPointToPoint(TLMLButtonUp(Message).Pos)) then
|
||
TreeView.Cursor := crDefault;
|
||
end;
|
||
if hsDragging in FStates then
|
||
begin
|
||
// successfull dragging moves columns
|
||
with TLMLButtonUp(Message) do
|
||
P := Treeview.ClientToScreen(Point(XPos, YPos));
|
||
GetWindowRect(Treeview.Handle, R);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'Header - EndDrag / R',R);{$endif}
|
||
with FColumns do
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'Header - EndDrag / FDropTarget: %d FDragIndex: %d FDragIndexPosition: %d',
|
||
[FDropTarget, FDragIndex, FColumns[FDragIndex].Position]);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'Header - EndDrag / FDropBefore', FColumns.FDropBefore);{$endif}
|
||
FDragImage.EndDrag;
|
||
if (FDropTarget > -1) and (FDropTarget <> FDragIndex) and PtInRect(R, P) then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'Header - EndDrag / FDropTargetPosition', FColumns[FDropTarget].Position);{$endif}
|
||
OldPosition := FColumns[FDragIndex].Position;
|
||
if FColumns.FDropBefore then
|
||
begin
|
||
if FColumns[FDragIndex].Position < FColumns[FDropTarget].Position then
|
||
FColumns[FDragIndex].Position := Max(0, FColumns[FDropTarget].Position - 1)
|
||
else
|
||
FColumns[FDragIndex].Position := FColumns[FDropTarget].Position;
|
||
end
|
||
else
|
||
begin
|
||
if FColumns[FDragIndex].Position < FColumns[FDropTarget].Position then
|
||
FColumns[FDragIndex].Position := FColumns[FDropTarget].Position
|
||
else
|
||
FColumns[FDragIndex].Position := FColumns[FDropTarget].Position + 1;
|
||
end;
|
||
Treeview.DoHeaderDragged(FDragIndex, OldPosition);
|
||
end
|
||
else
|
||
Treeview.DoHeaderDraggedOut(FDragIndex, P);
|
||
FDropTarget := NoColumn;
|
||
end;
|
||
Invalidate(nil);
|
||
end;
|
||
Result := True;
|
||
Message.Result := 0;
|
||
end;
|
||
|
||
case Message.Msg of
|
||
LM_LBUTTONUP:
|
||
with TLMLButtonUp(Message) do
|
||
begin
|
||
if FColumns.FDownIndex > NoColumn then
|
||
FColumns.HandleClick(Point(XPos, YPos), mbLeft, False, False);
|
||
if FStates <> [] then
|
||
FOwner.DoHeaderMouseUp(mbLeft, KeysToShiftState(Keys), XPos, YPos);
|
||
end;
|
||
//todo: there's a difference here
|
||
{
|
||
LM_NCLBUTTONUP:
|
||
with TLMLButtonUp(Message) do
|
||
begin
|
||
P := FOwner.ScreenToClient(Point(XPos, YPos));
|
||
FColumns.HandleClick(P, mbLeft, False, False);
|
||
FOwner.DoHeaderMouseUp(mbLeft, GetShiftState, P.X, P.Y + Integer(FHeight));
|
||
end;
|
||
}
|
||
end;
|
||
|
||
if FColumns.FTrackIndex > NoColumn then
|
||
begin
|
||
if hsColumnWidthTracking in FStates then
|
||
DoAfterColumnWidthTracking(FColumns.FTrackIndex);
|
||
Invalidate(Columns[FColumns.FTrackIndex]);
|
||
FColumns.FTrackIndex := NoColumn;
|
||
end;
|
||
if FColumns.FDownIndex > NoColumn then
|
||
begin
|
||
Invalidate(Columns[FColumns.FDownIndex]);
|
||
FColumns.FDownIndex := NoColumn;
|
||
end;
|
||
if hsHeightTracking in FStates then
|
||
DoAfterHeightTracking;
|
||
|
||
FStates := FStates - [hsDragging, hsDragPending,
|
||
hsColumnWidthTracking, hsColumnWidthTrackPending,
|
||
hsHeightTracking, hsHeightTrackPending];
|
||
end;
|
||
// hovering, mouse leave detection
|
||
CM_MOUSELEAVE:
|
||
with FColumns do
|
||
begin
|
||
if FHoverIndex > NoColumn then
|
||
Invalidate(Items[FHoverIndex]);
|
||
FHoverIndex := NoColumn;
|
||
FClickIndex := NoColumn;
|
||
FDownIndex := NoColumn;
|
||
end;
|
||
//todo: see the difference to below
|
||
LM_MOUSEMOVE:
|
||
with TLMMouseMove(Message), FColumns do
|
||
begin
|
||
//lcl
|
||
HandleMessage := HandleHeaderMouseMove(TLMMouseMove(Message));
|
||
|
||
P := Point(XPos,YPos);
|
||
//P := Treeview.ScreenToClient(Point(XPos, YPos));
|
||
IsInHeader := InHeader(P);
|
||
if IsInHeader then
|
||
begin
|
||
Treeview.DoHeaderMouseMove(GetShiftState, P.X, P.Y);
|
||
if ((AdjustHoverColumn(P)) or ((FDownIndex > NoColumn) and (FHoverIndex <> FDownIndex))) then
|
||
begin
|
||
Invalidate(nil);
|
||
// todo: under lcl, the hint is show even if HintMouseMessage is not implemented
|
||
// Is it necessary here?
|
||
// use Delphi's internal hint handling for header hints too
|
||
if hoShowHint in FOptions then
|
||
begin
|
||
// client coordinates!
|
||
XPos := P.x;
|
||
YPos := P.y;
|
||
Application.HintMouseMessage(Treeview, Message);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
if FHoverIndex > NoColumn then
|
||
Invalidate(Items[FHoverIndex]);
|
||
FHoverIndex := NoColumn;
|
||
FClickIndex := NoColumn;
|
||
FDownIndex := NoColumn;
|
||
end;
|
||
//Adjust Cursor
|
||
if not (csDesigning in FOwner.ComponentState) and (FStates = []) then
|
||
begin
|
||
//todo: see a way to store the user defined cursor.
|
||
IsHSplitterHit := IsInHeader and HSplitterHit;
|
||
IsVSplitterHit := (hoHeightResize in FOptions) and InHeaderSplitterArea(P);
|
||
|
||
if IsVSplitterHit or IsHSplitterHit then
|
||
begin
|
||
NewCursor := crDefault;
|
||
if IsVSplitterHit and (hoHeightResize in FOptions) then
|
||
NewCursor := crVertSplit
|
||
else if IsHSplitterHit then
|
||
NewCursor := crHeaderSplit;
|
||
|
||
Treeview.DoGetHeaderCursor(NewCursor);
|
||
if NewCursor <> crDefault then
|
||
begin
|
||
Treeview.Cursor := NewCursor;
|
||
HandleMessage := True;
|
||
Message.Result := 1;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
Message.Result := 1;
|
||
HandleMessage := True;
|
||
end;
|
||
end;
|
||
LM_KEYDOWN,
|
||
LM_KILLFOCUS:
|
||
if (Message.Msg = LM_KILLFOCUS) or
|
||
(TLMKeyDown(Message).CharCode = VK_ESCAPE) then
|
||
begin
|
||
if hsDragging in FStates then
|
||
begin
|
||
ReleaseCapture;
|
||
FDragImage.EndDrag;
|
||
Exclude(FStates, hsDragging);
|
||
FColumns.FDropTarget := NoColumn;
|
||
Invalidate(nil);
|
||
Result := True;
|
||
Message.Result := 0;
|
||
end
|
||
else
|
||
begin
|
||
if [hsColumnWidthTracking, hsHeightTracking] * FStates <> [] then
|
||
begin
|
||
ReleaseCapture;
|
||
if hsColumnWidthTracking in FStates then
|
||
DoAfterColumnWidthTracking(FColumns.FTrackIndex);
|
||
if hsHeightTracking in FStates then
|
||
DoAfterHeightTracking;
|
||
Result := True;
|
||
Message.Result := 0;
|
||
end;
|
||
|
||
FStates := FStates - [hsColumnWidthTracking, hsColumnWidthTrackPending,
|
||
hsHeightTracking, hsHeightTrackPending];
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.ImageListChange(Sender: TObject);
|
||
|
||
begin
|
||
if not (csDestroying in Treeview.ComponentState) then
|
||
Invalidate(nil);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.PrepareDrag(P, Start: TPoint);
|
||
|
||
// Initializes dragging of the header, P is the current mouse postion and Start the initial mouse position.
|
||
|
||
var
|
||
HeaderR: TRect;
|
||
Image: TBitmap;
|
||
ImagePos: TPoint;
|
||
DragColumn: TVirtualTreeColumn;
|
||
|
||
begin
|
||
// Determine initial position of drag image (screen coordinates).
|
||
FColumns.FDropTarget := NoColumn;
|
||
Start := Treeview.ScreenToClient(Start);
|
||
FColumns.FDragIndex := FColumns.ColumnFromPosition(Start);
|
||
DragColumn := FColumns[FColumns.FDragIndex];
|
||
|
||
HeaderR := Treeview.FHeaderRect;
|
||
|
||
// Set right border of the header rectangle to the maximum extent.
|
||
// Adjust top border too, it is already covered elsewhere.
|
||
HeaderR.Right := FColumns.TotalWidth;
|
||
HeaderR.Top := 0;
|
||
|
||
// Take out influence of border since we need a seamless drag image.
|
||
OffsetRect(HeaderR, -Treeview.BorderWidth, -Treeview.BorderWidth);
|
||
if Treeview.UseRightToLeftAlignment then
|
||
Dec(HeaderR.Left, Treeview.ComputeRTLOffset);
|
||
|
||
Image := TBitmap.Create;
|
||
with Image do
|
||
try
|
||
PixelFormat := pf32Bit;
|
||
Width := DragColumn.Width;
|
||
Height := FHeight;
|
||
|
||
// Erase the entire image with the color key value, for the case not everything
|
||
// in the image is covered by the header image.
|
||
Canvas.Brush.Color := clBtnFace;
|
||
Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
|
||
// Now move the window origin of bitmap DC so that although the entire header is painted
|
||
// only the dragged column becomes visible.
|
||
SetWindowOrgEx(Canvas.Handle, DragColumn.FLeft, 0, nil);
|
||
FColumns.PaintHeader(Canvas.Handle, HeaderR, 0);
|
||
SetWindowOrgEx(Canvas.Handle, 0, 0, nil);
|
||
|
||
if Treeview.UseRightToLeftAlignment then
|
||
ImagePos := Treeview.ClientToScreen(Point(DragColumn.Left + Treeview.ComputeRTLOffset(True), 0))
|
||
else
|
||
ImagePos := Treeview.ClientToScreen(Point(DragColumn.Left, 0));
|
||
//lclheader
|
||
// Column rectangles are given in local window coordinates not client coordinates.
|
||
// The above statement is not valid under LCL
|
||
//Dec(ImagePos.Y, FHeight);
|
||
|
||
if hoRestrictDrag in FOptions then
|
||
FDragImage.MoveRestriction := dmrHorizontalOnly
|
||
else
|
||
FDragImage.MoveRestriction := dmrNone;
|
||
FDragImage.PrepareDrag(Image, ImagePos, P, nil);
|
||
FDragImage.ShowDragImage;
|
||
finally
|
||
Image.Free;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.RecalculateHeader;
|
||
|
||
// Initiate a recalculation of the non-client area of the owner tree.
|
||
|
||
begin
|
||
if Treeview.HandleAllocated then
|
||
begin
|
||
Treeview.UpdateHeaderRect;
|
||
//lclheader
|
||
//not necessary since header is draw inside client area
|
||
//SetWindowPos(Treeview.Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOACTIVATE or SWP_NOOWNERZORDER or
|
||
// SWP_NOSENDCHANGING or SWP_NOSIZE or SWP_NOZORDER);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.RescaleHeader;
|
||
|
||
// Rescale the fixed elements (fixed columns, header itself) to FixedAreaConstraints.
|
||
|
||
var
|
||
FixedWidth,
|
||
MaxFixedWidth,
|
||
MinFixedWidth: Integer;
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
procedure ComputeConstraints;
|
||
|
||
var
|
||
I: TColumnIndex;
|
||
|
||
begin
|
||
with FColumns do
|
||
begin
|
||
I := GetFirstVisibleColumn;
|
||
while I > NoColumn do
|
||
begin
|
||
if (coFixed in FColumns[I].Options) and (FColumns[I].Width < FColumns[I].MinWidth) then
|
||
FColumns[I].FWidth := FColumns[I].FMinWidth;
|
||
I := GetNextVisibleColumn(I);
|
||
end;
|
||
FixedWidth := GetVisibleFixedWidth;
|
||
end;
|
||
|
||
with FFixedAreaConstraints do
|
||
begin
|
||
MinFixedWidth := (TreeView.ClientWidth * FMinWidthPercent) div 100;
|
||
MaxFixedWidth := (TreeView.ClientWidth * FMaxWidthPercent) div 100;
|
||
end;
|
||
end;
|
||
|
||
//----------- end local function --------------------------------------------
|
||
|
||
begin
|
||
if ([csLoading, csReading, csWriting, csDestroying] * Treeview.ComponentState = []) and not
|
||
(hsLoading in FStates) and Treeview.HandleAllocated then
|
||
begin
|
||
Include(FStates, hsScaling);
|
||
|
||
SetHeight(FHeight);
|
||
RecalculateHeader;
|
||
|
||
with FFixedAreaConstraints do
|
||
if (FMinHeightPercent > 0) or (FMaxHeightPercent > 0) then
|
||
begin
|
||
ComputeConstraints;
|
||
|
||
with FColumns do
|
||
if (FMaxWidthPercent > 0) and (FixedWidth > MaxFixedWidth) then
|
||
ResizeColumns(MaxFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed])
|
||
else if (FMinWidthPercent > 0) and (FixedWidth < MinFixedWidth) then
|
||
ResizeColumns(MinFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed]);
|
||
|
||
FColumns.UpdatePositions;
|
||
end;
|
||
|
||
Exclude(FStates, hsScaling);
|
||
Exclude(FStates, hsNeedScaling);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.UpdateMainColumn;
|
||
|
||
// Called once the load process of the owner tree is done.
|
||
|
||
begin
|
||
if FMainColumn < 0 then
|
||
FMainColumn := 0;
|
||
if FMainColumn > FColumns.Count - 1 then
|
||
FMainColumn := FColumns.Count - 1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.UpdateSpringColumns;
|
||
|
||
var
|
||
I: TColumnIndex;
|
||
SpringCount: Integer;
|
||
Sign: Integer;
|
||
ChangeBy: Single;
|
||
Difference: Single;
|
||
NewAccumulator: Single;
|
||
|
||
begin
|
||
with TreeView do
|
||
ChangeBy := FHeaderRect.Right - FHeaderRect.Left - FLastWidth;
|
||
if (hoAutoSpring in FOptions) and (FLastWidth <> 0) and (ChangeBy <> 0) then
|
||
begin
|
||
// Stay positive if downsizing the control.
|
||
if ChangeBy < 0 then
|
||
Sign := -1
|
||
else
|
||
Sign := 1;
|
||
ChangeBy := Abs(ChangeBy);
|
||
// Count how many columns have spring enabled.
|
||
SpringCount := 0;
|
||
for I := 0 to FColumns.Count-1 do
|
||
if [coVisible, coAutoSpring] * FColumns[I].FOptions = [coVisible, coAutoSpring] then
|
||
Inc(SpringCount);
|
||
if SpringCount > 0 then
|
||
begin
|
||
// Calculate the size to add/sub to each columns.
|
||
Difference := ChangeBy / SpringCount;
|
||
// Adjust the column's size accumulators and resize if the result is >= 1.
|
||
for I := 0 to FColumns.Count - 1 do
|
||
if [coVisible, coAutoSpring] * FColumns[I].FOptions = [coVisible, coAutoSpring] then
|
||
begin
|
||
// Sum up rest changes from previous runs and the amount from this one and store it in the
|
||
// column. If there is at least one pixel difference then do a resize and reset the accumulator.
|
||
NewAccumulator := FColumns[I].FSpringRest + Difference;
|
||
// Set new width if at least one pixel size difference is reached.
|
||
if NewAccumulator >= 1 then
|
||
FColumns[I].SetWidth(FColumns[I].FWidth + (Trunc(NewAccumulator) * Sign));
|
||
FColumns[I].FSpringRest := Frac(NewAccumulator);
|
||
|
||
// Keep track of the size count.
|
||
ChangeBy := ChangeBy - Difference;
|
||
// Exit loop if resize count drops below freezing point.
|
||
if ChangeBy < 0 then
|
||
Break;
|
||
end;
|
||
end;
|
||
end;
|
||
with TreeView do
|
||
FLastWidth := FHeaderRect.Right - FHeaderRect.Left;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.AllowFocus(ColumnIndex: TColumnIndex): Boolean;
|
||
begin
|
||
Result := False;
|
||
if not FColumns.IsValidColumn(ColumnIndex) then exit; // Just in case.
|
||
|
||
Result := (coAllowFocus in FColumns[ColumnIndex].Options);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.Assign(Source: TPersistent);
|
||
|
||
begin
|
||
if Source is TVTHeader then
|
||
begin
|
||
AutoSizeIndex := TVTHeader(Source).AutoSizeIndex;
|
||
Background := TVTHeader(Source).Background;
|
||
Columns := TVTHeader(Source).Columns;
|
||
Font := TVTHeader(Source).Font;
|
||
FixedAreaConstraints.Assign(TVTHeader(Source).FixedAreaConstraints);
|
||
Height := TVTHeader(Source).Height;
|
||
Images := TVTHeader(Source).Images;
|
||
MainColumn := TVTHeader(Source).MainColumn;
|
||
Options := TVTHeader(Source).Options;
|
||
ParentFont := TVTHeader(Source).ParentFont;
|
||
PopupMenu := TVTHeader(Source).PopupMenu;
|
||
SortColumn := TVTHeader(Source).SortColumn;
|
||
SortDirection := TVTHeader(Source).SortDirection;
|
||
Style := TVTHeader(Source).Style;
|
||
|
||
RescaleHeader;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.AutoFitColumns(Animated: Boolean = True; SmartAutoFitType: TSmartAutoFitType = smaUseColumnOption;
|
||
RangeStartCol: Integer = NoColumn; RangeEndCol: Integer = NoColumn);
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
function GetUseSmartColumnWidth(ColumnIndex: TColumnIndex): Boolean;
|
||
|
||
begin
|
||
Result := False;
|
||
case SmartAutoFitType of
|
||
smaAllColumns:
|
||
Result := True;
|
||
smaNoColumn:
|
||
Result := False;
|
||
smaUseColumnOption:
|
||
Result := coSmartResize in FColumns.Items[ColumnIndex].FOptions;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------
|
||
|
||
procedure DoAutoFitColumn(Column: TColumnIndex);
|
||
|
||
begin
|
||
with FColumns do
|
||
if ([coResizable, coVisible] * Items[FPositionToIndex[Column]].FOptions = [coResizable, coVisible]) and
|
||
DoBeforeAutoFitColumn(FPositionToIndex[Column], SmartAutoFitType) and not TreeView.OperationCanceled then
|
||
begin
|
||
if Animated then
|
||
AnimatedResize(FPositionToIndex[Column], Treeview.GetMaxColumnWidth(FPositionToIndex[Column],
|
||
GetUseSmartColumnWidth(FPositionToIndex[Column])))
|
||
else
|
||
FColumns[FPositionToIndex[Column]].Width := Treeview.GetMaxColumnWidth(FPositionToIndex[Column],
|
||
GetUseSmartColumnWidth(FPositionToIndex[Column]));
|
||
|
||
DoAfterAutoFitColumn(FPositionToIndex[Column]);
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local functions ----------------------------------------
|
||
|
||
var
|
||
I: Integer;
|
||
StartCol,
|
||
EndCol: Integer;
|
||
|
||
begin
|
||
StartCol := Max(NoColumn + 1, RangeStartCol);
|
||
|
||
if RangeEndCol <= NoColumn then
|
||
EndCol := FColumns.Count - 1
|
||
else
|
||
EndCol := Min(RangeEndCol, FColumns.Count - 1);
|
||
|
||
if StartCol > EndCol then
|
||
Exit; // nothing to do
|
||
|
||
TreeView.BeginOperation;
|
||
if Assigned(TreeView.FOnBeforeAutoFitColumns) then
|
||
TreeView.FOnBeforeAutoFitColumns(Self, SmartAutoFitType);
|
||
|
||
for I := StartCol to EndCol do
|
||
DoAutoFitColumn(I);
|
||
|
||
if Assigned(TreeView.FOnAfterAutoFitColumns) then
|
||
TreeView.FOnAfterAutoFitColumns(Self);
|
||
Treeview.EndOperation;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.InHeader(const P: TPoint): Boolean;
|
||
|
||
// Determines whether the given point (client coordinates!) is within the header rectangle (non-client coordinates).
|
||
|
||
begin
|
||
//lclheader
|
||
//todo: remove this function and use PtInRect directly ??
|
||
Result := PtInRect(TreeView.FHeaderRect, P);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.InHeaderSplitterArea(P: TPoint): Boolean;
|
||
|
||
// Determines whether the given point (client coordinates!) hits the horizontal splitter area of the header.
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
Result := (hoVisible in FOptions);
|
||
if Result then
|
||
begin
|
||
R := Treeview.FHeaderRect;
|
||
R.Top := R.Bottom - 2;
|
||
Inc(R.Bottom, 2);
|
||
Result := PtInRect(R, P);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.Invalidate(Column: TVirtualTreeColumn; ExpandToBorder: Boolean = False);
|
||
|
||
// Because the header is in the non-client area of the tree it needs some special handling in order to initiate its
|
||
// repainting.
|
||
// If ExpandToBorder is True then not only the given column but everything or (depending on hoFullRepaintOnResize) just
|
||
// everything to its right (or left, in RTL mode) will be invalidated (useful for resizing). This makes only sense when
|
||
// a column is given.
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
if (hoVisible in FOptions) and Treeview.HandleAllocated then
|
||
with Treeview do
|
||
begin
|
||
if Column = nil then
|
||
R := FHeaderRect
|
||
else
|
||
begin
|
||
R := Column.GetRect;
|
||
if not (coFixed in Column.Options) then
|
||
OffsetRect(R, -FEffectiveOffsetX, 0);
|
||
if UseRightToLeftAlignment then
|
||
OffsetRect(R, ComputeRTLOffset, 0);
|
||
if ExpandToBorder then
|
||
begin
|
||
if (hoFullRepaintOnResize in FHeader.FOptions) then
|
||
begin
|
||
R.Left := FHeaderRect.Left;
|
||
R.Right := FHeaderRect.Right;
|
||
end else
|
||
begin
|
||
if UseRightToLeftAlignment then
|
||
R.Left := FHeaderRect.Left
|
||
else
|
||
R.Right := FHeaderRect.Right;
|
||
end;
|
||
end;
|
||
end;
|
||
//lclheader
|
||
RedrawWindow(Handle, @R, 0, RDW_FRAME or RDW_INVALIDATE or RDW_VALIDATE or RDW_NOINTERNALPAINT or
|
||
RDW_NOERASE or RDW_NOCHILDREN);
|
||
|
||
{
|
||
// Current position of the owner in screen coordinates.
|
||
GetWindowRect(Handle, RW);
|
||
|
||
// Consider the header within this rectangle.
|
||
OffsetRect(R, RW.Left, RW.Top);
|
||
|
||
// Expressed in client coordinates (because RedrawWindow wants them so, they will actually become negative).
|
||
MapWindowPoints(0, Handle, R, 2);
|
||
RedrawWindow(Handle, @R, 0, RDW_FRAME or RDW_INVALIDATE or RDW_VALIDATE or RDW_NOINTERNALPAINT or
|
||
RDW_NOERASE or RDW_NOCHILDREN);
|
||
}
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.LoadFromStream(const Stream: TStream);
|
||
|
||
// restore the state of the header from the given stream
|
||
|
||
var
|
||
Dummy,
|
||
Version: Integer;
|
||
S: AnsiString;
|
||
OldOptions: TVTHeaderOptions;
|
||
|
||
begin
|
||
Include(FStates, hsLoading);
|
||
with Stream do
|
||
try
|
||
// Switch off all options which could influence loading the columns (they will be later set again).
|
||
OldOptions := FOptions;
|
||
FOptions := [];
|
||
|
||
// Determine whether the stream contains data without a version number.
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
if Dummy > -1 then
|
||
begin
|
||
// Seek back to undo the read operation if this is an old stream format.
|
||
Seek(-SizeOf(Dummy), soFromCurrent);
|
||
Version := -1;
|
||
end
|
||
else // Read version number if this is a "versionized" format.
|
||
ReadBuffer(Version, SizeOf(Version));
|
||
Columns.LoadFromStream(Stream, Version);
|
||
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
AutoSizeIndex := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Background := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Height := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
FOptions := OldOptions;
|
||
Options := TVTHeaderOptions(Word(Dummy));
|
||
// PopupMenu is neither saved nor restored
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Style := TVTHeaderStyle(Dummy);
|
||
// TFont has no own save routine so we do it manually
|
||
with Font do
|
||
begin
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Color := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Height := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
SetLength(S, Dummy);
|
||
ReadBuffer(PAnsiChar(S)^, Dummy);
|
||
Name := S;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Pitch := TFontPitch(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
Style := TFontStyles(LongWord(Dummy));
|
||
end;
|
||
// LCL port started with header stream version 6 so no need to do the check here
|
||
// Read data introduced by stream version 1+.
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
MainColumn := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
SortColumn := Dummy;
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
SortDirection := TSortDirection(Byte(Dummy));
|
||
|
||
// Read data introduced by stream version 5+.
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
ParentFont := Boolean(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
FMaxHeight := Integer(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
FMinHeight := Integer(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
FDefaultHeight := Integer(Dummy);
|
||
with FFixedAreaConstraints do
|
||
begin
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
FMaxHeightPercent := TVTConstraintPercent(Dummy);
|
||
ReadBuffer(Dummy, Sizeof(Dummy));
|
||
FMaxWidthPercent := TVTConstraintPercent(Dummy);
|
||
ReadBuffer(Dummy, SizeOf(Dummy));
|
||
FMinHeightPercent := TVTConstraintPercent(Dummy);
|
||
ReadBuffer(Dummy, Sizeof(Dummy));
|
||
FMinWidthPercent := TVTConstraintPercent(Dummy);
|
||
end
|
||
finally
|
||
Exclude(FStates, hsLoading);
|
||
Treeview.DoColumnResize(NoColumn);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTHeader.ResizeColumns(ChangeBy: Integer; RangeStartCol: TColumnIndex; RangeEndCol: TColumnIndex;
|
||
Options: TVTColumnOptions = [coVisible]): Integer;
|
||
|
||
// Distribute the given width change to a range of columns. A 'fair' way is used to distribute ChangeBy to the columns,
|
||
// while ensuring that everything that can be distributed will be distributed.
|
||
|
||
var
|
||
Start,
|
||
I: TColumnIndex;
|
||
ColCount,
|
||
ToGo,
|
||
Sign,
|
||
Rest,
|
||
MaxDelta,
|
||
Difference: Integer;
|
||
Constraints,
|
||
Widths: Array of Integer;
|
||
BonusPixel: Boolean;
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
function IsResizable (Column: TColumnIndex): Boolean;
|
||
|
||
begin
|
||
if BonusPixel then
|
||
Result := Widths[Column - RangeStartCol] < Constraints[Column - RangeStartCol]
|
||
else
|
||
Result := Widths[Column - RangeStartCol] > Constraints[Column - RangeStartCol];
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure IncDelta(Column: TColumnIndex);
|
||
|
||
begin
|
||
if BonusPixel then
|
||
Inc(MaxDelta, FColumns[Column].MaxWidth - Widths[Column - RangeStartCol])
|
||
else
|
||
Inc(MaxDelta, Widths[Column - RangeStartCol] - Constraints[Column - RangeStartCol]);
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
function ChangeWidth(Column: TColumnIndex; Delta: Integer): Integer;
|
||
|
||
begin
|
||
if Delta > 0 then
|
||
Delta := Min(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol])
|
||
else
|
||
Delta := Max(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol]);
|
||
|
||
Inc(Widths[Column - RangeStartCol], Delta);
|
||
Dec(ToGo, Abs(Delta));
|
||
Result := Abs(Delta);
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
function ReduceConstraints: Boolean;
|
||
|
||
var
|
||
MaxWidth,
|
||
MaxReserveCol,
|
||
Column: TColumnIndex;
|
||
|
||
begin
|
||
Result := True;
|
||
if not (hsScaling in FStates) or BonusPixel then
|
||
Exit;
|
||
|
||
MaxWidth := 0;
|
||
MaxReserveCol := NoColumn;
|
||
for Column := RangeStartCol to RangeEndCol do
|
||
if (Options * FColumns[Column].FOptions = Options) and
|
||
(FColumns[Column].FWidth > MaxWidth) then
|
||
begin
|
||
MaxWidth := Widths[Column - RangeStartCol];
|
||
MaxReserveCol := Column;
|
||
end;
|
||
|
||
if (MaxReserveCol <= NoColumn) or (Constraints[MaxReserveCol - RangeStartCol] <= 10) then
|
||
Result := False
|
||
else
|
||
Dec(Constraints[MaxReserveCol - RangeStartCol],
|
||
Constraints[MaxReserveCol - RangeStartCol] div 10);
|
||
end;
|
||
|
||
//----------- end local functions -------------------------------------------
|
||
|
||
begin
|
||
Result := 0;
|
||
if ChangeBy <> 0 then
|
||
begin
|
||
// Do some initialization here
|
||
BonusPixel := ChangeBy > 0;
|
||
Sign := IfThen(BonusPixel, 1, -1);
|
||
Start := IfThen(BonusPixel, RangeStartCol, RangeEndCol);
|
||
ToGo := Abs(ChangeBy);
|
||
SetLength(Widths, RangeEndCol - RangeStartCol + 1);
|
||
SetLength(Constraints, RangeEndCol - RangeStartCol + 1);
|
||
for I := RangeStartCol to RangeEndCol do
|
||
begin
|
||
Widths[I - RangeStartCol] := FColumns[I].FWidth;
|
||
Constraints[I - RangeStartCol] := IfThen(BonusPixel, FColumns[I].MaxWidth, FColumns[I].MinWidth);
|
||
end;
|
||
|
||
repeat
|
||
repeat
|
||
MaxDelta := 0;
|
||
ColCount := 0;
|
||
for I := RangeStartCol to RangeEndCol do
|
||
if (Options * FColumns[I].FOptions = Options) and IsResizable(I) then
|
||
begin
|
||
Inc(ColCount);
|
||
IncDelta(I);
|
||
end;
|
||
if MaxDelta < Abs(ChangeBy) then
|
||
if not ReduceConstraints then
|
||
Break;
|
||
until (MaxDelta >= Abs(ChangeBy)) or not (hsScaling in FStates);
|
||
|
||
if ColCount = 0 then
|
||
Break;
|
||
|
||
ToGo := Min(ToGo, MaxDelta);
|
||
Difference := ToGo div ColCount;
|
||
Rest := ToGo mod ColCount;
|
||
|
||
if Difference > 0 then
|
||
for I := RangeStartCol to RangeEndCol do
|
||
if (Options * FColumns[I].FOptions = Options) and IsResizable(I) then
|
||
ChangeWidth(I, Difference * Sign);
|
||
|
||
// Now distribute Rest.
|
||
I := Start;
|
||
while Rest > 0 do
|
||
begin
|
||
if (Options * FColumns[I].FOptions = Options) and IsResizable(I) then
|
||
if FColumns[I].FBonusPixel <> BonusPixel then
|
||
begin
|
||
Dec(Rest, ChangeWidth(I, Sign));
|
||
FColumns[I].FBonusPixel := BonusPixel;
|
||
end;
|
||
Inc(I, Sign);
|
||
if (BonusPixel and (I > RangeEndCol)) or (not BonusPixel and (I < RangeStartCol)) then
|
||
begin
|
||
for I := RangeStartCol to RangeEndCol do
|
||
if Options * FColumns[I].FOptions = Options then
|
||
FColumns[I].FBonusPixel := not FColumns[I].FBonusPixel;
|
||
I := Start;
|
||
end;
|
||
end;
|
||
until ToGo <= 0;
|
||
|
||
// Now set the computed widths. We also compute the result here.
|
||
Include(FStates, hsResizing);
|
||
for I := RangeStartCol to RangeEndCol do
|
||
if (Options * FColumns[I].FOptions = Options) then
|
||
begin
|
||
Inc(Result, Widths[I - RangeStartCol] - FColumns[I].FWidth);
|
||
FColumns[I].SetWidth(Widths[I - RangeStartCol]);
|
||
end;
|
||
Exclude(FStates, hsResizing);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.RestoreColumns;
|
||
|
||
// Restores all columns to their width which they had before they have been auto fitted.
|
||
|
||
var
|
||
I: TColumnIndex;
|
||
|
||
begin
|
||
with FColumns do
|
||
for I := Count - 1 downto 0 do
|
||
if [coResizable, coVisible] * Items[FPositionToIndex[I]].FOptions = [coResizable, coVisible] then
|
||
Items[I].RestoreLastWidth;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTHeader.SaveToStream(const Stream: TStream);
|
||
|
||
// Saves the complete state of the header into the provided stream.
|
||
|
||
var
|
||
Dummy: Integer;
|
||
Tmp: AnsiString;
|
||
|
||
begin
|
||
with Stream do
|
||
begin
|
||
// In previous version of VT was no header stream version defined.
|
||
// For feature enhancements it is necessary, however, to know which stream
|
||
// format we are trying to load.
|
||
// In order to distict from non-version streams an indicator is inserted.
|
||
Dummy := -1;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
// Write current stream version number, nothing more is required at the time being.
|
||
Dummy := VTHeaderStreamVersion;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
|
||
// Save columns in case they depend on certain options (like auto size).
|
||
Columns.SaveToStream(Stream);
|
||
|
||
Dummy := FAutoSizeIndex;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := FBackground;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := FHeight;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Word(FOptions);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
// PopupMenu is neither saved nor restored
|
||
Dummy := Ord(FStyle);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
// TFont has no own save routine so we do it manually
|
||
with Font do
|
||
begin
|
||
Dummy := Color;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
|
||
// Need only to write one: size or height, I decided to write height.
|
||
Dummy := Height;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Tmp := Name;
|
||
Dummy := Length(Tmp);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
WriteBuffer(PAnsiChar(Tmp)^, Dummy);
|
||
Dummy := Ord(Pitch);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(Style);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
end;
|
||
|
||
// Data introduced by stream version 1.
|
||
Dummy := FMainColumn;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := FSortColumn;
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Byte(FSortDirection);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
|
||
// Data introduced by stream version 5.
|
||
Dummy := Integer(ParentFont);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(FMaxHeight);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(FMinHeight);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(FDefaultHeight);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
with FFixedAreaConstraints do
|
||
begin
|
||
Dummy := Integer(FMaxHeightPercent);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(FMaxWidthPercent);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(FMinHeightPercent);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
Dummy := Integer(FMinWidthPercent);
|
||
WriteBuffer(Dummy, SizeOf(Dummy));
|
||
end
|
||
end;
|
||
end;
|
||
|
||
//----------------- TScrollBarOptions ----------------------------------------------------------------------------------
|
||
|
||
constructor TScrollBarOptions.Create(AOwner: TBaseVirtualTree);
|
||
|
||
begin
|
||
inherited Create;
|
||
|
||
FOwner := AOwner;
|
||
FAlwaysVisible := False;
|
||
FScrollBarStyle := sbmRegular;
|
||
FScrollBars := ssBoth;
|
||
FIncrementX := 20;
|
||
FIncrementY := 20;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TScrollBarOptions.SetAlwaysVisible(Value: Boolean);
|
||
|
||
begin
|
||
if FAlwaysVisible <> Value then
|
||
begin
|
||
FAlwaysVisible := Value;
|
||
//todo_lcl_check
|
||
if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then
|
||
RecreateWnd(FOwner);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TScrollBarOptions.SetScrollBars(Value: TScrollStyle);
|
||
|
||
begin
|
||
if FScrollbars <> Value then
|
||
begin
|
||
FScrollBars := Value;
|
||
//todo_lcl_check
|
||
if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then
|
||
RecreateWnd(FOwner);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TScrollBarOptions.SetScrollBarStyle(Value: TVTScrollBarStyle);
|
||
|
||
begin
|
||
{$ifndef UseFlatScrollbars}
|
||
Assert(Value = sbmRegular, 'Flat scrollbars styles are disabled. Enable UseFlatScrollbars in VTConfig.inc for' +
|
||
'flat scrollbar support.');
|
||
{$endif UseFlatScrollbars}
|
||
|
||
if FScrollBarStyle <> Value then
|
||
begin
|
||
FScrollBarStyle := Value;
|
||
{$ifdef UseFlatScrollbars}
|
||
if FOwner.HandleAllocated then
|
||
begin
|
||
// If set to regular style then don't use the emulation mode of the FlatSB APIs but the original APIs.
|
||
// This is necessary because the FlatSB APIs don't respect NC paint request with limited update region
|
||
// (which is necessary for the transparent drag image).
|
||
FOwner.RecreateWnd;
|
||
end;
|
||
{$endif UseFlatScrollbars}
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TScrollBarOptions.GetOwner: TPersistent;
|
||
|
||
begin
|
||
Result := FOwner;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TScrollBarOptions.Assign(Source: TPersistent);
|
||
|
||
begin
|
||
if Source is TScrollBarOptions then
|
||
begin
|
||
AlwaysVisible := TScrollBarOptions(Source).AlwaysVisible;
|
||
HorizontalIncrement := TScrollBarOptions(Source).HorizontalIncrement;
|
||
ScrollBars := TScrollBarOptions(Source).ScrollBars;
|
||
ScrollBarStyle := TScrollBarOptions(Source).ScrollBarStyle;
|
||
VerticalIncrement := TScrollBarOptions(Source).VerticalIncrement;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
//----------------- TVTColors ------------------------------------------------------------------------------------------
|
||
|
||
constructor TVTColors.Create(AOwner: TBaseVirtualTree);
|
||
|
||
begin
|
||
FOwner := AOwner;
|
||
FColors[0] := clBtnShadow; // DisabledColor
|
||
FColors[1] := clHighlight; // DropMarkColor
|
||
FColors[2] := clHighLight; // DropTargetColor
|
||
FColors[3] := clHighLight; // FocusedSelectionColor
|
||
FColors[4] := clBtnFace; // GridLineColor
|
||
FColors[5] := clBtnShadow; // TreeLineColor
|
||
FColors[6] := clBtnFace; // UnfocusedSelectionColor
|
||
FColors[7] := clBtnFace; // BorderColor
|
||
FColors[8] := clWindowText; // HotColor
|
||
FColors[9] := clHighLight; // FocusedSelectionBorderColor
|
||
FColors[10] := clBtnFace; // UnfocusedSelectionBorderColor
|
||
FColors[11] := clHighlight; // DropTargetBorderColor
|
||
FColors[12] := clHighlight; // SelectionRectangleBlendColor
|
||
FColors[13] := clHighlight; // SelectionRectangleBorderColor
|
||
FColors[14] := clBtnShadow; // HeaderHotColor
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVTColors.GetColor(const Index: Integer): TColor;
|
||
|
||
begin
|
||
Result := FColors[Index];
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTColors.SetColor(const Index: Integer; const Value: TColor);
|
||
|
||
begin
|
||
if FColors[Index] <> Value then
|
||
begin
|
||
FColors[Index] := Value;
|
||
if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then
|
||
begin
|
||
// Cause helper bitmap rebuild if the button color changed.
|
||
case Index of
|
||
5:
|
||
begin
|
||
FOwner.PrepareBitmaps(True, False);
|
||
FOwner.Invalidate;
|
||
end;
|
||
7:
|
||
RedrawWindow(FOwner.Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN)
|
||
else
|
||
FOwner.Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTColors.Assign(Source: TPersistent);
|
||
|
||
begin
|
||
if Source is TVTColors then
|
||
begin
|
||
FColors := TVTColors(Source).FColors;
|
||
if FOwner.FUpdateCount = 0 then
|
||
FOwner.Invalidate;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
//----------------- TClipboardFormats ----------------------------------------------------------------------------------
|
||
|
||
constructor TClipboardFormats.Create(AOwner: TBaseVirtualTree);
|
||
|
||
begin
|
||
FOwner := AOwner;
|
||
Sorted := True;
|
||
Duplicates := dupIgnore;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TClipboardFormats.Add(const S: string): Integer;
|
||
|
||
// Restrict additions to the clipbard formats to only those which are registered with the owner tree or one of its
|
||
// ancestors.
|
||
|
||
var
|
||
Format: Word;
|
||
RegisteredClass: TVirtualTreeClass;
|
||
|
||
begin
|
||
RegisteredClass := InternalClipboardFormats.FindFormat(S, Format);
|
||
if Assigned(RegisteredClass) and FOwner.ClassType.InheritsFrom(RegisteredClass) then
|
||
Result := inherited Add(S)
|
||
else
|
||
Result := -1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TClipboardFormats.Insert(Index: Integer; const S: string);
|
||
|
||
// Restrict additions to the clipbard formats to only those which are registered with the owner tree or one of its
|
||
// ancestors.
|
||
|
||
var
|
||
Format: Word;
|
||
RegisteredClass: TVirtualTreeClass;
|
||
|
||
begin
|
||
RegisteredClass := InternalClipboardFormats.FindFormat(S, Format);
|
||
if Assigned(RegisteredClass) and FOwner.ClassType.InheritsFrom(RegisteredClass) then
|
||
inherited Insert(Index, S);
|
||
end;
|
||
|
||
//----------------- TBaseVirtualTree -----------------------------------------------------------------------------------
|
||
|
||
constructor TBaseVirtualTree.Create(AOwner: TComponent);
|
||
|
||
begin
|
||
if not Initialized then
|
||
InitializeGlobalStructures;
|
||
|
||
inherited;
|
||
|
||
ControlStyle := ControlStyle - [csSetCaption] + [csCaptureMouse, csOpaque, csReplicatable, csDisplayDragImage,
|
||
csReflector];
|
||
FTotalInternalDataSize := 0;
|
||
FNodeDataSize := -1;
|
||
Width := 200;
|
||
Height := 100;
|
||
TabStop := True;
|
||
ParentColor := False;
|
||
FDefaultNodeHeight := 18;
|
||
FDragOperations := [doCopy, doMove];
|
||
FHotCursor := crDefault;
|
||
FScrollBarOptions := TScrollBarOptions.Create(Self);
|
||
FFocusedColumn := NoColumn;
|
||
FDragImageKind := diComplete;
|
||
FLastSelectionLevel := -1;
|
||
FSelectionBlendFactor := 128;
|
||
|
||
FIndent := 18;
|
||
|
||
FPlusBM := TBitmap.Create;
|
||
FHotPlusBM := TBitmap.Create;
|
||
FMinusBM := TBitmap.Create;
|
||
FHotMinusBM := TBitmap.Create;
|
||
BorderStyle := bsSingle;
|
||
FButtonStyle := bsRectangle;
|
||
FButtonFillMode := fmTreeColor;
|
||
|
||
FHeader := GetHeaderClass.Create(Self);
|
||
|
||
// we have an own double buffer handling
|
||
DoubleBuffered := False;
|
||
|
||
FCheckImageKind := ckSystemDefault;
|
||
|
||
FImageChangeLink := TChangeLink.Create;
|
||
FImageChangeLink.OnChange := ImageListChange;
|
||
FStateChangeLink := TChangeLink.Create;
|
||
FStateChangeLink.OnChange := ImageListChange;
|
||
FCustomCheckChangeLink := TChangeLink.Create;
|
||
FCustomCheckChangeLink.OnChange := ImageListChange;
|
||
|
||
FAutoExpandDelay := 1000;
|
||
FAutoScrollDelay := 1000;
|
||
FAutoScrollInterval := 1;
|
||
|
||
FBackground := TPicture.Create;
|
||
|
||
FDefaultPasteMode := amAddChildLast;
|
||
FMargin := 4;
|
||
FTextMargin := 4;
|
||
FDragType := dtOLE;
|
||
FDragHeight := 350;
|
||
FDragWidth := 200;
|
||
|
||
FColors := TVTColors.Create(Self);
|
||
FEditDelay := 1000;
|
||
|
||
FDragImage := TVTDragImage.Create(Self);
|
||
with FDragImage do
|
||
begin
|
||
Fade := True;
|
||
PostBlendBias := 0;
|
||
PreBlendBias := 0;
|
||
Transparency := 200;
|
||
end;
|
||
|
||
SetLength(FSingletonNodeArray, 1);
|
||
FAnimationDuration := 200;
|
||
FSearchTimeout := 1000;
|
||
FSearchStart := ssFocusedNode;
|
||
FNodeAlignment := naProportional;
|
||
FLineStyle := lsDotted;
|
||
FIncrementalSearch := isNone;
|
||
FClipboardFormats := TClipboardFormats.Create(Self);
|
||
FOptions := GetOptionsClass.Create(Self);
|
||
|
||
{$ifdef EnableThreadSupport}
|
||
AddThreadReference;
|
||
{$endif}
|
||
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
destructor TBaseVirtualTree.Destroy;
|
||
|
||
begin
|
||
Exclude(FOptions.FMiscOptions, toReadOnly);
|
||
{$ifdef EnableThreadSupport}
|
||
ReleaseThreadReference(Self);
|
||
{$endif}
|
||
StopWheelPanning;
|
||
//lcl
|
||
FPanningWindow.Free;
|
||
|
||
// Just in case it didn't happen already release the edit link.
|
||
FEditLink := nil;
|
||
FClipboardFormats.Free;
|
||
// Clear will also free the drag manager if it is still alive.
|
||
Clear;
|
||
FDragImage.Free;
|
||
FColors.Free;
|
||
FBackground.Free;
|
||
FImageChangeLink.Free;
|
||
FStateChangeLink.Free;
|
||
FCustomCheckChangeLink.Free;
|
||
FScrollBarOptions.Free;
|
||
FOptions.Free;
|
||
|
||
// The window handle must be destroyed before the header is freed because it is needed in WM_NCDESTROY.
|
||
//todo_lcl_check
|
||
{
|
||
if HandleAllocated then
|
||
DestroyWindowHandle;
|
||
}
|
||
FHeader.Free;
|
||
FHeader := nil;
|
||
|
||
if FCheckImages <> FCustomCheckImages then
|
||
FCheckImages.Free;
|
||
|
||
FreeMem(FRoot);
|
||
|
||
FPlusBM.Free;
|
||
FHotPlusBM.Free;
|
||
FMinusBM.Free;
|
||
FHotMinusBM.Free;
|
||
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdjustCoordinatesByIndent(var PaintInfo: TVTPaintInfo; Indent: Integer);
|
||
|
||
// During painting of the main column some coordinates must be adjusted due to the tree lines.
|
||
// The offset resulting from the tree lines and indentation level is given in Indent.
|
||
|
||
var
|
||
Offset: Integer;
|
||
|
||
begin
|
||
with PaintInfo do
|
||
begin
|
||
Offset := Indent * Integer(FIndent);
|
||
if BidiMode = bdLeftToRight then
|
||
begin
|
||
Inc(ContentRect.Left, Offset);
|
||
Inc(ImageInfo[iiNormal].XPos, Offset);
|
||
Inc(ImageInfo[iiState].XPos, Offset);
|
||
Inc(ImageInfo[iiCheck].XPos, Offset);
|
||
end
|
||
else
|
||
begin
|
||
Dec(ContentRect.Right, Offset);
|
||
Dec(ImageInfo[iiNormal].XPos, Offset);
|
||
Dec(ImageInfo[iiState].XPos, Offset);
|
||
Dec(ImageInfo[iiCheck].XPos, Offset);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdjustImageBorder(ImageWidth, ImageHeight: Integer; BidiMode: TBidiMode; VAlign: Integer; var R: TRect;
|
||
var ImageInfo: TVTImageInfo);
|
||
|
||
// Depending on the width of the image list as well as the given bidi mode R must be adjusted.
|
||
|
||
begin
|
||
if BidiMode = bdLeftToRight then
|
||
begin
|
||
ImageInfo.XPos := R.Left;
|
||
Inc(R.Left, ImageWidth + 2);
|
||
end
|
||
else
|
||
begin
|
||
ImageInfo.XPos := R.Right - Images.Width;
|
||
Dec(R.Right, ImageWidth + 2);
|
||
end;
|
||
ImageInfo.YPos := R.Top + VAlign - ImageHeight div 2;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdjustTotalCount(Node: PVirtualNode; Value: Integer; Relative: Boolean = False);
|
||
|
||
// Sets a node's total count to the given value and recursively adjusts the parent's total count
|
||
// (actually, the adjustment is done iteratively to avoid function call overheads).
|
||
|
||
var
|
||
Difference: Integer;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if Relative then
|
||
Difference := Value
|
||
else
|
||
Difference := Integer(Value) - Integer(Node.TotalCount);
|
||
if Difference <> 0 then
|
||
begin
|
||
Run := Node;
|
||
// Root node has as parent the tree view.
|
||
while Assigned(Run) and (Run <> Pointer(Self)) do
|
||
begin
|
||
Inc(Integer(Run.TotalCount), Difference);
|
||
Run := Run.Parent;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdjustTotalHeight(Node: PVirtualNode; Value: Integer; Relative: Boolean = False);
|
||
|
||
// Sets a node's total height to the given value and recursively adjusts the parent's total height.
|
||
|
||
var
|
||
Difference: Integer;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if Relative then
|
||
Difference := Value
|
||
else
|
||
Difference := Integer(Value) - Integer(Node.TotalHeight);
|
||
if Difference <> 0 then
|
||
begin
|
||
Run := Node;
|
||
repeat
|
||
Inc(Integer(Run.TotalHeight), Difference);
|
||
// If the node is not visible or the parent node is not expanded or we are already at the top
|
||
// then nothing more remains to do.
|
||
if not (vsVisible in Run.States) or (Run = FRoot) or
|
||
(Run.Parent = nil) or not (vsExpanded in Run.Parent.States) then
|
||
Break;
|
||
|
||
Run := Run.Parent;
|
||
until False;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CalculateCacheEntryCount: Integer;
|
||
|
||
// Calculates the size of the position cache.
|
||
|
||
begin
|
||
if FVisibleCount > 1 then
|
||
Result := Ceil(FVisibleCount / CacheThreshold)
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CalculateVerticalAlignments(ShowImages, ShowStateImages: Boolean; Node: PVirtualNode;
|
||
out VAlign, VButtonAlign: Integer);
|
||
|
||
// Calculates the vertical alignment of the given node and its associated expand/collapse button during
|
||
// a node paint cycle depending on the required node alignment style.
|
||
|
||
begin
|
||
// For absolute alignment the calculation is trivial.
|
||
case FNodeAlignment of
|
||
naFromTop:
|
||
VAlign := Node.Align;
|
||
naFromBottom:
|
||
VAlign := NodeHeight[Node] - Node.Align;
|
||
else // naProportional
|
||
// Consider button and line alignment, but make sure neither the image nor the button (whichever is taller)
|
||
// go out of the entire node height (100% means bottom alignment to the node's bounds).
|
||
if ShowImages or ShowStateImages then
|
||
begin
|
||
if ShowImages then
|
||
VAlign := FImages.Height
|
||
else
|
||
VAlign := FStateImages.Height;
|
||
VAlign := MulDiv((Integer(NodeHeight[Node]) - VAlign), Node.Align, 100) + VAlign div 2;
|
||
end
|
||
else
|
||
if toShowButtons in FOptions.FPaintOptions then
|
||
VAlign := MulDiv((Integer(NodeHeight[Node]) - FPlusBM.Height), Node.Align, 100) + FPlusBM.Height div 2
|
||
else
|
||
VAlign := MulDiv(Node.NodeHeight, Node.Align, 100);
|
||
end;
|
||
|
||
VButtonAlign := VAlign - FPlusBM.Height div 2;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ChangeCheckState(Node: PVirtualNode; Value: TCheckState): Boolean;
|
||
|
||
// Sets the check state of the node according to the given value and the node's check type.
|
||
// If the check state must be propagated to the parent nodes and one of them refuses to change then
|
||
// nothing happens and False is returned, otherwise True.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
UncheckedCount,
|
||
MixedCheckCount,
|
||
CheckedCount: Cardinal;
|
||
|
||
begin
|
||
Result := not (vsChecking in Node.States);
|
||
with Node^ do
|
||
if Result then
|
||
begin
|
||
Include(States, vsChecking);
|
||
if not (vsInitialized in States) then
|
||
InitNode(Node);
|
||
|
||
// Indicate that we are going to propagate check states up and down the hierarchy.
|
||
if FCheckPropagationCount = 0 then // WL, 05.02.2004: Do not enter tsCheckPropagation more than once
|
||
DoStateChange([tsCheckPropagation]);
|
||
Inc(FCheckPropagationCount); // WL, 05.02.2004
|
||
// Do actions which are associated with the given check state.
|
||
case CheckType of
|
||
// Check state change with additional consequences for check states of the children.
|
||
ctTriStateCheckBox:
|
||
begin
|
||
// Propagate state down to the children.
|
||
if toAutoTristateTracking in FOptions.FAutoOptions then
|
||
case Value of
|
||
csUncheckedNormal:
|
||
if Node.ChildCount > 0 then
|
||
begin
|
||
Run := FirstChild;
|
||
CheckedCount := 0;
|
||
MixedCheckCount := 0;
|
||
UncheckedCount := 0;
|
||
while Assigned(Run) do
|
||
begin
|
||
if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then
|
||
begin
|
||
SetCheckState(Run, csUncheckedNormal);
|
||
// Check if the new child state was set successfully, otherwise we have to adjust the
|
||
// node's new check state accordingly.
|
||
case Run.CheckState of
|
||
csCheckedNormal:
|
||
Inc(CheckedCount);
|
||
csMixedNormal:
|
||
Inc(MixedCheckCount);
|
||
csUncheckedNormal:
|
||
Inc(UncheckedCount);
|
||
end;
|
||
end;
|
||
Run := Run.NextSibling;
|
||
end;
|
||
|
||
// If there is still a mixed state child node checkbox then this node must be mixed checked too.
|
||
if MixedCheckCount > 0 then
|
||
Value := csMixedNormal
|
||
else
|
||
// If nodes are normally checked child nodes then the unchecked count determines what
|
||
// to set for the node itself.
|
||
if CheckedCount > 0 then
|
||
if UncheckedCount > 0 then
|
||
Value := csMixedNormal
|
||
else
|
||
Value := csCheckedNormal;
|
||
end;
|
||
csCheckedNormal:
|
||
if Node.ChildCount > 0 then
|
||
begin
|
||
Run := FirstChild;
|
||
CheckedCount := 0;
|
||
MixedCheckCount := 0;
|
||
UncheckedCount := 0;
|
||
while Assigned(Run) do
|
||
begin
|
||
if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then
|
||
begin
|
||
SetCheckState(Run, csCheckedNormal);
|
||
// Check if the new child state was set successfully, otherwise we have to adjust the
|
||
// node's new check state accordingly.
|
||
case Run.CheckState of
|
||
csCheckedNormal:
|
||
Inc(CheckedCount);
|
||
csMixedNormal:
|
||
Inc(MixedCheckCount);
|
||
csUncheckedNormal:
|
||
Inc(UncheckedCount);
|
||
end;
|
||
end;
|
||
Run := Run.NextSibling;
|
||
end;
|
||
|
||
// If there is still a mixed state child node checkbox then this node must be mixed checked too.
|
||
if MixedCheckCount > 0 then
|
||
Value := csMixedNormal
|
||
else
|
||
// If nodes are normally checked child nodes then the unchecked count determines what
|
||
// to set for the node itself.
|
||
if CheckedCount > 0 then
|
||
if UncheckedCount > 0 then
|
||
Value := csMixedNormal
|
||
else
|
||
Value := csCheckedNormal;
|
||
end;
|
||
end;
|
||
end;
|
||
// radio button check state change
|
||
ctRadioButton:
|
||
if Value = csCheckedNormal then
|
||
begin
|
||
// Make sure only this node is checked.
|
||
Run := Parent.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
if Run.CheckType = ctRadioButton then
|
||
Run.CheckState := csUncheckedNormal;
|
||
Run := Run.NextSibling;
|
||
end;
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
if Result then
|
||
CheckState := Value // Set new check state
|
||
else
|
||
CheckState := UnpressedState[CheckState]; // Reset dynamic check state.
|
||
|
||
// Propagate state up to the parent.
|
||
if not (vsInitialized in Parent.States) then
|
||
InitNode(Parent);
|
||
if (toAutoTristateTracking in FOptions.FAutoOptions) and ([vsChecking, vsDisabled] * Parent.States = []) and
|
||
(CheckType in [ctCheckBox, ctTriStateCheckBox]) and (Parent <> FRoot) and
|
||
(Parent.CheckType = ctTriStateCheckBox) then
|
||
Result := CheckParentCheckState(Node, Value)
|
||
else
|
||
Result := True;
|
||
|
||
InvalidateNode(Node);
|
||
Exclude(States, vsChecking);
|
||
|
||
Dec(FCheckPropagationCount); // WL, 05.02.2004
|
||
if FCheckPropagationCount = 0 then // WL, 05.02.2004: Allow state change event after all check operations finished
|
||
DoStateChange([], [tsCheckPropagation]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment;
|
||
OldRect: TRect; const NewRect: TRect): Boolean;
|
||
|
||
// Helper routine used when a draw selection takes place. This version handles left-to-right directionality.
|
||
// In the process of adding or removing nodes the current selection is modified which requires to pack it after
|
||
// the function returns. Another side effect of this method is that a temporary list of nodes will be created
|
||
// (see also InternalCacheNode) which must be inserted into the current selection by the caller.
|
||
|
||
var
|
||
Run,
|
||
NextNode: PVirtualNode;
|
||
TextRight,
|
||
TextLeft,
|
||
CheckOffset,
|
||
CurrentTop,
|
||
CurrentRight,
|
||
NextTop,
|
||
NextColumn,
|
||
NodeWidth,
|
||
Dummy: Integer;
|
||
MinY, MaxY: Integer;
|
||
ImageOffset,
|
||
StateImageOffset: Integer;
|
||
IsInOldRect,
|
||
IsInNewRect: Boolean;
|
||
|
||
// quick check variables for various parameters
|
||
WithCheck,
|
||
WithImages,
|
||
WithStateImages,
|
||
DoSwitch,
|
||
AutoSpan: Boolean;
|
||
SimpleSelection: Boolean;
|
||
|
||
begin
|
||
// A priori nothing changes.
|
||
Result := False;
|
||
|
||
// Determine minimum and maximum vertical coordinates to limit iteration to.
|
||
MinY := Min(OldRect.Top, NewRect.Top);
|
||
MaxY := Max(OldRect.Bottom, NewRect.Bottom);
|
||
|
||
// Initialize short hand variables to speed up tests below.
|
||
DoSwitch := ssCtrlOS in FDrawSelShiftState;
|
||
WithCheck := (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages);
|
||
// Don't check the events here as descendant trees might have overriden the DoGetImageIndex method.
|
||
WithImages := Assigned(FImages);
|
||
if WithImages then
|
||
ImageOffset := FImages.Width + 2
|
||
else
|
||
ImageOffset := 0;
|
||
WithStateImages := Assigned(FStateImages);
|
||
if WithStateImages then
|
||
StateImageOffset := FStateImages.Width + 2
|
||
else
|
||
StateImageOffset := 0;
|
||
if WithCheck then
|
||
CheckOffset := FCheckImages.Height + 2
|
||
else
|
||
CheckOffset := 0;
|
||
AutoSpan := FHeader.UseColumns and (toAutoSpanColumns in FOptions.FAutoOptions);
|
||
SimpleSelection := toSimpleDrawSelection in FOptions.FSelectionOptions;
|
||
|
||
// This is the node to start with.
|
||
Run := InternalGetNodeAt(0, MinY, False, CurrentTop);
|
||
|
||
if Assigned(Run) then
|
||
begin
|
||
// The initial minimal left border is determined by the identation level of the node and is dynamically adjusted.
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
Inc(NodeLeft, Integer((GetNodeLevel(Run) + 1) * FIndent) + FMargin)
|
||
else
|
||
Inc(NodeLeft, Integer(GetNodeLevel(Run) * FIndent) + FMargin);
|
||
|
||
// ----- main loop
|
||
// Change selection depending on the node's rectangle being in the selection rectangle or not, but
|
||
// touch only those nodes which overlap either the old selection rectangle or the new one but not both.
|
||
repeat
|
||
// Collect offsets for check, normal and state images.
|
||
TextLeft := NodeLeft;
|
||
if WithCheck and (Run.CheckType <> ctNone) then
|
||
Inc(TextLeft, CheckOffset);
|
||
if WithImages and HasImage(Run, ikNormal, MainColumn) then
|
||
Inc(TextLeft, ImageOffset);
|
||
if WithStateImages and HasImage(Run, ikState, MainColumn) then
|
||
Inc(TextLeft, StateImageOffset);
|
||
|
||
// Ensure the node's height is determined.
|
||
MeasureItemHeight(Canvas, Run);
|
||
|
||
NextTop := CurrentTop + Integer(NodeHeight[Run]);
|
||
|
||
// Simple selection allows to draw the selection rectangle anywhere. No intersection with node captions is
|
||
// required. Only top and bottom bounds of the rectangle matter.
|
||
if SimpleSelection then
|
||
begin
|
||
IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom);
|
||
IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom);
|
||
end
|
||
else
|
||
begin
|
||
// The right column border might be extended if column spanning is enabled.
|
||
if AutoSpan then
|
||
begin
|
||
with FHeader.FColumns do
|
||
begin
|
||
NextColumn := MainColumn;
|
||
repeat
|
||
Dummy := GetNextVisibleColumn(NextColumn);
|
||
if (Dummy = InvalidColumn) or not ColumnIsEmpty(Run, Dummy) or
|
||
(Items[Dummy].BidiMode <> bdLeftToRight) then
|
||
Break;
|
||
NextColumn := Dummy;
|
||
until False;
|
||
if NextColumn = MainColumn then
|
||
CurrentRight := NodeRight
|
||
else
|
||
GetColumnBounds(NextColumn, Dummy, CurrentRight);
|
||
end;
|
||
end
|
||
else
|
||
CurrentRight := NodeRight;
|
||
|
||
// Check if we need the node's width. This is the case when the node is not left aligned or the
|
||
// left border of the selection rectangle is to the right of the left node border.
|
||
if (TextLeft < OldRect.Left) or (TextLeft < NewRect.Left) or (Alignment <> taLeftJustify) then
|
||
begin
|
||
NodeWidth := DoGetNodeWidth(Run, MainColumn);
|
||
if NodeWidth >= (CurrentRight - TextLeft) then
|
||
TextRight := CurrentRight
|
||
else
|
||
case Alignment of
|
||
taLeftJustify:
|
||
TextRight := TextLeft + NodeWidth;
|
||
taCenter:
|
||
begin
|
||
TextLeft := (TextLeft + CurrentRight - NodeWidth) div 2;
|
||
TextRight := TextLeft + NodeWidth;
|
||
end;
|
||
else
|
||
// taRightJustify
|
||
TextRight := CurrentRight;
|
||
TextLeft := TextRight - NodeWidth;
|
||
end;
|
||
end
|
||
else
|
||
TextRight := CurrentRight;
|
||
|
||
// Now determine whether we need to change the state.
|
||
IsInOldRect := (OldRect.Left <= TextRight) and (OldRect.Right >= TextLeft) and
|
||
(NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom);
|
||
IsInNewRect := (NewRect.Left <= TextRight) and (NewRect.Right >= TextLeft) and
|
||
(NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom);
|
||
end;
|
||
|
||
if IsInOldRect xor IsInNewRect then
|
||
begin
|
||
Result := True;
|
||
if DoSwitch then
|
||
begin
|
||
if vsSelected in Run.States then
|
||
InternalRemoveFromSelection(Run)
|
||
else
|
||
InternalCacheNode(Run);
|
||
end
|
||
else
|
||
begin
|
||
if IsInNewRect then
|
||
InternalCacheNode(Run)
|
||
else
|
||
InternalRemoveFromSelection(Run);
|
||
end;
|
||
end;
|
||
|
||
CurrentTop := NextTop;
|
||
// Get next visible node and update left node position.
|
||
NextNode := GetNextVisibleNoInit(Run, True);
|
||
if NextNode = nil then
|
||
Break;
|
||
Inc(NodeLeft, CountLevelDifference(Run, NextNode) * Integer(FIndent));
|
||
Run := NextNode;
|
||
until CurrentTop > MaxY;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment;
|
||
OldRect: TRect; const NewRect: TRect): Boolean;
|
||
|
||
// Helper routine used when a draw selection takes place. This version handles right-to-left directionality.
|
||
// See also comments in CollectSelectedNodesLTR.
|
||
|
||
var
|
||
Run,
|
||
NextNode: PVirtualNode;
|
||
TextRight,
|
||
TextLeft,
|
||
CheckOffset,
|
||
CurrentTop,
|
||
CurrentLeft,
|
||
NextTop,
|
||
NextColumn,
|
||
NodeWidth,
|
||
Dummy: Integer;
|
||
MinY, MaxY: Integer;
|
||
ImageOffset,
|
||
StateImageOffset: Integer;
|
||
IsInOldRect,
|
||
IsInNewRect: Boolean;
|
||
|
||
// quick check variables for various parameters
|
||
WithCheck,
|
||
WithImages,
|
||
WithStateImages,
|
||
DoSwitch,
|
||
AutoSpan: Boolean;
|
||
SimpleSelection: Boolean;
|
||
|
||
begin
|
||
// A priori nothing changes.
|
||
Result := False;
|
||
// Switch the alignment to the opposite value in RTL context.
|
||
ChangeBiDiModeAlignment(Alignment);
|
||
|
||
// Determine minimum and maximum vertical coordinates to limit iteration to.
|
||
MinY := Min(OldRect.Top, NewRect.Top);
|
||
MaxY := Max(OldRect.Bottom, NewRect.Bottom);
|
||
|
||
// Initialize short hand variables to speed up tests below.
|
||
DoSwitch := ssCtrlOS in FDrawSelShiftState;
|
||
WithCheck := (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages);
|
||
// Don't check the events here as descendant trees might have overriden the DoGetImageIndex method.
|
||
WithImages := Assigned(FImages);
|
||
if WithImages then
|
||
ImageOffset := FImages.Width + 2
|
||
else
|
||
ImageOffset := 0;
|
||
WithStateImages := Assigned(FStateImages);
|
||
if WithStateImages then
|
||
StateImageOffset := FStateImages.Width + 2
|
||
else
|
||
StateImageOffset := 0;
|
||
if WithCheck then
|
||
CheckOffset := FCheckImages.Height + 2
|
||
else
|
||
CheckOffset := 0;
|
||
AutoSpan := FHeader.UseColumns and (toAutoSpanColumns in FOptions.FAutoOptions);
|
||
SimpleSelection := toSimpleDrawSelection in FOptions.FSelectionOptions;
|
||
|
||
// This is the node to start with.
|
||
Run := InternalGetNodeAt(0, MinY, False, CurrentTop);
|
||
|
||
if Assigned(Run) then
|
||
begin
|
||
// The initial minimal left border is determined by the identation level of the node and is dynamically adjusted.
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
Dec(NodeRight, Integer((GetNodeLevel(Run) + 1) * FIndent) + FMargin)
|
||
else
|
||
Dec(NodeRight, Integer(GetNodeLevel(Run) * FIndent) + FMargin);
|
||
|
||
// ----- main loop
|
||
// Change selection depending on the node's rectangle being in the selection rectangle or not, but
|
||
// touch only those nodes which overlap either the old selection rectangle or the new one but not both.
|
||
repeat
|
||
// Collect offsets for check, normal and state images.
|
||
TextRight := NodeRight;
|
||
if WithCheck and (Run.CheckType <> ctNone) then
|
||
Dec(TextRight, CheckOffset);
|
||
if WithImages and HasImage(Run, ikNormal, MainColumn) then
|
||
Dec(TextRight, ImageOffset);
|
||
if WithStateImages and HasImage(Run, ikState, MainColumn) then
|
||
Dec(TextRight, StateImageOffset);
|
||
|
||
// Ensure the node's height is determined.
|
||
MeasureItemHeight(Canvas, Run);
|
||
|
||
NextTop := CurrentTop + Integer(NodeHeight[Run]);
|
||
|
||
// Simple selection allows to draw the selection rectangle anywhere. No intersection with node captions is
|
||
// required. Only top and bottom bounds of the rectangle matter.
|
||
if SimpleSelection then
|
||
begin
|
||
IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom);
|
||
IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom);
|
||
end
|
||
else
|
||
begin
|
||
// The left column border might be extended if column spanning is enabled.
|
||
if AutoSpan then
|
||
begin
|
||
NextColumn := MainColumn;
|
||
repeat
|
||
Dummy := FHeader.FColumns.GetPreviousVisibleColumn(NextColumn);
|
||
if (Dummy = InvalidColumn) or not ColumnIsEmpty(Run, Dummy) or
|
||
(FHeader.FColumns[Dummy].BiDiMode = bdLeftToRight) then
|
||
Break;
|
||
NextColumn := Dummy;
|
||
until False;
|
||
if NextColumn = MainColumn then
|
||
CurrentLeft := NodeLeft
|
||
else
|
||
FHeader.FColumns.GetColumnBounds(NextColumn, CurrentLeft, Dummy);
|
||
end
|
||
else
|
||
CurrentLeft := NodeLeft;
|
||
|
||
// Check if we need the node's width. This is the case when the node is not left aligned (in RTL context this
|
||
// means actually right aligned) or the right border of the selection rectangle is to the left
|
||
// of the right node border.
|
||
if (TextRight > OldRect.Right) or (TextRight > NewRect.Right) or (Alignment <> taRightJustify) then
|
||
begin
|
||
NodeWidth := DoGetNodeWidth(Run, MainColumn);
|
||
if NodeWidth >= (TextRight - CurrentLeft) then
|
||
TextLeft := CurrentLeft
|
||
else
|
||
case Alignment of
|
||
taLeftJustify:
|
||
begin
|
||
TextLeft := CurrentLeft;
|
||
TextRight := TextLeft + NodeWidth;
|
||
end;
|
||
taCenter:
|
||
begin
|
||
TextLeft := (TextRight + CurrentLeft - NodeWidth) div 2;
|
||
TextRight := TextLeft + NodeWidth;
|
||
end;
|
||
else
|
||
// taRightJustify
|
||
TextLeft := TextRight - NodeWidth;
|
||
end;
|
||
end
|
||
else
|
||
TextLeft := CurrentLeft;
|
||
|
||
// Now determine whether we need to change the state.
|
||
IsInOldRect := (OldRect.Right >= TextLeft) and (OldRect.Left <= TextRight) and
|
||
(NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom);
|
||
IsInNewRect := (NewRect.Right >= TextLeft) and (NewRect.Left <= TextRight) and
|
||
(NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom);
|
||
end;
|
||
|
||
if IsInOldRect xor IsInNewRect then
|
||
begin
|
||
Result := True;
|
||
if DoSwitch then
|
||
begin
|
||
if vsSelected in Run.States then
|
||
InternalRemoveFromSelection(Run)
|
||
else
|
||
InternalCacheNode(Run);
|
||
end
|
||
else
|
||
begin
|
||
if IsInNewRect then
|
||
InternalCacheNode(Run)
|
||
else
|
||
InternalRemoveFromSelection(Run);
|
||
end;
|
||
end;
|
||
|
||
CurrentTop := NextTop;
|
||
// Get next visible node and update left node position.
|
||
NextNode := GetNextVisibleNoInit(Run, True);
|
||
if NextNode = nil then
|
||
Break;
|
||
Dec(NodeRight, CountLevelDifference(Run, NextNode) * Integer(FIndent));
|
||
Run := NextNode;
|
||
until CurrentTop > MaxY;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ClearNodeBackground(const PaintInfo: TVTPaintInfo; UseBackground, Floating: Boolean;
|
||
R: TRect);
|
||
|
||
// Erases a node's background depending on what the application decides to do.
|
||
// UseBackground determines whether or not to use the background picture, while Floating indicates
|
||
// that R is given in coordinates of the small node bitmap or the superordinated target bitmap used in PaintTree.
|
||
|
||
var
|
||
BackColor: TColor;
|
||
EraseAction: TItemEraseAction;
|
||
Offset: TPoint;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintDetails],'ClearNodeBackground');{$endif}
|
||
with PaintInfo do
|
||
begin
|
||
EraseAction := eaDefault;
|
||
BackColor := Brush.Color;
|
||
if Floating then
|
||
begin
|
||
Offset := Point(-FEffectiveOffsetX, R.Top);
|
||
OffsetRect(R, 0, -Offset.Y);
|
||
end
|
||
else
|
||
Offset := Point(0, 0);
|
||
|
||
DoBeforeItemErase(Canvas, Node, R, Backcolor, EraseAction);
|
||
|
||
with Canvas do
|
||
begin
|
||
case EraseAction of
|
||
eaNone:
|
||
;
|
||
eaColor:
|
||
begin
|
||
// User has given a new background color.
|
||
Brush.Color := BackColor;
|
||
FillRect(R);
|
||
end;
|
||
else // eaDefault
|
||
if UseBackground then
|
||
begin
|
||
if toStaticBackground in TreeOptions.PaintOptions then
|
||
StaticBackground(FBackground.Bitmap, Canvas, Offset, R)
|
||
else
|
||
TileBackground(FBackground.Bitmap, Canvas, Offset, R);
|
||
end
|
||
else
|
||
begin
|
||
//clear the node background
|
||
//note there's a bug in original VTV that can lead to wrong node paint
|
||
//so, here the node is always cleared even if is selected
|
||
Brush.Color := Self.Brush.Color;
|
||
FillRect(R);
|
||
{$ifdef DEBUG_VTV}Logger.SendColor([lcPaintDetails],'Clearing a node background - Brush.Color', Brush.Color);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'Clearing Rectangle (R)', R);{$endif}
|
||
|
||
if (poDrawSelection in PaintOptions) and (toFullRowSelect in FOptions.FSelectionOptions) and
|
||
(vsSelected in Node.States) and not (toUseBlendedSelection in FOptions.PaintOptions) and not
|
||
((tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions) and IsWinVistaOrAbove) then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails, lcDrag], 'Draw the background of a selected node');{$endif}
|
||
if toShowHorzGridLines in FOptions.PaintOptions then
|
||
Dec(R.Bottom);
|
||
if Focused or (toPopupMode in FOptions.FPaintOptions) then
|
||
begin
|
||
Brush.Color := FColors.FocusedSelectionColor;
|
||
Pen.Color := FColors.FocusedSelectionBorderColor;
|
||
end
|
||
else
|
||
begin
|
||
Brush.Color := FColors.UnfocusedSelectionColor;
|
||
Pen.Color := FColors.UnfocusedSelectionBorderColor;
|
||
end;
|
||
|
||
with R do
|
||
RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius);
|
||
end;
|
||
end;
|
||
end;
|
||
DoAfterItemErase(Canvas, Node, R);
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'ClearNodeBackground');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CompareNodePositions(Node1, Node2: PVirtualNode; ConsiderChildrenAbove: Boolean = False): Integer;
|
||
|
||
// Tries hard and smart to quickly determine whether Node1's structural position is before Node2's position.
|
||
// If ConsiderChildrenAbove is True, the nodes will be compared with their visual order in mind.
|
||
// Returns 0 if Node1 = Node2, < 0 if Node1 is located before Node2 else > 0.
|
||
|
||
var
|
||
Run1,
|
||
Run2: PVirtualNode;
|
||
Level1,
|
||
Level2: Cardinal;
|
||
|
||
begin
|
||
Assert(Assigned(Node1) and Assigned(Node2), 'Nodes must never be nil.');
|
||
|
||
if Node1 = Node2 then
|
||
Result := 0
|
||
else
|
||
begin
|
||
if HasAsParent(Node1, Node2) then
|
||
Result := IfThen(ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions), -1, 1)
|
||
else
|
||
if HasAsParent(Node2, Node1) then
|
||
Result := IfThen(ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions), 1, -1)
|
||
else
|
||
begin
|
||
// the given nodes are neither equal nor are they parents of each other, so go up to FRoot
|
||
// for each node and compare the child indices of the top level parents
|
||
// Note: neither Node1 nor Node2 can be FRoot at this point as this (a bit strange) circumstance would
|
||
// be caught by the previous code.
|
||
|
||
// start lookup at the same level
|
||
Level1 := GetNodeLevel(Node1);
|
||
Level2 := GetNodeLevel(Node2);
|
||
Run1 := Node1;
|
||
while Level1 > Level2 do
|
||
begin
|
||
Run1 := Run1.Parent;
|
||
Dec(Level1);
|
||
end;
|
||
Run2 := Node2;
|
||
while Level2 > Level1 do
|
||
begin
|
||
Run2 := Run2.Parent;
|
||
Dec(Level2);
|
||
end;
|
||
|
||
// now go up until we find a common parent node (loop will safely stop at FRoot if the nodes
|
||
// don't share a common parent)
|
||
while Run1.Parent <> Run2.Parent do
|
||
begin
|
||
Run1 := Run1.Parent;
|
||
Run2 := Run2.Parent;
|
||
end;
|
||
Result := Integer(Run1.Index) - Integer(Run2.Index);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DetermineLineImageAndSelectLevel(Node: PVirtualNode; out LineImage: TLineImage): Integer;
|
||
|
||
// This method is used during paint cycles and initializes an array of line type IDs. These IDs are used to paint
|
||
// the tree lines in front of the given node.
|
||
// Additionally an initial count of selected parents is determined and returned which is used for specific painting.
|
||
|
||
var
|
||
X: Integer;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Result := 0;
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
X := 1
|
||
else
|
||
X := 0;
|
||
Run := Node;
|
||
// Determine indentation level of top node.
|
||
while Run.Parent <> FRoot do
|
||
begin
|
||
Inc(X);
|
||
Run := Run.Parent;
|
||
// Count selected nodes (FRoot is never selected).
|
||
if vsSelected in Run.States then
|
||
Inc(Result);
|
||
end;
|
||
|
||
// Set initial size of line index array, this will automatically initialized all entries to ltNone.
|
||
SetLength(LineImage, X);
|
||
|
||
// Only use lines if requested.
|
||
if toShowTreeLines in FOptions.FPaintOptions then
|
||
begin
|
||
if toChildrenAbove in FOptions.FPaintOptions then
|
||
begin
|
||
Dec(X);
|
||
if not HasVisiblePreviousSibling(Node) then
|
||
begin
|
||
if (Node.Parent <> FRoot) or HasVisibleNextSibling(Node) then
|
||
LineImage[X] := ltBottomRight
|
||
else
|
||
LineImage[X] := ltRight;
|
||
end
|
||
else if (Node.Parent = FRoot) and (not HasVisibleNextSibling(Node)) then
|
||
LineImage[X] := ltTopRight
|
||
else
|
||
LineImage[X] := ltTopDownRight;
|
||
|
||
// Now go up to the root to determine the rest.
|
||
Run := Node.Parent;
|
||
while Run <> FRoot do
|
||
begin
|
||
Dec(X);
|
||
if HasVisiblePreviousSibling(Run) then
|
||
LineImage[X] := ltTopDown;
|
||
|
||
Run := Run.Parent;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// Start over parent traversal if necessary.
|
||
Run := Node;
|
||
|
||
if Run.Parent <> FRoot then
|
||
begin
|
||
// The very last image (the one immediately before the item label) is different.
|
||
if HasVisibleNextSibling(Run) then
|
||
LineImage[X - 1] := ltTopDownRight
|
||
else
|
||
LineImage[X - 1] := ltTopRight;
|
||
Run := Run.Parent;
|
||
|
||
// Now go up all parents.
|
||
repeat
|
||
if Run.Parent = FRoot then
|
||
Break;
|
||
Dec(X);
|
||
if HasVisibleNextSibling(Run) then
|
||
LineImage[X - 1] := ltTopDown
|
||
else
|
||
LineImage[X - 1] := ltNone;
|
||
Run := Run.Parent;
|
||
until False;
|
||
end;
|
||
|
||
// Prepare root level. Run points at this stage to a top level node.
|
||
if (toShowRoot in FOptions.FPaintOptions) and (toShowTreeLines in FOptions.FPaintOptions) then
|
||
begin
|
||
// Is the top node a root node?
|
||
if Run = Node then
|
||
begin
|
||
// First child gets the bottom-right bitmap if it isn't also the only child.
|
||
if IsFirstVisibleChild(FRoot, Run) then
|
||
// Is it the only child?
|
||
if IsLastVisibleChild(FRoot, Run) then
|
||
LineImage[0] := ltRight
|
||
else
|
||
LineImage[0] := ltBottomRight
|
||
else
|
||
// real last child
|
||
if IsLastVisibleChild(FRoot, Run) then
|
||
LineImage[0] := ltTopRight
|
||
else
|
||
LineImage[0] := ltTopDownRight;
|
||
end
|
||
else
|
||
begin
|
||
// No, top node is not a top level node. So we need different painting.
|
||
if HasVisibleNextSibling(Run) then
|
||
LineImage[0] := ltTopDown
|
||
else
|
||
LineImage[0] := ltNone;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DrawLineImage(const PaintInfo: TVTPaintInfo; X, Y, H, VAlign: Integer; Style: TVTLineType;
|
||
Reverse: Boolean);
|
||
|
||
// Draws (depending on Style) one of the 5 line types of the tree.
|
||
// If Reverse is True then a right-to-left column is being drawn, hence horizontal lines must be mirrored.
|
||
// X and Y describe the left upper corner of the line image rectangle, while H denotes its height (and width).
|
||
|
||
var
|
||
HalfWidth,
|
||
TargetX: Integer;
|
||
|
||
begin
|
||
HalfWidth := Integer(FIndent) div 2;
|
||
if Reverse then
|
||
TargetX := 0
|
||
else
|
||
TargetX := FIndent;
|
||
|
||
with PaintInfo.Canvas do
|
||
begin
|
||
case Style of
|
||
ltBottomRight:
|
||
begin
|
||
DrawDottedVLine(PaintInfo, Y + VAlign, Y + H, X + HalfWidth);
|
||
DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign);
|
||
end;
|
||
ltTopDown:
|
||
DrawDottedVLine(PaintInfo, Y, Y + H, X + HalfWidth);
|
||
ltTopDownRight:
|
||
begin
|
||
DrawDottedVLine(PaintInfo, Y, Y + H, X + HalfWidth);
|
||
DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign);
|
||
end;
|
||
ltRight:
|
||
DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign);
|
||
ltTopRight:
|
||
begin
|
||
DrawDottedVLine(PaintInfo, Y, Y + VAlign, X + HalfWidth);
|
||
DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign);
|
||
end;
|
||
ltLeft: // left can also mean right for RTL context
|
||
if Reverse then
|
||
DrawDottedVLine(PaintInfo, Y, Y + H, X + Integer(FIndent))
|
||
else
|
||
DrawDottedVLine(PaintInfo, Y, Y + H, X);
|
||
ltLeftBottom:
|
||
if Reverse then
|
||
begin
|
||
DrawDottedVLine(PaintInfo, Y, Y + H, X + Integer(FIndent));
|
||
DrawDottedHLine(PaintInfo, X, X + Integer(FIndent), Y + H);
|
||
end
|
||
else
|
||
begin
|
||
DrawDottedVLine(PaintInfo, Y, Y + H, X);
|
||
DrawDottedHLine(PaintInfo, X, X + Integer(FIndent), Y + H);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.FindInPositionCache(Node: PVirtualNode; var CurrentPos: Cardinal): PVirtualNode;
|
||
|
||
// Looks through the position cache and returns the node whose top position is the largest one which is smaller or equal
|
||
// to the position of the given node.
|
||
|
||
var
|
||
L, H, I: Integer;
|
||
|
||
begin
|
||
L := 0;
|
||
H := High(FPositionCache);
|
||
while L <= H do
|
||
begin
|
||
I := (L + H) shr 1;
|
||
if CompareNodePositions(FPositionCache[I].Node, Node) <= 0 then
|
||
L := I + 1
|
||
else
|
||
H := I - 1;
|
||
end;
|
||
if L = 0 then // High(FPositionCache) = -1
|
||
begin
|
||
Result := nil;
|
||
CurrentPos := 0;
|
||
end
|
||
else
|
||
begin
|
||
Result := FPositionCache[L - 1].Node;
|
||
CurrentPos := FPositionCache[L - 1].AbsoluteTop;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.FindInPositionCache(Position: Cardinal; var CurrentPos: Cardinal): PVirtualNode;
|
||
|
||
// Looks through the position cache and returns the node whose top position is the largest one which is smaller or equal
|
||
// to the given vertical position.
|
||
// The returned node does not necessarily occupy the given position but is the nearest one to start
|
||
// iterating from to approach the real node for a given position. CurrentPos receives the actual position of the found
|
||
// node which is needed for further iteration.
|
||
|
||
var
|
||
L, H, I: Integer;
|
||
|
||
begin
|
||
L := 0;
|
||
H := High(FPositionCache);
|
||
while L <= H do
|
||
begin
|
||
I := (L + H) shr 1;
|
||
if FPositionCache[I].AbsoluteTop <= Position then
|
||
L := I + 1
|
||
else
|
||
H := I - 1;
|
||
end;
|
||
if L = 0 then // High(FPositionCache) = -1
|
||
begin
|
||
Result := nil;
|
||
CurrentPos := 0;
|
||
end
|
||
else
|
||
begin
|
||
Result := FPositionCache[L - 1].Node;
|
||
CurrentPos := FPositionCache[L - 1].AbsoluteTop;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FixupTotalCount(Node: PVirtualNode);
|
||
|
||
// Called after loading a subtree from stream. The child count in each node is already set but not
|
||
// their total count.
|
||
|
||
var
|
||
Child: PVirtualNode;
|
||
|
||
begin
|
||
// Initial total count is set to one on node creation.
|
||
Child := Node.FirstChild;
|
||
while Assigned(Child) do
|
||
begin
|
||
FixupTotalCount(Child);
|
||
Inc(Node.TotalCount, Child.TotalCount);
|
||
Child := Child.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FixupTotalHeight(Node: PVirtualNode);
|
||
|
||
// Called after loading a subtree from stream. The individual height of each node is set already,
|
||
// but their total height needs an adjustment depending on their visibility state.
|
||
|
||
var
|
||
Child: PVirtualNode;
|
||
|
||
begin
|
||
// Initial total height is set to the node height on load.
|
||
Child := Node.FirstChild;
|
||
|
||
if vsExpanded in Node.States then
|
||
begin
|
||
while Assigned(Child) do
|
||
begin
|
||
FixupTotalHeight(Child);
|
||
if vsVisible in Child.States then
|
||
Inc(Node.TotalHeight, Child.TotalHeight);
|
||
Child := Child.NextSibling;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// The node is collapsed, so just update the total height of its child nodes.
|
||
while Assigned(Child) do
|
||
begin
|
||
FixupTotalHeight(Child);
|
||
Child := Child.NextSibling;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetBottomNode: PVirtualNode;
|
||
|
||
begin
|
||
Result := InternalGetNodeAt(0, ClientHeight - 1);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetCheckedCount: Integer;
|
||
|
||
var
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
Result := 0;
|
||
Node := GetFirstChecked;
|
||
while Assigned(Node) do begin
|
||
Inc(Result);
|
||
Node := GetNextChecked(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetCheckState(Node: PVirtualNode): TCheckState;
|
||
|
||
begin
|
||
Result := Node.CheckState;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetCheckType(Node: PVirtualNode): TCheckType;
|
||
|
||
begin
|
||
Result := Node.CheckType;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetChildCount(Node: PVirtualNode): Cardinal;
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.ChildCount
|
||
else
|
||
Result := Node.ChildCount;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetChildrenInitialized(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := not (vsHasChildren in Node.States) or (Node.ChildCount > 0);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetCutCopyCount: Integer;
|
||
|
||
var
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
Result := 0;
|
||
Node := GetFirstCutCopy;
|
||
while Assigned(Node) do begin
|
||
Inc(Result);
|
||
Node := GetNextCutCopy(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetDisabled(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := Assigned(Node) and (vsDisabled in Node.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetDragManager: IVTDragManager;
|
||
|
||
// Returns the internal drag manager interface. If this does not yet exist then it is created here.
|
||
|
||
begin
|
||
if FDragManager = nil then
|
||
begin
|
||
FDragManager := DoCreateDragManager;
|
||
if FDragManager = nil then
|
||
FDragManager := TVTDragManager.Create(Self);
|
||
end;
|
||
|
||
Result := FDragManager;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetExpanded(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
if Assigned(Node) then
|
||
Result := vsExpanded in Node.States
|
||
else
|
||
Result := False;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFullyVisible(Node: PVirtualNode): Boolean;
|
||
|
||
// Determines whether the given node has the visibility flag set as well as all its parents are expanded.
|
||
|
||
begin
|
||
Assert(Assigned(Node), 'Invalid parameter.');
|
||
Result := vsVisible in Node.States;
|
||
if Result and (Node <> FRoot) then
|
||
Result := VisiblePath[Node];
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetHasChildren(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
if Assigned(Node) then
|
||
Result := vsHasChildren in Node.States
|
||
else
|
||
Result := vsHasChildren in FRoot.States;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetMultiline(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := Assigned(Node) and (Node <> FRoot) and (vsMultiline in Node.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNodeHeight(Node: PVirtualNode): Cardinal;
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
if toVariableNodeHeight in FOptions.FMiscOptions then
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
|
||
// Ensure the node's height is determined.
|
||
MeasureItemHeight(Canvas, Node);
|
||
end;
|
||
Result := Node.NodeHeight
|
||
end
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNodeParent(Node: PVirtualNode): PVirtualNode;
|
||
|
||
begin
|
||
if Assigned(Node) and (Node.Parent <> FRoot) then
|
||
Result := Node.Parent
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetOffsetXY: TPoint;
|
||
|
||
begin
|
||
Result := Point(FOffsetX, FOffsetY);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetRootNodeCount: Cardinal;
|
||
|
||
begin
|
||
Result := FRoot.ChildCount;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetSelected(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := Assigned(Node) and (vsSelected in Node.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetTopNode: PVirtualNode;
|
||
begin
|
||
Result := InternalGetNodeAt(0, 0);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetTotalCount: Cardinal;
|
||
|
||
begin
|
||
Inc(FUpdateCount);
|
||
try
|
||
ValidateNode(FRoot, True);
|
||
finally
|
||
Dec(FUpdateCount);
|
||
end;
|
||
// The root node itself doesn't count as node.
|
||
Result := FRoot.TotalCount - 1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetVerticalAlignment(Node: PVirtualNode): Byte;
|
||
|
||
begin
|
||
Result := Node.Align;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetVisible(Node: PVirtualNode): Boolean;
|
||
|
||
// Determines if the given node is marked as being visible.
|
||
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
|
||
Result := vsVisible in Node.States;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetVisiblePath(Node: PVirtualNode): Boolean;
|
||
|
||
// Determines if all parents of the given node are expanded and have the visibility flag set.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameters.');
|
||
|
||
// FRoot is always expanded
|
||
repeat
|
||
Node := Node.Parent;
|
||
until (Node = FRoot) or not (vsExpanded in Node.States) or not (vsVisible in Node.States);
|
||
|
||
Result := Node = FRoot;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.HandleClickSelection(LastFocused, NewNode: PVirtualNode; Shift: TShiftState;
|
||
DragPending: Boolean);
|
||
|
||
// Handles multi-selection with mouse click.
|
||
|
||
begin
|
||
// Ctrl key down
|
||
if ssCtrlOS in Shift then
|
||
begin
|
||
if ssShift in Shift then
|
||
begin
|
||
SelectNodes(FRangeAnchor, NewNode, True);
|
||
Invalidate;
|
||
end
|
||
else
|
||
begin
|
||
if not (toSiblingSelectConstraint in FOptions.SelectionOptions) then
|
||
FRangeAnchor := NewNode;
|
||
// Delay selection change if a drag operation is pending.
|
||
// Otherwise switch selection state here.
|
||
if DragPending then
|
||
DoStateChange([tsToggleFocusedSelection])
|
||
else
|
||
if vsSelected in NewNode.States then
|
||
RemoveFromSelection(NewNode)
|
||
else
|
||
AddToSelection(NewNode);
|
||
end;
|
||
end
|
||
else
|
||
// Shift key down
|
||
if ssShift in Shift then
|
||
begin
|
||
if FRangeAnchor = nil then
|
||
FRangeAnchor := FRoot.FirstChild;
|
||
|
||
// select node range
|
||
if Assigned(FRangeAnchor) then
|
||
begin
|
||
SelectNodes(FRangeAnchor, NewNode, False);
|
||
Invalidate;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// any other case
|
||
if not (vsSelected in NewNode.States) then
|
||
begin
|
||
AddToSelection(NewNode);
|
||
InvalidateNode(NewNode);
|
||
end;
|
||
// assign new reference item
|
||
FRangeAnchor := NewNode;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.HandleDrawSelection(X, Y: Integer): Boolean;
|
||
|
||
// Handles multi-selection with a focus rectangle.
|
||
// Result is True if something changed in selection.
|
||
|
||
var
|
||
OldRect,
|
||
NewRect: TRect;
|
||
MainColumn: TColumnIndex;
|
||
MaxValue: Integer;
|
||
|
||
// limits of a node and its text
|
||
NodeLeft,
|
||
NodeRight: Integer;
|
||
|
||
// alignment and directionality
|
||
CurrentBidiMode: TBidiMode;
|
||
CurrentAlignment: TAlignment;
|
||
|
||
begin
|
||
Result := False;
|
||
|
||
// Selection changes are only done if the user drew a selection rectangle large
|
||
// enough to exceed the threshold.
|
||
if (FRoot.TotalCount > 1) and (tsDrawSelecting in FStates) then
|
||
begin
|
||
// Effective handling of node selection is done by using two rectangles stored in FSelectRec.
|
||
OldRect := OrderRect(FLastSelRect);
|
||
NewRect := OrderRect(FNewSelRect);
|
||
ClearTempCache;
|
||
|
||
MainColumn := FHeader.MainColumn;
|
||
|
||
// Alignment and bidi mode determine where the node text is located within a node.
|
||
if MainColumn <= NoColumn then
|
||
begin
|
||
CurrentBidiMode := BidiMode;
|
||
CurrentAlignment := Alignment;
|
||
end
|
||
else
|
||
begin
|
||
CurrentBidiMode := FHeader.FColumns[MainColumn].BidiMode;
|
||
CurrentAlignment := FHeader.FColumns[MainColumn].Alignment;
|
||
end;
|
||
|
||
// Determine initial left border of first node (take column reordering into account).
|
||
if FHeader.UseColumns then
|
||
begin
|
||
// The mouse coordinates don't include any horizontal scrolling hence take this also
|
||
// out from the returned column position.
|
||
NodeLeft := FHeader.FColumns[MainColumn].Left - FEffectiveOffsetX;
|
||
NodeRight := NodeLeft + FHeader.FColumns[MainColumn].Width;
|
||
end
|
||
else
|
||
begin
|
||
NodeLeft := 0;
|
||
NodeRight := ClientWidth;
|
||
end;
|
||
if CurrentBidiMode = bdLeftToRight then
|
||
Result := CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight, CurrentAlignment, OldRect, NewRect)
|
||
else
|
||
Result := CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight, CurrentAlignment, OldRect, NewRect);
|
||
end;
|
||
|
||
if Result then
|
||
begin
|
||
// Do some housekeeping if there was a change.
|
||
MaxValue := PackArray(FSelection, FSelectionCount);
|
||
if MaxValue > -1 then
|
||
begin
|
||
FSelectionCount := MaxValue;
|
||
SetLength(FSelection, FSelectionCount);
|
||
end;
|
||
if FTempNodeCount > 0 then
|
||
begin
|
||
AddToSelection(FTempNodeCache, FTempNodeCount);
|
||
ClearTempCache;
|
||
end;
|
||
|
||
Change(nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.HasVisibleNextSibling(Node: PVirtualNode): Boolean;
|
||
|
||
// Helper method to determine if the given node has a visible next sibling. This is needed to
|
||
// draw correct tree lines.
|
||
|
||
begin
|
||
// Check if there is a sibling at all.
|
||
Result := Assigned(Node.NextSibling);
|
||
|
||
if Result then
|
||
begin
|
||
repeat
|
||
Node := Node.NextSibling;
|
||
Result := vsVisible in Node.States;
|
||
until Result or (Node.NextSibling = nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.HasVisiblePreviousSibling(Node: PVirtualNode): Boolean;
|
||
|
||
// Helper method to determine if the given node has a visible previous sibling. This is needed to
|
||
// draw correct tree lines.
|
||
|
||
begin
|
||
// Check if there is a sibling at all.
|
||
Result := Assigned(Node.PrevSibling);
|
||
|
||
if Result then
|
||
begin
|
||
repeat
|
||
Node := Node.PrevSibling;
|
||
Result := vsVisible in Node.States;
|
||
until Result or (Node.PrevSibling = nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ImageListChange(Sender: TObject);
|
||
|
||
begin
|
||
if not (csDestroying in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InitializeFirstColumnValues(var PaintInfo: TVTPaintInfo);
|
||
|
||
// Determines initial index, position and cell size of the first visible column.
|
||
|
||
begin
|
||
PaintInfo.Column := FHeader.FColumns.GetFirstVisibleColumn;
|
||
with FHeader.FColumns, PaintInfo do
|
||
begin
|
||
if Column > NoColumn then
|
||
begin
|
||
CellRect.Right := CellRect.Left + Items[Column].Width;
|
||
Position := Items[Column].Position;
|
||
end
|
||
else
|
||
Position := 0;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InitRootNode(OldSize: Cardinal = 0);
|
||
|
||
// Reinitializes the root node.
|
||
|
||
var
|
||
NewSize: Cardinal;
|
||
|
||
begin
|
||
NewSize := TreeNodeSize + FTotalInternalDataSize;
|
||
if FRoot = nil then
|
||
FRoot := AllocMem(NewSize)
|
||
else
|
||
begin
|
||
ReallocMem(FRoot, NewSize);
|
||
FillChar(PAnsiChar(PAnsiChar(FRoot) + OldSize)^, NewSize - OldSize, 0);
|
||
end;
|
||
|
||
with FRoot^ do
|
||
begin
|
||
// Indication that this node is the root node.
|
||
PrevSibling := FRoot;
|
||
NextSibling := FRoot;
|
||
Parent := Pointer(Self);
|
||
States := [vsInitialized, vsExpanded, vsHasChildren, vsVisible];
|
||
TotalHeight := FDefaultNodeHeight;
|
||
TotalCount := 1;
|
||
TotalHeight := FDefaultNodeHeight;
|
||
NodeHeight := FDefaultNodeHeight;
|
||
Align := 50;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InterruptValidation;
|
||
|
||
var
|
||
WasValidating: Boolean;
|
||
|
||
begin
|
||
DoStateChange([tsStopValidation], [tsUseCache]);
|
||
{$ifdef EnableThreadSupport}
|
||
// Check the worker thread existance. It might already be gone (usually on destruction of the last tree).
|
||
if Assigned(WorkerThread) then
|
||
begin
|
||
WasValidating := (tsValidating in FStates);
|
||
WorkerThread.RemoveTree(Self);
|
||
if WasValidating then
|
||
DoStateChange([tsValidationNeeded]);
|
||
end;
|
||
{$endif}
|
||
end;
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean;
|
||
|
||
// Helper method to check if Node is the same as the first visible child of Parent.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
// Find first visible child.
|
||
Run := Parent.FirstChild;
|
||
while Assigned(Run) and not (vsVisible in Run.States) do
|
||
Run := Run.NextSibling;
|
||
|
||
Result := Assigned(Run) and (Run = Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean;
|
||
|
||
// Helper method to check if Node is the same as the last visible child of Parent.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
// Find last visible child.
|
||
Run := Parent.LastChild;
|
||
while Assigned(Run) and not (vsVisible in Run.States) do
|
||
Run := Run.PrevSibling;
|
||
|
||
Result := Assigned(Run) and (Run = Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.LimitPaintingToArea(Canvas: TCanvas; ClipRect: TRect; VisibleRegion: HRGN = 0);
|
||
|
||
// Limits further painting onto the given canvas to the given rectangle.
|
||
// VisibleRegion is an optional region which can be used to limit drawing further.
|
||
|
||
var
|
||
ClipRegion: HRGN;
|
||
|
||
begin
|
||
// Regions expect their coordinates in device coordinates, hence we have to transform the region rectangle.
|
||
LPtoDP(Canvas.Handle, ClipRect, 2);
|
||
ClipRegion := CreateRectRgnIndirect(ClipRect);
|
||
if VisibleRegion <> 0 then
|
||
CombineRgn(ClipRegion, ClipRegion, VisibleRegion, RGN_AND);
|
||
SelectClipRgn(Canvas.Handle, ClipRegion);
|
||
DeleteObject(ClipRegion);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.LoadPanningCursors;
|
||
|
||
begin
|
||
with Screen do
|
||
begin
|
||
Cursors[crVT_MOVEALL]:=LoadCursorFromLazarusResource('VT_MOVEALL');
|
||
Cursors[crVT_MOVEEW]:=LoadCursorFromLazarusResource('VT_MOVEEW');
|
||
Cursors[crVT_MOVENS]:=LoadCursorFromLazarusResource('VT_MOVENS');
|
||
Cursors[crVT_MOVENW]:=LoadCursorFromLazarusResource('VT_MOVENW');
|
||
Cursors[crVT_MOVESW]:=LoadCursorFromLazarusResource('VT_MOVESW');
|
||
Cursors[crVT_MOVESE]:=LoadCursorFromLazarusResource('VT_MOVESE');
|
||
Cursors[crVT_MOVENE]:=LoadCursorFromLazarusResource('VT_MOVENE');
|
||
Cursors[crVT_MOVEW]:=LoadCursorFromLazarusResource('VT_MOVEW');
|
||
Cursors[crVT_MOVEE]:=LoadCursorFromLazarusResource('VT_MOVEE');
|
||
Cursors[crVT_MOVEN]:=LoadCursorFromLazarusResource('VT_MOVEN');
|
||
Cursors[crVT_MOVES]:=LoadCursorFromLazarusResource('VT_MOVES');
|
||
end;
|
||
end;
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.MakeNewNode: PVirtualNode;
|
||
|
||
var
|
||
Size: Cardinal;
|
||
|
||
begin
|
||
Size := TreeNodeSize;
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
// Make sure FNodeDataSize is valid.
|
||
if FNodeDataSize = -1 then
|
||
ValidateNodeDataSize(FNodeDataSize);
|
||
|
||
// Take record alignment into account.
|
||
Inc(Size, FNodeDataSize);
|
||
end;
|
||
|
||
Result := AllocMem(Size + FTotalInternalDataSize);
|
||
|
||
// Fill in some default values.
|
||
with Result^ do
|
||
begin
|
||
TotalCount := 1;
|
||
TotalHeight := FDefaultNodeHeight;
|
||
NodeHeight := FDefaultNodeHeight;
|
||
States := [vsVisible];
|
||
Align := 50;
|
||
end;
|
||
end;
|
||
|
||
{$ifdef PACKARRAYPASCAL}
|
||
|
||
function TBaseVirtualTree.PackArray(const TheArray: TNodeArray; Count: Integer): Integer;
|
||
var
|
||
Source, Dest: ^PVirtualNode;
|
||
ConstOne: PtrInt;
|
||
begin
|
||
Source := Pointer(TheArray);
|
||
ConstOne := 1;
|
||
Result := 0;
|
||
// Do the fastest scan possible to find the first entry
|
||
while (Count <> 0) and {not Odd(NativeInt(Source^))} (PtrInt(Source^) and ConstOne = 0) do
|
||
begin
|
||
Inc(Result);
|
||
Inc(Source);
|
||
Dec(Count);
|
||
end;
|
||
|
||
if Count <> 0 then
|
||
begin
|
||
Dest := Source;
|
||
repeat
|
||
// Skip odd entries
|
||
if {not Odd(NativeInt(Source^))} PtrInt(Source^) and ConstOne = 0 then
|
||
begin
|
||
Dest^ := Source^;
|
||
Inc(Result);
|
||
Inc(Dest);
|
||
end;
|
||
Inc(Source); // Point to the next entry
|
||
Dec(Count);
|
||
until Count = 0;
|
||
end;
|
||
end;
|
||
|
||
{$else}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$IMPLICITEXCEPTIONS OFF}
|
||
|
||
function TBaseVirtualTree.PackArray(TheArray: TNodeArray; Count: Integer): Integer; assembler;
|
||
|
||
// Removes all entries from the selection array which are no longer in use. The selection array must be sorted for this
|
||
// algo to work. Values which must be removed are marked with bit 0 (LSB) set. This little trick works because memory
|
||
// is always allocated DWORD aligned. Since the selection array must be sorted while determining the entries to be
|
||
// removed it is much more efficient to increment the entry in question instead of setting it to nil (which would break
|
||
// the ordered appearance of the list).
|
||
//
|
||
// On enter EAX contains self reference, EDX the address to TheArray and ECX Count
|
||
// The returned value is the number of remaining entries in the array, so the caller can reallocate (shorten)
|
||
// the selection array if needed or -1 if nothing needs to be changed.
|
||
|
||
asm
|
||
PUSH EBX
|
||
PUSH EDI
|
||
PUSH ESI
|
||
MOV ECX, EDX //fpc: count is in EDX. Move to ECX
|
||
MOV ESI, [EBP+8] //fpc: TheArray is in EBP+8
|
||
MOV EDX, -1
|
||
JCXZ @@Finish // Empty list?
|
||
INC EDX // init remaining entries counter
|
||
MOV EDI, ESI // source and destination point to the list memory
|
||
MOV EBX, 1 // use a register instead of immediate operant to check against
|
||
@@PreScan:
|
||
TEST [ESI], EBX // do the fastest scan possible to find the first entry
|
||
// which must be removed
|
||
JNZ @@DoMainLoop
|
||
INC EDX
|
||
ADD ESI, 4
|
||
DEC ECX
|
||
JNZ @@PreScan
|
||
JMP @@Finish
|
||
|
||
@@DoMainLoop:
|
||
MOV EDI, ESI
|
||
@@MainLoop:
|
||
TEST [ESI], EBX // odd entry?
|
||
JNE @@Skip // yes, so skip this one
|
||
MOVSD // else move the entry to new location
|
||
INC EDX // count the moved entries
|
||
DEC ECX
|
||
JNZ @@MainLoop // do it until all entries are processed
|
||
JMP @@Finish
|
||
|
||
@@Skip:
|
||
ADD ESI, 4 // point to the next entry
|
||
DEC ECX
|
||
JNZ @@MainLoop // do it until all entries are processed
|
||
@@Finish:
|
||
MOV EAX, EDX // prepare return value
|
||
POP ESI
|
||
POP EDI
|
||
POP EBX
|
||
end;
|
||
|
||
{$IMPLICITEXCEPTIONS ON}
|
||
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PrepareBitmaps(NeedButtons, NeedLines: Boolean);
|
||
|
||
// initializes the contents of the internal bitmaps
|
||
|
||
const
|
||
LineBitsDotted: array [0..8] of Word = ($55, $AA, $55, $AA, $55, $AA, $55, $AA, $55);
|
||
LineBitsSolid: array [0..7] of Word = (0, 0, 0, 0, 0, 0, 0, 0);
|
||
|
||
var
|
||
PatternBitmap: HBITMAP;
|
||
Bits: Pointer;
|
||
Size: TSize;
|
||
{$ifdef ThemeSupport}
|
||
//Theme: HTHEME;
|
||
R: TRect;
|
||
|
||
const
|
||
TVP_HOTGLYPH = 4;
|
||
|
||
{$endif ThemeSupport}
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
procedure FillBitmap (ABitmap: TBitmap);
|
||
begin
|
||
with ABitmap, Canvas do
|
||
begin
|
||
ABitmap.Width := Size.cx;
|
||
ABitmap.Height := Size.cy;
|
||
|
||
{$Ifdef ThemeSupport}
|
||
if IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions) then
|
||
begin
|
||
if (FHeader.FMainColumn > NoColumn) and not
|
||
(coParentColor in FHeader.FColumns[FHeader.FMainColumn].FOptions) then
|
||
Brush.Color := FHeader.FColumns[FHeader.FMainColumn].Color
|
||
else
|
||
Brush.Color := Self.Brush.Color;
|
||
end
|
||
else
|
||
begin
|
||
{$EndIf ThemeSupport}
|
||
MaskHandle := 0;
|
||
Transparent := True;
|
||
TransparentColor := clFuchsia;
|
||
Brush.Color := clFuchsia;
|
||
{$Ifdef ThemeSupport}
|
||
end;
|
||
{$EndIf ThemeSupport}
|
||
|
||
FillRect(Rect(0, 0, ABitmap.Width, ABitmap.Height));
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
begin
|
||
Size.cx := 9;
|
||
Size.cy := 9;
|
||
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if tsUseThemes in FStates then
|
||
begin
|
||
Theme := OpenThemeData(Handle, 'TREEVIEW');
|
||
if IsWinVistaOrAbove and (toUseExplorerTheme in FOptions.FPaintOptions) then
|
||
begin
|
||
R := Rect(0, 0, 100, 100);
|
||
GetThemePartSize(Theme, FPlusBM.Canvas.Handle, TVP_GLYPH, GLPS_OPENED, @R, TS_TRUE, Size);
|
||
end;
|
||
end
|
||
else
|
||
Theme := 0;
|
||
}
|
||
{$endif ThemeSupport}
|
||
|
||
if NeedButtons then
|
||
begin
|
||
with FMinusBM, Canvas do
|
||
begin
|
||
// box is always of odd size
|
||
FillBitmap(FMinusBM);
|
||
FillBitmap(FHotMinusBM);
|
||
if not (IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions)) then
|
||
begin
|
||
if FButtonStyle = bsTriangle then
|
||
begin
|
||
Brush.Color := clBlack;
|
||
Pen.Color := clBlack;
|
||
Polygon([Point(0, 2), Point(8, 2), Point(4, 6)]);
|
||
end
|
||
else
|
||
begin
|
||
// Button style is rectangular. Now ButtonFillMode determines how to fill the interior.
|
||
if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then
|
||
begin
|
||
case FButtonFillMode of
|
||
fmTreeColor:
|
||
Brush.Color := Self.Brush.Color;
|
||
fmWindowColor:
|
||
Brush.Color := clWindow;
|
||
end;
|
||
Pen.Color := FColors.TreeLineColor;
|
||
Rectangle(0, 0, Width, Height);
|
||
Pen.Color := Self.Font.Color;
|
||
MoveTo(2, Width div 2);
|
||
LineTo(Width - 2 , Width div 2);
|
||
end
|
||
else
|
||
FMinusBM.LoadFromLazarusResource('VT_XPBUTTONMINUS');
|
||
FHotMinusBM.Canvas.Draw(0, 0, FMinusBM);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
with FPlusBM, Canvas do
|
||
begin
|
||
FillBitmap(FPlusBM);
|
||
FillBitmap(FHotPlusBM);
|
||
if not (IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions)) then
|
||
begin
|
||
if FButtonStyle = bsTriangle then
|
||
begin
|
||
Brush.Color := clBlack;
|
||
Pen.Color := clBlack;
|
||
Polygon([Point(2, 0), Point(6, 4), Point(2, 8)]);
|
||
end
|
||
else
|
||
begin
|
||
// Button style is rectangular. Now ButtonFillMode determines how to fill the interior.
|
||
if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then
|
||
begin
|
||
case FButtonFillMode of
|
||
fmTreeColor:
|
||
Brush.Color := Self.Brush.Color;
|
||
fmWindowColor:
|
||
Brush.Color := clWindow;
|
||
end;
|
||
|
||
Pen.Color := FColors.TreeLineColor;
|
||
Rectangle(0, 0, Width, Height);
|
||
Pen.Color := Self.Font.Color;
|
||
MoveTo(2, Width div 2);
|
||
LineTo(Width - 2 , Width div 2);
|
||
MoveTo(Width div 2, 2);
|
||
LineTo(Width div 2, Width - 2);
|
||
end
|
||
else
|
||
FPlusBM.LoadFromLazarusResource('VT_XPBUTTONPLUS');
|
||
FHotPlusBM.Canvas.Draw(0, 0, FPlusBM);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
// Overwrite glyph images if theme is active.
|
||
{
|
||
if (tsUseThemes in FStates) and (Theme <> 0) then
|
||
begin
|
||
R := Rect(0, 0, Size.cx, Size.cy);
|
||
DrawThemeBackground(Theme, FPlusBM.Canvas.Handle, TVP_GLYPH, GLPS_CLOSED, R, nil);
|
||
DrawThemeBackground(Theme, FMinusBM.Canvas.Handle, TVP_GLYPH, GLPS_OPENED, R, nil);
|
||
if IsWinVistaOrAbove and (toUseExplorerTheme in FOptions.FPaintOptions) then
|
||
begin
|
||
DrawThemeBackground(Theme, FHotPlusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_CLOSED, R, nil);
|
||
DrawThemeBackground(Theme, FHotMinusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_OPENED, R, nil);
|
||
end
|
||
else
|
||
begin
|
||
FHotPlusBM.Canvas.Draw(0, 0, FPlusBM);
|
||
FHotMinusBM.Canvas.Draw(0, 0, FMinusBM);
|
||
end;
|
||
end;
|
||
}
|
||
{$endif ThemeSupport}
|
||
end;
|
||
|
||
if NeedLines then
|
||
begin
|
||
if FDottedBrush <> 0 then
|
||
DeleteObject(FDottedBrush);
|
||
|
||
case FLineStyle of
|
||
lsDotted:
|
||
Bits := @LineBitsDotted;
|
||
lsSolid:
|
||
Bits := @LineBitsSolid;
|
||
else // lsCustomStyle
|
||
Bits := @LineBitsDotted;
|
||
DoGetLineStyle(Bits);
|
||
end;
|
||
PatternBitmap := CreateBitmap(8, 8, 1, 1, Bits);
|
||
FDottedBrush := CreatePatternBrush(PatternBitmap);
|
||
DeleteObject(PatternBitmap);
|
||
end;
|
||
|
||
{$ifdef ThemeSupport}
|
||
// if Theme <> 0 then
|
||
// CloseThemeData(Theme);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetAlignment(const Value: TAlignment);
|
||
|
||
begin
|
||
if FAlignment <> Value then
|
||
begin
|
||
FAlignment := Value;
|
||
if not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetAnimationDuration(const Value: Cardinal);
|
||
|
||
begin
|
||
FAnimationDuration := Value;
|
||
if FAnimationDuration = 0 then
|
||
Exclude(FOptions.FAnimationOptions, toAnimatedToggle)
|
||
else
|
||
Include(FOptions.FAnimationOptions, toAnimatedToggle);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetBackground(const Value: TPicture);
|
||
|
||
begin
|
||
FBackground.Assign(Value);
|
||
Invalidate;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetBackgroundOffset(const Index, Value: Integer);
|
||
|
||
begin
|
||
case Index of
|
||
0:
|
||
if FBackgroundOffsetX <> Value then
|
||
begin
|
||
FBackgroundOffsetX := Value;
|
||
Invalidate;
|
||
end;
|
||
1:
|
||
if FBackgroundOffsetY <> Value then
|
||
begin
|
||
FBackgroundOffsetY := Value;
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{
|
||
procedure TBaseVirtualTree.SetBorderStyle(Value: TBorderStyle);
|
||
|
||
begin
|
||
if FBorderStyle <> Value then
|
||
begin
|
||
FBorderStyle := Value;
|
||
RecreateWnd;
|
||
end;
|
||
end;
|
||
}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetBottomNode(Node: PVirtualNode);
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
R: TRect;
|
||
|
||
begin
|
||
if Assigned(Node) then
|
||
begin
|
||
// make sure all parents of the node are expanded
|
||
Run := Node.Parent;
|
||
while Run <> FRoot do
|
||
begin
|
||
if not (vsExpanded in Run.States) then
|
||
ToggleNode(Run);
|
||
Run := Run.Parent;
|
||
end;
|
||
R := GetDisplayRect(Node, FHeader.MainColumn, True);
|
||
DoSetOffsetXY(Point(FOffsetX, FOffsetY + ClientHeight - R.Top - Integer(NodeHeight[Node])),
|
||
[suoRepaintScrollbars, suoUpdateNCArea]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetBottomSpace(const Value: Cardinal);
|
||
|
||
begin
|
||
if FBottomSpace <> Value then
|
||
begin
|
||
FBottomSpace := Value;
|
||
UpdateVerticalScrollbar(True);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetButtonFillMode(const Value: TVTButtonFillMode);
|
||
|
||
begin
|
||
if FButtonFillMode <> Value then
|
||
begin
|
||
FButtonFillMode := Value;
|
||
if not (csLoading in ComponentState) then
|
||
begin
|
||
PrepareBitmaps(True, False);
|
||
if HandleAllocated then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetButtonStyle(const Value: TVTButtonStyle);
|
||
|
||
begin
|
||
if FButtonStyle <> Value then
|
||
begin
|
||
FButtonStyle := Value;
|
||
if not (csLoading in ComponentState) then
|
||
begin
|
||
PrepareBitmaps(True, False);
|
||
if HandleAllocated then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetCheckImageKind(Value: TCheckImageKind);
|
||
|
||
begin
|
||
if FCheckImageKind <> Value then
|
||
begin
|
||
FCheckImageKind := Value;
|
||
if toCheckSupport in FOptions.FMiscOptions then
|
||
UpdateCheckImageList;
|
||
if HandleAllocated and (FUpdateCount = 0) and not (csLoading in ComponentState) then
|
||
InvalidateRect(Handle, nil, False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetCheckState(Node: PVirtualNode; Value: TCheckState);
|
||
|
||
begin
|
||
if (Node.CheckState <> Value) and not (vsDisabled in Node.States) and DoChecking(Node, Value) then
|
||
DoCheckClick(Node, Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetCheckType(Node: PVirtualNode; Value: TCheckType);
|
||
|
||
begin
|
||
if (Node.CheckType <> Value) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
Node.CheckType := Value;
|
||
Node.CheckState := csUncheckedNormal;
|
||
// For check boxes with tri-state check box parents we have to initialize differently.
|
||
if (toAutoTriStateTracking in FOptions.FAutoOptions) and (Value in [ctCheckBox, ctTriStateCheckBox]) and
|
||
(Node.Parent <> FRoot) then
|
||
begin
|
||
if not (vsInitialized in Node.Parent.States) then
|
||
InitNode(Node.Parent);
|
||
if (Node.Parent.CheckType = ctTriStateCheckBox) and
|
||
(Node.Parent.CheckState in [csUncheckedNormal, csCheckedNormal]) then
|
||
CheckState[Node] := Node.Parent.CheckState;
|
||
end;
|
||
InvalidateNode(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal);
|
||
|
||
// Changes a node's child structure to accomodate the new child count. This is used to add or delete
|
||
// child nodes to/from the end of the node's child list. To insert or delete a specific node a separate
|
||
// routine is used.
|
||
|
||
var
|
||
Remaining: Cardinal;
|
||
Index: Cardinal;
|
||
Child: PVirtualNode;
|
||
Count: Integer;
|
||
NewHeight: Integer;
|
||
|
||
begin
|
||
if not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
|
||
if NewChildCount = 0 then
|
||
DeleteChildren(Node)
|
||
else
|
||
begin
|
||
// If nothing changed then do nothing.
|
||
if NewChildCount <> Node.ChildCount then
|
||
begin
|
||
InterruptValidation;
|
||
NewHeight := 0;
|
||
|
||
if NewChildCount > Node.ChildCount then
|
||
begin
|
||
Remaining := NewChildCount - Node.ChildCount;
|
||
Count := Remaining;
|
||
|
||
// New nodes to add.
|
||
if Assigned(Node.LastChild) then
|
||
Index := Node.LastChild.Index + 1
|
||
else
|
||
begin
|
||
Index := 0;
|
||
Include(Node.States, vsHasChildren);
|
||
end;
|
||
Node.States := Node.States - [vsAllChildrenHidden, vsHeightMeasured];
|
||
|
||
// New nodes are by default always visible, so we don't need to check the visibility.
|
||
while Remaining > 0 do
|
||
begin
|
||
Child := MakeNewNode;
|
||
Child.Index := Index;
|
||
Child.PrevSibling := Node.LastChild;
|
||
if Assigned(Node.LastChild) then
|
||
Node.LastChild.NextSibling := Child;
|
||
Child.Parent := Node;
|
||
Node.LastChild := Child;
|
||
if Node.FirstChild = nil then
|
||
Node.FirstChild := Child;
|
||
Dec(Remaining);
|
||
Inc(Index);
|
||
|
||
// The actual node height will later be computed once it is clear
|
||
// whether this node has a variable node height or not.
|
||
Inc(NewHeight, Child.NodeHeight);
|
||
end;
|
||
|
||
if vsExpanded in Node.States then
|
||
begin
|
||
AdjustTotalHeight(Node, NewHeight, True);
|
||
if FullyVisible[Node] then
|
||
Inc(Integer(FVisibleCount), Count);
|
||
end;
|
||
|
||
AdjustTotalCount(Node, Count, True);
|
||
Node.ChildCount := NewChildCount;
|
||
if (FUpdateCount = 0) and (toAutoSort in FOptions.FAutoOptions) and (FHeader.FSortColumn > InvalidColumn) then
|
||
Sort(Node, FHeader.FSortColumn, FHeader.FSortDirection, True);
|
||
|
||
InvalidateCache;
|
||
end
|
||
else
|
||
begin
|
||
// Nodes have to be deleted.
|
||
Remaining := Node.ChildCount - NewChildCount;
|
||
while Remaining > 0 do
|
||
begin
|
||
DeleteNode(Node.LastChild);
|
||
Dec(Remaining);
|
||
end;
|
||
end;
|
||
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
UpdateScrollBars(True);
|
||
Invalidate;
|
||
end;
|
||
|
||
if Node = FRoot then
|
||
StructureChange(nil, crChildAdded)
|
||
else
|
||
StructureChange(Node, crChildAdded);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetClipboardFormats(const Value: TClipboardFormats);
|
||
|
||
var
|
||
I: Integer;
|
||
|
||
begin
|
||
// Add string by string instead doing an Assign or AddStrings because the list may return -1 for
|
||
// invalid entries which cause trouble for the standard implementation.
|
||
FClipboardFormats.Clear;
|
||
for I := 0 to Value.Count - 1 do
|
||
FClipboardFormats.Add(Value[I]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetColors(const Value: TVTColors);
|
||
|
||
begin
|
||
FColors.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetCustomCheckImages(const Value: TBitmap);
|
||
|
||
begin
|
||
if FCustomCheckImages <> Value then
|
||
begin
|
||
if Assigned(FCustomCheckImages) then
|
||
begin
|
||
// Reset the internal check image list reference too, if necessary.
|
||
if FCheckImages = FCustomCheckImages then
|
||
FCheckImages := nil;
|
||
end;
|
||
FCustomCheckImages := Value;
|
||
// Check if currently custom check images are active.
|
||
if FCheckImageKind = ckCustom then
|
||
FCheckImages := Value;
|
||
if not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetDefaultNodeHeight(Value: Cardinal);
|
||
|
||
begin
|
||
if Value = 0 then
|
||
Value := 18;
|
||
if FDefaultNodeHeight <> Value then
|
||
begin
|
||
DoStateChange([tsNeedScale]);
|
||
Inc(Integer(FRoot.TotalHeight), Integer(Value) - Integer(FDefaultNodeHeight));
|
||
Inc(SmallInt(FRoot.NodeHeight), Integer(Value) - Integer(FDefaultNodeHeight));
|
||
FDefaultNodeHeight := Value;
|
||
InvalidateCache;
|
||
if (FUpdateCount = 0) and HandleAllocated and not (csLoading in ComponentState) then
|
||
begin
|
||
ValidateCache;
|
||
UpdateScrollBars(True);
|
||
ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True);
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetDisabled(Node: PVirtualNode; Value: Boolean);
|
||
|
||
begin
|
||
if Assigned(Node) and (Value xor (vsDisabled in Node.States)) then
|
||
begin
|
||
if Value then
|
||
Include(Node.States, vsDisabled)
|
||
else
|
||
Exclude(Node.States, vsDisabled);
|
||
|
||
if FUpdateCount = 0 then
|
||
InvalidateNode(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetExpanded(Node: PVirtualNode; Value: Boolean);
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) and (Value xor (vsExpanded in Node.States)) then
|
||
ToggleNode(Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetFocusedColumn(Value: TColumnIndex);
|
||
|
||
begin
|
||
if (FFocusedColumn <> Value) and
|
||
DoFocusChanging(FFocusedNode, FFocusedNode, FFocusedColumn, Value) then
|
||
begin
|
||
CancelEditNode;
|
||
InvalidateColumn(FFocusedColumn);
|
||
InvalidateColumn(Value);
|
||
FFocusedColumn := Value;
|
||
if Assigned(FFocusedNode) and not (toDisableAutoscrollOnFocus in FOptions.FAutoOptions) then
|
||
begin
|
||
if ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True) then
|
||
InvalidateNode(FFocusedNode);
|
||
end;
|
||
|
||
if Assigned(FDropTargetNode) then
|
||
InvalidateNode(FDropTargetNode);
|
||
|
||
DoFocusChange(FFocusedNode, FFocusedColumn);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetFocusedNode(Value: PVirtualNode);
|
||
|
||
var
|
||
WasDifferent: Boolean;
|
||
|
||
begin
|
||
WasDifferent := Value <> FFocusedNode;
|
||
DoFocusNode(Value, True);
|
||
// Do change event only if there was actually a change.
|
||
if WasDifferent and (FFocusedNode = Value) then
|
||
DoFocusChange(FFocusedNode, FFocusedColumn);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetFullyVisible(Node: PVirtualNode; Value: Boolean);
|
||
|
||
// This method ensures that a node is visible and all its parent nodes are expanded and also visible
|
||
// if Value is True. Otherwise the visibility flag of the node is reset but the expand state
|
||
// of the parent nodes stays untouched.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter');
|
||
|
||
IsVisible[Node] := Value;
|
||
if Value then
|
||
begin
|
||
repeat
|
||
Node := Node.Parent;
|
||
if Node = FRoot then
|
||
Break;
|
||
if not (vsExpanded in Node.States) then
|
||
ToggleNode(Node);
|
||
if not (vsVisible in Node.States) then
|
||
IsVisible[Node] := True;
|
||
until False;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetHasChildren(Node: PVirtualNode; Value: Boolean);
|
||
|
||
begin
|
||
if Assigned(Node) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
if Value then
|
||
Include(Node.States, vsHasChildren)
|
||
else
|
||
begin
|
||
Exclude(Node.States, vsHasChildren);
|
||
DeleteChildren(Node);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetHeader(const Value: TVTHeader);
|
||
|
||
begin
|
||
FHeader.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
procedure TBaseVirtualTree.SetImages(const Value: TCustomImageList);
|
||
|
||
begin
|
||
if FImages <> Value then
|
||
begin
|
||
if Assigned(FImages) then
|
||
begin
|
||
FImages.UnRegisterChanges(FImageChangeLink);
|
||
FImages.RemoveFreeNotification(Self);
|
||
end;
|
||
FImages := Value;
|
||
if Assigned(FImages) then
|
||
begin
|
||
FImages.RegisterChanges(FImageChangeLink);
|
||
FImages.FreeNotification(Self);
|
||
end;
|
||
if not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetIndent(Value: Cardinal);
|
||
|
||
begin
|
||
if FIndent <> Value then
|
||
begin
|
||
FIndent := Value;
|
||
if not (csLoading in ComponentState) and (FUpdateCount = 0) and HandleAllocated then
|
||
begin
|
||
UpdateScrollBars(True);
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetLineMode(const Value: TVTLineMode);
|
||
|
||
begin
|
||
if FLineMode <> Value then
|
||
begin
|
||
FLineMode := Value;
|
||
if HandleAllocated and not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetLineStyle(const Value: TVTLineStyle);
|
||
|
||
begin
|
||
if FLineStyle <> Value then
|
||
begin
|
||
FLineStyle := Value;
|
||
if not (csLoading in ComponentState) then
|
||
begin
|
||
PrepareBitmaps(False, True);
|
||
if HandleAllocated then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetMargin(Value: Integer);
|
||
|
||
begin
|
||
if FMargin <> Value then
|
||
begin
|
||
FMargin := Value;
|
||
if HandleAllocated and not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetMultiline(Node: PVirtualNode; const Value: Boolean);
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
if Value <> (vsMultiline in Node.States) then
|
||
begin
|
||
if Value then
|
||
Include(Node.States, vsMultiline)
|
||
else
|
||
Exclude(Node.States, vsMultiline);
|
||
|
||
if FUpdateCount = 0 then
|
||
InvalidateNode(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetNodeAlignment(const Value: TVTNodeAlignment);
|
||
|
||
begin
|
||
if FNodeAlignment <> Value then
|
||
begin
|
||
FNodeAlignment := Value;
|
||
if HandleAllocated and not (csReading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetNodeDataSize(Value: Integer);
|
||
|
||
var
|
||
LastRootCount: Cardinal;
|
||
|
||
begin
|
||
if Value < -1 then
|
||
Value := -1;
|
||
if FNodeDataSize <> Value then
|
||
begin
|
||
FNodeDataSize := Value;
|
||
if not (csLoading in ComponentState) and not (csDesigning in ComponentState) then
|
||
begin
|
||
LastRootCount := FRoot.ChildCount;
|
||
Clear;
|
||
SetRootNodeCount(LastRootCount);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetNodeHeight(Node: PVirtualNode; Value: Cardinal);
|
||
|
||
var
|
||
Difference: Integer;
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) and (Node.NodeHeight <> Value) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
Difference := Integer(Value) - Integer(Node.NodeHeight);
|
||
Node.NodeHeight := Value;
|
||
AdjustTotalHeight(Node, Difference, True);
|
||
|
||
// If an edit operation is currently active then update the editors boundaries as well.
|
||
UpdateEditBounds;
|
||
|
||
// Stay away from touching the node cache while it is being validated.
|
||
if not (tsValidating in FStates) and FullyVisible[Node] then
|
||
begin
|
||
InvalidateCache;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
InvalidateToBottom(Node);
|
||
UpdateScrollBars(True);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetNodeParent(Node: PVirtualNode; const Value: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(Node) and Assigned(Value) and (Node.Parent <> Value) then
|
||
MoveTo(Node, Value, amAddChildLast, False);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetOffsetX(const Value: Integer);
|
||
|
||
begin
|
||
DoSetOffsetXY(Point(Value, FOffsetY), DefaultScrollUpdateFlags);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetOffsetXY(const Value: TPoint);
|
||
|
||
begin
|
||
DoSetOffsetXY(Value, DefaultScrollUpdateFlags);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetOffsetY(const Value: Integer);
|
||
|
||
begin
|
||
DoSetOffsetXY(Point(FOffsetX, Value), DefaultScrollUpdateFlags);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetOptions(const Value: TCustomVirtualTreeOptions);
|
||
|
||
begin
|
||
FOptions.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetRootNodeCount(Value: Cardinal);
|
||
|
||
begin
|
||
// Don't set the root node count until all other properties (in particular the OnInitNode event) have been set.
|
||
if csLoading in ComponentState then
|
||
begin
|
||
FRoot.ChildCount := Value;
|
||
DoStateChange([tsNeedRootCountUpdate]);
|
||
end
|
||
else
|
||
if FRoot.ChildCount <> Value then
|
||
begin
|
||
BeginUpdate;
|
||
InterruptValidation;
|
||
SetChildCount(FRoot, Value);
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetScrollBarOptions(Value: TScrollBarOptions);
|
||
|
||
begin
|
||
FScrollBarOptions.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetSearchOption(const Value: TVTIncrementalSearch);
|
||
|
||
begin
|
||
if FIncrementalSearch <> Value then
|
||
begin
|
||
FIncrementalSearch := Value;
|
||
if FIncrementalSearch = isNone then
|
||
begin
|
||
KillTimer(Handle, SearchTimer);
|
||
FSearchBuffer := '';
|
||
FLastSearchNode := nil;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetSelected(Node: PVirtualNode; Value: Boolean);
|
||
|
||
begin
|
||
if not FSelectionLocked and Assigned(Node) and (Node <> FRoot) and (Value xor (vsSelected in Node.States)) then
|
||
begin
|
||
if Value then
|
||
begin
|
||
if FSelectionCount = 0 then
|
||
FRangeAnchor := Node
|
||
else
|
||
if not (toMultiSelect in FOptions.FSelectionOptions) then
|
||
ClearSelection;
|
||
|
||
AddToSelection(Node);
|
||
|
||
// Make sure there is a valid column selected (if there are columns at all).
|
||
if ((FFocusedColumn < 0) or not (coVisible in FHeader.Columns[FFocusedColumn].Options)) and
|
||
(FHeader.MainColumn > NoColumn) then
|
||
if ([coVisible, coAllowFocus] * FHeader.Columns[FHeader.MainColumn].Options = [coVisible, coAllowFocus]) then
|
||
FFocusedColumn := FHeader.MainColumn
|
||
else
|
||
FFocusedColumn := FHeader.Columns.GetFirstVisibleColumn(True);
|
||
if FRangeAnchor = nil then
|
||
FRangeAnchor := Node;
|
||
end
|
||
else
|
||
begin
|
||
RemoveFromSelection(Node);
|
||
if FSelectionCount = 0 then
|
||
ResetRangeAnchor;
|
||
end;
|
||
if FullyVisible[Node] then
|
||
InvalidateNode(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetSelectionCurveRadius(const Value: Cardinal);
|
||
|
||
begin
|
||
if FSelectionCurveRadius <> Value then
|
||
begin
|
||
FSelectionCurveRadius := Value;
|
||
if HandleAllocated and not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetStateImages(const Value: TCustomImageList);
|
||
|
||
begin
|
||
if FStateImages <> Value then
|
||
begin
|
||
if Assigned(FStateImages) then
|
||
begin
|
||
FStateImages.UnRegisterChanges(FStateChangeLink);
|
||
FStateImages.RemoveFreeNotification(Self);
|
||
end;
|
||
FStateImages := Value;
|
||
if Assigned(FStateImages) then
|
||
begin
|
||
FStateImages.RegisterChanges(FStateChangeLink);
|
||
FStateImages.FreeNotification(Self);
|
||
end;
|
||
if HandleAllocated and not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetTextMargin(Value: Integer);
|
||
|
||
begin
|
||
if FTextMargin <> Value then
|
||
begin
|
||
FTextMargin := Value;
|
||
if not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetTopNode(Node: PVirtualNode);
|
||
|
||
var
|
||
R: TRect;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if Assigned(Node) then
|
||
begin
|
||
// make sure all parents of the node are expanded
|
||
Run := Node.Parent;
|
||
while Run <> FRoot do
|
||
begin
|
||
if not (vsExpanded in Run.States) then
|
||
ToggleNode(Run);
|
||
Run := Run.Parent;
|
||
end;
|
||
R := GetDisplayRect(Node, FHeader.MainColumn, True);
|
||
//lclheader
|
||
if hoVisible in FHeader.Options then
|
||
Dec(R.Top, FHeader.Height);
|
||
SetOffsetY(FOffsetY - R.Top);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetUpdateState(Updating: Boolean);
|
||
|
||
begin
|
||
// The check for visibility is necessary otherwise the tree is automatically shown when
|
||
// updating is allowed. As this happens internally the VCL does not get notified and
|
||
// still assumes the control is hidden. This results in weird "cannot focus invisible control" errors.
|
||
//lcl todo
|
||
if Visible and HandleAllocated and (FUpdateCount = 0) then
|
||
SendMessage(Handle, WM_SETREDRAW, Ord(not Updating), 0);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetVerticalAlignment(Node: PVirtualNode; Value: Byte);
|
||
|
||
begin
|
||
if Value > 100 then
|
||
Value := 100;
|
||
if Node.Align <> Value then
|
||
begin
|
||
Node.Align := Value;
|
||
if FullyVisible[Node] then
|
||
InvalidateNode(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetVisible(Node: PVirtualNode; Value: Boolean);
|
||
|
||
// Sets the visibility style of the given node according to Value.
|
||
|
||
var
|
||
NeedUpdate: Boolean;
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
||
|
||
if Value <> (vsVisible in Node.States) then
|
||
begin
|
||
InterruptValidation;
|
||
NeedUpdate := False;
|
||
if Value then
|
||
begin
|
||
Include(Node.States, vsVisible);
|
||
if vsExpanded in Node.Parent.States then
|
||
AdjustTotalHeight(Node.Parent, Node.TotalHeight, True);
|
||
if VisiblePath[Node] then
|
||
begin
|
||
Inc(FVisibleCount, 1 + CountVisibleChildren(Node));
|
||
NeedUpdate := True;
|
||
end;
|
||
|
||
// Update the hidden children flag of the parent.
|
||
// Since this node is now visible we simply have to remove the flag.
|
||
Exclude(Node.Parent.States, vsAllChildrenHidden);
|
||
end
|
||
else
|
||
begin
|
||
Exclude(Node.States, vsVisible);
|
||
if vsExpanded in Node.Parent.States then
|
||
AdjustTotalHeight(Node.Parent, -Integer(Node.TotalHeight), True);
|
||
if VisiblePath[Node] then
|
||
begin
|
||
Dec(FVisibleCount, 1 + CountVisibleChildren(Node));
|
||
NeedUpdate := True;
|
||
end;
|
||
|
||
if FUpdateCount = 0 then
|
||
DetermineHiddenChildrenFlag(Node.Parent)
|
||
else
|
||
Include(FStates, tsUpdateHiddenChildrenNeeded)
|
||
end;
|
||
|
||
InvalidateCache;
|
||
if NeedUpdate and (FUpdateCount = 0) then
|
||
begin
|
||
ValidateCache;
|
||
UpdateScrollBars(True);
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetVisiblePath(Node: PVirtualNode; Value: Boolean);
|
||
|
||
// If Value is True then all parent nodes of Node are expanded.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
||
|
||
if Value then
|
||
begin
|
||
repeat
|
||
Node := Node.Parent;
|
||
if Node = FRoot then
|
||
Break;
|
||
if not (vsExpanded in Node.States) then
|
||
ToggleNode(Node);
|
||
until False;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.StaticBackground(Source: TBitmap; Target: TCanvas; const Offset: TPoint; const R: TRect);
|
||
|
||
// Draws the given source graphic so that it stays static in the given rectangle which is relative to the target bitmap.
|
||
// The graphic is aligned so that it always starts at the upper left corner of the target canvas.
|
||
// Offset gives the position of the target window as a possible superordinated surface.
|
||
|
||
const
|
||
DST = $00AA0029; // Ternary Raster Operation - Destination unchanged
|
||
|
||
var
|
||
PicRect: TRect;
|
||
AreaRect: TRect;
|
||
DrawRect: TRect;
|
||
|
||
begin
|
||
// clear background
|
||
Target.Brush.Color := Brush.Color;
|
||
Target.FillRect(R);
|
||
|
||
// Picture rect in relation to client viewscreen.
|
||
PicRect := Rect(FBackgroundOffsetX, FBackgroundOffsetY, FBackgroundOffsetX + Source.Width, FBackgroundOffsetY + Source.Height);
|
||
|
||
// Area to be draw in relation to client viewscreen.
|
||
AreaRect := Rect(Offset.X + R.Left, Offset.Y + R.Top, Offset.X + R.Right, Offset.Y + R.Bottom);
|
||
|
||
// If picture falls in AreaRect, return intersection (DrawRect).
|
||
if IntersectRect(DrawRect, PicRect, AreaRect) then
|
||
begin
|
||
// Draw portion of image which falls in canvas area.
|
||
if Source.Transparent then
|
||
begin
|
||
// Leave transparent area as destination unchanged (DST), copy non-transparent areas to canvas (SRCCOPY).
|
||
with DrawRect do
|
||
MaskBlt(Target.Handle, Left - Offset.X, Top - Offset.Y, (Right - Offset.X) - (Left - Offset.X),
|
||
(Bottom - Offset.Y) - (Top - Offset.Y), Source.Canvas.Handle, Left - PicRect.Left, DrawRect.Top - PicRect.Top,
|
||
Source.MaskHandle, Left - PicRect.Left, Top - PicRect.Top, MakeROP4(DST, SRCCOPY));
|
||
end
|
||
else
|
||
begin
|
||
// copy image to destination
|
||
with DrawRect do
|
||
BitBlt(Target.Handle, Left - Offset.X, Top - Offset.Y, (Right - Offset.X) - (Left - Offset.X),
|
||
(Bottom - Offset.Y) - (Top - Offset.Y) + R.Top, Source.Canvas.Handle, Left - PicRect.Left, DrawRect.Top - PicRect.Top,
|
||
SRCCOPY);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.TileBackground(Source: TBitmap; Target: TCanvas; const Offset: TPoint; R: TRect);
|
||
|
||
// Draws the given source graphic so that it tiles into the given rectangle which is relative to the target bitmap.
|
||
// The graphic is aligned so that it always starts at the upper left corner of the target canvas.
|
||
// Offset gives the position of the target window in an possible superordinated surface.
|
||
|
||
var
|
||
SourceX,
|
||
SourceY,
|
||
TargetX,
|
||
|
||
DeltaY: Integer;
|
||
|
||
begin
|
||
with Target do
|
||
begin
|
||
SourceY := (R.Top + Offset.Y + FBackgroundOffsetY) mod Source.Height;
|
||
// Always wrap the source coordinates into positive range.
|
||
if SourceY < 0 then
|
||
SourceY := Source.Height + SourceY;
|
||
|
||
// Tile image vertically until target rect is filled.
|
||
while R.Top < R.Bottom do
|
||
begin
|
||
SourceX := (R.Left + Offset.X + FBackgroundOffsetX) mod Source.Width;
|
||
// always wrap the source coordinates into positive range
|
||
if SourceX < 0 then
|
||
SourceX := Source.Width + SourceX;
|
||
|
||
TargetX := R.Left;
|
||
// height of strip to draw
|
||
DeltaY := Min(R.Bottom - R.Top, Source.Height - SourceY);
|
||
|
||
// tile the image horizontally
|
||
while TargetX < R.Right do
|
||
begin
|
||
BitBlt(Handle, TargetX, R.Top, Min(R.Right - TargetX, Source.Width - SourceX), DeltaY,
|
||
Source.Canvas.Handle, SourceX, SourceY, SRCCOPY);
|
||
Inc(TargetX, Source.Width - SourceX);
|
||
SourceX := 0;
|
||
end;
|
||
Inc(R.Top, Source.Height - SourceY);
|
||
SourceY := 0;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean;
|
||
|
||
var
|
||
Column: TColumnIndex;
|
||
Run: TRect;
|
||
SecondaryStepSize: Integer;
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
procedure EraseLine;
|
||
|
||
var
|
||
LocalBrush: HBRUSH;
|
||
|
||
begin
|
||
with TToggleAnimationData(Data^), FHeader.FColumns do
|
||
begin
|
||
// Iterate through all columns and erase background in their local color.
|
||
// LocalBrush is a brush in the color of the particular column.
|
||
Column := GetFirstVisibleColumn;
|
||
while (Column > InvalidColumn) and (Run.Left < ClientWidth) do
|
||
begin
|
||
GetColumnBounds(Column, Run.Left, Run.Right);
|
||
if coParentColor in Items[Column].FOptions then
|
||
FillRect(DC, Run, Brush)
|
||
else
|
||
begin
|
||
LocalBrush := CreateSolidBrush(ColorToRGB(Items[Column].Color));
|
||
FillRect(DC, Run, LocalBrush);
|
||
DeleteObject(LocalBrush);
|
||
end;
|
||
Column := GetNextVisibleColumn(Column);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure DoScrollUp(DC: HDC; Brush: HBRUSH; Area: TRect; Steps: Integer);
|
||
|
||
begin
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
ScrollDC(DC, 0, -Steps, Area, Area, 0, nil);
|
||
{$endif}
|
||
|
||
if Step = 0 then
|
||
if not FHeader.UseColumns then
|
||
FillRect(DC, Rect(Area.Left, Area.Bottom - Steps - 1, Area.Right, Area.Bottom), Brush)
|
||
else
|
||
begin
|
||
Run := Rect(Area.Left, Area.Bottom - Steps - 1, Area.Right, Area.Bottom);
|
||
EraseLine;
|
||
end;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure DoScrollDown(DC: HDC; Brush: HBRUSH; Area: TRect; Steps: Integer);
|
||
|
||
begin
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
ScrollDC(DC, 0, Steps, Area, Area, 0, nil);
|
||
{$endif}
|
||
|
||
if Step = 0 then
|
||
if not FHeader.UseColumns then
|
||
FillRect(DC, Rect(Area.Left, Area.Top, Area.Right, Area.Top + Steps + 1), Brush)
|
||
else
|
||
begin
|
||
Run := Rect(Area.Left, Area.Top, Area.Right, Area.Top + Steps + 1);
|
||
EraseLine;
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
begin
|
||
Result := True;
|
||
if StepSize > 0 then
|
||
begin
|
||
SecondaryStepSize := 0;
|
||
with TToggleAnimationData(Data^) do
|
||
begin
|
||
if Mode1 <> tamNoScroll then
|
||
begin
|
||
if Mode1 = tamScrollUp then
|
||
DoScrollUp(DC, Brush, R1, StepSize)
|
||
else
|
||
DoScrollDown(DC, Brush, R1, StepSize);
|
||
|
||
if (Mode2 <> tamNoScroll) and (ScaleFactor > 0) then
|
||
begin
|
||
// As this routine is able to scroll two independent areas at once, the missing StepSize is
|
||
// computed in that case. To ensure the maximal accuracy the rounding error is accumulated.
|
||
SecondaryStepSize := Round((StepSize + MissedSteps) * ScaleFactor);
|
||
MissedSteps := MissedSteps + StepSize * ScaleFactor - SecondaryStepSize;
|
||
end;
|
||
end
|
||
else
|
||
SecondaryStepSize := StepSize;
|
||
|
||
if Mode2 <> tamNoScroll then
|
||
if Mode2 = tamScrollUp then
|
||
DoScrollUp(DC, Brush, R2, SecondaryStepSize)
|
||
else
|
||
DoScrollDown(DC, Brush, R2, SecondaryStepSize);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMColorChange(var Message: TLMessage);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'CMColorChange');{$endif}
|
||
if not (csLoading in ComponentState) then
|
||
begin
|
||
PrepareBitmaps(True, False);
|
||
if HandleAllocated then
|
||
Invalidate;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'CMColorChange');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMBiDiModeChanged(var Message: TLMessage);
|
||
|
||
begin
|
||
inherited;
|
||
|
||
if UseRightToLeftAlignment then
|
||
FEffectiveOffsetX := Integer(FRangeX) - ClientWidth + FOffsetX
|
||
else
|
||
FEffectiveOffsetX := -FOffsetX;
|
||
if FEffectiveOffsetX < 0 then
|
||
FEffectiveOffsetX := 0;
|
||
|
||
if toAutoBidiColumnOrdering in FOptions.FAutoOptions then
|
||
FHeader.FColumns.ReorderColumns(UseRightToLeftAlignment);
|
||
FHeader.Invalidate(nil);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'FEffectiveOffsetX after CMBidiModeChanged',FEffectiveOffsetX);{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMDenySubclassing(var Message: TLMessage);
|
||
|
||
// If a Windows XP Theme Manager component is used in the application it will try to subclass all controls which do not
|
||
// explicitly deny this. Virtual Treeview knows how to handle XP themes so it does not need subclassing.
|
||
|
||
begin
|
||
Message.Result := 1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoDragMsg(ADragMessage: TDragMessage; APosition: TPoint;
|
||
ADragObject: TDragObject; ATarget: TControl; ADocking: Boolean): LRESULT;
|
||
|
||
var
|
||
S: TObject;
|
||
KeyState: LongWord;
|
||
P: TPoint;
|
||
Formats: TFormatArray;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DoDragMsg');{$endif}
|
||
S := ADragObject;
|
||
Formats := nil;
|
||
|
||
// Let the ancestor handle dock operations.
|
||
if S is TDragDockObject then
|
||
inherited
|
||
else
|
||
begin
|
||
// We need an extra check for the control drag object as there might be other objects not derived from
|
||
// this class (e.g. TActionDragObject).
|
||
if not (tsUserDragObject in FStates) and (S is TDragControlObject) then
|
||
S := (S as TDragControlObject).Control;
|
||
case ADragMessage of
|
||
dmDragEnter, dmDragLeave, dmDragMove:
|
||
begin
|
||
if ADragMessage = dmDragEnter then
|
||
DoStateChange([tsVCLDragging]);
|
||
if ADragMessage = dmDragLeave then
|
||
DoStateChange([], [tsVCLDragging]);
|
||
|
||
if ADragMessage = dmDragMove then
|
||
with ScreenToClient(APosition) do
|
||
DoAutoScroll(X, Y);
|
||
|
||
KeyState := 0;
|
||
// Alt key will be queried by the KeysToShiftState function in DragOver.
|
||
if GetKeyState(VK_SHIFT) < 0 then
|
||
KeyState := KeyState or MK_SHIFT;
|
||
if GetKeyState(VK_CONTROL) < 0 then
|
||
KeyState := KeyState or MK_CONTROL;
|
||
|
||
// Allowed drop effects are simulated for VCL dd.
|
||
FVCLDragEffect := DROPEFFECT_MOVE or DROPEFFECT_COPY;
|
||
DragOver(S, KeyState, TDragState(ADragMessage), APosition, FVCLDragEffect);
|
||
Result := LRESULT(FVCLDragEffect);
|
||
FLastVCLDragTarget := FDropTargetNode;
|
||
if (ADragMessage = dmDragLeave) and Assigned(FDropTargetNode) then
|
||
begin
|
||
InvalidateNode(FDropTargetNode);
|
||
FDropTargetNode := nil;
|
||
end;
|
||
end;
|
||
dmDragDrop:
|
||
begin
|
||
KeyState := 0;
|
||
// Alt key will be queried by the KeysToShiftState function in DragOver
|
||
if GetKeyState(VK_SHIFT) < 0 then
|
||
KeyState := KeyState or MK_SHIFT;
|
||
if GetKeyState(VK_CONTROL) < 0 then
|
||
KeyState := KeyState or MK_CONTROL;
|
||
|
||
// allowed drop effects are simulated for VCL dd,
|
||
// replace target node with cached node from other VCL dd messages
|
||
if Assigned(FDropTargetNode) then
|
||
InvalidateNode(FDropTargetNode);
|
||
FDropTargetNode := FLastVCLDragTarget;
|
||
P := ScreenToClient(APosition);
|
||
DoDragDrop(S, nil, Formats, KeysToShiftState(KeyState), P, FVCLDragEffect, FLastDropMode);
|
||
if Assigned(FDropTargetNode) then
|
||
begin
|
||
InvalidateNode(FDropTargetNode);
|
||
FDropTargetNode := nil;
|
||
end;
|
||
end;
|
||
dmFindTarget:
|
||
begin
|
||
Result := LRESULT(ControlAtPos(ScreenToClient(APosition), False));
|
||
if Result = 0 then
|
||
Result := LRESULT(Self);
|
||
|
||
// This is a reliable place to check whether VCL drag has
|
||
// really begun.
|
||
if tsVCLDragPending in FStates then
|
||
DoStateChange([tsVCLDragging], [tsVCLDragPending, tsEditPending, tsClearPending]);
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DoDragMsg');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMFontChanged(var Message: TLMessage);
|
||
|
||
var
|
||
HeaderMessage: TLMessage;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
if not (csLoading in ComponentState) then
|
||
PrepareBitmaps(True, False);
|
||
|
||
HeaderMessage.Msg := CM_PARENTFONTCHANGED;
|
||
HeaderMessage.WParam := 0;
|
||
HeaderMessage.LParam := 0;
|
||
HeaderMessage.Result := 0;
|
||
FHeader.HandleMessage(HeaderMessage);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMHintShow(var Message: TCMHintShow);
|
||
|
||
// Determines hint message (tooltip) and out-of-hint rect.
|
||
// Note: A special handling is needed here because we cannot pass wide strings back to the caller.
|
||
// I had to introduce the hint data record anyway so we can use this to pass the hint string.
|
||
// We still need to set a dummy hint string in the message to make the VCL showing the hint window.
|
||
|
||
var
|
||
NodeRect: TRect;
|
||
SpanColumn,
|
||
Dummy,
|
||
ColLeft,
|
||
ColRight: Integer;
|
||
HitInfo: THitInfo;
|
||
ShowOwnHint: Boolean;
|
||
IsFocusedOrEditing: Boolean;
|
||
ParentForm: TCustomForm;
|
||
BottomRightCellContentMargin: TPoint;
|
||
LineBreakStyle: TVTTooltipLineBreakStyle;
|
||
begin
|
||
with Message do
|
||
begin
|
||
Result := 1;
|
||
|
||
if PtInRect(FLastHintRect, HintInfo.CursorPos) then
|
||
Exit;
|
||
|
||
// Determine node for which to show hint/tooltip.
|
||
with HintInfo^ do
|
||
GetHitTestInfoAt(CursorPos.X, CursorPos.Y, True, HitInfo);
|
||
|
||
// Make sure a hint is only shown if the tree or at least its parent form is active.
|
||
// Active editing is ok too as long as we don't want the hint for the current edit node.
|
||
if IsEditing then
|
||
IsFocusedOrEditing := HitInfo.HitNode <> FFocusedNode
|
||
else
|
||
begin
|
||
IsFocusedOrEditing := Focused;
|
||
ParentForm := GetParentForm(Self);
|
||
if Assigned(ParentForm) then
|
||
IsFocusedOrEditing := ParentForm.Focused or Application.Active;
|
||
end;
|
||
|
||
if (GetCapture = 0) and ShowHint and not (Dragging or IsMouseSelecting) and ([tsScrolling] * FStates = []) and
|
||
(FHeader.States = []) and IsFocusedOrEditing then
|
||
begin
|
||
with HintInfo^ do
|
||
begin
|
||
Result := 0;
|
||
ShowOwnHint := False;
|
||
|
||
// First check whether there is a header hint to show.
|
||
if FHeader.UseColumns and (hoShowHint in FHeader.FOptions) and FHeader.InHeader(CursorPos) then
|
||
begin
|
||
CursorRect := FHeaderRect;
|
||
// Convert the cursor rectangle into real client coordinates.
|
||
OffsetRect(CursorRect, 0, -Integer(FHeader.FHeight));
|
||
HitInfo.HitColumn := FHeader.FColumns.GetColumnAndBounds(CursorPos, CursorRect.Left, CursorRect.Right);
|
||
// align the vertical hint position on the bottom bound of the header, but
|
||
// avoid overlapping of mouse cursor and hint
|
||
HintPos.Y := Max(HintPos.Y, ClientToScreen(Point(0, CursorRect.Bottom)).Y);
|
||
// Note: the test for the left mouse button in ControlState might cause problems whenever the VCL does not
|
||
// realize when the button is released. This, for instance, happens when doing OLE drag'n drop and
|
||
// cancel this with ESC.
|
||
if (HitInfo.HitColumn > -1) and not (csLButtonDown in ControlState) then
|
||
begin
|
||
FHintData.DefaultHint := FHeader.FColumns[HitInfo.HitColumn].FHint;
|
||
if FHintData.DefaultHint <> '' then
|
||
ShowOwnHint := True
|
||
else
|
||
Result := 1;
|
||
end
|
||
else
|
||
Result := 1;
|
||
end
|
||
else
|
||
begin
|
||
// Default mode is handled as would the tree be a usual VCL control (no own hint window necessary).
|
||
if FHintMode = hmDefault then
|
||
HintStr := GetShortHint(Hint)
|
||
else
|
||
begin
|
||
if Assigned(HitInfo.HitNode) and (HitInfo.HitColumn > InvalidColumn) then
|
||
begin
|
||
// A draw tree should only display a hint when at least its OnGetHintSize
|
||
// event handler is assigned.
|
||
if Self is TCustomVirtualDrawTree then
|
||
begin
|
||
FHintData.HintRect := Rect(0, 0, 0, 0);
|
||
with Self as TCustomVirtualDrawTree do
|
||
DoGetHintSize(HitInfo.HitNode, HitInfo.HitColumn, FHintData.HintRect);
|
||
ShowOwnHint := not IsRectEmpty(FHintData.HintRect);
|
||
end
|
||
else
|
||
// For string trees a decision about showing the hint or not is based
|
||
// on the hint string (if it is empty then no hint is shown).
|
||
ShowOwnHint := True;
|
||
|
||
if ShowOwnHint then
|
||
begin
|
||
if HitInfo.HitColumn > NoColumn then
|
||
begin
|
||
FHeader.FColumns.GetColumnBounds(HitInfo.HitColumn, ColLeft, ColRight);
|
||
// The right column border might be extended if column spanning is enabled.
|
||
if toAutoSpanColumns in FOptions.FAutoOptions then
|
||
begin
|
||
SpanColumn := HitInfo.HitColumn;
|
||
repeat
|
||
Dummy := FHeader.FColumns.GetNextVisibleColumn(SpanColumn);
|
||
if (Dummy = InvalidColumn) or not ColumnIsEmpty(HitInfo.HitNode, Dummy) then
|
||
Break;
|
||
SpanColumn := Dummy;
|
||
until False;
|
||
if SpanColumn <> HitInfo.HitColumn then
|
||
FHeader.FColumns.GetColumnBounds(SpanColumn, Dummy, ColRight);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
ColLeft := 0;
|
||
ColRight := ClientWidth;
|
||
end;
|
||
|
||
FHintData.DefaultHint := '';
|
||
if FHintMode <> hmTooltip then
|
||
begin
|
||
// Node specific hint text.
|
||
CursorRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, False);
|
||
CursorRect.Left := ColLeft;
|
||
CursorRect.Right := ColRight;
|
||
// Align the vertical hint position on the bottom bound of the node, but
|
||
// avoid overlapping of mouse cursor and hint.
|
||
HintPos.Y := Max(HintPos.Y, ClientToScreen(CursorRect.BottomRight).Y) + 2;
|
||
end
|
||
else
|
||
begin
|
||
// Tool tip to show. This means the full caption of the node must be displayed.
|
||
if vsMultiline in HitInfo.HitNode.States then
|
||
begin
|
||
if hiOnItemLabel in HitInfo.HitPositions then
|
||
begin
|
||
ShowOwnHint := True;
|
||
NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, False);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, True, True);
|
||
BottomRightCellContentMargin := DoGetCellContentMargin(HitInfo.HitNode, HitInfo.HitColumn, ccmtBottomRightOnly);
|
||
|
||
ShowOwnHint := (HitInfo.HitColumn > InvalidColumn) and PtInRect(NodeRect, CursorPos) and
|
||
(CursorPos.X <= ColRight) and (CursorPos.X >= ColLeft) and
|
||
(
|
||
// Show hint also if the node text is partially out of the client area.
|
||
// "ColRight - 1", since the right column border is not part of this cell.
|
||
( (NodeRect.Right + BottomRightCellContentMargin.X) > Min(ColRight - 1, ClientWidth) ) or
|
||
(NodeRect.Left < Max(ColLeft, 0)) or
|
||
( (NodeRect.Bottom + BottomRightCellContentMargin.Y) > ClientHeight ) or
|
||
(NodeRect.Top < 0)
|
||
);
|
||
end;
|
||
|
||
if ShowOwnHint then
|
||
begin
|
||
// Node specific hint text given will be retrieved when needed.
|
||
FHintData.DefaultHint := '';
|
||
HintPos := ClientToScreen(Point(NodeRect.Left, NodeRect.Top));
|
||
CursorRect := NodeRect;
|
||
end
|
||
else
|
||
// nothing to show
|
||
Result := 1;
|
||
end;
|
||
end
|
||
else
|
||
Result := 1; // Avoid hint if this is a draw tree returning an empty hint rectangle.
|
||
end
|
||
else
|
||
begin
|
||
// No node so fall back to control's hint (if indicated) or show nothing.
|
||
if FHintMode = hmHintAndDefault then
|
||
HintStr := GetShortHint(Hint)
|
||
else
|
||
Result := 1;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcHint], 'ShowOwnHint: %s Result: %d', [BoolToStr(ShowOwnHint, True), Result]);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcHint], 'CursorRect', CursorRect);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcHint], 'CursorPos', CursorPos);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcHint], 'HintMaxWidth', HintMaxWidth);{$endif}
|
||
// If hint must be show and is not the control's hint then get the hint
|
||
// from the node or from the DefaultHint
|
||
if ShowOwnHint and (Result = 0) then
|
||
begin
|
||
LineBreakStyle := hlbDefault;
|
||
FLastHintRect := CursorRect;
|
||
if Length(FHintData.DefaultHint) > 0 then
|
||
HintStr := FHintData.DefaultHint
|
||
else
|
||
if FHintMode = hmToolTip then
|
||
HintStr := DoGetNodeToolTip(HitInfo.HitNode, HitInfo.HitColumn, LineBreakStyle)
|
||
else
|
||
HintStr := DoGetNodeHint(HitInfo.HitNode, HitInfo.HitColumn, LineBreakStyle);
|
||
// Determine actual line break style depending on what was returned by the methods and what's in the node.
|
||
if (LineBreakStyle = hlbDefault) and Assigned(HitInfo.HitNode)
|
||
and (vsMultiline in HitInfo.HitNode.States) then
|
||
LineBreakStyle := hlbForceMultiLine;
|
||
if LineBreakStyle = hlbForceMultiLine then
|
||
begin
|
||
// NodeRect is already calculated for ToolTip
|
||
if FHintMode <> hmTooltip then
|
||
NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, False);
|
||
HintMaxWidth := NodeRect.Right - NodeRect.Left;
|
||
end;
|
||
|
||
HintWindowClass := GetHintWindowClass;
|
||
FHintData.Tree := Self;
|
||
FHintData.Column := HitInfo.HitColumn;
|
||
FHintData.Node := HitInfo.HitNode;
|
||
FHintData.HintInfo := HintInfo;
|
||
HintData := @FHintData;
|
||
end
|
||
else
|
||
FLastHintRect := Rect(0, 0, 0, 0);
|
||
end;
|
||
|
||
// Remind that a hint is about to show.
|
||
if Result = 0 then
|
||
DoStateChange([tsHint])
|
||
else
|
||
DoStateChange([], [tsHint]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMMouseLeave(var Message: TLMessage);
|
||
|
||
var
|
||
LeaveStates: TVirtualTreeStates;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'CMMouseLeave');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcMessages],'FCurrentHotNode',hexStr(FCurrentHotNode));{$endif}
|
||
// Reset the last used hint rectangle in case the mouse enters the window within the bounds
|
||
if Assigned(FHintData.Tree) then
|
||
FHintData.Tree.FLastHintRect := Rect(0, 0, 0, 0);
|
||
|
||
LeaveStates := [tsHint];
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates = [] then
|
||
begin
|
||
KillTimer(Handle, ScrollTimer);
|
||
LeaveStates := LeaveStates + [tsScrollPending, tsScrolling];
|
||
end;
|
||
DoStateChange([], LeaveStates);
|
||
if Assigned(FCurrentHotNode) then
|
||
begin
|
||
DoHotChange(FCurrentHotNode, nil);
|
||
if (toHotTrack in FOptions.PaintOptions) or (toCheckSupport in FOptions.FMiscOptions) then
|
||
InvalidateNode(FCurrentHotNode);
|
||
FCurrentHotNode := nil;
|
||
end;
|
||
|
||
Header.FColumns.FDownIndex := NoColumn;
|
||
Header.FColumns.FHoverIndex := NoColumn;
|
||
|
||
inherited CMMouseLeave(Message);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'CMMouseLeave');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CMMouseWheel(var Message: TLMMouseEvent);
|
||
|
||
var
|
||
ScrollAmount: Integer;
|
||
ScrollLines: DWORD;
|
||
RTLFactor: Integer;
|
||
WheelFactor: Double;
|
||
|
||
begin
|
||
//todo: rename to WM*
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcScroll],'CMMouseWheel');{$endif}
|
||
StopWheelPanning;
|
||
|
||
inherited WMMouseWheel(Message);
|
||
|
||
if Message.Result = 0 then
|
||
begin
|
||
with Message do
|
||
begin
|
||
Result := 1;
|
||
WheelFactor := WheelDelta / WHEEL_DELTA;
|
||
if (FRangeY > Cardinal(ClientHeight)) and (not (ssShift in State)) then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcScroll],'Scroll Vertical - WheelDelta', WheelDelta);{$endif}
|
||
// Scroll vertically if there's something to scroll...
|
||
if ssCtrlOS in State then
|
||
ScrollAmount := Trunc(WheelFactor * ClientHeight)
|
||
else
|
||
begin
|
||
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @ScrollLines, 0);
|
||
if ScrollLines = WHEEL_PAGESCROLL then
|
||
ScrollAmount := Integer(Trunc(WheelFactor * ClientHeight))
|
||
else
|
||
ScrollAmount := Integer(Trunc(WheelFactor * ScrollLines * FDefaultNodeHeight));
|
||
end;
|
||
SetOffsetY(FOffsetY + ScrollAmount);
|
||
end
|
||
else
|
||
begin
|
||
// ...else scroll horizontally if there's something to scroll.
|
||
if UseRightToLeftAlignment then
|
||
RTLFactor := -1
|
||
else
|
||
RTLFactor := 1;
|
||
|
||
if ssCtrlOS in State then
|
||
ScrollAmount := Trunc(WheelFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth))
|
||
else
|
||
begin
|
||
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @ScrollLines, 0);
|
||
ScrollAmount := Trunc(WheelFactor * ScrollLines * FHeader.Columns.GetScrollWidth);
|
||
end;
|
||
SetOffsetX(FOffsetX + RTLFactor * ScrollAmount);
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcScroll],'CMMouseWheel');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$ifdef EnableNativeTVM}
|
||
|
||
procedure TBaseVirtualTree.TVMGetItem(var Message: TLMessage);
|
||
|
||
// Screen reader support function. The method returns information about a particular node.
|
||
|
||
const
|
||
StateMask = TVIS_STATEIMAGEMASK or TVIS_OVERLAYMASK or TVIS_EXPANDED or TVIS_DROPHILITED or TVIS_CUT or
|
||
TVIS_SELECTED or TVIS_FOCUSED;
|
||
|
||
var
|
||
Item: PTVItemEx;
|
||
Node: PVirtualNode;
|
||
Ghosted: Boolean;
|
||
ImageIndex: Integer;
|
||
R: TRect;
|
||
Text: String;
|
||
{$ifndef UNICODE}
|
||
ANSIText: ANSIString;
|
||
{$endif}
|
||
|
||
begin
|
||
// We can only return valid data if a nodes reference is given.
|
||
Item := Pointer(Message.LParam);
|
||
Message.Result := Ord(((Item.mask and TVIF_HANDLE) <> 0) and Assigned(Item.hItem));
|
||
if Message.Result = 1 then
|
||
begin
|
||
Node := Pointer(Item.hItem);
|
||
// Child count requested?
|
||
if (Item.mask and TVIF_CHILDREN) <> 0 then
|
||
Item.cChildren := Node.ChildCount;
|
||
// Index for normal image requested?
|
||
if (Item.mask and TVIF_IMAGE) <> 0 then
|
||
begin
|
||
Item.iImage := -1;
|
||
DoGetImageIndex(Node, ikNormal, -1, Ghosted, Item.iImage);
|
||
end;
|
||
// Index for selected image requested?
|
||
if (Item.mask and TVIF_SELECTEDIMAGE) <> 0 then
|
||
begin
|
||
Item.iSelectedImage := -1;
|
||
DoGetImageIndex(Node, ikSelected, -1, Ghosted, Item.iSelectedImage);
|
||
end;
|
||
// State info requested?
|
||
if (Item.mask and TVIF_STATE) <> 0 then
|
||
begin
|
||
// Everything, which is possible is returned.
|
||
Item.stateMask := StateMask;
|
||
Item.state := 0;
|
||
if Node = FFocusedNode then
|
||
Item.state := Item.state or TVIS_FOCUSED;
|
||
if vsSelected in Node.States then
|
||
Item.state := Item.state or TVIS_SELECTED;
|
||
if vsCutOrCopy in Node.States then
|
||
Item.state := Item.state or TVIS_CUT;
|
||
if Node = FDropTargetNode then
|
||
Item.state := Item.state or TVIS_DROPHILITED;
|
||
if vsExpanded in Node.States then
|
||
Item.state := Item.state or TVIS_EXPANDED;
|
||
|
||
// Construct state image and overlay image indices. They are one based, btw.
|
||
// and zero means there is no image.
|
||
ImageIndex := -1;
|
||
DoGetImageIndex(Node, ikState, -1, Ghosted, ImageIndex);
|
||
Item.state := Item.state or Byte(IndexToStateImageMask(ImageIndex + 1));
|
||
ImageIndex := -1;
|
||
DoGetImageIndex(Node, ikOverlay, -1, Ghosted, ImageIndex);
|
||
Item.state := Item.state or Byte(IndexToOverlayMask(ImageIndex + 1));
|
||
end;
|
||
// Node caption requested?
|
||
if (Item.mask and TVIF_TEXT) <> 0 then
|
||
begin
|
||
GetTextInfo(Node, -1, Font, R, Text);
|
||
|
||
{$ifdef UNICODE}
|
||
StrLCopy(Item.pszText, PChar(Text), Item.cchTextMax - 1);
|
||
Item.pszText[Length(Text)] := #0;
|
||
{$else}
|
||
// Convert the Unicode implicitely to ANSI using the current locale.
|
||
ANSIText := Text;
|
||
StrLCopy(Item.pszText, PChar(ANSIText), Item.cchTextMax - 1);
|
||
Item.pszText[Length(ANSIText)] := #0;
|
||
{$endif}
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.TVMGetItemRect(var Message: TLMessage);
|
||
|
||
// Screen read support function. This method returns a node's display rectangle.
|
||
|
||
var
|
||
TextOnly: Boolean;
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
// The lparam member is used two-way. On enter it contains a pointer to the item (node).
|
||
// On exit it is to be considered as pointer to a rectangle structure.
|
||
Node := Pointer(Pointer(Message.LParam)^);
|
||
Message.Result := Ord(IsVisible[Node]);
|
||
if Message.Result <> 0 then
|
||
begin
|
||
TextOnly := Message.WParam <> 0;
|
||
PRect(Message.LParam)^ := GetDisplayRect(Node, -1, TextOnly);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.TVMGetNextItem(var Message: TLMessage);
|
||
|
||
// Screen read support function. This method returns a node depending on the requested case.
|
||
|
||
var
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
// Start with a nil result.
|
||
Message.Result := 0;
|
||
Node := Pointer(Message.LParam);
|
||
case Message.WParam of
|
||
TVGN_CARET:
|
||
Message.Result := Integer(FFocusedNode);
|
||
TVGN_CHILD:
|
||
if Assigned(Node) then
|
||
Message.Result := Integer(GetFirstChild(Node));
|
||
TVGN_DROPHILITE:
|
||
Message.Result := Integer(FDropTargetNode);
|
||
TVGN_FIRSTVISIBLE:
|
||
Message.Result := Integer(GetFirstVisible(nil, True));
|
||
TVGN_LASTVISIBLE:
|
||
Message.Result := Integer(GetLastVisible(nil, True));
|
||
TVGN_NEXT:
|
||
if Assigned(Node) then
|
||
Message.Result := Integer(GetNextSibling(Node));
|
||
TVGN_NEXTVISIBLE:
|
||
if Assigned(Node) then
|
||
Message.Result := Integer(GetNextVisible(Node, True));
|
||
TVGN_PARENT:
|
||
if Assigned(Node) and (Node <> FRoot) and (Node.Parent <> FRoot) then
|
||
Message.Result := Integer(Node.Parent);
|
||
TVGN_PREVIOUS:
|
||
if Assigned(Node) then
|
||
Message.Result := Integer(GetPreviousSibling(Node));
|
||
TVGN_PREVIOUSVISIBLE:
|
||
if Assigned(Node) then
|
||
Message.Result := Integer(GetPreviousVisible(Node, True));
|
||
TVGN_ROOT:
|
||
Message.Result := Integer(GetFirst);
|
||
end;
|
||
end;
|
||
|
||
{$endif}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMCancelMode(var Message: TLMNoParams);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMCancelMode');{$endif}
|
||
// Clear any transient state.
|
||
KillTimer(Handle, ExpandTimer);
|
||
KillTimer(Handle, EditTimer);
|
||
KillTimer(Handle, ScrollTimer);
|
||
KillTimer(Handle, SearchTimer);
|
||
FSearchBuffer := '';
|
||
FLastSearchNode := nil;
|
||
|
||
DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending, tsDrawSelecting,
|
||
tsDrawSelPending, tsIncrementalSearching]);
|
||
//lcl does not has a inherited procedure
|
||
//inherited WMCancelMode(Message);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMCancelMode');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMChangeState(var Message: TLMessage);
|
||
|
||
var
|
||
EnterStates,
|
||
LeaveStates: TVirtualTreeStates;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMChangeState');{$endif}
|
||
EnterStates := [];
|
||
if csStopValidation in TChangeStates(Byte(Message.WParam)) then
|
||
Include(EnterStates, tsStopValidation);
|
||
if csUseCache in TChangeStates(Byte(Message.WParam)) then
|
||
Include(EnterStates, tsUseCache);
|
||
if csValidating in TChangeStates(Byte(Message.WParam)) then
|
||
Include(EnterStates, tsValidating);
|
||
if csValidationNeeded in TChangeStates(Byte(Message.WParam)) then
|
||
Include(EnterStates, tsValidationNeeded);
|
||
|
||
LeaveStates := [];
|
||
if csStopValidation in TChangeStates(Byte(Message.LParam)) then
|
||
Include(LeaveStates, tsStopValidation);
|
||
if csUseCache in TChangeStates(Byte(Message.LParam)) then
|
||
Include(LeaveStates, tsUseCache);
|
||
if csValidating in TChangeStates(Byte(Message.LParam)) then
|
||
Include(LeaveStates, tsValidating);
|
||
if csValidationNeeded in TChangeStates(Byte(Message.LParam)) then
|
||
Include(LeaveStates, tsValidationNeeded);
|
||
|
||
DoStateChange(EnterStates, LeaveStates);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMChangeState');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMChar(var Message: TLMChar);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMChar');{$endif}
|
||
if tsIncrementalSearchPending in FStates then
|
||
begin
|
||
HandleIncrementalSearch(Message.CharCode);
|
||
DoStateChange([], [tsIncrementalSearchPending]);
|
||
end;
|
||
|
||
inherited WMChar(Message);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMChar');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMContextMenu(var Message: TLMContextMenu);
|
||
|
||
// This method is called when a popup menu is about to be displayed.
|
||
// We have to cancel some pending states here to avoid interferences.
|
||
//lcl: handle mouse up here because MouseUp is not called when popup is show
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMContextMenu');{$endif}
|
||
DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending]);
|
||
{$ifdef ContextMenuBeforeMouseUp}
|
||
if Assigned(PopupMenu) then
|
||
begin
|
||
if FHeader.FStates = [] then
|
||
begin
|
||
Application.CancelHint;
|
||
if IsMouseSelecting then
|
||
begin
|
||
// Reset selection state already here, before the inherited handler opens the default menu.
|
||
DoStateChange([], [tsDrawSelecting, tsDrawSelPending]);
|
||
Invalidate;
|
||
end;
|
||
inherited WMContextMenu(Message);
|
||
if (toRightClickSelect in FOptions.FSelectionOptions) then
|
||
begin
|
||
// get information about the hit
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseUp(0, HitInfo);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
inherited WMContextMenu(Message);
|
||
{$else}
|
||
if not (tsPopupMenuShown in FStates) then
|
||
inherited WMContextMenu(Message);
|
||
{$endif}
|
||
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMContextMenu');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMCopy(var Message: TLMNoParams);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMCopy');{$endif}
|
||
CopyToClipboard;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMCopy');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMCut(var Message: TLMNoParams);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMCut');{$endif}
|
||
CutToClipboard;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMCut');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMEnable(var Message: TLMNoParams);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMEnable');{$endif}
|
||
//LCL does not has inherited WMEnable
|
||
//inherited WMEnable(Message);
|
||
RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN);
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMEnable');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMEraseBkgnd(var Message: TLMEraseBkgnd);
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcEraseBkgnd],'WMEraseBkgnd');{$endif}
|
||
Message.Result := 1;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcEraseBkgnd],'WMEraseBkgnd');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMGetDlgCode(var Message: TLMNoParams);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcMessages],'WMGetDlgCode');{$endif}
|
||
Message.Result := DLGC_WANTCHARS or DLGC_WANTARROWS;
|
||
if FWantTabs then
|
||
Message.Result := Message.Result or DLGC_WANTTAB;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$ifdef EnableAccessible}
|
||
procedure TBaseVirtualTree.WMGetObject(var Message: TLMessage);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMGetObject');{$endif}
|
||
if GetAccessibilityFactory <> nil then
|
||
begin
|
||
// Create the IAccessibles for the tree view and tree view items, if necessary.
|
||
if FAccessible = nil then
|
||
FAccessible := GetAccessibilityFactory.CreateIAccessible(Self);
|
||
if FAccessibleItem = nil then
|
||
FAccessibleItem := GetAccessibilityFactory.CreateIAccessible(Self);
|
||
if Cardinal(Message.LParam) = OBJID_CLIENT then
|
||
if Assigned(Accessible) then
|
||
Message.Result := LresultFromObject(IID_IAccessible, Message.WParam, FAccessible)
|
||
else
|
||
Message.Result := 0;
|
||
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMGetObject');{$endif}
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMHScroll(var Message: TLMHScroll);
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
function GetRealScrollPosition: Integer;
|
||
|
||
var
|
||
SI: TScrollInfo;
|
||
Code: Integer;
|
||
|
||
begin
|
||
SI.cbSize := SizeOf(TScrollInfo);
|
||
SI.fMask := SIF_TRACKPOS;
|
||
Code := SB_HORZ;
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_GetScrollInfo(Handle, Code, SI);
|
||
{$else}
|
||
GetScrollInfo(Handle, Code, SI);
|
||
{$endif UseFlatScrollbars}
|
||
Result := SI.nTrackPos;
|
||
end;
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
var
|
||
RTLFactor: Integer;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMHScroll');{$endif}
|
||
if UseRightToLeftAlignment then
|
||
RTLFactor := -1
|
||
else
|
||
RTLFactor := 1;
|
||
|
||
case Message.ScrollCode of
|
||
SB_BOTTOM:
|
||
SetOffsetX(-Integer(FRangeX));
|
||
SB_ENDSCROLL:
|
||
begin
|
||
DoStateChange([], [tsThumbTracking]);
|
||
// avoiding to adjust the vertical scroll position while tracking makes it much smoother
|
||
// but we need to adjust the final position here then
|
||
UpdateHorizontalScrollBar(False);
|
||
end;
|
||
SB_LINELEFT:
|
||
SetOffsetX(FOffsetX + RTLFactor * FScrollBarOptions.FIncrementX);
|
||
SB_LINERIGHT:
|
||
SetOffsetX(FOffsetX - RTLFactor * FScrollBarOptions.FIncrementX);
|
||
SB_PAGELEFT:
|
||
SetOffsetX(FOffsetX + RTLFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth));
|
||
SB_PAGERIGHT:
|
||
SetOffsetX(FOffsetX - RTLFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth));
|
||
SB_THUMBPOSITION,
|
||
SB_THUMBTRACK:
|
||
begin
|
||
DoStateChange([tsThumbTracking]);
|
||
{$if DEFINED(LCLQt) OR DEFINED(LCLCarbon)}
|
||
if UseRightToLeftAlignment then
|
||
SetOffsetX(-Integer(FRangeX) + ClientWidth + Message.Pos)
|
||
else
|
||
SetOffsetX(-Message.Pos);
|
||
{$else}
|
||
if UseRightToLeftAlignment then
|
||
SetOffsetX(-Integer(FRangeX) + ClientWidth + GetRealScrollPosition)
|
||
else
|
||
SetOffsetX(-GetRealScrollPosition);
|
||
{$endif}
|
||
end;
|
||
SB_TOP:
|
||
SetOffsetX(0);
|
||
end;
|
||
|
||
Message.Result := 0;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMHScroll');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMKeyDown(var Message: TLMKeyDown);
|
||
|
||
// Keyboard event handling for node focus, selection, node specific popup menus and help invokation.
|
||
// For a detailed description of every action done here read the help.
|
||
|
||
var
|
||
Shift: TShiftState;
|
||
Node, Temp,
|
||
LastFocused: PVirtualNode;
|
||
Offset: Integer;
|
||
ClearPending,
|
||
NeedInvalidate,
|
||
DoRangeSelect,
|
||
HandleMultiSelect: Boolean;
|
||
Context: Integer;
|
||
ParentControl: TWinControl;
|
||
R: TRect;
|
||
NewCheckState: TCheckState;
|
||
TempColumn,
|
||
NewColumn: TColumnIndex;
|
||
ActAsGrid: Boolean;
|
||
ForceSelection: Boolean;
|
||
NewWidth,
|
||
NewHeight: Integer;
|
||
RTLFactor: Integer;
|
||
|
||
// for tabulator handling
|
||
GetStartColumn: function(ConsiderAllowFocus: Boolean = False): TColumnIndex of object;
|
||
GetNextColumn: function(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex of object;
|
||
GetNextNode: TGetNextNodeProc;
|
||
|
||
KeyState: TKeyboardState;
|
||
Buffer: array[0..1] of Char;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMKeyDown');{$endif}
|
||
// Make form key preview work and let application modify the key if it wants this.
|
||
inherited WMKeyDown(Message);
|
||
|
||
with Message do
|
||
begin
|
||
Shift := KeyDataToShiftState(KeyData);
|
||
// Ask the application if the default key handling is desired.
|
||
if DoKeyAction(CharCode, Shift) then
|
||
begin
|
||
if (tsKeyCheckPending in FStates) and (CharCode <> VK_SPACE) then
|
||
begin
|
||
DoStateChange([], [tskeyCheckPending]);
|
||
FCheckNode.CheckState := UnpressedState[FCheckNode.CheckState];
|
||
RepaintNode(FCheckNode);
|
||
FCheckNode := nil;
|
||
end;
|
||
|
||
if CharCode in [VK_HOME, VK_END, VK_PRIOR, VK_NEXT, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_BACK, VK_TAB] then
|
||
begin
|
||
HandleMultiSelect := (ssShift in Shift) and (toMultiSelect in FOptions.FSelectionOptions) and not IsEditing;
|
||
|
||
// Flag to avoid range selection in case of single node advance.
|
||
DoRangeSelect := (CharCode in [VK_HOME, VK_END, VK_PRIOR, VK_NEXT]) and HandleMultiSelect and not IsEditing;
|
||
|
||
NeedInvalidate := DoRangeSelect or (FSelectionCount > 1);
|
||
ActAsGrid := toGridExtensions in FOptions.FMiscOptions;
|
||
ClearPending := (Shift = []) or (ActAsGrid and not (ssShift in Shift)) or
|
||
not (toMultiSelect in FOptions.FSelectionOptions) or (CharCode in [VK_TAB, VK_BACK]);
|
||
|
||
// Keep old focused node for range selection. Use a default node if none was focused until now.
|
||
LastFocused := FFocusedNode;
|
||
if (LastFocused = nil) and (Shift <> []) then
|
||
LastFocused := GetFirstVisible(nil, True);
|
||
|
||
// Set an initial range anchor if there is not yet one.
|
||
if FRangeAnchor = nil then
|
||
FRangeAnchor := GetFirstSelected;
|
||
if FRangeAnchor = nil then
|
||
FRangeAnchor := GetFirst;
|
||
|
||
if UseRightToLeftAlignment then
|
||
RTLFactor := -1
|
||
else
|
||
RTLFactor := 1;
|
||
|
||
// Determine new focused node.
|
||
case CharCode of
|
||
VK_HOME, VK_END:
|
||
begin
|
||
if (CharCode = VK_END) xor UseRightToLeftAlignment then
|
||
begin
|
||
GetStartColumn := FHeader.FColumns.GetLastVisibleColumn;
|
||
GetNextColumn := FHeader.FColumns.GetPreviousVisibleColumn;
|
||
GetNextNode := GetPreviousVisible;
|
||
Node := GetLastVisible(nil, True);
|
||
end
|
||
else
|
||
begin
|
||
GetStartColumn := FHeader.FColumns.GetFirstVisibleColumn;
|
||
GetNextColumn := FHeader.FColumns.GetNextVisibleColumn;
|
||
GetNextNode := GetNextVisible;
|
||
Node := GetFirstVisible(nil, True);
|
||
end;
|
||
|
||
// Advance to next/previous visible column.
|
||
if FHeader.UseColumns then
|
||
NewColumn := GetStartColumn
|
||
else
|
||
NewColumn := NoColumn;
|
||
// Find a column for the new/current node which can be focused.
|
||
// Make the 'DoFocusChanging' for finding a valid column
|
||
// identifiable from the 'DoFocusChanging' raised later on by
|
||
// "FocusedNode := Node;"
|
||
while (NewColumn > NoColumn) and not DoFocusChanging(FFocusedNode, FFocusedNode, FFocusedColumn, NewColumn) do
|
||
NewColumn := GetNextColumn(NewColumn);
|
||
if NewColumn > InvalidColumn then
|
||
begin
|
||
if (Shift = [ssCtrlOS]) and not ActAsGrid then
|
||
begin
|
||
ScrollIntoView(Node, toCenterScrollIntoView in FOptions.SelectionOptions,
|
||
not (toDisableAutoscrollOnFocus in FOptions.FAutoOptions));
|
||
if (CharCode = VK_HOME) and not UseRightToLeftAlignment then
|
||
SetOffsetX(0)
|
||
else
|
||
SetOffsetX(-MaxInt);
|
||
end
|
||
else
|
||
begin
|
||
if not ActAsGrid or (ssCtrlOS in Shift) then
|
||
FocusedNode := Node;
|
||
if ActAsGrid and not (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
FocusedColumn := NewColumn;
|
||
end;
|
||
end;
|
||
end;
|
||
VK_PRIOR:
|
||
if ssCtrlOS in Shift then
|
||
SetOffsetY(FOffsetY + ClientHeight)
|
||
else
|
||
begin
|
||
Offset := 0;
|
||
// If there's no focused node then just take the very first visible one.
|
||
if FFocusedNode = nil then
|
||
Node := GetFirstVisible(nil, True)
|
||
else
|
||
begin
|
||
// Go up as many nodes as comprise together a size of ClientHeight.
|
||
Node := FFocusedNode;
|
||
while True do
|
||
begin
|
||
Temp := GetPreviousVisible(Node, True);
|
||
NewHeight := NodeHeight[Node];
|
||
if (Temp = nil) or (Offset + NewHeight >= ClientHeight) then
|
||
Break;
|
||
Node := Temp;
|
||
Inc(Offset, NodeHeight[Node]);
|
||
end;
|
||
end;
|
||
FocusedNode := Node;
|
||
end;
|
||
VK_NEXT:
|
||
if ssCtrlOS in Shift then
|
||
SetOffsetY(FOffsetY - ClientHeight)
|
||
else
|
||
begin
|
||
Offset := 0;
|
||
// If there's no focused node then just take the very last one.
|
||
if FFocusedNode = nil then
|
||
Node := GetLastVisible(nil, True)
|
||
else
|
||
begin
|
||
// Go up as many nodes as comprise together a size of ClientHeight.
|
||
Node := FFocusedNode;
|
||
while True do
|
||
begin
|
||
Temp := GetNextVisible(Node, True);
|
||
NewHeight := NodeHeight[Node];
|
||
if (Temp = nil) or (Offset + NewHeight >= ClientHeight) then
|
||
Break;
|
||
Node := Temp;
|
||
Inc(Offset, NewHeight);
|
||
end;
|
||
end;
|
||
FocusedNode := Node;
|
||
end;
|
||
VK_UP:
|
||
begin
|
||
// scrolling without selection change
|
||
if ssCtrlOS in Shift then
|
||
SetOffsetY(FOffsetY + Integer(FDefaultNodeHeight))
|
||
else
|
||
begin
|
||
if FFocusedNode = nil then
|
||
Node := GetLastVisible(nil, True)
|
||
else
|
||
Node := GetPreviousVisible(FFocusedNode, True);
|
||
|
||
if Assigned(Node) then
|
||
begin
|
||
EndEditNode;
|
||
if HandleMultiSelect and (CompareNodePositions(LastFocused, FRangeAnchor) > 0) and
|
||
Assigned(FFocusedNode) then
|
||
RemoveFromSelection(FFocusedNode);
|
||
if FFocusedColumn <= NoColumn then
|
||
FFocusedColumn := FHeader.MainColumn;
|
||
FocusedNode := Node;
|
||
end
|
||
else
|
||
if Assigned(FFocusedNode) then
|
||
InvalidateNode(FFocusedNode);
|
||
end;
|
||
end;
|
||
VK_DOWN:
|
||
begin
|
||
// scrolling without selection change
|
||
if ssCtrlOS in Shift then
|
||
SetOffsetY(FOffsetY - Integer(FDefaultNodeHeight))
|
||
else
|
||
begin
|
||
if FFocusedNode = nil then
|
||
Node := GetFirstVisible(nil, True)
|
||
else
|
||
Node := GetNextVisible(FFocusedNode, True);
|
||
|
||
if Assigned(Node) then
|
||
begin
|
||
EndEditNode;
|
||
if HandleMultiSelect and (CompareNodePositions(LastFocused, FRangeAnchor) < 0) and
|
||
Assigned(FFocusedNode) then
|
||
RemoveFromSelection(FFocusedNode);
|
||
if FFocusedColumn <= NoColumn then
|
||
FFocusedColumn := FHeader.MainColumn;
|
||
FocusedNode := Node;
|
||
end
|
||
else
|
||
if Assigned(FFocusedNode) then
|
||
InvalidateNode(FFocusedNode);
|
||
end;
|
||
end;
|
||
VK_LEFT:
|
||
begin
|
||
// special handling
|
||
if ssCtrlOS in Shift then
|
||
SetOffsetX(FOffsetX + RTLFactor * FHeader.Columns.GetScrollWidth)
|
||
else
|
||
begin
|
||
// other special cases
|
||
Context := NoColumn;
|
||
if (toExtendedFocus in FOptions.FSelectionOptions) and (toGridExtensions in FOptions.FMiscOptions) then
|
||
begin
|
||
Context := FHeader.Columns.GetPreviousVisibleColumn(FFocusedColumn, True);
|
||
if Context > -1 then
|
||
FocusedColumn := Context
|
||
end
|
||
else
|
||
if Assigned(FFocusedNode) and (vsExpanded in FFocusedNode.States) and
|
||
(Shift = []) and (vsHasChildren in FFocusedNode.States) then
|
||
ToggleNode(FFocusedNode)
|
||
else
|
||
begin
|
||
if FFocusedNode = nil then
|
||
FocusedNode := GetFirstVisible(nil, True)
|
||
else
|
||
begin
|
||
if FFocusedNode.Parent <> FRoot then
|
||
Node := FFocusedNode.Parent
|
||
else
|
||
Node := nil;
|
||
if Assigned(Node) then
|
||
begin
|
||
if HandleMultiSelect then
|
||
begin
|
||
// and a third special case
|
||
if FFocusedNode.Index > 0 then
|
||
DoRangeSelect := True
|
||
else
|
||
if CompareNodePositions(Node, FRangeAnchor) > 0 then
|
||
RemoveFromSelection(FFocusedNode);
|
||
end;
|
||
FocusedNode := Node;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
VK_RIGHT:
|
||
begin
|
||
// special handling
|
||
if ssCtrlOS in Shift then
|
||
SetOffsetX(FOffsetX - RTLFactor * FHeader.Columns.GetScrollWidth)
|
||
else
|
||
begin
|
||
// other special cases
|
||
Context := NoColumn;
|
||
if (toExtendedFocus in FOptions.FSelectionOptions) and (toGridExtensions in FOptions.FMiscOptions) then
|
||
begin
|
||
Context := FHeader.Columns.GetNextVisibleColumn(FFocusedColumn, True);
|
||
if Context > -1 then
|
||
FocusedColumn := Context;
|
||
end
|
||
else
|
||
if Assigned(FFocusedNode) and not (vsExpanded in FFocusedNode.States) and
|
||
(Shift = []) and (vsHasChildren in FFocusedNode.States) then
|
||
ToggleNode(FFocusedNode)
|
||
else
|
||
begin
|
||
if FFocusedNode = nil then
|
||
FocusedNode := GetFirstVisible(nil, True)
|
||
else
|
||
begin
|
||
Node := GetFirstVisibleChild(FFocusedNode);
|
||
if Assigned(Node) then
|
||
begin
|
||
if HandleMultiSelect and (CompareNodePositions(Node, FRangeAnchor) < 0) then
|
||
RemoveFromSelection(FFocusedNode);
|
||
FocusedNode := Node;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
VK_BACK:
|
||
if tsIncrementalSearching in FStates then
|
||
DoStateChange([tsIncrementalSearchPending])
|
||
else
|
||
if Assigned(FFocusedNode) and (FFocusedNode.Parent <> FRoot) then
|
||
FocusedNode := FocusedNode.Parent;
|
||
VK_TAB:
|
||
if (toExtendedFocus in FOptions.FSelectionOptions) and FHeader.UseColumns then
|
||
begin
|
||
// In order to avoid duplicating source code just to change the direction
|
||
// we use function variables.
|
||
if ssShift in Shift then
|
||
begin
|
||
GetStartColumn := FHeader.FColumns.GetLastVisibleColumn;
|
||
GetNextColumn := FHeader.FColumns.GetPreviousVisibleColumn;
|
||
GetNextNode := GetPreviousVisible;
|
||
end
|
||
else
|
||
begin
|
||
GetStartColumn := FHeader.FColumns.GetFirstVisibleColumn;
|
||
GetNextColumn := FHeader.FColumns.GetNextVisibleColumn;
|
||
GetNextNode := GetNextVisible;
|
||
end;
|
||
|
||
// Advance to next/previous visible column/node.
|
||
Node := FFocusedNode;
|
||
NewColumn := GetNextColumn(FFocusedColumn, True);
|
||
repeat
|
||
// Find a column for the current node which can be focused.
|
||
while (NewColumn > NoColumn) and not DoFocusChanging(FFocusedNode, Node, FFocusedColumn, NewColumn) do
|
||
NewColumn := GetNextColumn(NewColumn, True);
|
||
|
||
if NewColumn > NoColumn then
|
||
begin
|
||
// Set new node and column in one go.
|
||
SetFocusedNodeAndColumn(Node, NewColumn);
|
||
Break;
|
||
end;
|
||
|
||
// No next column was accepted for the current node. So advance to next node and try again.
|
||
Node := GetNextNode(Node);
|
||
NewColumn := GetStartColumn;
|
||
until Node = nil;
|
||
end;
|
||
end;
|
||
|
||
// Clear old selection if required but take care to select the new focused node if it was not selected before.
|
||
ForceSelection := False;
|
||
if ClearPending and ((LastFocused <> FFocusedNode) or (FSelectionCount <> 1)) then
|
||
begin
|
||
ClearSelection;
|
||
ForceSelection := True;
|
||
end;
|
||
|
||
// Determine new selection anchor.
|
||
if Shift = [] then
|
||
begin
|
||
FRangeAnchor := FFocusedNode;
|
||
FLastSelectionLevel := GetNodeLevel(FFocusedNode);
|
||
end;
|
||
// Finally change the selection for a specific range of nodes.
|
||
if DoRangeSelect then
|
||
ToggleSelection(LastFocused, FFocusedNode);
|
||
|
||
// Make sure the new focused node is also selected.
|
||
if Assigned(FFocusedNode) and ((LastFocused <> FFocusedNode) or ForceSelection) then
|
||
AddToSelection(FFocusedNode);
|
||
|
||
// If a repaint is needed then paint the entire tree because of the ClearSelection call,
|
||
if NeedInvalidate then
|
||
Invalidate;
|
||
{$ifdef LCLGtk2}
|
||
//workaround for changing focus bug
|
||
if CharCode <> VK_TAB then
|
||
CharCode := 0;
|
||
{$endif}
|
||
end
|
||
else
|
||
begin
|
||
// Second chance for keys not directly concerned with selection changes.
|
||
|
||
// For +, -, /, * keys on the main keyboard (not numpad) there is no virtual key code defined.
|
||
// We have to do special processing to get them working too.
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
GetKeyboardState(KeyState);
|
||
// Avoid conversion to control characters. We have captured the control key state already in Shift.
|
||
KeyState[VK_CONTROL] := 0;
|
||
if ToASCII(Message.CharCode, (Message.KeyData shr 16) and 7, KeyState, @Buffer, 0) > 0 then
|
||
begin
|
||
case Buffer[0] of
|
||
'*':
|
||
CharCode := VK_MULTIPLY;
|
||
'+':
|
||
CharCode := VK_ADD;
|
||
'/':
|
||
CharCode := VK_DIVIDE;
|
||
'-':
|
||
CharCode := VK_SUBTRACT;
|
||
end;
|
||
end;
|
||
|
||
// According to http://www.it-faq.pl/mskb/99/337.HTM there is a problem with ToASCII when used in conjunction
|
||
// with dead chars. The article recommends to call ToASCII twice to restore a deleted flag in the key message
|
||
// structure under certain circumstances. It turned out it is best to always call ToASCII twice.
|
||
ToASCII(Message.CharCode, (Message.KeyData shr 16) and 7, KeyState, @Buffer, 0);
|
||
{$endif}
|
||
case CharCode of
|
||
VK_F2:
|
||
if (Shift = []) and Assigned(FFocusedNode) and CanEdit(FFocusedNode, FFocusedColumn) then
|
||
begin
|
||
FEditColumn := FFocusedColumn;
|
||
DoEdit;
|
||
end;
|
||
VK_ADD:
|
||
if not (tsIncrementalSearching in FStates) then
|
||
begin
|
||
if ssCtrlOS in Shift then
|
||
if {$ifdef ReverseFullExpandHotKey} not {$endif ReverseFullExpandHotKey} (ssShift in Shift) then
|
||
FullExpand
|
||
else
|
||
FHeader.AutoFitColumns
|
||
else
|
||
if Assigned(FFocusedNode) and not (vsExpanded in FFocusedNode.States) then
|
||
ToggleNode(FFocusedNode);
|
||
end
|
||
else
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
VK_SUBTRACT:
|
||
if not (tsIncrementalSearching in FStates) then
|
||
begin
|
||
if ssCtrlOS in Shift then
|
||
if {$ifdef ReverseFullExpandHotKey} not {$endif ReverseFullExpandHotKey} (ssShift in Shift) then
|
||
FullCollapse
|
||
else
|
||
FHeader.RestoreColumns
|
||
else
|
||
if Assigned(FFocusedNode) and (vsExpanded in FFocusedNode.States) then
|
||
ToggleNode(FFocusedNode);
|
||
end
|
||
else
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
VK_MULTIPLY:
|
||
if not (tsIncrementalSearching in FStates) then
|
||
begin
|
||
if Assigned(FFocusedNode) then
|
||
FullExpand(FFocusedNode);
|
||
end
|
||
else
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
VK_DIVIDE:
|
||
if not (tsIncrementalSearching in FStates) then
|
||
begin
|
||
if Assigned(FFocusedNode) then
|
||
FullCollapse(FFocusedNode);
|
||
end
|
||
else
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
VK_ESCAPE: // cancel actions currently in progress
|
||
begin
|
||
if IsMouseSelecting then
|
||
begin
|
||
DoStateChange([], [tsDrawSelecting, tsDrawSelPending]);
|
||
Invalidate;
|
||
end
|
||
//gtk1 does not like to free a component in KeyDown
|
||
{$ifndef LCLGtk}
|
||
else
|
||
if IsEditing then
|
||
CancelEditNode;
|
||
{$endif}
|
||
end;
|
||
VK_SPACE:
|
||
if (toCheckSupport in FOptions.FMiscOptions) and Assigned(FFocusedNode) and
|
||
(FFocusedNode.CheckType <> ctNone) then
|
||
begin
|
||
if (FStates * [tsKeyCheckPending, tsMouseCheckPending] = []) and Assigned(FFocusedNode) and
|
||
not (vsDisabled in FFocusedNode.States) then
|
||
begin
|
||
with FFocusedNode^ do
|
||
NewCheckState := DetermineNextCheckState(CheckType, CheckState);
|
||
if DoChecking(FFocusedNode, NewCheckState) then
|
||
begin
|
||
DoStateChange([tsKeyCheckPending]);
|
||
FCheckNode := FFocusedNode;
|
||
FPendingCheckState := NewCheckState;
|
||
FCheckNode.CheckState := PressedState[FCheckNode.CheckState];
|
||
RepaintNode(FCheckNode);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
VK_F1:
|
||
if Assigned(FOnGetHelpContext) then
|
||
begin
|
||
Context := 0;
|
||
if Assigned(FFocusedNode) then
|
||
begin
|
||
Node := FFocusedNode;
|
||
// Traverse the tree structure up to the root.
|
||
repeat
|
||
FOnGetHelpContext(Self, Node, 0, Context);
|
||
Node := Node.Parent;
|
||
until (Node = FRoot) or (Context <> 0);
|
||
end;
|
||
|
||
// If no help context could be found try the tree's one or its parent's contexts.
|
||
ParentControl := Self;
|
||
while Assigned(ParentControl) and (Context = 0) do
|
||
begin
|
||
Context := ParentControl.HelpContext;
|
||
ParentControl := ParentControl.Parent;
|
||
end;
|
||
if Context <> 0 then
|
||
Application.HelpContext(Context);
|
||
end;
|
||
VK_APPS:
|
||
if Assigned(FFocusedNode) then
|
||
begin
|
||
R := GetDisplayRect(FFocusedNode, FFocusedColumn, True);
|
||
Offset := DoGetNodeWidth(FFocusedNode, FFocusedColumn);
|
||
if FFocusedColumn >= 0 then
|
||
begin
|
||
if Offset > FHeader.Columns[FFocusedColumn].Width then
|
||
Offset := FHeader.Columns[FFocusedColumn].Width;
|
||
end
|
||
else
|
||
begin
|
||
if Offset > ClientWidth then
|
||
Offset := ClientWidth;
|
||
end;
|
||
DoPopupMenu(FFocusedNode, FFocusedColumn, Point(R.Left + Offset div 2, (R.Top + R.Bottom) div 2));
|
||
end;
|
||
Ord('a'), Ord('A'):
|
||
if ssCtrlOS in Shift then
|
||
SelectAll(True)
|
||
else
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
else
|
||
begin
|
||
// Use the key for incremental search.
|
||
// Since we are dealing with Unicode all the time there should be a more sophisticated way
|
||
// of checking for valid characters for incremental search.
|
||
// This is available but would require to include a significant amount of Unicode character
|
||
// properties, so we stick with the simple space check.
|
||
if (Shift * [ssCtrlOS, ssAlt] = []) and (CharCode >= 32) then
|
||
DoStateChange([tsIncrementalSearchPending]);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMKeyDown');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMKeyUp(var Message: TLMKeyUp);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMKeyUp');{$endif}
|
||
inherited WMKeyUp(Message);
|
||
|
||
case Message.CharCode of
|
||
VK_SPACE:
|
||
if tsKeyCheckPending in FStates then
|
||
begin
|
||
DoStateChange([], [tskeyCheckPending]);
|
||
if FCheckNode = FFocusedNode then
|
||
DoCheckClick(FCheckNode, FPendingCheckState);
|
||
InvalidateNode(FCheckNode);
|
||
FCheckNode := nil;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMKeyUp');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMKillFocus(var Msg: TLMKillFocus);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMKillFocus');{$endif}
|
||
inherited WMKillFocus(Msg);
|
||
|
||
// Remove hint if shown currently.
|
||
Application.CancelHint;
|
||
|
||
// Stop wheel panning if active.
|
||
StopWheelPanning;
|
||
|
||
// Don't let any timer continue if the tree is no longer the active control (except change timers).
|
||
KillTimer(Handle, ExpandTimer);
|
||
KillTimer(Handle, EditTimer);
|
||
KillTimer(Handle, ScrollTimer);
|
||
KillTimer(Handle, SearchTimer);
|
||
FSearchBuffer := '';
|
||
FLastSearchNode := nil;
|
||
|
||
DoStateChange([], [tsScrollPending, tsScrolling, tsEditPending, tsLeftButtonDown, tsRightButtonDown,
|
||
tsMiddleButtonDown, tsOLEDragPending, tsVCLDragPending, tsIncrementalSearching, tsNodeHeightTrackPending,
|
||
tsNodeHeightTracking]);
|
||
|
||
if (FSelectionCount > 0) or not (toGhostedIfUnfocused in FOptions.FPaintOptions) then
|
||
Invalidate
|
||
else
|
||
if Assigned(FFocusedNode) then
|
||
InvalidateNode(FFocusedNode);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMKillFocus');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMLButtonDblClk(var Message: TLMLButtonDblClk);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMLButtonDblClk');{$endif}
|
||
DoStateChange([tsLeftDblClick]);
|
||
|
||
inherited WMLButtonDblClk(Message);
|
||
|
||
// get information about the hit
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseDblClick(Message, HitInfo);
|
||
DoStateChange([], [tsLeftDblClick]);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMLButtonDblClk');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMLButtonDown(var Message: TLMLButtonDown);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMLButtonDown');{$endif}
|
||
DoStateChange([tsLeftButtonDown]);
|
||
inherited WMLButtonDown(Message);
|
||
|
||
// get information about the hit
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
if HitInfo.HitNode <> nil then
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader, lcMouseEvent],'WMLButtonDown - HitNode.Index', HitInfo.HitNode^.Index);{$endif}
|
||
HandleMouseDown(Message, HitInfo);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMLButtonDown');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMLButtonUp(var Message: TLMLButtonUp);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
DoStateChange([], [tsLeftButtonDown, tsNodeHeightTracking, tsNodeHeightTrackPending]);
|
||
|
||
// get information about the hit
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseUp(Message.Keys, HitInfo);
|
||
|
||
inherited WMLButtonUp(Message);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMLButtonUp');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMMButtonDblClk(var Message: TLMMButtonDblClk);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMMButtonDblClk');{$endif}
|
||
DoStateChange([tsMiddleDblClick]);
|
||
inherited WMMButtonDblClk(Message);
|
||
|
||
// get information about the hit
|
||
if toMiddleClickSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseDblClick(Message, HitInfo);
|
||
end;
|
||
DoStateChange([], [tsMiddleDblClick]);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMMButtonDblClk');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMMButtonDown(var Message: TLMMButtonDown);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMMButtonDown');{$endif}
|
||
DoStateChange([tsMiddleButtonDown]);
|
||
|
||
if FHeader.FStates = [] then
|
||
begin
|
||
inherited WMMButtonDown(Message);
|
||
|
||
// Start wheel panning or scrolling if not already active, allowed and scrolling is useful at all.
|
||
if (toWheelPanning in FOptions.FMiscOptions) and ([tsWheelScrolling, tsWheelPanning] * FStates = []) and
|
||
((Integer(FRangeX) > ClientWidth) or (Integer(FRangeY) > ClientHeight)) then
|
||
begin
|
||
FLastClickPos := SmallPointToPoint(Message.Pos);
|
||
StartWheelPanning(FLastClickPos);
|
||
end
|
||
else
|
||
begin
|
||
StopWheelPanning;
|
||
|
||
// Get information about the hit.
|
||
if toMiddleClickSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseDown(Message, HitInfo);
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMMButtonDown');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMMButtonUp(var Message: TLMMButtonUp);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMMButtonUp');{$endif}
|
||
DoStateChange([], [tsMiddleButtonDown]);
|
||
|
||
// If wheel panning/scrolling is active and the mouse has not yet been moved then the user starts wheel auto scrolling.
|
||
// Indicate this by removing the panning flag. Otherwise (the mouse has moved meanwhile) stop panning.
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then
|
||
begin
|
||
if tsWheelScrolling in FStates then
|
||
DoStateChange([], [tsWheelPanning])
|
||
else
|
||
StopWheelPanning;
|
||
end
|
||
else
|
||
if FHeader.FStates = [] then
|
||
begin
|
||
inherited WMMButtonUp(Message);
|
||
|
||
// get information about the hit
|
||
if toMiddleClickSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseUp(Message.Keys, HitInfo);
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod('WMMButtonUp');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$ifdef EnableNCFunctions}
|
||
|
||
procedure TBaseVirtualTree.WMNCCalcSize(var Message: TLMNCCalcSize);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMNCCalcSize');{$endif}
|
||
inherited WMNCCalcSize(Message);
|
||
|
||
with FHeader do
|
||
if hoVisible in FHeader.FOptions then
|
||
with Message.CalcSize_Params^ do
|
||
Inc(rgrc[0].Top, FHeight);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMNCCalcSize');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMNCHitTest(var Message: TWMNCHitTest);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMNCHitTest');{$endif}
|
||
inherited WMNCHitTest(Message);
|
||
if not (csDesigning in ComponentState) and (hoVisible in FHeader.FOptions) and
|
||
FHeader.InHeader(ScreenToClient(SmallPointToPoint(Message.Pos))) then
|
||
Message.Result := HTBORDER;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMNCHitTest');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMNCPaint(var Message: TRealWMNCPaint);
|
||
|
||
var
|
||
DC: HDC;
|
||
R: TRect;
|
||
Flags: DWORD;
|
||
{$ifdef ThemeSupport}
|
||
ExStyle: Integer;
|
||
TempRgn: HRGN;
|
||
BorderWidth,
|
||
BorderHeight: Integer;
|
||
{$endif ThemeSupport}
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMNCPaint');{$endif}
|
||
{$ifdef ThemeSupport}
|
||
if tsUseThemes in FStates then
|
||
begin
|
||
// If theming is enabled and the client edge border is set for the window then prevent the default window proc
|
||
// from painting the old border to avoid flickering.
|
||
ExStyle := GetWindowLong(Handle, GWL_EXSTYLE);
|
||
if (ExStyle and WS_EX_CLIENTEDGE) <> 0 then
|
||
begin
|
||
GetWindowRect(Handle, R);
|
||
// Determine width of the client edge.
|
||
BorderWidth := GetSystemMetrics(SM_CXEDGE);
|
||
BorderHeight := GetSystemMetrics(SM_CYEDGE);
|
||
InflateRect(R, -BorderWidth, -BorderHeight);
|
||
TempRgn := CreateRectRgnIndirect(R);
|
||
// Exclude the border from the message region if there is one. Otherwise just use the inflated
|
||
// window area region.
|
||
if Message.Rgn <> 1 then
|
||
CombineRgn(TempRgn, Message.Rgn, TempRgn, RGN_AND);
|
||
DefWindowProc(Handle, Message.Msg, Integer(TempRgn), 0);
|
||
DeleteObject(TempRgn);
|
||
end
|
||
else
|
||
DefaultHandler(Message);
|
||
end
|
||
else
|
||
{$endif ThemeSupport}
|
||
DefaultHandler(Message);
|
||
|
||
Flags := DCX_CACHE or DCX_CLIPSIBLINGS or DCX_WINDOW or DCX_VALIDATE;
|
||
|
||
if (Message.Rgn = 1) or not IsWinNT then
|
||
DC := GetDCEx(Handle, 0, Flags)
|
||
else
|
||
DC := GetDCEx(Handle, Message.Rgn, Flags or DCX_INTERSECTRGN);
|
||
|
||
if DC <> 0 then
|
||
begin
|
||
if hoVisible in FHeader.FOptions then
|
||
begin
|
||
R := FHeaderRect;
|
||
FHeader.FColumns.PaintHeader(DC, R, -FEffectiveOffsetX);
|
||
end;
|
||
OriginalWMNCPaint(DC);
|
||
ReleaseDC(Handle, DC);
|
||
end;
|
||
{$ifdef ThemeSupport}
|
||
if tsUseThemes in FStates then
|
||
ThemeServices.PaintBorder(Self, False);
|
||
{$endif ThemeSupport}
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMNCPaint');{$endif}
|
||
end;
|
||
|
||
{$endif}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMPaint(var Message: TLMPaint);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMPaint');{$endif}
|
||
//todo:
|
||
//Windows.GetUpdateRect is always empty because BeginPaint was called
|
||
//see if PaintStruct has the same rect
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if tsVCLDragging in FStates then
|
||
ImageList_DragShowNolock(False);
|
||
{$endif}
|
||
//todo: workaround to bug 18211. Remove when fixed.
|
||
{$ifndef LCLCarbon}
|
||
if csPaintCopy in ControlState then
|
||
FUpdateRect := ClientRect
|
||
else
|
||
FUpdateRect := Message.PaintStruct^.rcPaint;
|
||
{$else}
|
||
FUpdateRect := ClientRect;
|
||
{$endif}
|
||
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaint],'FUpdateRect', FUpdateRect);{$endif}
|
||
|
||
inherited WMPaint(Message);
|
||
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if tsVCLDragging in FStates then
|
||
ImageList_DragShowNolock(True);
|
||
{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMPaint');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMPaste(var Message: TLMNoParams);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMPaste');{$endif}
|
||
PasteFromClipboard;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMPaste');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$ifdef EnablePrintFunctions}
|
||
|
||
procedure TBaseVirtualTree.WMPrint(var Message: TWMPrint);
|
||
|
||
// This message is sent to request that the tree draws itself to a given device context. This includes not only
|
||
// the client area but also the non-client area (header!).
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMPrint');{$endif}
|
||
// Draw only if the window is visible or visibility is not required.
|
||
if ((Message.Flags and PRF_CHECKVISIBLE) = 0) or IsWindowVisible(Handle) then
|
||
Header.Columns.PaintHeader(Message.DC, FHeaderRect, -FEffectiveOffsetX);
|
||
|
||
inherited WMPrint(Message);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMPrint');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMPrintClient(var Message: TWMPrintClient);
|
||
|
||
var
|
||
Window: TRect;
|
||
Target: TPoint;
|
||
Canvas: TCanvas;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMPrintClient');{$endif}
|
||
// Draw only if the window is visible or visibility is not required.
|
||
if ((Message.Flags and PRF_CHECKVISIBLE) = 0) or IsWindowVisible(Handle) then
|
||
begin
|
||
// Determine area of the entire tree to be displayed in the control.
|
||
Window := ClientRect;
|
||
Target := Window.TopLeft;
|
||
|
||
// The Window rectangle is given in client coordinates. We have to convert it into
|
||
// a sliding window of the tree image.
|
||
OffsetRect(Window, FEffectiveOffsetX, -FOffsetY);
|
||
|
||
Canvas := TCanvas.Create;
|
||
try
|
||
Canvas.Handle := Message.DC;
|
||
PaintTree(Canvas, Window, Target, [poBackground, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines]);
|
||
finally
|
||
Canvas.Handle := 0;
|
||
Canvas.Free;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMPrintClient');{$endif}
|
||
end;
|
||
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMRButtonDblClk(var Message: TLMRButtonDblClk);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMRButtonDblClk');{$endif}
|
||
DoStateChange([tsRightDblClick]);
|
||
inherited WMRButtonDblClk(Message);
|
||
|
||
// get information about the hit
|
||
if toMiddleClickSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseDblClick(Message, HitInfo);
|
||
end;
|
||
DoStateChange([], [tsRightDblClick]);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMRButtonDblClk');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMRButtonDown(var Message: TLMRButtonDown);
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMRButtonDown');{$endif}
|
||
DoStateChange([tsRightButtonDown]);
|
||
|
||
if FHeader.FStates = [] then
|
||
begin
|
||
inherited WMRButtonDown(Message);
|
||
|
||
// get information about the hit
|
||
if toRightClickSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
HandleMouseDown(Message, HitInfo);
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMRButtonDown');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMRButtonUp(var Message: TLMRButtonUp);
|
||
|
||
// handle right click selection and node specific popup menu
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMRButtonUp');{$endif}
|
||
DoStateChange([], [tsPopupMenuShown, tsRightButtonDown]);
|
||
|
||
if FHeader.FStates = [] then
|
||
begin
|
||
Application.CancelHint;
|
||
|
||
if IsMouseSelecting and Assigned(PopupMenu) then
|
||
begin
|
||
// Reset selection state already here, before the inherited handler opens the default menu.
|
||
DoStateChange([], [tsDrawSelecting, tsDrawSelPending]);
|
||
Invalidate;
|
||
end;
|
||
|
||
inherited WMRButtonUp(Message);
|
||
|
||
// get information about the hit
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
|
||
if toRightClickSelect in FOptions.FSelectionOptions then
|
||
HandleMouseUp(Message.Keys, HitInfo);
|
||
|
||
if not Assigned(PopupMenu) then
|
||
DoPopupMenu(HitInfo.HitNode, HitInfo.HitColumn, Point(Message.XPos, Message.YPos));
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMRButtonUp');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMSetFocus(var Msg: TLMSetFocus);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMSetFocus') ;{$endif}
|
||
inherited WMSetFocus(Msg);
|
||
if (FSelectionCount > 0) or not (toGhostedIfUnfocused in FOptions.FPaintOptions) then
|
||
Invalidate;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMSetFocus');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMSize(var Message: TLMSize);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'WMSize');{$endif}
|
||
inherited WMSize(Message);
|
||
|
||
// Need to update scroll bars here. This will cause a recursion because of the change of the client area
|
||
// when changing a scrollbar. Usually this is no problem since with the second level recursion no change of the
|
||
// window size happens (the same values for the scrollbars are set, which shouldn't cause a window size change).
|
||
// Appearently, this applies not to all systems, however.
|
||
if HandleAllocated and ([tsSizing, tsWindowCreating] * FStates = []) and (ClientHeight > 0) then
|
||
try
|
||
DoStateChange([tsSizing]);
|
||
// This call will invalidate the entire non-client area which needs recalculation on resize.
|
||
FHeader.RescaleHeader;
|
||
FHeader.UpdateSpringColumns;
|
||
UpdateScrollBars(True);
|
||
|
||
if (tsEditing in FStates) and not FHeader.UseColumns then
|
||
UpdateEditBounds;
|
||
finally
|
||
DoStateChange([], [tsSizing]);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'WMSize');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$ifdef ThemeSupport}
|
||
{$ifdef Windows}
|
||
//todo
|
||
procedure TBaseVirtualTree.WMThemeChanged(var Message: TLMessage);
|
||
|
||
begin
|
||
inherited;
|
||
ThemeServices.UpdateThemes;
|
||
if ThemeServices.ThemesEnabled and (toThemeAware in TreeOptions.PaintOptions) then
|
||
DoStateChange([tsUseThemes])
|
||
else
|
||
DoStateChange([], [tsUseThemes]);
|
||
RedrawWindow(Handle, nil, 0, RDW_INVALIDATE or RDW_VALIDATE or RDW_FRAME);
|
||
end;
|
||
{$endif}
|
||
{$endif ThemeSupport}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMTimer(var Message: TLMTimer);
|
||
|
||
// centralized timer handling happens here
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages,lcTimer],'WMTimer');{$endif}
|
||
with Message do
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcTimer],'TimerId',TimerId);{$endif}
|
||
case TimerID of
|
||
ExpandTimer:
|
||
DoDragExpand;
|
||
EditTimer:
|
||
DoEdit;
|
||
ScrollTimer:
|
||
begin
|
||
if tsScrollPending in FStates then
|
||
begin
|
||
Application.CancelHint;
|
||
// Scroll delay has elapsed, set to normal scroll interval now.
|
||
SetTimer(Handle, ScrollTimer, FAutoScrollInterval, nil);
|
||
DoStateChange([tsScrolling], [tsScrollPending]);
|
||
end;
|
||
DoTimerScroll;
|
||
end;
|
||
ChangeTimer:
|
||
DoChange(FLastChangedNode);
|
||
StructureChangeTimer:
|
||
DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason);
|
||
SearchTimer:
|
||
begin
|
||
// When this event triggers then the user did not pressed any key for the specified timeout period.
|
||
// Hence incremental searching is stopped.
|
||
DoStateChange([], [tsIncrementalSearching]);
|
||
KillTimer(Handle, SearchTimer);
|
||
FSearchBuffer := '';
|
||
FLastSearchNode := nil;
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages,lcTimer],'WMTimer');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WMVScroll(var Message: TLMVScroll);
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
function GetRealScrollPosition: Integer;
|
||
|
||
var
|
||
SI: TScrollInfo;
|
||
Code: Integer;
|
||
|
||
begin
|
||
SI.cbSize := SizeOf(TScrollInfo);
|
||
SI.fMask := SIF_TRACKPOS;
|
||
Code := SB_VERT;
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_GetScrollInfo(Handle, Code, SI);
|
||
{$else}
|
||
GetScrollInfo(Handle, Code, SI);
|
||
{$endif UseFlatScrollbars}
|
||
Result := SI.nTrackPos;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcScroll],'GetRealScrollPosition',Result);{$endif}
|
||
end;
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcScroll],'WMVScroll');{$endif}
|
||
//{$ifdef DEBUG_VTV}Logger.SendCallStack([lcScroll],'CallStack');{$endif}
|
||
case Message.ScrollCode of
|
||
SB_BOTTOM:
|
||
SetOffsetY(-Integer(FRoot.TotalHeight));
|
||
SB_ENDSCROLL:
|
||
begin
|
||
DoStateChange([], [tsThumbTracking]);
|
||
// Avoiding to adjust the horizontal scroll position while tracking makes scrolling much smoother
|
||
// but we need to adjust the final position here then.
|
||
UpdateScrollBars(True);
|
||
// Really weird invalidation needed here (and I do it only because it happens so rarely), because
|
||
// when showing the horizontal scrollbar while scrolling down using the down arrow button,
|
||
// the button will be repainted on mouse up (at the wrong place in the far right lower corner)...
|
||
RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN);
|
||
end;
|
||
SB_LINEUP:
|
||
SetOffsetY(FOffsetY + FScrollBarOptions.FIncrementY);
|
||
SB_LINEDOWN:
|
||
SetOffsetY(FOffsetY - FScrollBarOptions.FIncrementY);
|
||
SB_PAGEUP:
|
||
SetOffsetY(FOffsetY + ClientHeight);
|
||
SB_PAGEDOWN:
|
||
SetOffsetY(FOffsetY - ClientHeight);
|
||
|
||
SB_THUMBPOSITION,
|
||
SB_THUMBTRACK:
|
||
begin
|
||
DoStateChange([tsThumbTracking]);
|
||
{$if DEFINED(LCLQt) OR DEFINED(LCLCarbon)}
|
||
SetOffsetY(-Message.Pos);
|
||
{$else}
|
||
SetOffsetY(-GetRealScrollPosition);
|
||
{$endif}
|
||
end;
|
||
SB_TOP:
|
||
SetOffsetY(0);
|
||
end;
|
||
Message.Result := 0;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcScroll],'WMVScroll');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AddToSelection(Node: PVirtualNode);
|
||
|
||
var
|
||
Changed: Boolean;
|
||
|
||
begin
|
||
if not FSelectionLocked then
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil!');
|
||
FSingletonNodeArray[0] := Node;
|
||
Changed := InternalAddToSelection(FSingletonNodeArray, 1, False);
|
||
if Changed then
|
||
begin
|
||
InvalidateNode(Node);
|
||
Change(Node);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False);
|
||
|
||
// Adds the given items all at once into the current selection array. NewLength is the amount of
|
||
// nodes to add (necessary to allow NewItems to be larger than the actual used entries).
|
||
// ForceInsert is True if nodes must be inserted without consideration of level select constraint or
|
||
// already set selected flags (e.g. when loading from stream).
|
||
// Note: In the case ForceInsert is True the caller is responsible for making sure the new nodes aren't already in the
|
||
// selection array!
|
||
|
||
var
|
||
Changed: Boolean;
|
||
|
||
begin
|
||
Changed := InternalAddToSelection(NewItems, NewLength, ForceInsert);
|
||
if Changed then
|
||
begin
|
||
if NewLength = 1 then
|
||
begin
|
||
InvalidateNode(NewItems[0]);
|
||
Change(NewItems[0]);
|
||
end
|
||
else
|
||
begin
|
||
Invalidate;
|
||
Change(nil);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; out NextNonEmpty: TColumnIndex);
|
||
|
||
// Used in descendants to modify the paint rectangle of the current column while painting a certain node.
|
||
|
||
begin
|
||
// Since cells are always drawn from left to right the next column index is independent of the
|
||
// bidi mode, but not the column borders, which might change depending on the cell's content.
|
||
NextNonEmpty := FHeader.FColumns.GetNextVisibleColumn(PaintInfo.Column);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdjustPanningCursor(X, Y: Integer);
|
||
|
||
// Triggered by a mouse move when wheel panning/scrolling is active.
|
||
// Loads the proper cursor which indicates into which direction scrolling is done.
|
||
|
||
var
|
||
NewCursor: HCURSOR;
|
||
ScrollHorizontal,
|
||
ScrollVertical: Boolean;
|
||
|
||
begin
|
||
ScrollHorizontal := Integer(FRangeX) > ClientWidth;
|
||
ScrollVertical := Integer(FRangeY) > ClientHeight;
|
||
|
||
if (Abs(X - FLastClickPos.X) < 8) and (Abs(Y - FLastClickPos.Y) < 8) then
|
||
begin
|
||
// Mouse is in the neutral zone.
|
||
if ScrollHorizontal then
|
||
begin
|
||
if ScrollVertical then
|
||
NewCursor := crVT_MOVEALL
|
||
else
|
||
NewCursor := crVT_MOVEEW
|
||
end
|
||
else
|
||
NewCursor := crVT_MOVENS;
|
||
end
|
||
else
|
||
begin
|
||
// One of 8 directions applies: north, north-east, east, south-east, south, south-west, west and north-west.
|
||
// Check also if scrolling in the particular direction is possible.
|
||
if ScrollVertical and ScrollHorizontal then
|
||
begin
|
||
// All directions allowed.
|
||
if X - FlastClickPos.X < -8 then
|
||
begin
|
||
// Left hand side.
|
||
if Y - FLastClickPos.Y < -8 then
|
||
NewCursor := crVT_MOVENW
|
||
else
|
||
if Y - FLastClickPos.Y > 8 then
|
||
NewCursor := crVT_MOVESW
|
||
else
|
||
NewCursor := crVT_MOVEW;
|
||
end
|
||
else
|
||
if X - FLastClickPos.X > 8 then
|
||
begin
|
||
// Right hand side.
|
||
if Y - FLastClickPos.Y < -8 then
|
||
NewCursor := crVT_MOVENE
|
||
else
|
||
if Y - FLastClickPos.Y > 8 then
|
||
NewCursor := crVT_MOVESE
|
||
else
|
||
NewCursor := crVT_MOVEE;
|
||
end
|
||
else
|
||
begin
|
||
// Up or down.
|
||
if Y < FLastClickPos.Y then
|
||
NewCursor := crVT_MOVEN
|
||
else
|
||
NewCursor := crVT_MOVES;
|
||
end;
|
||
end
|
||
else
|
||
if ScrollHorizontal then
|
||
begin
|
||
// Only horizontal movement allowed.
|
||
if X < FlastClickPos.X then
|
||
NewCursor := crVT_MOVEW
|
||
else
|
||
NewCursor := crVT_MOVEE;
|
||
end
|
||
else
|
||
begin
|
||
// Only vertical movement allowed.
|
||
if Y < FlastClickPos.Y then
|
||
NewCursor := crVT_MOVEN
|
||
else
|
||
NewCursor := crVT_MOVES;
|
||
end;
|
||
end;
|
||
|
||
// Now load the cursor and apply it.
|
||
{$ifdef Windows}
|
||
LCLIntf.SetCursor(Screen.Cursors[NewCursor]);
|
||
{$else}
|
||
Cursor := NewCursor;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AdviseChangeEvent(StructureChange: Boolean; Node: PVirtualNode; Reason: TChangeReason);
|
||
|
||
// Used to register a delayed change event. If StructureChange is False then we have a selection change event (without
|
||
// a specific reason) otherwise it is a structure change.
|
||
|
||
begin
|
||
if StructureChange then
|
||
begin
|
||
if tsStructureChangePending in FStates then
|
||
begin
|
||
if HandleAllocated then
|
||
KillTimer(Handle,StructureChangeTimer);
|
||
end
|
||
else
|
||
DoStateChange([tsStructureChangePending]);
|
||
|
||
FLastStructureChangeNode := Node;
|
||
if FLastStructureChangeReason = crIgnore then
|
||
FLastStructureChangeReason := Reason
|
||
else
|
||
if Reason <> crIgnore then
|
||
FLastStructureChangeReason := crAccumulated;
|
||
end
|
||
else
|
||
begin
|
||
if tsChangePending in FStates then
|
||
KillTimer(Handle, ChangeTimer)
|
||
else
|
||
DoStateChange([tsChangePending]);
|
||
|
||
FLastChangedNode := Node;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.AllocateInternalDataArea(Size: Cardinal): Cardinal;
|
||
|
||
// Simple registration method to be called by each descendant to claim their internal data area.
|
||
// Result is the offset from the begin of the node to the internal data area of the calling tree class.
|
||
|
||
begin
|
||
Assert((FRoot = nil) or (FRoot.ChildCount = 0), 'Internal data allocation must be done before any node is created.');
|
||
{$ifdef DEBUG_VTV}Logger.Send('FTotalInternalDataSize BEFORE',FTotalInternalDataSize);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send('Size',Size);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send('TreeNodeSize',TreeNodeSize);{$endif}
|
||
Result := TreeNodeSize + FTotalInternalDataSize;
|
||
{$ifdef DEBUG_VTV}Logger.Send('Result',Result);{$endif}
|
||
Inc(FTotalInternalDataSize, (Size + 3) and not 3);
|
||
{$ifdef DEBUG_VTV}Logger.Send('FTotalInternalDataSize AFTER', FTotalInternalDataSize);{$endif}
|
||
InitRootNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Animate(Steps, Duration: Cardinal; Callback: TVTAnimationCallback; Data: Pointer);
|
||
|
||
// This method does the calculation part of an animation as used for node toggling and hint animations.
|
||
// Steps is the maximum amount of animation steps to do and Duration determines the milliseconds the animation
|
||
// has to run. Callback is a task specific method which is called in the loop for every step and Data is simply
|
||
// something to pass on to the callback.
|
||
// The callback is called with the current step, the current step size and the Data parameter. Since the step amount
|
||
// as well as the step size are possibly adjusted during the animation, it is impossible to determine if the current
|
||
// step is the last step, even if the original step amount is known. To solve this problem the callback will be
|
||
// called after the loop has finished with a step size of 0 indicating so to execute any post processing.
|
||
|
||
var
|
||
StepSize,
|
||
RemainingTime,
|
||
RemainingSteps,
|
||
NextTimeStep,
|
||
CurrentStep,
|
||
StartTime,
|
||
CurrentTime: Cardinal;
|
||
|
||
begin
|
||
{$ifndef Windows}
|
||
//Is necessary to properly implement timeGetTime in non Windows
|
||
Exit;
|
||
{$endif}
|
||
if not (tsInAnimation in FStates) and (Duration > 0) then
|
||
begin
|
||
DoStateChange([tsInAnimation]);
|
||
try
|
||
RemainingTime := Duration;
|
||
RemainingSteps := Steps;
|
||
|
||
// Determine the initial step size which is either 1 if the needed steps are less than the number of
|
||
// steps possible given by the duration or > 1 otherwise.
|
||
StepSize := Round(Max(1, RemainingSteps / Duration));
|
||
RemainingSteps := RemainingSteps div StepSize;
|
||
CurrentStep := 0;
|
||
|
||
while (RemainingSteps > 0) and (RemainingTime > 0) and not Application.Terminated do
|
||
begin
|
||
StartTime := timeGetTime;
|
||
NextTimeStep := StartTime + RemainingTime div RemainingSteps;
|
||
if not Callback(CurrentStep, StepSize, Data) then
|
||
Break;
|
||
|
||
// Keep duration for this step for rest calculation.
|
||
CurrentTime := timeGetTime;
|
||
// Wait until the calculated time has been reached.
|
||
while CurrentTime < NextTimeStep do
|
||
CurrentTime := timeGetTime;
|
||
|
||
// Subtract the time this step really needed.
|
||
if RemainingTime >= CurrentTime - StartTime then
|
||
begin
|
||
Dec(RemainingTime, CurrentTime - StartTime);
|
||
Dec(RemainingSteps);
|
||
end
|
||
else
|
||
begin
|
||
RemainingTime := 0;
|
||
RemainingSteps := 0;
|
||
end;
|
||
// If the remaining time per step is less than one time step then we have to decrease the
|
||
// step count and increase the step size.
|
||
if (RemainingSteps > 0) and ((RemainingTime div RemainingSteps) < 1) then
|
||
begin
|
||
repeat
|
||
Inc(StepSize);
|
||
RemainingSteps := RemainingTime div StepSize;
|
||
until (RemainingSteps <= 0) or ((RemainingTime div RemainingSteps) >= 1);
|
||
end;
|
||
CurrentStep := Cardinal(Steps) - RemainingSteps;
|
||
end;
|
||
|
||
if not Application.Terminated then
|
||
Callback(0, 0, Data);
|
||
finally
|
||
DoStateChange([], [tsCancelHintAnimation, tsInAnimation]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.BeginOperation;
|
||
|
||
// Called to indicate that a long-running operation has been started.
|
||
|
||
begin
|
||
Inc(FOperationCount);
|
||
if FOperationCount = 1 then
|
||
FOperationCanceled := False;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CalculateSelectionRect(X, Y: Integer): Boolean;
|
||
|
||
// Recalculates old and new selection rectangle given that X, Y are new mouse coordinates.
|
||
// Returns True if there was a change since the last call.
|
||
|
||
var
|
||
MaxValue: Integer;
|
||
|
||
begin
|
||
//lclheader
|
||
if hoVisible in FHeader.Options then
|
||
Dec(Y, FHeader.Height);
|
||
if tsDrawSelecting in FStates then
|
||
FLastSelRect := FNewSelRect;
|
||
FNewSelRect.BottomRight := Point(X + FEffectiveOffsetX, Y - FOffsetY);
|
||
if FNewSelRect.Right < 0 then
|
||
FNewSelRect.Right := 0;
|
||
if FNewSelRect.Bottom < 0 then
|
||
FNewSelRect.Bottom := 0;
|
||
MaxValue := ClientWidth;
|
||
if FRangeX > Cardinal(MaxValue) then
|
||
MaxValue := FRangeX;
|
||
if FNewSelRect.Right > MaxValue then
|
||
FNewSelRect.Right := MaxValue;
|
||
MaxValue := ClientHeight;
|
||
if FRangeY > Cardinal(MaxValue) then
|
||
MaxValue := FRangeY;
|
||
if FNewSelRect.Bottom > MaxValue then
|
||
FNewSelRect.Bottom := MaxValue;
|
||
|
||
Result := not CompareMem(@FLastSelRect, @FNewSelRect, SizeOf(FNewSelRect));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CanAutoScroll: Boolean;
|
||
|
||
// Determines if auto scrolling is currently allowed.
|
||
|
||
var
|
||
IsDropTarget: Boolean;
|
||
IsDrawSelecting: Boolean;
|
||
IsWheelPanning: Boolean;
|
||
|
||
begin
|
||
// Don't scroll the client area if the header is currently doing tracking or dragging.
|
||
// Do auto scroll only if there is a draw selection in progress or the tree is the current drop target or
|
||
// wheel panning/scrolling is active.
|
||
IsDropTarget := Assigned(FDragManager) and VTVDragManager.IsDropTarget;
|
||
IsDrawSelecting := [tsDrawSelPending, tsDrawSelecting] * FStates <> [];
|
||
IsWheelPanning := [tsWheelPanning, tsWheelScrolling] * FStates <> [];
|
||
Result := ((toAutoScroll in FOptions.FAutoOptions) or IsWheelPanning) and
|
||
(FHeader.FStates = []) and (IsDrawSelecting or IsDropTarget or (tsVCLDragging in FStates) or IsWheelPanning);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CanShowDragImage: Boolean;
|
||
|
||
// Determines whether a drag image should be shown.
|
||
|
||
begin
|
||
Result := FDragImageKind <> diNoImage;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Change(Node: PVirtualNode);
|
||
|
||
begin
|
||
AdviseChangeEvent(False, Node, crIgnore);
|
||
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
if (FChangeDelay > 0) and not (tsSynchMode in FStates) then
|
||
SetTimer(Handle, ChangeTimer, FChangeDelay, nil)
|
||
else
|
||
DoChange(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ChangeScale(M, D: Integer);
|
||
|
||
var
|
||
DoScale: Boolean;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
if (M <> D) and (toAutoChangeScale in FOptions.FAutoOptions) then
|
||
begin
|
||
if (csLoading in ComponentState) then
|
||
DoScale := tsNeedScale in FStates
|
||
else
|
||
DoScale := True;
|
||
if DoScale then
|
||
begin
|
||
FDefaultNodeHeight := MulDiv(FDefaultNodeHeight, M, D);
|
||
FHeader.ChangeScale(M, D);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CheckParentCheckState(Node: PVirtualNode; NewCheckState: TCheckState): Boolean;
|
||
|
||
// Checks all siblings of node to determine which check state Node's parent must get.
|
||
|
||
var
|
||
CheckCount,
|
||
BoxCount: Cardinal;
|
||
PartialCheck: Boolean;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
CheckCount := 0;
|
||
BoxCount := 0;
|
||
PartialCheck := False;
|
||
Run := Node.Parent.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
if Run = Node then
|
||
begin
|
||
// The given node cannot be checked because it does not yet have its new check state (as this depends
|
||
// on the outcome of this method). Instead NewCheckState is used as this contains the new state the node
|
||
// will get if this method returns True.
|
||
if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then
|
||
begin
|
||
Inc(BoxCount);
|
||
if NewCheckState in [csCheckedNormal, csCheckedPressed] then
|
||
Inc(CheckCount);
|
||
PartialCheck := PartialCheck or (NewCheckState = csMixedNormal);
|
||
end;
|
||
end
|
||
else
|
||
if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then
|
||
begin
|
||
Inc(BoxCount);
|
||
if Run.CheckState in [csCheckedNormal, csCheckedPressed] then
|
||
Inc(CheckCount);
|
||
PartialCheck := PartialCheck or (Run.CheckState = csMixedNormal);
|
||
end;
|
||
Run := Run.NextSibling;
|
||
end;
|
||
|
||
if (CheckCount = 0) and not PartialCheck then
|
||
NewCheckState := csUncheckedNormal
|
||
else
|
||
if CheckCount < BoxCount then
|
||
NewCheckState := csMixedNormal
|
||
else
|
||
NewCheckState := csCheckedNormal;
|
||
|
||
Node := Node.Parent;
|
||
Result := DoChecking(Node, NewCheckState);
|
||
if Result then
|
||
begin
|
||
DoCheckClick(Node, NewCheckState);
|
||
// Recursively adjust parent of parent.
|
||
with Node^ do
|
||
begin
|
||
if not (vsInitialized in Parent.States) then
|
||
InitNode(Parent);
|
||
if ([vsChecking, vsDisabled] * Parent.States = []) and (Parent <> FRoot) and
|
||
(Parent.CheckType = ctTriStateCheckBox) then
|
||
Result := CheckParentCheckState(Node, NewCheckState);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ClearTempCache;
|
||
|
||
// make sure the temporary node cache is in a reliable state
|
||
|
||
begin
|
||
FTempNodeCache := nil;
|
||
FTempNodeCount := 0;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
||
|
||
// Returns True if the given column is to be considered as being empty. This will usually be determined by
|
||
// descendants as the base tree implementation has not enough information to decide.
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnGetCellIsEmpty) then
|
||
FOnGetCellIsEmpty(Self, Node, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ComputeRTLOffset(ExcludeScrollbar: Boolean): Integer;
|
||
|
||
// Computes the horizontal offset needed when all columns are automatically right aligned (in RTL bidi mode).
|
||
// ExcludeScrollbar determines if the left-hand vertical scrollbar is to be included (if visible) or not.
|
||
|
||
var
|
||
HeaderWidth: Integer;
|
||
ScrollbarVisible: Boolean;
|
||
begin
|
||
ScrollbarVisible := (Integer(FRangeY) > ClientHeight) and (ScrollbarOptions.Scrollbars in [ssVertical, ssBoth]);
|
||
if ScrollbarVisible then
|
||
Result := GetSystemMetrics(SM_CXVSCROLL)
|
||
else
|
||
Result := 0;
|
||
|
||
// Make everything right aligned.
|
||
HeaderWidth := FHeaderRect.Right - FHeaderRect.Left;
|
||
if Integer(FRangeX) + Result <= HeaderWidth then
|
||
Result := HeaderWidth - Integer(FRangeX);
|
||
// Otherwise take only left-hand vertical scrollbar into account.
|
||
|
||
if ScrollbarVisible and ExcludeScrollbar then
|
||
Dec(Result, GetSystemMetrics(SM_CXVSCROLL));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CountLevelDifference(Node1, Node2: PVirtualNode): Integer;
|
||
|
||
// This method counts how many indentation levels the given nodes are apart. If both nodes have the same parent then the
|
||
// difference is 0 otherwise the result is basically GetNodeLevel(Node2) - GetNodeLevel(Node1), but with sign.
|
||
// If the result is negative then Node2 is less intended than Node1.
|
||
|
||
var
|
||
Level1, Level2: Integer;
|
||
|
||
begin
|
||
Assert(Assigned(Node1) and Assigned(Node2), 'Both nodes must be Assigned.');
|
||
|
||
Level1 := 0;
|
||
while Node1.Parent <> FRoot do
|
||
begin
|
||
Inc(Level1);
|
||
Node1 := Node1.Parent;
|
||
end;
|
||
|
||
Level2 := 0;
|
||
while Node2.Parent <> FRoot do
|
||
begin
|
||
Inc(Level2);
|
||
Node2 := Node2.Parent;
|
||
end;
|
||
|
||
Result := Level2 - Level1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CountVisibleChildren(Node: PVirtualNode): Cardinal;
|
||
|
||
// Returns the number of visible child nodes of the given node.
|
||
|
||
begin
|
||
Result := 0;
|
||
|
||
// The node's direct children...
|
||
if vsExpanded in Node.States then
|
||
begin
|
||
// ...and their children.
|
||
Node := Node.FirstChild;
|
||
while Assigned(Node) do
|
||
begin
|
||
if vsVisible in Node.States then
|
||
Inc(Result, CountVisibleChildren(Node) + 1);
|
||
Node := Node.NextSibling;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CreateParams(var Params: TCreateParams);
|
||
|
||
const
|
||
ScrollBar: array[TScrollStyle] of Cardinal = (0, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL,
|
||
0,0,0);
|
||
|
||
begin
|
||
//todo_lcl
|
||
|
||
inherited CreateParams(Params);
|
||
|
||
with Params do
|
||
begin
|
||
Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or ScrollBar[ScrollBarOptions.FScrollBars];
|
||
if toFullRepaintOnResize in FOptions.FMiscOptions then
|
||
WindowClass.style := WindowClass.style or CS_HREDRAW or CS_VREDRAW
|
||
else
|
||
WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
|
||
//lcl: Ctl3D is not used in LCL. Has the same meaning of BorderStyle = bsSingle
|
||
{
|
||
if BorderStyle = bsSingle then
|
||
begin
|
||
if Ctl3D then
|
||
begin
|
||
ExStyle := ExStyle or WS_EX_CLIENTEDGE;
|
||
Style := Style and not WS_BORDER;
|
||
end
|
||
else
|
||
Style := Style or WS_BORDER;
|
||
end
|
||
else
|
||
Style := Style and not WS_BORDER;
|
||
}
|
||
//todo_lcl_low
|
||
//AddBiDiModeExStyle(ExStyle);
|
||
end;
|
||
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CreateWnd;
|
||
|
||
// Initializes data which depends on a valid window handle.
|
||
|
||
begin
|
||
DoStateChange([tsWindowCreating]);
|
||
inherited;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcInfo],'Handle (CreateWnd)',Handle);{$endif}
|
||
DoStateChange([], [tsWindowCreating]);
|
||
|
||
{$ifdef ThemeSupport}
|
||
if ThemeServices.ThemesEnabled and (toThemeAware in TreeOptions.PaintOptions) then
|
||
begin
|
||
DoStateChange([tsUseThemes]);
|
||
//todo
|
||
//if (toUseExplorerTheme in FOptions.FPaintOptions) and IsWinVistaOrAbove then
|
||
// SetWindowTheme(Handle, 'explorer', nil);
|
||
end
|
||
else
|
||
{$endif ThemeSupport}
|
||
DoStateChange([], [tsUseThemes]);
|
||
|
||
// Because of the special recursion and update stopper when creating the window (or resizing it)
|
||
// we have to manually trigger the auto size calculation here.
|
||
if hsNeedScaling in FHeader.FStates then
|
||
FHeader.RescaleHeader;
|
||
//lcl: Call with Force argument to true since AdjustAutoSize is not called in Loaded
|
||
if hoAutoResize in FHeader.FOptions then
|
||
FHeader.FColumns.AdjustAutoSize(InvalidColumn, True);
|
||
|
||
// Initialize flat scroll bar library if required.
|
||
{$ifdef UseFlatScrollbars}
|
||
if FScrollBarOptions.FScrollBarStyle <> sbmRegular then
|
||
begin
|
||
InitializeFlatSB(Handle);
|
||
FlatSB_SetScrollProp(Handle, WSB_PROP_HSTYLE, ScrollBarProp[FScrollBarOptions.ScrollBarStyle], False);
|
||
FlatSB_SetScrollProp(Handle, WSB_PROP_VSTYLE, ScrollBarProp[FScrollBarOptions.ScrollBarStyle], False);
|
||
end;
|
||
{$endif UseFlatScrollbars}
|
||
|
||
PrepareBitmaps(True, True);
|
||
|
||
{$ifdef Windows}
|
||
// Register tree as OLE drop target.
|
||
if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.FMiscOptions) then
|
||
RegisterDragDrop(Handle, VTVDragManager as IDropTarget);
|
||
{$endif}
|
||
|
||
if toCheckSupport in FOptions.FMiscOptions then
|
||
CheckImageListNeeded;
|
||
UpdateScrollBars(True);
|
||
UpdateHeaderRect;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DestroyHandle;
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcMessages],'DestroyHandle');{$endif}
|
||
//lcl: this code was originally called is response to WM_NCDESTROY
|
||
// see if there will be issues calling here
|
||
InterruptValidation;
|
||
|
||
KillTimer(Handle, ChangeTimer);
|
||
KillTimer(Handle, StructureChangeTimer);
|
||
|
||
{$ifdef Windows}
|
||
if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.FMiscOptions) then
|
||
RevokeDragDrop(Handle);
|
||
{$endif}
|
||
|
||
// Clean up other stuff.
|
||
DeleteObject(FDottedBrush);
|
||
FDottedBrush := 0;
|
||
|
||
CancelEditNode;
|
||
inherited;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcMessages],'DestroyHandle');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DetermineHiddenChildrenFlag(Node: PVirtualNode);
|
||
|
||
// Update the hidden children flag of the given node.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if Node.ChildCount = 0 then
|
||
begin
|
||
if vsHasChildren in Node.States then
|
||
Exclude(Node.States, vsAllChildrenHidden)
|
||
else
|
||
Include(Node.States, vsAllChildrenHidden);
|
||
end
|
||
else
|
||
begin
|
||
// Iterate through all siblings and stop when one visible is found.
|
||
Run := Node.FirstChild;
|
||
while Assigned(Run) do
|
||
Run := Run.NextSibling;
|
||
if Assigned(Run) then
|
||
Exclude(Node.States, vsAllChildrenHidden)
|
||
else
|
||
Include(Node.States, vsAllChildrenHidden);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DetermineHiddenChildrenFlagAllNodes;
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Run := GetFirstNoInit(False);
|
||
while Assigned(Run) do
|
||
begin
|
||
DetermineHiddenChildrenFlag(Run);
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DetermineHitPositionLTR(var HitInfo: THitInfo; Offset, Right: Integer;
|
||
Alignment: TAlignment);
|
||
|
||
// This method determines the hit position within a node with left-to-right orientation.
|
||
|
||
var
|
||
MainColumnHit: Boolean;
|
||
Run: PVirtualNode;
|
||
Indent,
|
||
TextWidth,
|
||
ImageOffset: Integer;
|
||
|
||
begin
|
||
MainColumnHit := HitInfo.HitColumn = FHeader.MainColumn;
|
||
Indent := 0;
|
||
|
||
// If columns are not used or the main column is hit then the tree indentation must be considered too.
|
||
if MainColumnHit then
|
||
begin
|
||
if toFixedIndent in FOptions.FPaintOptions then
|
||
Indent := FIndent
|
||
else
|
||
begin
|
||
Run := HitInfo.HitNode;
|
||
while (Run.Parent <> FRoot) do
|
||
begin
|
||
Inc(Indent, FIndent);
|
||
Run := Run.Parent;
|
||
end;
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
Inc(Indent, FIndent);
|
||
end;
|
||
end;
|
||
|
||
if Offset < Indent then
|
||
begin
|
||
// Position is to the left of calculated indentation which can only happen for the main column.
|
||
// Check whether it corresponds to a button/checkbox.
|
||
if (toShowButtons in FOptions.FPaintOptions) and (vsHasChildren in HitInfo.HitNode.States) then
|
||
begin
|
||
// Position of button is interpreted very generously to avoid forcing the user
|
||
// to click exactly into the 9x9 pixels area. The entire node height and one full
|
||
// indentation level is accepted as button hit.
|
||
if Offset >= Indent - Integer(FIndent) then
|
||
Include(HitInfo.HitPositions, hiOnItemButton);
|
||
if Offset >= Indent - FPlusBM.Width then
|
||
Include(HitInfo.HitPositions, hiOnItemButtonExact);
|
||
end;
|
||
// no button hit so position is on indent
|
||
if HitInfo.HitPositions = [] then
|
||
Include(HitInfo.HitPositions, hiOnItemIndent);
|
||
end
|
||
else
|
||
begin
|
||
// The next hit positions can be:
|
||
// - on the check box
|
||
// - on the state image
|
||
// - on the normal image
|
||
// - to the left of the text area
|
||
// - on the label or
|
||
// - to the right of the text area
|
||
// (in this order).
|
||
|
||
// In report mode no hit other than in the main column is possible.
|
||
if MainColumnHit or not (toReportMode in FOptions.FMiscOptions) then
|
||
begin
|
||
ImageOffset := Indent + FMargin;
|
||
|
||
// Check support is only available for the main column.
|
||
if MainColumnHit and (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages) and
|
||
(HitInfo.HitNode.CheckType <> ctNone) then
|
||
Inc(ImageOffset, FCheckImages.Height + 2);
|
||
|
||
if MainColumnHit and (Offset < ImageOffset) then
|
||
begin
|
||
HitInfo.HitPositions := [hiOnItem];
|
||
if (HitInfo.HitNode.CheckType <> ctNone) then
|
||
Include(HitInfo.HitPositions, hiOnItemCheckBox);
|
||
end
|
||
else
|
||
begin
|
||
if Assigned(FStateImages) and HasImage(HitInfo.HitNode, ikState, HitInfo.HitColumn) then
|
||
Inc(ImageOffset, FStateImages.Width + 2);
|
||
if Offset < ImageOffset then
|
||
Include(HitInfo.HitPositions, hiOnStateIcon)
|
||
else
|
||
begin
|
||
if Assigned(FImages) and HasImage(HitInfo.HitNode, ikNormal, HitInfo.HitColumn) then
|
||
Inc(ImageOffset, FImages.Width + 2);
|
||
if Offset < ImageOffset then
|
||
Include(HitInfo.HitPositions, hiOnNormalIcon)
|
||
else
|
||
begin
|
||
// ImageOffset contains now the left border of the node label area. This is used to calculate the
|
||
// correct alignment in the column.
|
||
TextWidth := DoGetNodeWidth(HitInfo.HitNode, HitInfo.HitColumn);
|
||
|
||
// Check if the text can be aligned at all. This is only possible if there is enough room
|
||
// in the remaining text rectangle.
|
||
if TextWidth > Right - ImageOffset then
|
||
Include(HitInfo.HitPositions, hiOnItemLabel)
|
||
else
|
||
begin
|
||
case Alignment of
|
||
taCenter:
|
||
begin
|
||
Indent := (ImageOffset + Right - TextWidth) div 2;
|
||
if Offset < Indent then
|
||
Include(HitInfo.HitPositions, hiOnItemLeft)
|
||
else
|
||
if Offset < Indent + TextWidth then
|
||
Include(HitInfo.HitPositions, hiOnItemLabel)
|
||
else
|
||
Include(HitInfo.HitPositions, hiOnItemRight)
|
||
end;
|
||
taRightJustify:
|
||
begin
|
||
Indent := Right - TextWidth;
|
||
if Offset < Indent then
|
||
Include(HitInfo.HitPositions, hiOnItemLeft)
|
||
else
|
||
Include(HitInfo.HitPositions, hiOnItemLabel);
|
||
end;
|
||
else // taLeftJustify
|
||
if Offset < ImageOffset + TextWidth then
|
||
Include(HitInfo.HitPositions, hiOnItemLabel)
|
||
else
|
||
Include(HitInfo.HitPositions, hiOnItemRight);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DetermineHitPositionRTL(var HitInfo: THitInfo; Offset, Right: Integer; Alignment: TAlignment);
|
||
|
||
// This method determines the hit position within a node with right-to-left orientation.
|
||
|
||
var
|
||
MainColumnHit: Boolean;
|
||
Run: PVirtualNode;
|
||
Indent,
|
||
TextWidth,
|
||
ImageOffset: Integer;
|
||
|
||
begin
|
||
MainColumnHit := HitInfo.HitColumn = FHeader.MainColumn;
|
||
|
||
// If columns are not used or the main column is hit then the tree indentation must be considered too.
|
||
if MainColumnHit then
|
||
begin
|
||
if toFixedIndent in FOptions.FPaintOptions then
|
||
Dec(Right, FIndent)
|
||
else
|
||
begin
|
||
Run := HitInfo.HitNode;
|
||
while (Run.Parent <> FRoot) do
|
||
begin
|
||
Dec(Right, FIndent);
|
||
Run := Run.Parent;
|
||
end;
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
Dec(Right, FIndent);
|
||
end;
|
||
end;
|
||
|
||
if Offset >= Right then
|
||
begin
|
||
// Position is to the right of calculated indentation which can only happen for the main column.
|
||
// Check whether it corresponds to a button/checkbox.
|
||
if (toShowButtons in FOptions.FPaintOptions) and (vsHasChildren in HitInfo.HitNode.States) then
|
||
begin
|
||
// Position of button is interpreted very generously to avoid forcing the user
|
||
// to click exactly into the 9x9 pixels area. The entire node height and one full
|
||
// indentation level is accepted as button hit.
|
||
if Offset <= Right + Integer(FIndent) then
|
||
Include(HitInfo.HitPositions, hiOnItemButton);
|
||
if Offset <= Right + FPlusBM.Width then
|
||
Include(HitInfo.HitPositions, hiOnItemButtonExact);
|
||
end;
|
||
// no button hit so position is on indent
|
||
if HitInfo.HitPositions = [] then
|
||
Include(HitInfo.HitPositions, hiOnItemIndent);
|
||
end
|
||
else
|
||
begin
|
||
// The next hit positions can be:
|
||
// - on the check box
|
||
// - on the state image
|
||
// - on the normal image
|
||
// - to the left of the text area
|
||
// - on the label or
|
||
// - to the right of the text area
|
||
// (in this order).
|
||
|
||
// In report mode no hit other than in the main column is possible.
|
||
if MainColumnHit or not (toReportMode in FOptions.FMiscOptions) then
|
||
begin
|
||
ImageOffset := Right - FMargin;
|
||
|
||
// Check support is only available for the main column.
|
||
if MainColumnHit and (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages) and
|
||
(HitInfo.HitNode.CheckType <> ctNone) then
|
||
Dec(ImageOffset, FCheckImages.Height + 2);
|
||
|
||
if MainColumnHit and (Offset > ImageOffset) then
|
||
begin
|
||
HitInfo.HitPositions := [hiOnItem];
|
||
if (HitInfo.HitNode.CheckType <> ctNone) then
|
||
Include(HitInfo.HitPositions, hiOnItemCheckBox);
|
||
end
|
||
else
|
||
begin
|
||
if Assigned(FStateImages) and HasImage(HitInfo.HitNode, ikState, HitInfo.HitColumn) then
|
||
Dec(ImageOffset, FStateImages.Width + 2);
|
||
if Offset > ImageOffset then
|
||
Include(HitInfo.HitPositions, hiOnStateIcon)
|
||
else
|
||
begin
|
||
if Assigned(FImages) and HasImage(HitInfo.HitNode, ikNormal, HitInfo.HitColumn) then
|
||
Dec(ImageOffset, FImages.Width + 2);
|
||
if Offset > ImageOffset then
|
||
Include(HitInfo.HitPositions, hiOnNormalIcon)
|
||
else
|
||
begin
|
||
// ImageOffset contains now the right border of the node label area. This is used to calculate the
|
||
// correct alignment in the column.
|
||
TextWidth := DoGetNodeWidth(HitInfo.HitNode, HitInfo.HitColumn);
|
||
|
||
// Check if the text can be aligned at all. This is only possible if there is enough room
|
||
// in the remaining text rectangle.
|
||
if TextWidth > ImageOffset then
|
||
Include(HitInfo.HitPositions, hiOnItemLabel)
|
||
else
|
||
begin
|
||
// Consider bidi mode here. In RTL context does left alignment actually mean right alignment
|
||
// and vice versa.
|
||
ChangeBiDiModeAlignment(Alignment);
|
||
|
||
case Alignment of
|
||
taCenter:
|
||
begin
|
||
Indent := (ImageOffset - TextWidth) div 2;
|
||
if Offset < Indent then
|
||
Include(HitInfo.HitPositions, hiOnItemLeft)
|
||
else
|
||
if Offset < Indent + TextWidth then
|
||
Include(HitInfo.HitPositions, hiOnItemLabel)
|
||
else
|
||
Include(HitInfo.HitPositions, hiOnItemRight)
|
||
end;
|
||
taRightJustify:
|
||
begin
|
||
Indent := ImageOffset - TextWidth;
|
||
if Offset < Indent then
|
||
Include(HitInfo.HitPositions, hiOnItemLeft)
|
||
else
|
||
Include(HitInfo.HitPositions, hiOnItemLabel);
|
||
end;
|
||
else // taLeftJustify
|
||
if Offset > TextWidth then
|
||
Include(HitInfo.HitPositions, hiOnItemRight)
|
||
else
|
||
Include(HitInfo.HitPositions, hiOnItemLabel);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DetermineNextCheckState(CheckType: TCheckType; CheckState: TCheckState): TCheckState;
|
||
|
||
// Determines the next check state in case the user click the check image or pressed the space key.
|
||
|
||
begin
|
||
case CheckType of
|
||
ctTriStateCheckBox,
|
||
ctCheckBox:
|
||
if CheckState = csCheckedNormal then
|
||
Result := csUncheckedNormal
|
||
else
|
||
Result := csCheckedNormal;
|
||
ctRadioButton:
|
||
Result := csCheckedNormal;
|
||
ctButton:
|
||
Result := csUncheckedNormal;
|
||
else
|
||
Result := csMixedNormal;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DetermineScrollDirections(X, Y: Integer): TScrollDirections;
|
||
|
||
// Determines which direction the client area must be scrolled depending on the given position.
|
||
|
||
begin
|
||
Result:= [];
|
||
|
||
if CanAutoScroll then
|
||
begin
|
||
// Calculation for wheel panning/scrolling is a bit different to normal auto scroll.
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then
|
||
begin
|
||
if (X - FLastClickPos.X) < -8 then
|
||
Include(Result, sdLeft);
|
||
if (X - FLastClickPos.X) > 8 then
|
||
Include(Result, sdRight);
|
||
|
||
if (Y - FLastClickPos.Y) < -8 then
|
||
Include(Result, sdUp);
|
||
if (Y - FLastClickPos.Y) > 8 then
|
||
Include(Result, sdDown);
|
||
end
|
||
else
|
||
begin
|
||
if (X < Integer(FDefaultNodeHeight)) and (FEffectiveOffsetX <> 0) then
|
||
Include(Result, sdLeft);
|
||
if (ClientWidth + FEffectiveOffsetX < Integer(FRangeX)) and (X > ClientWidth - Integer(FDefaultNodeHeight)) then
|
||
Include(Result, sdRight);
|
||
//lclheader
|
||
if (ClientHeight - FOffsetY < Integer(FRangeY)) and (Y > inherited GetClientRect.Bottom - Integer(FDefaultNodeHeight)) then
|
||
Include(Result, sdDown);
|
||
if hoVisible in FHeader.FOptions then
|
||
Dec(Y, FHeader.Height);
|
||
|
||
if (Y > 0) and (Y < Integer(FDefaultNodeHeight)) and (FOffsetY <> 0) then
|
||
Include(Result, sdUp);
|
||
|
||
//todo: probably the code below is bug due to poor timeGetTime implementation
|
||
// Since scrolling during dragging is not handled via the timer we do a check here whether the auto
|
||
// scroll timeout already has elapsed or not.
|
||
if (Result <> []) and
|
||
((Assigned(FDragManager) and VTVDragManager.IsDropTarget) or
|
||
(FindDragTarget(Point(X, Y), False) = Self)) then
|
||
begin
|
||
if FDragScrollStart = 0 then
|
||
FDragScrollStart := timeGetTime;
|
||
// Reset any scroll direction to avoid scroll in the case the user is dragging and the auto scroll time has not
|
||
// yet elapsed.
|
||
if ((timeGetTime - FDragScrollStart) < FAutoScrollDelay) then
|
||
Result := [];
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoAdvancedHeaderDraw(var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements);
|
||
|
||
begin
|
||
if Assigned(FOnAdvancedHeaderDraw) then
|
||
FOnAdvancedHeaderDraw(FHeader, PaintInfo, Elements);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoAfterCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const CellRect: TRect);
|
||
|
||
begin
|
||
if Assigned(FOnAfterCellPaint) then
|
||
FOnAfterCellPaint(Self, Canvas, Node, Column, CellRect);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoAfterItemErase(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect);
|
||
|
||
begin
|
||
if Assigned(FOnAfterItemErase) then
|
||
FOnAfterItemErase(Self, Canvas, Node, ItemRect);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoAfterItemPaint(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect);
|
||
|
||
begin
|
||
if Assigned(FOnAfterItemPaint) then
|
||
FOnAfterItemPaint(Self, Canvas, Node, ItemRect);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoAfterPaint(Canvas: TCanvas);
|
||
|
||
begin
|
||
if Assigned(FOnAfterPaint) then
|
||
FOnAfterPaint(Self, Canvas);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoAutoScroll(X, Y: Integer);
|
||
|
||
begin
|
||
FScrollDirections := DetermineScrollDirections(X, Y);
|
||
|
||
if FStates * [tsWheelPanning, tsWheelScrolling] = [] then
|
||
begin
|
||
if FScrollDirections = [] then
|
||
begin
|
||
if ((FStates * [tsScrollPending, tsScrolling]) <> []) then
|
||
begin
|
||
KillTimer(Handle, ScrollTimer);
|
||
DoStateChange([], [tsScrollPending, tsScrolling]);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// start auto scroll if not yet done
|
||
if (FStates * [tsScrollPending, tsScrolling]) = [] then
|
||
begin
|
||
DoStateChange([tsScrollPending]);
|
||
SetTimer(Handle, ScrollTimer, FAutoScrollDelay, nil);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
|
||
procedure TBaseVirtualTree.DoAutoSize;
|
||
begin
|
||
//The default DoAutoSize makes the editors be placed wrongly when scrolling
|
||
end;
|
||
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoBeforeDrag(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
||
|
||
begin
|
||
Result := False;
|
||
if Assigned(FOnDragAllowed) then
|
||
FOnDragAllowed(Self, Node, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoBeforeCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
||
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
|
||
|
||
{$ifdef LCLWin32}
|
||
var
|
||
UpdateRect: TRect;
|
||
{$endif}
|
||
|
||
begin
|
||
//todo: implement under non win32
|
||
if Assigned(FOnBeforeCellPaint) then
|
||
begin
|
||
{$ifdef LCLWin32}
|
||
if CellPaintMode = cpmGetContentMargin then
|
||
begin
|
||
// Prevent drawing if we are only about to get the margin. As this also clears the update rect we need to save it.
|
||
GetUpdateRect(Handle, UpdateRect, False);
|
||
SetUpdateState(True);
|
||
end;
|
||
{$endif}
|
||
|
||
FOnBeforeCellPaint(Self, Canvas, Node, Column, CellPaintMode, CellRect, ContentRect);
|
||
|
||
{$ifdef LCLWin32}
|
||
if CellPaintMode = cpmGetContentMargin then
|
||
begin
|
||
SetUpdateState(False);
|
||
InvalidateRect(Handle, @UpdateRect, False);
|
||
end;
|
||
{$endif}
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoBeforeItemErase(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect; var Color: TColor;
|
||
var EraseAction: TItemEraseAction);
|
||
|
||
begin
|
||
if Assigned(FOnBeforeItemErase) then
|
||
FOnBeforeItemErase(Self, Canvas, Node, ItemRect, Color, EraseAction);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoBeforeItemPaint(Canvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect): Boolean;
|
||
|
||
begin
|
||
// By default custom draw will not be used, so the tree handles drawing the node.
|
||
Result := False;
|
||
if Assigned(FOnBeforeItemPaint) then
|
||
FOnBeforeItemPaint(Self, Canvas, Node, ItemRect, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoBeforePaint(Canvas: TCanvas);
|
||
|
||
begin
|
||
if Assigned(FOnBeforePaint) then
|
||
FOnBeforePaint(Self, Canvas);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoCancelEdit: Boolean;
|
||
|
||
// Called when the current edit action or a pending edit must be cancelled.
|
||
|
||
begin
|
||
KillTimer(Handle, EditTimer);
|
||
DoStateChange([], [tsEditPending]);
|
||
Result := (tsEditing in FStates) and FEditLink.CancelEdit;
|
||
if Result then
|
||
begin
|
||
DoStateChange([], [tsEditing]);
|
||
if Assigned(FOnEditCancelled) then
|
||
FOnEditCancelled(Self, FEditColumn);
|
||
FEditLink := nil;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoCanEdit(Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
|
||
|
||
begin
|
||
if Assigned(FOnEditing) then
|
||
FOnEditing(Self, Node, Column, Allowed);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoChange(Node: PVirtualNode);
|
||
|
||
begin
|
||
KillTimer(Handle, ChangeTimer);
|
||
if Assigned(FOnChange) then
|
||
FOnChange(Self, Node);
|
||
|
||
// This is a good place to reset the cached node. This is the same as the node passed in here.
|
||
// This is necessary to allow descendants to override this method and get the node then.
|
||
DoStateChange([], [tsChangePending]);
|
||
FLastChangedNode := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoCheckClick(Node: PVirtualNode; NewCheckState: TCheckState);
|
||
|
||
begin
|
||
if ChangeCheckState(Node, NewCheckState) then
|
||
DoChecked(Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoChecked(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnChecked) then
|
||
FOnChecked(Self, Node);
|
||
|
||
{$ifdef EnableAccessible}
|
||
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoChecking(Node: PVirtualNode; var NewCheckState: TCheckState): Boolean;
|
||
|
||
// Determines if a node is allowed to change its check state to NewCheckState.
|
||
|
||
begin
|
||
if toReadOnly in FOptions.FMiscOptions then
|
||
Result := False
|
||
else
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnChecking) then
|
||
FOnChecking(Self, Node, NewCheckState, Result);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoCollapsed(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnCollapsed) then
|
||
FOnCollapsed(Self, Node);
|
||
{$ifdef EnableAccessible}
|
||
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoCollapsing(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnCollapsing) then
|
||
FOnCollapsing(Self, Node, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoColumnClick(Column: TColumnIndex; Shift: TShiftState);
|
||
|
||
begin
|
||
if Assigned(FOnColumnClick) then
|
||
FOnColumnClick(Self, Column, Shift);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoColumnDblClick(Column: TColumnIndex; Shift: TShiftState);
|
||
|
||
begin
|
||
if Assigned(FOnColumnDblClick) then
|
||
FOnColumnDblClick(Self, Column, Shift);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoColumnResize(Column: TColumnIndex);
|
||
|
||
var
|
||
R: TRect;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if not (csLoading in ComponentState) and HandleAllocated then
|
||
begin
|
||
// Reset all vsHeightMeasured flags if we are in multiline mode.
|
||
Run := GetFirstInitialized;
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsMultiline in Run.States then
|
||
Exclude(Run.States, vsHeightMeasured);
|
||
Run := GetNextInitialized(Run);
|
||
end;
|
||
|
||
UpdateHorizontalScrollBar(True);
|
||
if Column > NoColumn then
|
||
begin
|
||
// Invalidate client area from the current column all to the right (or left in RTL mode).
|
||
R := ClientRect;
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
Inc(R.Bottom, FHeader.Height);
|
||
if not (toAutoSpanColumns in FOptions.FAutoOptions) then
|
||
if UseRightToLeftAlignment then
|
||
R.Right := FHeader.Columns[Column].Left + FHeader.Columns[Column].Width + ComputeRTLOffset
|
||
else
|
||
R.Left := FHeader.Columns[Column].Left;
|
||
InvalidateRect(Handle, @R, False);
|
||
FHeader.Invalidate(FHeader.Columns[Column], True);
|
||
end;
|
||
if [hsColumnWidthTracking, hsResizing] * FHeader.States = [hsColumnWidthTracking] then
|
||
UpdateWindow(Handle);
|
||
|
||
if not (tsUpdating in FStates) then
|
||
UpdateDesigner; // design time only
|
||
|
||
if Assigned(FOnColumnResize) and not (hsResizing in FHeader.States) then
|
||
FOnColumnResize(FHeader, Column);
|
||
|
||
// If the tree is currently in edit state then notify edit link.
|
||
if tsEditing in FStates then
|
||
UpdateEditBounds;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoCompare(Node1, Node2: PVirtualNode; Column: TColumnIndex): Integer;
|
||
|
||
begin
|
||
Result := 0;
|
||
if Assigned(FOnCompareNodes) then
|
||
FOnCompareNodes(Self, Node1, Node2, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoCreateDataObject: IDataObject;
|
||
|
||
begin
|
||
Result := nil;
|
||
if Assigned(FOnCreateDataObject) then
|
||
FOnCreateDataObject(Self, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoCreateDragManager: IVTDragManager;
|
||
|
||
begin
|
||
Result := nil;
|
||
if Assigned(FOnCreateDragManager) then
|
||
FOnCreateDragManager(Self, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink;
|
||
|
||
begin
|
||
Result := nil;
|
||
if Assigned(FOnCreateEditor) then
|
||
begin
|
||
FOnCreateEditor(Self, Node, Column, Result);
|
||
if Result = nil then
|
||
ShowError(SEditLinkIsNil, hcTFEditLinkIsNil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoDragging(P: TPoint);
|
||
|
||
// Initiates finally the drag'n drop operation and returns after DD is finished.
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
function GetDragOperations: LongWord;
|
||
|
||
begin
|
||
if FDragOperations = [] then
|
||
Result := DROPEFFECT_COPY or DROPEFFECT_MOVE or DROPEFFECT_LINK
|
||
else
|
||
begin
|
||
Result := 0;
|
||
if doCopy in FDragOperations then
|
||
Result := Result or DROPEFFECT_COPY;
|
||
if doLink in FDragOperations then
|
||
Result := Result or DROPEFFECT_LINK;
|
||
if doMove in FDragOperations then
|
||
Result := Result or DROPEFFECT_MOVE;
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
var
|
||
DragEffect, AllowedEffects: LongWord;
|
||
I: Integer;
|
||
DragObject: TDragObject;
|
||
|
||
DataObject: IDataObject;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DoDragging');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.SendCallStack([lcDrag],'Stack');{$endif}
|
||
DataObject := nil;
|
||
// Dragging is dragging, nothing else.
|
||
DoCancelEdit;
|
||
|
||
if Assigned(FCurrentHotNode) then
|
||
begin
|
||
InvalidateNode(FCurrentHotNode);
|
||
FCurrentHotNode := nil;
|
||
end;
|
||
// Select the focused node if not already done.
|
||
if Assigned(FFocusedNode) and not (vsSelected in FFocusedNode.States) then
|
||
begin
|
||
InternalAddToSelection(FFocusedNode, False);
|
||
InvalidateNode(FFocusedNode);
|
||
end;
|
||
|
||
UpdateWindow(Handle);
|
||
|
||
// Keep a list of all currently selected nodes as this list might change,
|
||
// but we have probably to delete currently selected nodes.
|
||
FDragSelection := GetSortedSelection(True);
|
||
try
|
||
DoStateChange([tsOLEDragging], [tsOLEDragPending, tsClearPending]);
|
||
|
||
// An application might create a drag object like used during VCL dd. This is not required for OLE dd but
|
||
// required as parameter.
|
||
DragObject := nil;
|
||
DoStartDrag(DragObject);
|
||
DragObject.Free;
|
||
|
||
DataObject := VTVDragManager.DataObject;
|
||
PrepareDragImage(P, DataObject);
|
||
|
||
FLastDropMode := dmOnNode;
|
||
// Don't forget to initialize the result. It might never be touched.
|
||
DragEffect := DROPEFFECT_NONE;
|
||
AllowedEffects := GetDragOperations;
|
||
try
|
||
DragAndDrop(AllowedEffects, DataObject, DragEffect);
|
||
VTVDragManager.ForceDragLeave;
|
||
finally
|
||
GetCursorPos(P);
|
||
P := ScreenToClient(P);
|
||
DoEndDrag(Self, P.X, P.Y);
|
||
|
||
FDragImage.EndDrag;
|
||
|
||
// Finish the operation.
|
||
if (DragEffect = DROPEFFECT_MOVE) and (toAutoDeleteMovedNodes in TreeOptions.AutoOptions) then
|
||
begin
|
||
// The operation was a move so delete the previously selected nodes.
|
||
BeginUpdate;
|
||
try
|
||
// The list of selected nodes was retrieved in resolved state. That means there can never be a node
|
||
// in the list whose parent (or its parent etc.) is also selected.
|
||
for I := 0 to High(FDragSelection) do
|
||
DeleteNode(FDragSelection[I]);
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
|
||
DoStateChange([], [tsOLEDragging]);
|
||
end;
|
||
finally
|
||
FDragSelection := nil;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DoDragging');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoDragExpand;
|
||
|
||
var
|
||
SourceTree: TBaseVirtualTree;
|
||
|
||
begin
|
||
KillTimer(Handle, ExpandTimer);
|
||
if Assigned(FDropTargetNode) and (vsHasChildren in FDropTargetNode.States) and
|
||
not (vsExpanded in FDropTargetNode.States) then
|
||
begin
|
||
if Assigned(FDragManager) then
|
||
SourceTree := TBaseVirtualTree(VTVDragManager.DragSource)
|
||
else
|
||
SourceTree := nil;
|
||
|
||
if not VTVDragManager.DropTargetHelperSupported and Assigned(SourceTree) then
|
||
SourceTree.FDragImage.HideDragImage;
|
||
ToggleNode(FDropTargetNode);
|
||
UpdateWindow(Handle);
|
||
if not VTVDragManager.DropTargetHelperSupported and Assigned(SourceTree) then
|
||
SourceTree.FDragImage.ShowDragImage;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoDragOver(Source: TObject; Shift: TShiftState; State: TDragState; const Pt: TPoint; Mode: TDropMode;
|
||
var Effect: LongWord): Boolean;
|
||
|
||
begin
|
||
Result := False;
|
||
if Assigned(FOnDragOver) then
|
||
FOnDragOver(Self, Source, Shift, State, Pt, Mode, Effect, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoDragDrop(Source: TObject; DataObject: IDataObject; Formats: TFormatArray;
|
||
Shift: TShiftState; const Pt: TPoint; var Effect: LongWord; Mode: TDropMode);
|
||
|
||
begin
|
||
if Assigned(FOnDragDrop) then
|
||
FOnDragDrop(Self, Source, DataObject, Formats, Shift, Pt, Effect, Mode);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoEdit;
|
||
|
||
begin
|
||
Application.CancelHint;
|
||
KillTimer(Handle, ScrollTimer);
|
||
KillTimer(Handle, EditTimer);
|
||
DoStateChange([], [tsEditPending]);
|
||
if Assigned(FFocusedNode) and not (vsDisabled in FFocusedNode.States) and
|
||
not (toReadOnly in FOptions.FMiscOptions) and (FEditLink = nil) then
|
||
begin
|
||
FEditLink := DoCreateEditor(FFocusedNode, FEditColumn);
|
||
if Assigned(FEditLink) then
|
||
begin
|
||
DoStateChange([tsEditing], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection, tsOLEDragPending,
|
||
tsOLEDragging, tsClearPending, tsScrollPending, tsScrolling, tsMouseCheckPending]);
|
||
ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions,
|
||
not (toDisableAutoscrollOnEdit in FOptions.AutoOptions));
|
||
if FEditLink.PrepareEdit(Self, FFocusedNode, FEditColumn) then
|
||
begin
|
||
UpdateEditBounds;
|
||
// Node needs repaint because the selection rectangle and static text must disappear.
|
||
InvalidateNode(FFocusedNode);
|
||
if not FEditLink.BeginEdit then
|
||
DoStateChange([], [tsEditing]);
|
||
end
|
||
else
|
||
DoStateChange([], [tsEditing]);
|
||
if not (tsEditing in FStates) then
|
||
FEditLink := nil;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoEndDrag(Target: TObject; X, Y: Integer);
|
||
|
||
// Does some housekeeping for VCL drag'n drop;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
DragFinished;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoEndEdit: Boolean;
|
||
|
||
begin
|
||
KillTimer(Handle, EditTimer);
|
||
Result := (tsEditing in FStates) and FEditLink.EndEdit;
|
||
if Result then
|
||
begin
|
||
DoStateChange([], [tsEditing]);
|
||
FEditLink := nil;
|
||
if Assigned(FOnEdited) then
|
||
FOnEdited(Self, FFocusedNode, FEditColumn);
|
||
end;
|
||
DoStateChange([], [tsEditPending]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoExpanded(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnExpanded) then
|
||
FOnExpanded(Self, Node);
|
||
{$ifdef EnableAccessible}
|
||
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoExpanding(Node: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnExpanding) then
|
||
FOnExpanding(Self, Node, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoFocusChange(Node: PVirtualNode; Column: TColumnIndex);
|
||
|
||
begin
|
||
if Assigned(FOnFocusChanged) then
|
||
FOnFocusChanged(Self, Node, Column);
|
||
{$ifdef EnableAccessible}
|
||
NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
NotifyWinEvent(EVENT_OBJECT_SELECTION, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
NotifyWinEvent(EVENT_OBJECT_FOCUS, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoFocusChanging(OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex): Boolean;
|
||
|
||
begin
|
||
Result := (OldColumn = NewColumn) or FHeader.AllowFocus(NewColumn);
|
||
if Assigned(FOnFocusChanging) then
|
||
FOnFocusChanging(Self, OldNode, NewNode, OldColumn, NewColumn, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoFocusNode(Node: PVirtualNode; Ask: Boolean);
|
||
|
||
begin
|
||
if not (tsEditing in FStates) or EndEditNode then
|
||
begin
|
||
if Node = FRoot then
|
||
Node := nil;
|
||
if (FFocusedNode <> Node) and (not Ask or DoFocusChanging(FFocusedNode, Node, FFocusedColumn, FFocusedColumn)) then
|
||
begin
|
||
if Assigned(FFocusedNode) then
|
||
begin
|
||
// Do automatic collapsing of last focused node if enabled. This is however only done if
|
||
// old and new focused node have a common parent node.
|
||
if (toAutoExpand in FOptions.FAutoOptions) and Assigned(Node) and (Node.Parent = FFocusedNode.Parent) and
|
||
(vsExpanded in FFocusedNode.States) then
|
||
ToggleNode(FFocusedNode)
|
||
else
|
||
InvalidateNode(FFocusedNode);
|
||
end;
|
||
FFocusedNode := Node;
|
||
end;
|
||
|
||
// Have to scroll the node into view, even it is the same node as before.
|
||
if Assigned(FFocusedNode) then
|
||
begin
|
||
// Make sure a valid column is set if columns are used and no column has currently the focus.
|
||
if FHeader.UseColumns and (not FHeader.FColumns.IsValidColumn(FFocusedColumn)) then
|
||
FFocusedColumn := FHeader.MainColumn;
|
||
// Do automatic expansion of the newly focused node if enabled.
|
||
if (toAutoExpand in FOptions.FAutoOptions) and not (vsExpanded in FFocusedNode.States) then
|
||
ToggleNode(FFocusedNode);
|
||
InvalidateNode(FFocusedNode);
|
||
if (FUpdateCount = 0) and not (toDisableAutoscrollOnFocus in FOptions.FAutoOptions) then
|
||
ScrollIntoView(FFocusedNode, (toCenterScrollIntoView in FOptions.SelectionOptions) and
|
||
(MouseButtonDown * FStates = []), True);
|
||
end;
|
||
|
||
// Reset range anchor if necessary.
|
||
if FSelectionCount = 0 then
|
||
ResetRangeAnchor;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoFreeNode(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Node = FLastChangedNode then
|
||
FLastChangedNode := nil;
|
||
if Node = FCurrentHotNode then
|
||
FCurrentHotNode := nil;
|
||
if Node = FDropTargetNode then
|
||
FDropTargetNode := nil;
|
||
if Assigned(FOnFreeNode) and ([vsInitialized, vsInitialUserData] * Node.States <> []) then
|
||
FOnFreeNode(Self, Node);
|
||
FreeMem(Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex;
|
||
CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint;
|
||
|
||
// Determines the margins of the content rectangle caused by DoBeforeCellPaint.
|
||
// Note that shrinking the content rectangle results in positive margins whereas enlarging the content rectangle results
|
||
// in negative margins.
|
||
|
||
var
|
||
CellRect,
|
||
ContentRect: TRect;
|
||
|
||
begin
|
||
Result := Point(0, 0);
|
||
|
||
if Assigned(FOnBeforeCellPaint) then // Otherwise DoBeforeCellPaint has no effect.
|
||
begin
|
||
if Canvas = nil then
|
||
Canvas := Self.Canvas;
|
||
|
||
// Determine then node's cell rectangle and content rectangle before calling DoBeforeCellPaint.
|
||
CellRect := GetDisplayRect(Node, Column, True);
|
||
ContentRect := CellRect;
|
||
DoBeforeCellPaint(Canvas, Node, Column, cpmGetContentMargin, CellRect, ContentRect);
|
||
|
||
// Calculate the changes caused by DoBeforeCellPaint.
|
||
case CellContentMarginType of
|
||
ccmtAllSides:
|
||
// Calculate the width difference and high difference.
|
||
Result := Point((CellRect.Right - CellRect.Left) - (ContentRect.Right - ContentRect.Left),
|
||
(CellRect.Bottom - CellRect.Top) - (ContentRect.Bottom - ContentRect.Top));
|
||
ccmtTopLeftOnly:
|
||
// Calculate the left margin and top margin only.
|
||
Result := Point(ContentRect.Left - CellRect.Left, ContentRect.Top - CellRect.Top);
|
||
ccmtBottomRightOnly:
|
||
// Calculate the right margin and bottom margin only.
|
||
Result := Point(CellRect.Right - ContentRect.Right, CellRect.Bottom - ContentRect.Bottom);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoGetCursor(var Cursor: TCursor);
|
||
|
||
begin
|
||
if Assigned(FOnGetCursor) then
|
||
FOnGetCursor(Self, Cursor);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoGetHeaderCursor(var Cursor: HCURSOR);
|
||
|
||
begin
|
||
if Assigned(FOnGetHeaderCursor) then
|
||
FOnGetHeaderCursor(FHeader, Cursor);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
||
var Ghosted: Boolean; var Index: Integer): TCustomImageList;
|
||
|
||
// Queries the application/descendant about certain image properties for a node.
|
||
// Returns a custom image list if given by the callee, otherwise nil.
|
||
|
||
begin
|
||
Result := nil;
|
||
|
||
// First try the enhanced event to allow for custom image lists.
|
||
if Assigned(FOnGetImageEx) then
|
||
FOnGetImageEx(Self, Node, Kind, Column, Ghosted, Index, Result)
|
||
else
|
||
if Assigned(FOnGetImage) then
|
||
FOnGetImage(Self, Node, Kind, Column, Ghosted, Index);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoGetImageText(Node: PVirtualNode; Kind: TVTImageKind;
|
||
Column: TColumnIndex; var ImageText: String);
|
||
|
||
// Queries the application/descendant about alternative image text for a node.
|
||
|
||
begin
|
||
if Assigned(FOnGetImageText) then
|
||
FOnGetImageText(Self, Node, Kind, Column, ImageText);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoGetLineStyle(var Bits: Pointer);
|
||
|
||
begin
|
||
if Assigned(FOnGetLineStyle) then
|
||
FOnGetLineStyle(Self, Bits);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex;
|
||
var LineBreakStyle: TVTTooltipLineBreakStyle): String;
|
||
|
||
begin
|
||
Result := Hint;
|
||
LineBreakStyle := hlbDefault;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex;
|
||
var LineBreakStyle: TVTTooltipLineBreakStyle): String;
|
||
|
||
begin
|
||
Result := Hint;
|
||
LineBreakStyle := hlbDefault;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer;
|
||
|
||
begin
|
||
Result := 0;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Position: TPoint): TPopupMenu;
|
||
|
||
// Queries the application whether there is a node specific popup menu.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
AskParent: Boolean;
|
||
|
||
begin
|
||
Result := nil;
|
||
if Assigned(FOnGetPopupMenu) then
|
||
begin
|
||
Run := Node;
|
||
|
||
if Assigned(Run) then
|
||
begin
|
||
AskParent := True;
|
||
repeat
|
||
FOnGetPopupMenu(Self, Run, Column, Position, AskParent, Result);
|
||
Run := Run.Parent;
|
||
until (Run = FRoot) or Assigned(Result) or not AskParent;
|
||
end
|
||
else
|
||
FOnGetPopupMenu(Self, nil, -1, Position, AskParent, Result);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoGetUserClipboardFormats(var Formats: TFormatEtcArray);
|
||
|
||
begin
|
||
if Assigned(FOnGetUserClipboardFormats) then
|
||
FOnGetUserClipboardFormats(Self, Formats);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderClick) then
|
||
FOnHeaderClick(FHeader, Column, Button, Shift, X, Y);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderDblClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderDblClick) then
|
||
FOnHeaderDblClick(FHeader, Column, Button, Shift, X, Y);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderImageClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState;
|
||
X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderImageClick) then
|
||
FOnHeaderImageClick(FHeader, Column, Button, Shift, X, Y)
|
||
else if Assigned(FOnHeaderClick) then
|
||
FOnHeaderClick(FHeader, Column, Button, Shift, X, Y)
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderCheckBoxClick(Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState;
|
||
X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderCheckBoxClick) then
|
||
FOnHeaderCheckBoxClick(FHeader, Column, Button, Shift, X, Y);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderDragged(Column: TColumnIndex; OldPosition: TColumnPosition);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderDragged) then
|
||
FOnHeaderDragged(FHeader, Column, OldPosition);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderDraggedOut(Column: TColumnIndex; const DropPosition: TPoint);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderDraggedOut) then
|
||
FOnHeaderDraggedOut(FHeader, Column, DropPosition);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoHeaderDragging(Column: TColumnIndex): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnHeaderDragging) then
|
||
FOnHeaderDragging(FHeader, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderDraw(Canvas: TCanvas; Column: TVirtualTreeColumn; const R: TRect; Hover, Pressed: Boolean;
|
||
DropMark: TVTDropMarkMode);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderDraw) then
|
||
FOnHeaderDraw(FHeader, Canvas, Column, R, Hover, Pressed, DropMark);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderDrawQueryElements(var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderDrawQueryElements) then
|
||
FOnHeaderDrawQueryElements(FHeader, PaintInfo, Elements);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderMouseDown) then
|
||
FOnHeaderMouseDown(FHeader, Button, Shift, X, Y);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderMouseMove(Shift: TShiftState; X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderMouseMove) then
|
||
FOnHeaderMouseMove(FHeader, Shift, X, Y);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHeaderMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnHeaderMouseUp) then
|
||
FOnHeaderMouseUp(FHeader, Button, Shift, X, Y);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoHotChange(Old, New: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnHotChange) then
|
||
FOnHotChange(Self, Old, New);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoIncrementalSearch(Node: PVirtualNode; const Text: String): Integer;
|
||
|
||
begin
|
||
Result := 0;
|
||
if Assigned(FOnIncrementalSearch) then
|
||
FOnIncrementalSearch(Self, Node, Text, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal);
|
||
|
||
begin
|
||
if Assigned(FOnInitChildren) then
|
||
FOnInitChildren(Self, Node, ChildCount);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoInitNode(Parent, Node: PVirtualNode; var InitStates: TVirtualNodeInitStates);
|
||
|
||
begin
|
||
if Assigned(FOnInitNode) then
|
||
FOnInitNode(Self, Parent, Node, InitStates);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoKeyAction(var CharCode: Word; var Shift: TShiftState): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnKeyAction) then
|
||
FOnKeyAction(Self, CharCode, Shift, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoLoadUserData(Node: PVirtualNode; Stream: TStream);
|
||
|
||
begin
|
||
if Assigned(FOnLoadNode) then
|
||
if Node = FRoot then
|
||
FOnLoadNode(Self, nil, Stream)
|
||
else
|
||
FOnLoadNode(Self, Node, Stream);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoMeasureItem(TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnMeasureItem) then
|
||
FOnMeasureItem(Self, TargetCanvas, Node, NodeHeight);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoNodeCopied(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnNodeCopied) then
|
||
FOnNodeCopied(Self, Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoNodeCopying(Node, NewParent: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnNodeCopying) then
|
||
FOnNodeCopying(Self, Node, NewParent, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoNodeHeightDblClickResize(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState;
|
||
P: TPoint): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnNodeHeightDblClickResize) then
|
||
FOnNodeHeightDblClickResize(Self, Node, Column, Shift, P, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoNodeHeightTracking(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState;
|
||
var TrackPoint: TPoint; P: TPoint): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnNodeHeightTracking) then
|
||
FOnNodeHeightTracking(Self, Node, Column, Shift, TrackPoint, P, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoNodeMoved(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnNodeMoved) then
|
||
FOnNodeMoved(Self, Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoNodeMoving(Node, NewParent: PVirtualNode): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
if Assigned(FOnNodeMoving) then
|
||
FOnNodeMoving(Self, Node, NewParent, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoPaintBackground(Canvas: TCanvas; const R: TRect): Boolean;
|
||
|
||
begin
|
||
Result := False;
|
||
if Assigned(FOnPaintBackground) then
|
||
FOnPaintBackground(Self, Canvas, R, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoPaintDropMark(Canvas: TCanvas; Node: PVirtualNode; const R: TRect);
|
||
|
||
// draws the drop mark into the given rectangle
|
||
// Note: Changed properties of the given canvas should be reset to their previous values.
|
||
|
||
var
|
||
SaveBrushColor: TColor;
|
||
SavePenStyle: TPenStyle;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DoPaintDropMark');{$endif}
|
||
if FLastDropMode in [dmAbove, dmBelow] then
|
||
with Canvas do
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'DropMode in [dmAbove,dmBelow]');{$endif}
|
||
SavePenStyle := Pen.Style;
|
||
Pen.Style := psClear;
|
||
SaveBrushColor := Brush.Color;
|
||
Brush.Color := FColors.DropMarkColor;
|
||
{$ifdef DEBUG_VTV}Logger.SendColor([lcDrag],'Brush.Color',Brush.Color);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'R',R);{$endif}
|
||
if FLastDropMode = dmAbove then
|
||
begin
|
||
Polygon([Point(R.Left + 2, R.Top),
|
||
Point(R.Right - 2, R.Top),
|
||
Point(R.Right - 2, R.Top + 6),
|
||
Point(R.Right - 6, R.Top + 2),
|
||
Point(R.Left + 6 , R.Top + 2),
|
||
Point(R.Left + 2, R.Top + 6)
|
||
]);
|
||
end
|
||
else
|
||
Polygon([Point(R.Left + 2, R.Bottom - 1),
|
||
Point(R.Right - 2, R.Bottom - 1),
|
||
Point(R.Right - 2, R.Bottom - 8),
|
||
Point(R.Right - 7, R.Bottom - 3),
|
||
Point(R.Left + 7 , R.Bottom - 3),
|
||
Point(R.Left + 2, R.Bottom - 8)
|
||
]);
|
||
Brush.Color := SaveBrushColor;
|
||
Pen.Style := SavePenStyle;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DoPaintDropMark');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoPaintNode(var PaintInfo: TVTPaintInfo);
|
||
|
||
begin
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Position: TPoint);
|
||
|
||
// Support for node dependent popup menus.
|
||
|
||
var
|
||
Menu: TPopupMenu;
|
||
|
||
begin
|
||
Menu := DoGetPopupMenu(Node, Column, Position);
|
||
|
||
if Assigned(Menu) then
|
||
begin
|
||
DoStateChange([tsPopupMenuShown]);
|
||
KillTimer(Handle, EditTimer);
|
||
Menu.PopupComponent := Self;
|
||
with ClientToScreen(Position) do
|
||
Menu.Popup(X, Y);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium;
|
||
ForClipboard: Boolean): HRESULT;
|
||
|
||
begin
|
||
Result := E_FAIL;
|
||
if Assigned(FOnRenderOLEData) then
|
||
FOnRenderOLEData(Self, FormatEtcIn, Medium, ForClipboard, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoReset(Node: PVirtualNode);
|
||
|
||
begin
|
||
if Assigned(FOnResetNode) then
|
||
FOnResetNode(Self, Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoSaveUserData(Node: PVirtualNode; Stream: TStream);
|
||
|
||
begin
|
||
if Assigned(FOnSaveNode) then
|
||
if Node = FRoot then
|
||
FOnSaveNode(Self, nil, Stream)
|
||
else
|
||
FOnSaveNode(Self, Node, Stream);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoScroll(DeltaX, DeltaY: Integer);
|
||
|
||
begin
|
||
if Assigned(FOnScroll) then
|
||
FOnScroll(Self, DeltaX, DeltaY);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean;
|
||
|
||
// Actual offset setter used to scroll the client area, update scroll bars and invalidating the header (all optional).
|
||
// Returns True if the offset really changed otherwise False is returned.
|
||
|
||
var
|
||
DeltaX: Integer;
|
||
DeltaY: Integer;
|
||
DWPStructure: THandle;//HDWP;
|
||
I: Integer;
|
||
P: TPoint;
|
||
R: TRect;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcScroll],'DoSetOffsetXY');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcScroll],'Value',Value);{$endif}
|
||
//{$ifdef DEBUG_VTV}Logger.SendCallStack([lcScroll],'CallStack');{$endif}
|
||
// Range check, order is important here.
|
||
if Value.X < (ClientWidth - Integer(FRangeX)) then
|
||
Value.X := ClientWidth - Integer(FRangeX);
|
||
if Value.X > 0 then
|
||
Value.X := 0;
|
||
DeltaX := Value.X - FOffsetX;
|
||
if UseRightToLeftAlignment then
|
||
DeltaX := -DeltaX;
|
||
if Value.Y < (ClientHeight - Integer(FRangeY)) then
|
||
Value.Y := ClientHeight - Integer(FRangeY);
|
||
if Value.Y > 0 then
|
||
Value.Y := 0;
|
||
DeltaY := Value.Y - FOffsetY;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcScroll],'FOffsetX: %d FOffsetY: %d',[FOffsetX,FOffsetY]);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcScroll],'DeltaX: %d DeltaY: %d',[DeltaX,DeltaY]);{$endif}
|
||
Result := (DeltaX <> 0) or (DeltaY <> 0);
|
||
if Result then
|
||
begin
|
||
FOffsetX := Value.X;
|
||
FOffsetY := Value.Y;
|
||
|
||
Application.CancelHint;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
// The drag image from VCL controls need special consideration.
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if tsVCLDragging in FStates then
|
||
ImageList_DragShowNolock(False);
|
||
{$endif}
|
||
|
||
if (suoScrollClientArea in Options) and not (tsToggling in FStates) then
|
||
begin
|
||
// Have to invalidate the entire window if there's a background.
|
||
if (toShowBackground in FOptions.FPaintOptions) and (FBackground.Graphic is TBitmap) then
|
||
begin
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
// Since we don't use ScrollWindow here we have to move all client windows ourselves.
|
||
DWPStructure := BeginDeferWindowPos(ControlCount);
|
||
for I := 0 to ControlCount - 1 do
|
||
if Controls[I] is TWinControl then
|
||
begin
|
||
with Controls[I] as TWinControl do
|
||
DWPStructure := DeferWindowPos(DWPStructure, Handle, 0, Left + DeltaX, Top + DeltaY, 0, 0,
|
||
SWP_NOZORDER or SWP_NOACTIVATE or SWP_NOSIZE);
|
||
if DWPStructure = 0 then
|
||
Break;
|
||
end;
|
||
if DWPStructure <> 0 then
|
||
EndDeferWindowPos(DWPStructure);
|
||
InvalidateRect(Handle, nil, False);
|
||
{$endif}
|
||
end
|
||
else
|
||
begin
|
||
if (DeltaX <> 0) and (Header.Columns.GetVisibleFixedWidth > 0) then
|
||
begin
|
||
// When fixed columns exists we have to scroll separately horizontally and vertically.
|
||
// Horizontally is scroll only the client area not occupied by fixed columns and
|
||
// vertically entire client area (or clipping area if one exists).
|
||
R := ClientRect;
|
||
R.Left := Header.Columns.GetVisibleFixedWidth;
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
begin
|
||
Inc(R.Top,FHeader.Height);
|
||
Inc(R.Bottom,FHeader.Height);
|
||
end;
|
||
//scrollwindow implementation under gtk is broken
|
||
{$ifdef Gtk}
|
||
InvalidateRect(Handle, nil, True);
|
||
{$else}
|
||
ScrollWindow(Handle, DeltaX, 0, @R, @R);
|
||
if DeltaY <> 0 then
|
||
ScrollWindow(Handle, 0, DeltaY, @R, @R);
|
||
{$endif}
|
||
end
|
||
else
|
||
begin
|
||
//lclheader
|
||
if ClipRect <> nil then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.SendWarning([lcWarning], 'DoSetOffsetXY called with a non nil ClipRect');{$endif}
|
||
R := ClipRect^;
|
||
end
|
||
else
|
||
R := ClientRect;
|
||
if hoVisible in FHeader.FOptions then
|
||
begin
|
||
Inc(R.Top, FHeader.Height);
|
||
Inc(R.Bottom, FHeader.Height);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcScroll], 'Rect to Scroll', R);{$endif}
|
||
//todo: temporary hack to avoid some drawing problems.
|
||
//Will be removed when scrollwindowex is properly implemented in all widgets
|
||
{$ifdef LCLQt}
|
||
ScrollWindow(Handle, DeltaX, DeltaY, @R, @R);
|
||
{$else}
|
||
{$ifdef Gtk}
|
||
InvalidateRect(Handle, nil, True);
|
||
{$else}
|
||
ScrollWindowEx(Handle, DeltaX, DeltaY, @R, @R,0, nil, SW_INVALIDATE or SW_SCROLLCHILDREN);
|
||
{$endif}
|
||
{$endif}
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if suoUpdateNCArea in Options then
|
||
begin
|
||
if DeltaX <> 0 then
|
||
begin
|
||
if (suoRepaintHeader in Options) and (hoVisible in FHeader.FOptions) then
|
||
FHeader.Invalidate(nil);
|
||
if not (tsSizing in FStates) and (FScrollBarOptions.ScrollBars in [ssHorizontal, ssBoth]) then
|
||
UpdateHorizontalScrollBar(suoRepaintScrollbars in Options);
|
||
end;
|
||
|
||
if (DeltaY <> 0) and ([tsThumbTracking, tsSizing] * FStates = []) then
|
||
begin
|
||
UpdateVerticalScrollBar(suoRepaintScrollbars in Options);
|
||
if not (FHeader.UseColumns or IsMouseSelecting) and
|
||
(FScrollBarOptions.ScrollBars in [ssHorizontal, ssBoth]) then
|
||
UpdateHorizontalScrollBar(suoRepaintScrollbars in Options);
|
||
end;
|
||
end;
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if tsVCLDragging in FStates then
|
||
ImageList_DragShowNolock(True);
|
||
{$endif}
|
||
end;
|
||
|
||
// Finally update "hot" node if hot tracking is activated
|
||
GetCursorPos(P);
|
||
P := ScreenToClient(P);
|
||
if PtInRect(ClientRect, P) then
|
||
HandleHotTrack(P.X, P.Y);
|
||
|
||
DoScroll(DeltaX, DeltaY);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcScroll],'DoSetOffsetXY');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoShowScrollbar(Bar: Integer; Show: Boolean);
|
||
|
||
begin
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_ShowScrollBar(Handle, Bar, Show);
|
||
{$else}
|
||
ShowScrollBar(Handle, Bar, Show);
|
||
{$endif UseFlatScrollbars};
|
||
|
||
if Assigned(FOnShowScrollbar) then
|
||
FOnShowScrollbar(Self, Bar, Show);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoStartDrag(var DragObject: TDragObject);
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DoStartDrag');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.SendCallStack([lcDrag],'Stack');{$endif}
|
||
|
||
inherited;
|
||
|
||
// Check if the application created an own drag object. This is needed to pass the correct source in
|
||
// OnDragOver and OnDragDrop.
|
||
if Assigned(DragObject) then
|
||
DoStateChange([tsUserDragObject]);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DoStartDrag');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []);
|
||
|
||
var
|
||
ActualEnter,
|
||
ActualLeave: TVirtualTreeStates;
|
||
|
||
begin
|
||
if Assigned(FOnStateChange) then
|
||
begin
|
||
ActualEnter := Enter - FStates;
|
||
ActualLeave := FStates * Leave;
|
||
if (ActualEnter + ActualLeave) <> [] then
|
||
FOnStateChange(Self, Enter, Leave);
|
||
end;
|
||
FStates := FStates + Enter - Leave;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoStructureChange(Node: PVirtualNode; Reason: TChangeReason);
|
||
|
||
begin
|
||
if HandleAllocated then
|
||
KillTimer(Handle, StructureChangeTimer);
|
||
if Assigned(FOnStructureChange) then
|
||
FOnStructureChange(Self, Node, Reason);
|
||
|
||
// This is a good place to reset the cached node and reason. These are the same as the values passed in here.
|
||
// This is necessary to allow descendants to override this method and get them.
|
||
DoStateChange([], [tsStructureChangePending]);
|
||
FLastStructureChangeNode := nil;
|
||
FLastStructureChangeReason := crIgnore;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoTimerScroll;
|
||
|
||
var
|
||
P,
|
||
ClientP: TPoint;
|
||
InRect,
|
||
Panning: Boolean;
|
||
R,
|
||
ClipRect: TRect;
|
||
DeltaX,
|
||
DeltaY: Integer;
|
||
|
||
begin
|
||
GetCursorPos(P);
|
||
//lclheader
|
||
R := inherited GetClientRect;
|
||
ClipRect := R;
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
MapWindowPoints(Handle, 0, R, 2);
|
||
{$endif}
|
||
InRect := PtInRect(R, P);
|
||
ClientP := ScreenToClient(P);
|
||
Panning := [tsWheelPanning, tsWheelScrolling] * FStates <> [];
|
||
|
||
if IsMouseSelecting or InRect or Panning then
|
||
begin
|
||
DeltaX := 0;
|
||
DeltaY := 0;
|
||
if sdUp in FScrollDirections then
|
||
begin
|
||
if Panning then
|
||
DeltaY := FLastClickPos.Y - ClientP.Y - 8
|
||
else
|
||
if InRect then
|
||
DeltaY := Min(FScrollBarOptions.FIncrementY, ClientHeight)
|
||
else
|
||
DeltaY := Min(FScrollBarOptions.FIncrementY, ClientHeight) * Abs(R.Top - P.Y);
|
||
if FOffsetY = 0 then
|
||
Exclude(FScrollDirections, sdUp);
|
||
end;
|
||
|
||
if sdDown in FScrollDirections then
|
||
begin
|
||
if Panning then
|
||
DeltaY := FLastClickPos.Y - ClientP.Y + 8
|
||
else
|
||
if InRect then
|
||
DeltaY := -Min(FScrollBarOptions.FIncrementY, ClientHeight)
|
||
else
|
||
DeltaY := -Min(FScrollBarOptions.FIncrementY, ClientHeight) * Abs(P.Y - R.Bottom);
|
||
if (ClientHeight - FOffsetY) = Integer(FRangeY) then
|
||
Exclude(FScrollDirections, sdDown);
|
||
end;
|
||
|
||
if sdLeft in FScrollDirections then
|
||
begin
|
||
if Panning then
|
||
DeltaX := FLastClickPos.X - ClientP.X - 8
|
||
else
|
||
if InRect then
|
||
DeltaX := FScrollBarOptions.FIncrementX
|
||
else
|
||
DeltaX := FScrollBarOptions.FIncrementX * Abs(R.Left - P.X);
|
||
if FEffectiveOffsetX = 0 then
|
||
Exclude(FScrollDirections, sdleft);
|
||
end;
|
||
|
||
if sdRight in FScrollDirections then
|
||
begin
|
||
if Panning then
|
||
DeltaX := FLastClickPos.X - ClientP.X + 8
|
||
else
|
||
if InRect then
|
||
DeltaX := -FScrollBarOptions.FIncrementX
|
||
else
|
||
DeltaX := -FScrollBarOptions.FIncrementX * Abs(P.X - R.Right);
|
||
|
||
if (ClientWidth + FEffectiveOffsetX) = Integer(FRangeX) then
|
||
Exclude(FScrollDirections, sdRight);
|
||
end;
|
||
|
||
if UseRightToLeftAlignment then
|
||
DeltaX := - DeltaX;
|
||
|
||
if IsMouseSelecting then
|
||
begin
|
||
// In order to avoid scrolling the area which needs a repaint due to the changed selection rectangle
|
||
// we limit the scroll area explicitely.
|
||
OffsetRect(ClipRect, DeltaX, DeltaY);
|
||
DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), DefaultScrollUpdateFlags, @ClipRect);
|
||
// When selecting with the mouse then either update only the parts of the window which have been uncovered
|
||
// by the scroll operation if no change in the selection happend or invalidate and redraw the entire
|
||
// client area otherwise (to avoid the time consuming task of determining the display rectangles of every
|
||
// changed node).
|
||
if CalculateSelectionRect(ClientP.X, ClientP.Y) and HandleDrawSelection(ClientP.X, ClientP.Y) then
|
||
InvalidateRect(Handle, nil, False)
|
||
else
|
||
begin
|
||
// The selection did not change so invalidate only the part of the window which really needs an update.
|
||
// 1) Invalidate the parts uncovered by the scroll operation. Add another offset range, we have to
|
||
// scroll only one stripe but have to update two.
|
||
OffsetRect(ClipRect, DeltaX, DeltaY);
|
||
SubtractRect(ClipRect, ClientRect, ClipRect);
|
||
InvalidateRect(Handle, @ClipRect, False);
|
||
|
||
// 2) Invalidate the selection rectangles.
|
||
UnionRect(ClipRect, OrderRect(FNewSelRect), OrderRect(FLastSelRect));
|
||
OffsetRect(ClipRect, FOffsetX, FOffsetY);
|
||
InvalidateRect(Handle, @ClipRect, False);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// Scroll only if there is no drag'n drop in progress. Drag'n drop scrolling is handled in DragOver.
|
||
if ((FDragManager = nil) or not VTVDragManager.IsDropTarget) and ((DeltaX <> 0) or (DeltaY <> 0)) then
|
||
DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), DefaultScrollUpdateFlags, nil);
|
||
end;
|
||
UpdateWindow(Handle);
|
||
|
||
if (FScrollDirections = []) and ([tsWheelPanning, tsWheelScrolling] * FStates = []) then
|
||
begin
|
||
KillTimer(Handle, ScrollTimer);
|
||
DoStateChange([], [tsScrollPending, tsScrolling]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DoUpdating(State: TVTUpdateState);
|
||
|
||
begin
|
||
if Assigned(FOnUpdating) then
|
||
FOnUpdating(Self, State);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DoValidateCache: Boolean;
|
||
|
||
// This method fills the cache, which is used to speed up searching for nodes.
|
||
// The strategy is simple: Take the current number of visible nodes and distribute evenly a number of marks
|
||
// (which are stored in FPositionCache) so that iterating through the tree doesn't cost too much time.
|
||
// If there are less than 'CacheThreshold' nodes in the tree then the cache remains empty.
|
||
// Result is True if the cache was filled without interruption, otherwise False.
|
||
// Note: You can adjust the maximum number of nodes between two cache entries by changing CacheThreshold.
|
||
|
||
var
|
||
EntryCount,
|
||
CurrentTop,
|
||
Index: Cardinal;
|
||
CurrentNode,
|
||
Temp: PVirtualNode;
|
||
|
||
begin
|
||
EntryCount := 0;
|
||
if not (tsStopValidation in FStates) then
|
||
begin
|
||
if FStartIndex = 0 then
|
||
FPositionCache := nil;
|
||
|
||
if FVisibleCount > CacheThreshold then
|
||
begin
|
||
EntryCount := CalculateCacheEntryCount;
|
||
SetLength(FPositionCache, EntryCount);
|
||
if FStartIndex > EntryCount then
|
||
FStartIndex := EntryCount;
|
||
|
||
// Optimize validation by starting with FStartIndex if set.
|
||
if (FStartIndex > 0) and Assigned(FPositionCache[FStartIndex - 1].Node) then
|
||
begin
|
||
// Index is the current entry in FPositionCache.
|
||
Index := FStartIndex - 1;
|
||
// Running term for absolute top value.
|
||
CurrentTop := FPositionCache[Index].AbsoluteTop;
|
||
// Running node pointer.
|
||
CurrentNode := FPositionCache[Index].Node;
|
||
end
|
||
else
|
||
begin
|
||
// Index is the current entry in FPositionCache.
|
||
Index := 0;
|
||
// Running term for absolute top value.
|
||
CurrentTop := 0;
|
||
// Running node pointer.
|
||
CurrentNode := GetFirstVisibleNoInit(nil, True);
|
||
end;
|
||
|
||
// EntryCount serves as counter for processed nodes here. This value can always start at 0 as
|
||
// the validation either starts also at index 0 or an index which is always a multiple of CacheThreshold
|
||
// and EntryCount is only used with modulo CacheThreshold.
|
||
EntryCount := 0;
|
||
if Assigned(CurrentNode) then
|
||
begin
|
||
while not (tsStopValidation in FStates) do
|
||
begin
|
||
if (EntryCount mod CacheThreshold) = 0 then
|
||
begin
|
||
// New cache entry to set up.
|
||
with FPositionCache[Index] do
|
||
begin
|
||
Node := CurrentNode;
|
||
AbsoluteTop := CurrentTop;
|
||
end;
|
||
Inc(Index);
|
||
end;
|
||
|
||
Inc(CurrentTop, NodeHeight[CurrentNode]);
|
||
// Advance to next visible node.
|
||
Temp := GetNextVisibleNoInit(CurrentNode, True);
|
||
// If there is no further node or the cache is full then stop the loop.
|
||
if (Temp = nil) or (Integer(Index) = Length(FPositionCache)) then
|
||
Break;
|
||
|
||
CurrentNode := Temp;
|
||
Inc(EntryCount);
|
||
end;
|
||
end;
|
||
// Finalize the position cache so no nil entry remains there.
|
||
if not (tsStopValidation in FStates) and (Integer(Index) <= High(FPositionCache)) then
|
||
begin
|
||
SetLength(FPositionCache, Index + 1);
|
||
with FPositionCache[Index] do
|
||
begin
|
||
Node := CurrentNode;
|
||
AbsoluteTop := CurrentTop;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
Result := (EntryCount > 0) and not (tsStopValidation in FStates);
|
||
|
||
// In variable node height mode it might have happend that some or all of the nodes have been adjusted in their
|
||
// height. During validation updates of the scrollbars is disabled so let's do this here.
|
||
if Result and (toVariableNodeHeight in FOptions.FMiscOptions) then
|
||
UpdateScrollbars(True);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DragAndDrop(AllowedEffects: LongWord;
|
||
DataObject: IDataObject; DragEffect: LongWord);
|
||
|
||
begin
|
||
{$ifdef Windows}
|
||
ActiveX.DoDragDrop(DataObject, VTVDragManager as IDropSource, AllowedEffects, @DragEffect);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DragCanceled;
|
||
|
||
// Does some housekeeping for VCL drag'n drop;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
DragFinished;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DragDrop(const DataObject: IDataObject; KeyState: LongWord; Pt: TPoint;
|
||
var Effect: LongWord): HResult;
|
||
|
||
var
|
||
Shift: TShiftState;
|
||
EnumFormat: IEnumFormatEtc;
|
||
Fetched: LongWord;
|
||
OLEFormat: TFormatEtc;
|
||
Formats: TFormatArray;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DragDrop');{$endif}
|
||
KillTimer(Handle, ExpandTimer);
|
||
KillTimer(Handle, ScrollTimer);
|
||
DoStateChange([], [tsScrollPending, tsScrolling]);
|
||
Formats := nil;
|
||
|
||
// Ask explicitly again whether the action is allowed. Otherwise we may accept a drop which is intentionally not
|
||
// allowed but cannot be prevented by the application because when the tree was scrolling while dropping
|
||
// no DragOver event is created by the OLE subsystem.
|
||
Result := DragOver(VTVDragManager.DragSource, KeyState, dsDragMove, Pt, Effect);
|
||
try
|
||
if (Result <> NOERROR) or ((Effect and not DROPEFFECT_SCROLL) = DROPEFFECT_NONE) then
|
||
Result := E_FAIL
|
||
else
|
||
begin
|
||
try
|
||
Shift := KeysToShiftState(KeyState);
|
||
if tsLeftButtonDown in FStates then
|
||
Include(Shift, ssLeft);
|
||
if tsMiddleButtonDown in FStates then
|
||
Include(Shift, ssMiddle);
|
||
if tsRightButtonDown in FStates then
|
||
Include(Shift, ssRight);
|
||
Pt := ScreenToClient(Pt);
|
||
// Determine which formats we can get and pass them along with the data object to the drop handler.
|
||
Result := DataObject.EnumFormatEtc(DATADIR_GET, EnumFormat);
|
||
if Failed(Result) then
|
||
Abort;
|
||
Result := EnumFormat.Reset;
|
||
if Failed(Result) then
|
||
Abort;
|
||
// create a list of available formats
|
||
while EnumFormat.Next(1, OLEFormat, @Fetched) = S_OK do
|
||
begin
|
||
SetLength(Formats, Length(Formats) + 1);
|
||
Formats[High(Formats)] := OLEFormat.cfFormat;
|
||
end;
|
||
DoDragDrop(VTVDragManager.DragSource, DataObject, Formats, Shift, Pt, Effect, FLastDropMode);
|
||
except
|
||
// An unhandled exception here leaks memory.
|
||
Application.HandleException(Self);
|
||
Result := E_UNEXPECTED;
|
||
end;
|
||
end;
|
||
finally
|
||
if Assigned(FDropTargetNode) then
|
||
begin
|
||
InvalidateNode(FDropTargetNode);
|
||
FDropTargetNode := nil;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DragDrop');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DragEnter(KeyState: LongWord; Pt: TPoint; var Effect: LongWord): HResult;
|
||
|
||
// callback routine for the drop target interface
|
||
|
||
var
|
||
Shift: TShiftState;
|
||
Accept: Boolean;
|
||
R: TRect;
|
||
HitInfo: THitInfo;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DragEnter');{$endif}
|
||
try
|
||
// Determine acceptance of drag operation and reset scroll start time.
|
||
FDragScrollStart := 0;
|
||
|
||
Shift := KeysToShiftState(KeyState);
|
||
if tsLeftButtonDown in FStates then
|
||
Include(Shift, ssLeft);
|
||
if tsMiddleButtonDown in FStates then
|
||
Include(Shift, ssMiddle);
|
||
if tsRightButtonDown in FStates then
|
||
Include(Shift, ssRight);
|
||
Pt := ScreenToClient(Pt);
|
||
Effect := SuggestDropEffect(VTVDragManager.DragSource, Shift, Pt, Effect);
|
||
Accept := DoDragOver(VTVDragManager.DragSource, Shift, dsDragEnter, Pt, FLastDropMode, Effect);
|
||
if not Accept then
|
||
Effect := DROPEFFECT_NONE
|
||
else
|
||
begin
|
||
// Set initial drop target node and drop mode.
|
||
GetHitTestInfoAt(Pt.X, Pt.Y, True, HitInfo);
|
||
if Assigned(HitInfo.HitNode) then
|
||
begin
|
||
FDropTargetNode := HitInfo.HitNode;
|
||
R := GetDisplayRect(HitInfo.HitNode, FHeader.MainColumn, False);
|
||
if hiOnItemLabel in HitInfo.HitPositions then
|
||
FLastDropMode := dmOnNode
|
||
else
|
||
if ((R.Top + R.Bottom) div 2) > Pt.Y then
|
||
FLastDropMode := dmAbove
|
||
else
|
||
FLastDropMode := dmBelow;
|
||
end
|
||
else
|
||
FLastDropMode := dmNowhere;
|
||
end;
|
||
|
||
// If the drag source is a virtual tree then we know how to control the drag image
|
||
// and can show it even if the source is not the target tree.
|
||
// This is only necessary if we cannot use the drag image helper interfaces.
|
||
if not VTVDragManager.DropTargetHelperSupported and Assigned(VTVDragManager.DragSource) then
|
||
TBaseVirtualTree(VTVDragManager.DragSource).FDragImage.ShowDragImage;
|
||
Result := NOERROR;
|
||
except
|
||
Result := E_UNEXPECTED;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DragEnter');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DragFinished;
|
||
|
||
// Called by DragCancelled or EndDrag to make up for the still missing mouse button up messages.
|
||
// These are important for such important things like popup menus.
|
||
|
||
var
|
||
P: TPoint;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DragFinished');{$endif}
|
||
DoStateChange([], [tsVCLDragPending, tsVCLDragging, tsUserDragObject]);
|
||
|
||
GetCursorPos(P);
|
||
P := ScreenToClient(P);
|
||
if tsRightButtonDown in FStates then
|
||
Perform(LM_RBUTTONUP, 0, Longint(PointToSmallPoint(P)))
|
||
else
|
||
if tsMiddleButtonDown in FStates then
|
||
Perform(LM_MBUTTONUP, 0, Longint(PointToSmallPoint(P)))
|
||
else
|
||
Perform(LM_LBUTTONUP, 0, Longint(PointToSmallPoint(P)));
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DragFinished');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DragLeave;
|
||
|
||
var
|
||
Effect: LongWord;
|
||
|
||
begin
|
||
KillTimer(Handle, ExpandTimer);
|
||
|
||
if not VTVDragManager.DropTargetHelperSupported and Assigned(VTVDragManager.DragSource) then
|
||
TBaseVirtualTree(VTVDragManager.DragSource).FDragImage.HideDragImage;
|
||
|
||
if Assigned(FDropTargetNode) then
|
||
begin
|
||
InvalidateNode(FDropTargetNode);
|
||
FDropTargetNode := nil;
|
||
end;
|
||
UpdateWindow(Handle);
|
||
|
||
Effect := 0;
|
||
DoDragOver(nil, [], dsDragLeave, Point(0, 0), FLastDropMode, Effect);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.DragOver(Source: TObject; KeyState: LongWord; DragState: TDragState; Pt: TPoint;
|
||
var Effect: LongWord): HResult;
|
||
|
||
// callback routine for the drop target interface
|
||
|
||
var
|
||
Shift: TShiftState;
|
||
Accept,
|
||
DragImageWillMove,
|
||
WindowScrolled: Boolean;
|
||
OldR, R: TRect;
|
||
NewDropMode: TDropMode;
|
||
HitInfo: THitInfo;
|
||
ImageHit: Boolean;
|
||
LabelHit: Boolean;
|
||
DragPos: TPoint;
|
||
Tree: TBaseVirtualTree;
|
||
LastNode: PVirtualNode;
|
||
|
||
DeltaX,
|
||
DeltaY: Integer;
|
||
ScrollOptions: TScrollUpdateOptions;
|
||
|
||
begin
|
||
//{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'DragOver');{$endif}
|
||
//todo: the check to FDragManager disable drag images in non windows.
|
||
//This should be reviewed as soon as drag image is implemented in non windows
|
||
if Assigned(FDragManager) and not VTVDragManager.DropTargetHelperSupported and (Source is TBaseVirtualTree) then
|
||
begin
|
||
Tree := Source as TBaseVirtualTree;
|
||
ScrollOptions := [suoUpdateNCArea];
|
||
end
|
||
else
|
||
begin
|
||
Tree := nil;
|
||
ScrollOptions := DefaultScrollUpdateFlags;
|
||
end;
|
||
|
||
try
|
||
DragPos := Pt;
|
||
Pt := ScreenToClient(Pt);
|
||
//{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'Pt',Pt);{$endif}
|
||
// Check if we have to scroll the client area.
|
||
FScrollDirections := DetermineScrollDirections(Pt.X, Pt.Y);
|
||
DeltaX := 0;
|
||
DeltaY := 0;
|
||
if FScrollDirections <> [] then
|
||
begin
|
||
// Determine amount to scroll.
|
||
if sdUp in FScrollDirections then
|
||
begin
|
||
DeltaY := Min(FScrollBarOptions.FIncrementY, ClientHeight);
|
||
if FOffsetY = 0 then
|
||
Exclude(FScrollDirections, sdUp);
|
||
end;
|
||
if sdDown in FScrollDirections then
|
||
begin
|
||
DeltaY := -Min(FScrollBarOptions.FIncrementY, ClientHeight);
|
||
if (ClientHeight - FOffsetY) = Integer(FRangeY) then
|
||
Exclude(FScrollDirections, sdDown);
|
||
end;
|
||
if sdLeft in FScrollDirections then
|
||
begin
|
||
DeltaX := FScrollBarOptions.FIncrementX;
|
||
if FEffectiveOffsetX = 0 then
|
||
Exclude(FScrollDirections, sdleft);
|
||
end;
|
||
if sdRight in FScrollDirections then
|
||
begin
|
||
DeltaX := -FScrollBarOptions.FIncrementX;
|
||
if (ClientWidth + FEffectiveOffsetX) = Integer(FRangeX) then
|
||
Exclude(FScrollDirections, sdRight);
|
||
end;
|
||
WindowScrolled := DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), ScrollOptions, nil);
|
||
end
|
||
else
|
||
WindowScrolled := False;
|
||
|
||
// Determine acceptance of drag operation as well as drag target.
|
||
Shift := KeysToShiftState(KeyState);
|
||
if tsLeftButtonDown in FStates then
|
||
Include(Shift, ssLeft);
|
||
if tsMiddleButtonDown in FStates then
|
||
Include(Shift, ssMiddle);
|
||
if tsRightButtonDown in FStates then
|
||
Include(Shift, ssRight);
|
||
GetHitTestInfoAt(Pt.X, Pt.Y, True, HitInfo);
|
||
ImageHit := HitInfo.HitPositions * [hiOnNormalIcon, hiOnStateIcon] <> [];
|
||
LabelHit := hiOnItemLabel in HitInfo.HitPositions;
|
||
// In report mode only direct hits of the node captions/images in the main column are accepted as hits.
|
||
if (toReportMode in FOptions.FMiscOptions) and not ((LabelHit or ImageHit) and
|
||
(HitInfo.HitColumn = FHeader.MainColumn)) then
|
||
HitInfo.HitNode := nil;
|
||
|
||
if Assigned(HitInfo.HitNode) then
|
||
begin
|
||
R := GetDisplayRect(HitInfo.HitNode, NoColumn, False);
|
||
if LabelHit or ImageHit or not (toShowDropmark in FOptions.FPaintOptions) then
|
||
NewDropMode := dmOnNode
|
||
else
|
||
if ((R.Top + R.Bottom) div 2) > Pt.Y then
|
||
NewDropMode := dmAbove
|
||
else
|
||
NewDropMode := dmBelow;
|
||
end
|
||
else
|
||
begin
|
||
NewDropMode := dmNowhere;
|
||
R := Rect(0, 0, 0, 0);
|
||
end;
|
||
|
||
if Assigned(Tree) then
|
||
DragImageWillMove := Tree.FDragImage.WillMove(DragPos)
|
||
else
|
||
DragImageWillMove := False;
|
||
|
||
if (HitInfo.HitNode <> FDropTargetNode) or (FLastDropMode <> NewDropMode) then
|
||
begin
|
||
// Something in the tree will change. This requires to update the screen and/or the drag image.
|
||
FLastDropMode := NewDropMode;
|
||
if HitInfo.HitNode <> FDropTargetNode then
|
||
begin
|
||
KillTimer(Handle, ExpandTimer);
|
||
// The last target node is needed for the rectangle determination but must already be set for
|
||
// the recapture call, hence it must be stored somewhere.
|
||
LastNode := FDropTargetNode;
|
||
FDropTargetNode := HitInfo.HitNode;
|
||
// In order to show a selection rectangle a column must be focused.
|
||
if FFocusedColumn <= NoColumn then
|
||
FFocusedColumn := FHeader.MainColumn;
|
||
|
||
if Assigned(LastNode) and Assigned(FDropTargetNode) then
|
||
begin
|
||
// Optimize the case that the selection moved between two nodes.
|
||
OldR := GetDisplayRect(LastNode, NoColumn, False);
|
||
UnionRect(R, R, OldR);
|
||
if Assigned(Tree) then
|
||
begin
|
||
if WindowScrolled then
|
||
UpdateWindowAndDragImage(Tree, ClientRect, True, not DragImageWillMove)
|
||
else
|
||
UpdateWindowAndDragImage(Tree, R, False, not DragImageWillMove);
|
||
end
|
||
else
|
||
InvalidateRect(Handle, @R, False);
|
||
end
|
||
else
|
||
begin
|
||
if Assigned(LastNode) then
|
||
begin
|
||
// Repaint last target node.
|
||
OldR := GetDisplayRect(LastNode, NoColumn, False);
|
||
if Assigned(Tree) then
|
||
begin
|
||
if WindowScrolled then
|
||
UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove)
|
||
else
|
||
UpdateWindowAndDragImage(Tree, OldR, False, not DragImageWillMove);
|
||
end
|
||
else
|
||
InvalidateRect(Handle, @OldR, False);
|
||
end
|
||
else
|
||
begin
|
||
if Assigned(Tree) then
|
||
begin
|
||
if WindowScrolled then
|
||
UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove)
|
||
else
|
||
UpdateWindowAndDragImage(Tree, R, False, not DragImageWillMove);
|
||
end
|
||
else
|
||
InvalidateRect(Handle, @R, False);
|
||
end;
|
||
end;
|
||
|
||
// Start auto expand timer if necessary.
|
||
if (toAutoDropExpand in FOptions.FAutoOptions) and Assigned(FDropTargetNode) and
|
||
(vsHasChildren in FDropTargetNode.States) then
|
||
SetTimer(Handle, ExpandTimer, FAutoExpandDelay, nil);
|
||
end
|
||
else
|
||
begin
|
||
// Only the drop mark position changed so invalidate the current drop target node.
|
||
if Assigned(Tree) then
|
||
begin
|
||
if WindowScrolled then
|
||
UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove)
|
||
else
|
||
UpdateWindowAndDragImage(Tree, R, False, not DragImageWillMove);
|
||
end
|
||
else
|
||
InvalidateRect(Handle, @R, False);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// No change in the current drop target or drop mode. This might still mean horizontal or vertical scrolling.
|
||
if Assigned(Tree) and ((DeltaX <> 0) or (DeltaY <> 0)) then
|
||
UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove);
|
||
end;
|
||
|
||
Update;
|
||
|
||
if Assigned(Tree) and DragImageWillMove then
|
||
Tree.FDragImage.DragTo(DragPos, False);
|
||
|
||
Effect := SuggestDropEffect(Source, Shift, Pt, Effect);
|
||
Accept := DoDragOver(Source, Shift, DragState, Pt, FLastDropMode, Effect);
|
||
if not Accept then
|
||
Effect := DROPEFFECT_NONE;
|
||
if WindowScrolled then
|
||
Effect := Effect or LongWord(DROPEFFECT_SCROLL);
|
||
Result := NOERROR;
|
||
except
|
||
Result := E_UNEXPECTED;
|
||
end;
|
||
//{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'DragOver');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DrawDottedHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: Integer);
|
||
|
||
// Draws a horizontal line with alternating pixels (this style is not supported for pens under Win9x).
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
with PaintInfo, Canvas do
|
||
begin
|
||
Brush.Color := Self.Brush.Color;
|
||
R := Rect(Min(Left, Right), Top, Max(Left, Right) + 1, Top + 1);
|
||
LCLIntf.FillRect(Handle, R, FDottedBrush);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DrawDottedVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: Integer);
|
||
|
||
// Draws a vertical line with alternating pixels (this style is not supported for pens under Win9x).
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
with PaintInfo, Canvas do
|
||
begin
|
||
Brush.Color := Self.Brush.Color;
|
||
R := Rect(Left, Min(Top, Bottom), Left + 1, Max(Top, Bottom) + 1);
|
||
LCLIntf.FillRect(Handle, R, FDottedBrush);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.EndOperation;
|
||
|
||
// Called to indicate that a long-running operation has finished.
|
||
|
||
begin
|
||
Assert(FOperationCount > 0, 'EndOperation must not be called when no operation in progress.');
|
||
Dec(FOperationCount);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.FindNodeInSelection(P: PVirtualNode; out Index: Integer; LowBound,
|
||
HighBound: Integer): Boolean;
|
||
|
||
// Search routine to find a specific node in the selection array.
|
||
// LowBound and HighBound determine the range in which to search the node.
|
||
// Either value can be -1 to denote the maximum range otherwise LowBound must be less or equal HighBound.
|
||
|
||
var
|
||
L, H,
|
||
I, C: PtrInt;
|
||
|
||
begin
|
||
Result := False;
|
||
L := 0;
|
||
if LowBound >= 0 then
|
||
L := LowBound;
|
||
H := FSelectionCount - 1;
|
||
if HighBound >= 0 then
|
||
H := HighBound;
|
||
while L <= H do
|
||
begin
|
||
I := (L + H) shr 1;
|
||
C := PtrInt(FSelection[I]) - PtrInt(P);
|
||
if C < 0 then
|
||
L := I + 1
|
||
else
|
||
begin
|
||
H := I - 1;
|
||
if C = 0 then
|
||
begin
|
||
Result := True;
|
||
L := I;
|
||
end;
|
||
end;
|
||
end;
|
||
Index := L;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer);
|
||
|
||
// used while streaming out a node to finally write out the size of the chunk
|
||
|
||
var
|
||
Size: Integer;
|
||
|
||
begin
|
||
// seek back to the second entry in the chunk header
|
||
Stream.Position := StartPos + SizeOf(Integer);
|
||
// determine size of chunk without the chunk header
|
||
Size := EndPos - StartPos - SizeOf(TChunkHeader);
|
||
// write the size...
|
||
Stream.Write(Size, SizeOf(Size));
|
||
// ... and seek to the last endposition
|
||
Stream.Position := EndPos;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FontChanged(AFont: TObject);
|
||
|
||
// Little helper function for font changes (as they are not tracked in TBitmap/TCanvas.OnChange).
|
||
|
||
begin
|
||
FFontChanged := True;
|
||
FOldFontChange(AFont);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetBorderDimensions: TSize;
|
||
|
||
// Returns the overall width of the current window border, depending on border styles.
|
||
// Note: these numbers represent the system's standards not special properties, which can be set for TWinControl
|
||
// (e.g. bevels, border width).
|
||
|
||
var
|
||
Styles: Integer;
|
||
|
||
begin
|
||
Result.cx := 0;
|
||
Result.cy := 0;
|
||
|
||
Styles := GetWindowLong(Handle, GWL_STYLE);
|
||
if (Styles and WS_BORDER) <> 0 then
|
||
begin
|
||
Dec(Result.cx);
|
||
Dec(Result.cy);
|
||
end;
|
||
if (Styles and WS_THICKFRAME) <> 0 then
|
||
begin
|
||
Dec(Result.cx, GetSystemMetrics(SM_CXFIXEDFRAME));
|
||
Dec(Result.cy, GetSystemMetrics(SM_CYFIXEDFRAME));
|
||
end;
|
||
Styles := GetWindowLong(Handle, GWL_EXSTYLE);
|
||
if (Styles and WS_EX_CLIENTEDGE) <> 0 then
|
||
begin
|
||
Dec(Result.cx, GetSystemMetrics(SM_CXEDGE));
|
||
Dec(Result.cy, GetSystemMetrics(SM_CYEDGE));
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone; ImgCheckState:
|
||
TCheckState = csUncheckedNormal; ImgEnabled: Boolean = False): Integer;
|
||
|
||
// Determines the index into the check image list for the given node depending on the check type
|
||
// and enabled state.
|
||
|
||
const
|
||
// Four dimensional array consisting of image indices for the check type, the check state, the enabled state and the
|
||
// hot state.
|
||
CheckStateToCheckImage: array[ctCheckBox..ctButton, csUncheckedNormal..csMixedPressed, Boolean, Boolean] of Integer = (
|
||
// ctCheckBox, ctTriStateCheckBox
|
||
(
|
||
// csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot])
|
||
((ckCheckUncheckedDisabled, ckCheckUncheckedDisabled), (ckCheckUncheckedNormal, ckCheckUncheckedHot)),
|
||
// csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot])
|
||
((ckCheckUncheckedDisabled, ckCheckUncheckedDisabled), (ckCheckUncheckedPressed, ckCheckUncheckedPressed)),
|
||
// csCheckedNormal
|
||
((ckCheckCheckedDisabled, ckCheckCheckedDisabled), (ckCheckCheckedNormal, ckCheckCheckedHot)),
|
||
// csCheckedPressed
|
||
((ckCheckCheckedDisabled, ckCheckCheckedDisabled), (ckCheckCheckedPressed, ckCheckCheckedPressed)),
|
||
// csMixedNormal
|
||
((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)),
|
||
// csMixedPressed
|
||
((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed))
|
||
),
|
||
// ctRadioButton
|
||
(
|
||
// csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot])
|
||
((ckRadioUncheckedDisabled, ckRadioUncheckedDisabled), (ckRadioUncheckedNormal, ckRadioUncheckedHot)),
|
||
// csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot])
|
||
((ckRadioUncheckedDisabled, ckRadioUncheckedDisabled), (ckRadioUncheckedPressed, ckRadioUncheckedPressed)),
|
||
// csCheckedNormal
|
||
((ckRadioCheckedDisabled, ckRadioCheckedDisabled), (ckRadioCheckedNormal, ckRadioCheckedHot)),
|
||
// csCheckedPressed
|
||
((ckRadioCheckedDisabled, ckRadioCheckedDisabled), (ckRadioCheckedPressed, ckRadioCheckedPressed)),
|
||
// csMixedNormal (should never appear with ctRadioButton)
|
||
((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)),
|
||
// csMixedPressed (should never appear with ctRadioButton)
|
||
((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed))
|
||
),
|
||
// ctButton
|
||
(
|
||
// csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot])
|
||
((ckButtonDisabled, ckButtonDisabled), (ckButtonNormal, ckButtonHot)),
|
||
// csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot])
|
||
((ckButtonDisabled, ckButtonDisabled), (ckButtonPressed, ckButtonPressed)),
|
||
// csCheckedNormal
|
||
((ckButtonDisabled, ckButtonDisabled), (ckButtonNormal, ckButtonHot)),
|
||
// csCheckedPressed
|
||
((ckButtonDisabled, ckButtonDisabled), (ckButtonPressed, ckButtonPressed)),
|
||
// csMixedNormal (should never appear with ctButton)
|
||
((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)),
|
||
// csMixedPressed (should never appear with ctButton)
|
||
((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed))
|
||
)
|
||
);
|
||
|
||
var
|
||
AType: TCheckType;
|
||
|
||
begin
|
||
if not Assigned(Node) then
|
||
Result := CheckStateToCheckImage[ImgCheckType, ImgCheckState, ImgEnabled, False]
|
||
else if Node.CheckType = ctNone then
|
||
Result := -1
|
||
else
|
||
begin
|
||
AType := Node.CheckType;
|
||
if AType = ctTriStateCheckBox then
|
||
AType := ctCheckBox;
|
||
Result := CheckStateToCheckImage[AType, Node.CheckState, not (vsDisabled in Node.States) and Enabled,
|
||
Node = FCurrentHotNode];
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CheckImageListNeeded;
|
||
begin
|
||
if FCheckImages <> nil then
|
||
Exit;
|
||
if FCheckImageKind = ckCustom then
|
||
FCheckImages := FCustomCheckImages
|
||
else
|
||
begin
|
||
FCheckImages := TBitmap.Create;
|
||
FCheckImages.Transparent := True;
|
||
FCheckImages.LoadFromLazarusResource(CheckImagesStrings[FCheckImageKind]);
|
||
end;
|
||
end;
|
||
|
||
function TBaseVirtualTree.GetClientRect: TRect;
|
||
begin
|
||
Result := inherited;
|
||
//lclheader
|
||
if HandleAllocated and (hoVisible in FHeader.FOptions) then
|
||
Dec(Result.Bottom, FHeader.Height);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetColumnClass: TVirtualTreeColumnClass;
|
||
|
||
begin
|
||
Result := TVirtualTreeColumn;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetHeaderClass: TVTHeaderClass;
|
||
|
||
begin
|
||
Result := TVTHeader;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetHintWindowClass: THintWindowClass;
|
||
|
||
// Returns the default hint window class used for the tree. Descendants can override it to use their own classes.
|
||
|
||
begin
|
||
Result := TVirtualTreeHintWindow;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex;
|
||
DefaultImages: TCustomImageList);
|
||
|
||
// Retrieves the image index and an eventual customized image list for drawing.
|
||
|
||
var
|
||
CustomImages: TCustomImageList;
|
||
|
||
begin
|
||
with Info do
|
||
begin
|
||
ImageInfo[InfoIndex].Index := -1;
|
||
ImageInfo[InfoIndex].Ghosted := False;
|
||
|
||
CustomImages := DoGetImageIndex(Node, Kind, Column, ImageInfo[InfoIndex].Ghosted, ImageInfo[InfoIndex].Index);
|
||
if Assigned(CustomImages) then
|
||
ImageInfo[InfoIndex].Images := CustomImages
|
||
else
|
||
ImageInfo[InfoIndex].Images := DefaultImages;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetMaxRightExtend: Cardinal;
|
||
|
||
// Determines the maximum with of the currently visible part of the tree, depending on the length
|
||
// of the node texts. This method is used for determining the horizontal scroll range if no columns are used.
|
||
|
||
var
|
||
Node,
|
||
NextNode: PVirtualNode;
|
||
TopPosition: Integer;
|
||
NodeLeft,
|
||
CurrentWidth: Integer;
|
||
WithCheck: Boolean;
|
||
CheckOffset: Integer;
|
||
|
||
begin
|
||
Node := InternalGetNodeAt(0, 0, True, TopPosition);
|
||
Result := 0;
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
NodeLeft := (GetNodeLevel(Node) + 1) * FIndent
|
||
else
|
||
NodeLeft := GetNodeLevel(Node) * FIndent;
|
||
|
||
if Assigned(FStateImages) then
|
||
Inc(NodeLeft, FStateImages.Width + 2);
|
||
if Assigned(FImages) then
|
||
Inc(NodeLeft, FImages.Width + 2);
|
||
WithCheck := (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages);
|
||
if WithCheck then
|
||
CheckOffset := FCheckImages.Height + 2
|
||
else
|
||
CheckOffset := 0;
|
||
|
||
while Assigned(Node) do
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
|
||
if WithCheck and (Node.CheckType <> ctNone) then
|
||
Inc(NodeLeft, CheckOffset);
|
||
CurrentWidth := DoGetNodeWidth(Node, NoColumn);
|
||
if Integer(Result) < (NodeLeft + CurrentWidth) then
|
||
Result := NodeLeft + CurrentWidth;
|
||
Inc(TopPosition, NodeHeight[Node]);
|
||
//lclheader: Height -> ClientHeight
|
||
if TopPosition > ClientHeight then
|
||
Break;
|
||
|
||
if WithCheck and (Node.CheckType <> ctNone) then
|
||
Dec(NodeLeft, CheckOffset);
|
||
|
||
// Get next visible node and update left node position.
|
||
NextNode := GetNextVisible(Node, True);
|
||
if NextNode = nil then
|
||
Break;
|
||
Inc(NodeLeft, CountLevelDifference(Node, NextNode) * Integer(FIndent));
|
||
Node := NextNode;
|
||
end;
|
||
|
||
Inc(Result, FMargin);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.GetNativeClipboardFormats(var Formats: TFormatEtcArray);
|
||
|
||
// Returns the supported clipboard formats of the tree.
|
||
|
||
begin
|
||
InternalClipboardFormats.EnumerateFormats(TVirtualTreeClass(ClassType), Formats, FClipboardFormats);
|
||
// Ask application/descendants for self defined formats.
|
||
DoGetUserClipboardFormats(Formats);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetOperationCanceled;
|
||
|
||
begin
|
||
Result := FOperationCanceled and (FOperationCount > 0);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetOptionsClass: TTreeOptionsClass;
|
||
|
||
begin
|
||
Result := TCustomVirtualTreeOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$i olemethods.inc}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.HandleHotTrack(X, Y: Integer);
|
||
|
||
// Updates the current "hot" node.
|
||
|
||
var
|
||
HitInfo: THitInfo;
|
||
CheckPositions: THitPositions;
|
||
ButtonIsHit,
|
||
DoInvalidate: Boolean;
|
||
|
||
begin
|
||
DoInvalidate := False;
|
||
// Get information about the hit.
|
||
GetHitTestInfoAt(X, Y, True, HitInfo);
|
||
|
||
// Only make the new node being "hot" if its label is hit or full row selection is enabled.
|
||
CheckPositions := [hiOnItemLabel, hiOnItemCheckbox];
|
||
|
||
// If running under Windows Vista using the explorer theme hitting the buttons makes the node hot, too.
|
||
if (IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions)) then
|
||
Include(CheckPositions, hiOnItemButtonExact);
|
||
|
||
if (CheckPositions * HitInfo.HitPositions = []) and not (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
HitInfo.HitNode := nil;
|
||
if (HitInfo.HitNode <> FCurrentHotNode) or (HitInfo.HitColumn <> FCurrentHotColumn) then
|
||
begin
|
||
DoInvalidate := (toHotTrack in FOptions.PaintOptions) or (toCheckSupport in FOptions.FMiscOptions);
|
||
DoHotChange(FCurrentHotNode, HitInfo.HitNode);
|
||
if Assigned(FCurrentHotNode) and DoInvalidate then
|
||
InvalidateNode(FCurrentHotNode);
|
||
FCurrentHotNode := HitInfo.HitNode;
|
||
FCurrentHotColumn := HitInfo.HitColumn;
|
||
end;
|
||
|
||
ButtonIsHit := (hiOnItemButtonExact in HitInfo.HitPositions) and (toHotTrack in FOptions.FPaintOptions);
|
||
if Assigned(FCurrentHotNode) and ((FHotNodeButtonHit <> ButtonIsHit) or DoInvalidate) then
|
||
begin
|
||
FHotNodeButtonHit := ButtonIsHit and (toHotTrack in FOptions.FPaintOptions);
|
||
InvalidateNode(FCurrentHotNode);
|
||
end
|
||
else
|
||
if not Assigned(FCurrentHotNode) then
|
||
FHotNodeButtonHit := False;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.HandleIncrementalSearch(CharCode: Word);
|
||
|
||
var
|
||
Run, Stop: PVirtualNode;
|
||
GetNextNode: TGetNextNodeProc;
|
||
NewSearchText: String;
|
||
SingleLetter,
|
||
PreviousSearch: Boolean; // True if VK_BACK was sent.
|
||
SearchDirection: TVTSearchDirection;
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
procedure SetupNavigation;
|
||
|
||
// If the search buffer is empty then we start searching with the next node after the last one, otherwise
|
||
// we continue with the last one. Node navigation function is set up too here, to avoid frequent checks.
|
||
|
||
var
|
||
FindNextNode: Boolean;
|
||
|
||
begin
|
||
FindNextNode := (Length(FSearchBuffer) = 0) or (Run = nil) or SingleLetter or PreviousSearch;
|
||
case FIncrementalSearch of
|
||
isVisibleOnly:
|
||
if SearchDirection = sdForward then
|
||
begin
|
||
GetNextNode := GetNextVisible;
|
||
if FindNextNode then
|
||
begin
|
||
if Run = nil then
|
||
Run := GetFirstVisible(nil, True)
|
||
else
|
||
begin
|
||
Run := GetNextVisible(Run, True);
|
||
// Do wrap around.
|
||
if Run = nil then
|
||
Run := GetFirstVisible(nil, True);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
GetNextNode := GetPreviousVisible;
|
||
if FindNextNode then
|
||
begin
|
||
if Run = nil then
|
||
Run := GetLastVisible(nil, True)
|
||
else
|
||
begin
|
||
Run := GetPreviousVisible(Run, True);
|
||
// Do wrap around.
|
||
if Run = nil then
|
||
Run := GetLastVisible(nil, True);
|
||
end;
|
||
end;
|
||
end;
|
||
isInitializedOnly:
|
||
if SearchDirection = sdForward then
|
||
begin
|
||
GetNextNode := GetNextNoInit;
|
||
if FindNextNode then
|
||
begin
|
||
if Run = nil then
|
||
Run := GetFirstNoInit
|
||
else
|
||
begin
|
||
Run := GetNextNoInit(Run);
|
||
// Do wrap around.
|
||
if Run = nil then
|
||
Run := GetFirstNoInit;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
GetNextNode := GetPreviousNoInit;
|
||
if FindNextNode then
|
||
begin
|
||
if Run = nil then
|
||
Run := GetLastNoInit
|
||
else
|
||
begin
|
||
Run := GetPreviousNoInit(Run);
|
||
// Do wrap around.
|
||
if Run = nil then
|
||
Run := GetLastNoInit;
|
||
end;
|
||
end;
|
||
end;
|
||
else
|
||
// isAll
|
||
if SearchDirection = sdForward then
|
||
begin
|
||
GetNextNode := GetNext;
|
||
if FindNextNode then
|
||
begin
|
||
if Run = nil then
|
||
Run := GetFirst
|
||
else
|
||
begin
|
||
Run := GetNext(Run);
|
||
// Do wrap around.
|
||
if Run = nil then
|
||
Run := GetFirst;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
GetNextNode := GetPrevious;
|
||
if FindNextNode then
|
||
begin
|
||
if Run = nil then
|
||
Run := GetLast
|
||
else
|
||
begin
|
||
Run := GetPrevious(Run);
|
||
// Do wrap around.
|
||
if Run = nil then
|
||
Run := GetLast;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
function CodePageFromLocale(Language: DWord): Integer;
|
||
|
||
// Determines the code page for a given locale.
|
||
// Unfortunately there is no easier way than this, currently.
|
||
|
||
var
|
||
Buf: array[0..6] of Char;
|
||
|
||
begin
|
||
GetLocaleInfo(Language, LOCALE_IDEFAULTANSICODEPAGE, Buf, 6);
|
||
Result := StrToIntDef(Buf, GetACP);
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
function KeyUnicode(C: Char): WideChar;
|
||
|
||
// Converts the given character into its corresponding Unicode character
|
||
// depending on the active keyboard layout.
|
||
|
||
begin
|
||
MultiByteToWideChar(CodePageFromLocale(GetKeyboardLayout(0) and $FFFF),
|
||
MB_USEGLYPHCHARS, @C, 1, @Result, 1);
|
||
end;
|
||
{$endif}
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
var
|
||
FoundMatch: Boolean;
|
||
NewChar: WideChar;
|
||
|
||
begin
|
||
//todo: handle correctly unicode char after WideString -> String conversion
|
||
KillTimer(Handle, SearchTimer);
|
||
|
||
if FIncrementalSearch <> isNone then
|
||
begin
|
||
if CharCode <> 0 then
|
||
begin
|
||
DoStateChange([tsIncrementalSearching]);
|
||
|
||
// Convert the given virtual key code into a Unicode character based on the current locale.
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
NewChar := KeyUnicode(Char(CharCode));
|
||
{$else}
|
||
NewChar := Char(CharCode);
|
||
{$endif}
|
||
PreviousSearch := NewChar = WideChar(VK_BACK);
|
||
// We cannot do a search with an empty search buffer.
|
||
if not PreviousSearch or (FSearchBuffer <> '') then
|
||
begin
|
||
// Determine which method to use to advance nodes and the start node to search from.
|
||
case FSearchStart of
|
||
ssAlwaysStartOver:
|
||
Run := nil;
|
||
ssFocusedNode:
|
||
Run := FFocusedNode;
|
||
else // ssLastHit
|
||
Run := FLastSearchNode;
|
||
end;
|
||
|
||
// Make sure the start node corresponds to the search criterion.
|
||
if Assigned(Run) then
|
||
begin
|
||
case FIncrementalSearch of
|
||
isInitializedOnly:
|
||
if not (vsInitialized in Run.States) then
|
||
Run := nil;
|
||
isVisibleOnly:
|
||
if not FullyVisible[Run] then
|
||
Run := nil;
|
||
end;
|
||
end;
|
||
Stop := Run;
|
||
|
||
// VK_BACK temporarily changes search direction to opposite mode.
|
||
if PreviousSearch then
|
||
begin
|
||
if SearchDirection = sdBackward then
|
||
SearchDirection := sdForward
|
||
else
|
||
SearchDirection := sdBackward
|
||
end
|
||
else
|
||
SearchDirection := FSearchDirection;
|
||
// The "single letter mode" is used to advance quickly from node to node when pressing the same key several times.
|
||
SingleLetter := (Length(FSearchBuffer) = 1) and not PreviousSearch and (FSearchBuffer[1] = NewChar);
|
||
// However if the current hit (if there is one) would fit also with a repeated character then
|
||
// don't use single letter mode.
|
||
if SingleLetter and (DoIncrementalSearch(Run, FSearchBuffer + NewChar) = 0) then
|
||
SingleLetter := False;
|
||
SetupNavigation;
|
||
FoundMatch := False;
|
||
|
||
if Assigned(Run) then
|
||
begin
|
||
if SingleLetter then
|
||
NewSearchText := FSearchBuffer
|
||
else
|
||
if PreviousSearch then
|
||
begin
|
||
SetLength(FSearchBuffer, Length(FSearchBuffer) - 1);
|
||
NewSearchText := FSearchBuffer;
|
||
end
|
||
else
|
||
NewSearchText := FSearchBuffer + NewChar;
|
||
|
||
repeat
|
||
if DoIncrementalSearch(Run, NewSearchText) = 0 then
|
||
begin
|
||
FoundMatch := True;
|
||
Break;
|
||
end;
|
||
|
||
// Advance to next node if we have not found a match.
|
||
Run := GetNextNode(Run);
|
||
// Do wrap around start or end of tree.
|
||
if (Run <> Stop) and (Run = nil) then
|
||
SetupNavigation;
|
||
until Run = Stop;
|
||
end;
|
||
|
||
if FoundMatch then
|
||
begin
|
||
ClearSelection;
|
||
FSearchBuffer := NewSearchText;
|
||
FLastSearchNode := Run;
|
||
FocusedNode := Run;
|
||
Selected[Run] := True;
|
||
FLastSearchNode := Run;
|
||
end
|
||
else
|
||
// Play an acoustic signal if nothing could be found but don't beep if only the currently
|
||
// focused node matches.
|
||
if Assigned(Run) and (DoIncrementalSearch(Run, NewSearchText) <> 0) then
|
||
Beep;
|
||
end;
|
||
end;
|
||
|
||
// Restart search timeout interval.
|
||
SetTimer(Handle, SearchTimer, FSearchTimeout, nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.HandleMouseDblClick(var Message: TLMMouse; const HitInfo: THitInfo);
|
||
|
||
var
|
||
NewCheckState: TCheckState;
|
||
Node: PVirtualNode;
|
||
MayEdit: Boolean;
|
||
|
||
begin
|
||
MayEdit := not (tsEditing in FStates) and (toEditOnDblClick in FOptions.FMiscOptions);
|
||
if tsEditPending in FStates then
|
||
begin
|
||
KillTimer(Handle, EditTimer);
|
||
DoStateChange([], [tsEditPending]);
|
||
end;
|
||
|
||
if not (tsEditing in FStates) or DoEndEdit then
|
||
begin
|
||
if HitInfo.HitColumn = FHeader.FColumns.FClickIndex then
|
||
DoColumnDblClick(HitInfo.HitColumn, KeysToShiftState(Message.Keys));
|
||
|
||
Node := nil;
|
||
if (hiOnItem in HitInfo.HitPositions) and (hitInfo.HitColumn > NoColumn) and
|
||
(coFixed in FHeader.FColumns[HitInfo.HitColumn].FOptions) then
|
||
begin
|
||
if hiUpperSplitter in HitInfo.HitPositions then
|
||
Node := GetPreviousVisible(HitInfo.HitNode, True)
|
||
else if hiLowerSplitter in HitInfo.HitPositions then
|
||
Node := HitInfo.HitNode
|
||
end;
|
||
|
||
if Assigned(Node) and (Node <> FRoot) and (toNodeHeightDblClickResize in FOptions.FMiscOptions) then
|
||
begin
|
||
if DoNodeHeightDblClickResize(Node, HitInfo.HitColumn, KeysToShiftState(Message.Keys), Point(Message.XPos, Message.YPos)) then
|
||
begin
|
||
SetNodeHeight(Node, FDefaultNodeHeight);
|
||
UpdateWindow(Handle);
|
||
MayEdit := False;
|
||
end;
|
||
end
|
||
else if hiOnItemCheckBox in HitInfo.HitPositions then
|
||
begin
|
||
if (FStates * [tsMouseCheckPending, tsKeyCheckPending] = []) and not (vsDisabled in HitInfo.HitNode.States) then
|
||
begin
|
||
with HitInfo.HitNode^ do
|
||
NewCheckState := DetermineNextCheckState(CheckType, CheckState);
|
||
if DoChecking(HitInfo.HitNode, NewCheckState) then
|
||
begin
|
||
DoStateChange([tsMouseCheckPending]);
|
||
FCheckNode := HitInfo.HitNode;
|
||
FPendingCheckState := NewCheckState;
|
||
FCheckNode.CheckState := PressedState[FCheckNode.CheckState];
|
||
InvalidateNode(HitInfo.HitNode);
|
||
MayEdit := False;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
if hiOnItemButton in HitInfo.HitPositions then
|
||
begin
|
||
ToggleNode(HitInfo.HitNode);
|
||
MayEdit := False;
|
||
end
|
||
else
|
||
begin
|
||
if toToggleOnDblClick in FOptions.FMiscOptions then
|
||
begin
|
||
if ((([hiOnItemButton, hiOnItemLabel, hiOnNormalIcon, hiOnStateIcon] * HitInfo.HitPositions) <> []) or
|
||
((toFullRowSelect in FOptions.FSelectionOptions) and Assigned(HitInfo.HitNode))) then
|
||
begin
|
||
ToggleNode(HitInfo.HitNode);
|
||
MayEdit := False;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if MayEdit and Assigned(FFocusedNode) and (FFocusedNode = HitInfo.HitNode) and
|
||
(FFocusedColumn = HitInfo.HitColumn) and CanEdit(FFocusedNode, HitInfo.HitColumn) then
|
||
begin
|
||
DoStateChange([tsEditPending]);
|
||
FEditColumn := FFocusedcolumn;
|
||
SetTimer(Handle, EditTimer, FEditDelay, nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.HandleMouseDown(var Message: TLMMouse; var HitInfo: THitInfo);
|
||
|
||
// centralized mouse button down handling
|
||
|
||
var
|
||
LastFocused: PVirtualNode;
|
||
Column: TColumnIndex;
|
||
ShiftState: TShiftState;
|
||
|
||
// helper variables to shorten boolean equations/expressions
|
||
AutoDrag, // automatic (or allowed) drag start
|
||
IsHit, // the node's caption or images are hit
|
||
IsCellHit, // for grid extension or full row select (but not check box, button)
|
||
IsAnyHit, // either IsHit or IsCellHit
|
||
IsHeightTracking, // height tracking
|
||
MultiSelect, // multiselection is enabled
|
||
ShiftEmpty, // ShiftState = []
|
||
NodeSelected: Boolean; // the new node (if any) is selected
|
||
NewColumn: Boolean; // column changed
|
||
NewNode: Boolean; // Node changed.
|
||
NeedChange: Boolean; // change event is required for selection change
|
||
CanClear: Boolean;
|
||
NewCheckState: TCheckState;
|
||
AltPressed: Boolean; // Pressing the Alt key enables special processing for selection.
|
||
FullRowDrag: Boolean; // Start dragging anywhere within a node's bound.
|
||
NodeRect: TRect;
|
||
FocusCanChange: Boolean;
|
||
begin
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then
|
||
begin
|
||
StopWheelPanning;
|
||
Exit;
|
||
end;
|
||
|
||
if tsEditPending in FStates then
|
||
begin
|
||
KillTimer(Handle, EditTimer);
|
||
DoStateChange([], [tsEditPending]);
|
||
end;
|
||
|
||
if not (tsEditing in FStates) or DoEndEdit then
|
||
begin
|
||
// Focus change. Don't use the SetFocus method as this does not work for MDI windows.
|
||
if not Focused and CanFocus then
|
||
begin
|
||
LCLIntf.SetFocus(Handle);
|
||
// Repeat the hit test as an OnExit event might got triggered that could modify the tree.
|
||
GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo);
|
||
end;
|
||
|
||
// Keep clicked column in case the application needs it.
|
||
FHeader.FColumns.FClickIndex := HitInfo.HitColumn;
|
||
|
||
// Change column only if we have hit the node label.
|
||
if (hiOnItemLabel in HitInfo.HitPositions) or
|
||
(toFullRowSelect in FOptions.FSelectionOptions) or
|
||
(toGridExtensions in FOptions.FMiscOptions) then
|
||
begin
|
||
NewColumn := FFocusedColumn <> HitInfo.HitColumn;
|
||
if toExtendedFocus in FOptions.FSelectionOptions then
|
||
Column := HitInfo.HitColumn
|
||
else
|
||
Column := FHeader.MainColumn;
|
||
end
|
||
else
|
||
begin
|
||
NewColumn := False;
|
||
Column := FFocusedColumn;
|
||
end;
|
||
|
||
if NewColumn and
|
||
(not FHeader.AllowFocus(Column)) then
|
||
begin
|
||
NewColumn := False;
|
||
Column := FFocusedColumn;
|
||
end;
|
||
|
||
NewNode := FFocusedNode <> HitInfo.HitNode;
|
||
|
||
// Translate keys and filter out shift and control key.
|
||
ShiftState := KeysToShiftState(Message.Keys) * [ssShift, ssCtrlOS, ssAlt];
|
||
if ssAlt in ShiftState then
|
||
begin
|
||
AltPressed := True;
|
||
// Remove the Alt key from the shift state. It is not meaningful there.
|
||
Exclude(ShiftState, ssAlt);
|
||
end
|
||
else
|
||
AltPressed := False;
|
||
|
||
// Various combinations determine what states the tree enters now.
|
||
// We initialize shorthand variables to avoid the following expressions getting too large
|
||
// and to avoid repeative expensive checks.
|
||
IsHit := not AltPressed and not (toSimpleDrawSelection in FOptions.FSelectionOptions) and
|
||
((hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions));
|
||
IsCellHit := not AltPressed and not IsHit and Assigned(HitInfo.HitNode) and
|
||
([hiOnItemButton, hiOnItemCheckBox] * HitInfo.HitPositions = []) and
|
||
((toFullRowSelect in FOptions.FSelectionOptions) or
|
||
((toGridExtensions in FOptions.FMiscOptions) and (HitInfo.HitColumn > NoColumn)));
|
||
IsAnyHit := IsHit or IsCellHit;
|
||
MultiSelect := toMultiSelect in FOptions.FSelectionOptions;
|
||
ShiftEmpty := ShiftState = [];
|
||
NodeSelected := IsAnyHit and (vsSelected in HitInfo.HitNode.States);
|
||
FullRowDrag := toFullRowDrag in FOptions.FMiscOptions;
|
||
IsHeightTracking := (Message.Msg = LM_LBUTTONDOWN) and
|
||
(toNodeHeightResize in FOptions.FMiscOptions) and (hiOnItem in HitInfo.HitPositions) and
|
||
([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []) and
|
||
((HitInfo.HitColumn > NoColumn) and (coFixed in FHeader.FColumns[HitInfo.HitColumn].Options));
|
||
|
||
// Dragging might be started in the inherited handler manually (which is discouraged for stability reasons)
|
||
// the test for manual mode is done below (after the focused node is set).
|
||
AutoDrag := ((DragMode = dmAutomatic) or Dragging) and (not IsCellHit or FullRowDrag);
|
||
|
||
// Query the application to learn if dragging may start now (if set to dmManual).
|
||
if Assigned(HitInfo.HitNode) and not AutoDrag and (DragMode = dmManual) then
|
||
AutoDrag := DoBeforeDrag(HitInfo.HitNode, Column) and (not IsCellHit or FullRowDrag);
|
||
|
||
// handle node height tracking
|
||
if IsHeightTracking then
|
||
begin
|
||
if hiUpperSplitter in HitInfo.HitPositions then
|
||
FHeightTrackNode := GetPreviousVisible(HitInfo.HitNode, True)
|
||
else
|
||
FHeightTrackNode := HitInfo.HitNode;
|
||
|
||
if Assigned(FHeightTrackNode) and (FHeightTrackNode <> FRoot) then
|
||
begin
|
||
FHeightTrackColumn := HitInfo.HitColumn;
|
||
NodeRect := GetDisplayRect(FHeightTrackNode, FHeightTrackColumn, False);
|
||
FHeightTrackPoint := Point(NodeRect.Left, NodeRect.Top);
|
||
DoStateChange([tsNodeHeightTrackPending]);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
// handle button clicks
|
||
if (hiOnItemButton in HitInfo.HitPositions) and (vsHasChildren in HitInfo.HitNode.States) then
|
||
begin
|
||
ToggleNode(HitInfo.HitNode);
|
||
Exit;
|
||
end;
|
||
|
||
// check event
|
||
if hiOnItemCheckBox in HitInfo.HitPositions then
|
||
begin
|
||
if (FStates * [tsMouseCheckPending, tsKeyCheckPending] = []) and not (vsDisabled in HitInfo.HitNode.States) then
|
||
begin
|
||
with HitInfo.HitNode^ do
|
||
NewCheckState := DetermineNextCheckState(CheckType, CheckState);
|
||
if DoChecking(HitInfo.HitNode, NewCheckState) then
|
||
begin
|
||
DoStateChange([tsMouseCheckPending]);
|
||
FCheckNode := HitInfo.HitNode;
|
||
FPendingCheckState := NewCheckState;
|
||
FCheckNode.CheckState := PressedState[FCheckNode.CheckState];
|
||
InvalidateNode(HitInfo.HitNode);
|
||
end;
|
||
end;
|
||
Exit;
|
||
end;
|
||
|
||
// Keep this node's level in case we need it for constraint selection.
|
||
if (FRoot.ChildCount > 0) and ShiftEmpty or (FSelectionCount = 0) then
|
||
if Assigned(HitInfo.HitNode) then
|
||
FLastSelectionLevel := GetNodeLevel(HitInfo.HitNode)
|
||
else
|
||
FLastSelectionLevel := GetNodeLevel(GetLastVisibleNoInit(nil, True));
|
||
|
||
// pending clearance
|
||
if MultiSelect and ShiftEmpty and not (hiOnItemCheckbox in HitInfo.HitPositions) and IsAnyHit and AutoDrag and
|
||
NodeSelected and not FSelectionLocked then
|
||
DoStateChange([tsClearPending]);
|
||
|
||
// immediate clearance
|
||
// Determine for the right mouse button if there is a popup menu. In this case and if drag'n drop is pending
|
||
// the current selection has to stay as it is.
|
||
with HitInfo, Message do
|
||
CanClear := not AutoDrag and
|
||
(not (tsRightButtonDown in FStates) or not HasPopupMenu(HitNode, HitColumn, Point(XPos, YPos)));
|
||
|
||
//lcl
|
||
FocusCanChange := DoFocusChanging(FFocusedNode, HitInfo.HitNode, FFocusedColumn, Column);
|
||
|
||
if not FSelectionLocked and FocusCanChange and ((not (IsAnyHit or FullRowDrag) and MultiSelect and ShiftEmpty) or
|
||
(IsAnyHit and (not NodeSelected or (NodeSelected and CanClear)) and (ShiftEmpty or not MultiSelect))) then
|
||
begin
|
||
Assert(not (tsClearPending in FStates), 'Pending and direct clearance are mutual exclusive!');
|
||
|
||
// If the currently hit node was already selected then we have to reselect it again after clearing the current
|
||
// selection, but without a change event if it is the only selected node.
|
||
// The same applies if the Alt key is pressed, which allows to start drawing the selection rectangle also
|
||
// on node captions and images. Here the previous selection state does not matter, though.
|
||
if NodeSelected or (AltPressed and Assigned(HitInfo.HitNode) and (HitInfo.HitColumn = FHeader.MainColumn)) then
|
||
begin
|
||
NeedChange := FSelectionCount > 1;
|
||
InternalClearSelection;
|
||
InternalAddToSelection(HitInfo.HitNode, True);
|
||
if NeedChange then
|
||
begin
|
||
Invalidate;
|
||
Change(nil);
|
||
end;
|
||
end
|
||
else
|
||
ClearSelection;
|
||
end;
|
||
|
||
// pending node edit
|
||
if Focused and
|
||
((hiOnItemLabel in HitInfo.HitPositions) or ((toGridExtensions in FOptions.FMiscOptions) and
|
||
(hiOnItem in HitInfo.HitPositions))) and NodeSelected and not NewColumn and ShiftEmpty then
|
||
DoStateChange([tsEditPending]);
|
||
|
||
// User starts a selection with a selection rectangle.
|
||
if not (toDisableDrawSelection in FOptions.FSelectionOptions) and not (IsHit or FullRowDrag) and MultiSelect then
|
||
begin
|
||
SetCapture(Handle);
|
||
DoStateChange([tsDrawSelPending]);
|
||
FDrawSelShiftState := ShiftState;
|
||
FNewSelRect := Rect(Message.XPos + FEffectiveOffsetX, Message.YPos - FOffsetY, Message.XPos + FEffectiveOffsetX,
|
||
Message.YPos - FOffsetY);
|
||
//lclheader
|
||
if hoVisible in FHeader.Options then
|
||
OffsetRect(FNewSelRect, 0, -FHeader.Height);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcSelection],'FNewSelRect', FNewSelRect);{$endif}
|
||
|
||
FLastSelRect := Rect(0, 0, 0, 0);
|
||
if not IsCellHit then
|
||
Exit;
|
||
end;
|
||
|
||
// Keep current mouse position.
|
||
FLastClickPos := Point(Message.XPos, Message.YPos);
|
||
|
||
// Handle selection and node focus change.
|
||
if IsAnyHit and FocusCanChange then
|
||
begin
|
||
if NewColumn then
|
||
begin
|
||
InvalidateColumn(FFocusedColumn);
|
||
InvalidateColumn(Column);
|
||
FFocusedColumn := Column;
|
||
end;
|
||
if DragKind = dkDock then
|
||
begin
|
||
KillTimer(Handle, ScrollTimer);
|
||
DoStateChange([], [tsScrollPending, tsScrolling]);
|
||
end;
|
||
// Get the currently focused node to make multiple multi-selection blocks possible.
|
||
LastFocused := FFocusedNode;
|
||
if NewNode then
|
||
DoFocusNode(HitInfo.HitNode, False);
|
||
|
||
if MultiSelect and not ShiftEmpty then
|
||
HandleClickSelection(LastFocused, HitInfo.HitNode, ShiftState, AutoDrag)
|
||
else
|
||
begin
|
||
if ShiftEmpty then
|
||
FRangeAnchor := HitInfo.HitNode;
|
||
|
||
// If the hit node is not yet selected then do it now.
|
||
if not NodeSelected then
|
||
AddToSelection(HitInfo.HitNode);
|
||
end;
|
||
|
||
if NewNode or NewColumn then
|
||
begin
|
||
ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions,
|
||
not (toDisableAutoscrollOnFocus in FOptions.FAutoOptions));
|
||
DoFocusChange(FFocusedNode, FFocusedColumn);
|
||
end;
|
||
end;
|
||
|
||
// Drag'n drop initiation
|
||
// If we lost focus in the interim the button states would be cleared in WM_KILLFOCUS.
|
||
if AutoDrag and IsAnyHit and (FStates * [tsLeftButtonDown, tsRightButtonDown, tsMiddleButtonDown] <> []) then
|
||
BeginDrag(False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.HandleMouseUp(Keys: PtrUInt; const HitInfo: THitInfo);
|
||
|
||
// Counterpart to the mouse down handler.
|
||
|
||
var
|
||
ReselectFocusedNode: Boolean;
|
||
|
||
begin
|
||
ReleaseCapture;
|
||
|
||
if not (tsVCLDragPending in FStates) then
|
||
begin
|
||
// reset pending or persistent states
|
||
if IsMouseSelecting then
|
||
begin
|
||
DoStateChange([], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection]);
|
||
Invalidate;
|
||
end;
|
||
|
||
if tsClearPending in FStates then
|
||
begin
|
||
ReselectFocusedNode := Assigned(FFocusedNode) and (vsSelected in FFocusedNode.States);
|
||
ClearSelection;
|
||
if ReselectFocusedNode then
|
||
AddToSelection(FFocusedNode);
|
||
end;
|
||
|
||
if (tsToggleFocusedSelection in FStates) and (HitInfo.HitNode = FFocusedNode) then
|
||
begin
|
||
if vsSelected in HitInfo.HitNode.States then
|
||
RemoveFromSelection(HitInfo.HitNode)
|
||
else
|
||
AddToSelection(HitInfo.HitNode);
|
||
InvalidateNode(HitInfo.HitNode);
|
||
end;
|
||
|
||
DoStateChange([], [tsOLEDragPending, tsOLEDragging, tsClearPending, tsDrawSelPending, tsToggleFocusedSelection,
|
||
tsScrollPending, tsScrolling]);
|
||
KillTimer(Handle, ScrollTimer);
|
||
|
||
if tsMouseCheckPending in FStates then
|
||
begin
|
||
DoStateChange([], [tsMouseCheckPending]);
|
||
// Is the mouse still over the same node?
|
||
if (HitInfo.HitNode = FCheckNode) and (hiOnItem in HitInfo.HitPositions) then
|
||
DoCheckClick(FCheckNode, FPendingCheckState)
|
||
else
|
||
FCheckNode.CheckState := UnpressedState[FCheckNode.CheckState];
|
||
InvalidateNode(FCheckNode);
|
||
FCheckNode := nil;
|
||
end;
|
||
|
||
if (FHeader.FColumns.FClickIndex > NoColumn) and (FHeader.FColumns.FClickIndex = HitInfo.HitColumn) then
|
||
DoColumnClick(HitInfo.HitColumn, KeysToShiftState(Keys));
|
||
|
||
// handle a pending edit event
|
||
if tsEditPending in FStates then
|
||
begin
|
||
// Is the mouse still over the same node?
|
||
if (HitInfo.HitNode = FFocusedNode) and (hiOnItem in HitInfo.HitPositions) and
|
||
CanEdit(FFocusedNode, HitInfo.HitColumn) and (toEditOnClick in FOptions.FMiscOptions) then
|
||
begin
|
||
FEditColumn := FFocusedColumn;
|
||
SetTimer(Handle, EditTimer, FEditDelay, nil);
|
||
end
|
||
else
|
||
DoStateChange([], [tsEditPending]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.HasImage(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex): Boolean;
|
||
|
||
// Determines whether the given node has got an image of the given kind in the given column.
|
||
// Returns True if so, otherwise False.
|
||
|
||
var
|
||
Ghosted: Boolean;
|
||
Index: Integer;
|
||
|
||
begin
|
||
Index := -1;
|
||
Ghosted := False;
|
||
DoGetImageIndex(Node, Kind, Column, Ghosted, Index);
|
||
Result := Index > -1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.HasPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Pos: TPoint): Boolean;
|
||
|
||
// Determines whether the tree got a popup menu, either in its PopupMenu property, via the OnGetPopupMenu event or
|
||
// through inheritance. The latter case must be checked by the descendant which must override this method.
|
||
|
||
begin
|
||
Result := Assigned(PopupMenu) or Assigned(DoGetPopupMenu(Node, Column, Pos));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InitChildren(Node: PVirtualNode);
|
||
|
||
// Initiates the initialization of the child number of the given node.
|
||
|
||
var
|
||
Count: Cardinal;
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) and (vsHasChildren in Node.States) then
|
||
begin
|
||
Count := Node.ChildCount;
|
||
DoInitChildren(Node, Count);
|
||
if Count = 0 then
|
||
begin
|
||
// Remove any child node which is already there.
|
||
DeleteChildren(Node);
|
||
Exclude(Node.States, vsHasChildren);
|
||
end
|
||
else
|
||
SetChildCount(Node, Count);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InitNode(Node: PVirtualNode);
|
||
|
||
// Initiates the initialization of the given node to allow the application to load needed data for it.
|
||
|
||
var
|
||
InitStates: TVirtualNodeInitStates;
|
||
|
||
begin
|
||
with Node^ do
|
||
begin
|
||
Include(States, vsInitialized);
|
||
InitStates := [];
|
||
if Parent = FRoot then
|
||
DoInitNode(nil, Node, InitStates)
|
||
else
|
||
DoInitNode(Parent, Node, InitStates);
|
||
if ivsDisabled in InitStates then
|
||
Include(States, vsDisabled);
|
||
if ivsHasChildren in InitStates then
|
||
Include(States, vsHasChildren);
|
||
if ivsSelected in InitStates then
|
||
begin
|
||
FSingletonNodeArray[0] := Node;
|
||
InternalAddToSelection(FSingletonNodeArray, 1, False);
|
||
end;
|
||
if ivsMultiline in InitStates then
|
||
Include(States, vsMultiline);
|
||
|
||
// Expanded may already be set (when called from ReinitNode) or be set in DoInitNode, allow both.
|
||
if (vsExpanded in Node.States) xor (ivsExpanded in InitStates) then
|
||
begin
|
||
// Expand node if not yet done (this will automatically initialize child nodes).
|
||
if ivsExpanded in InitStates then
|
||
ToggleNode(Node)
|
||
else
|
||
// If the node already was expanded then explicitly trigger child initialization.
|
||
if vsHasChildren in Node.States then
|
||
InitChildren(Node);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InternalAddFromStream(Stream: TStream; Version: Integer; Node: PVirtualNode);
|
||
|
||
// Loads all details for Node (including its children) from the given stream.
|
||
// Because the new nodes might be selected this method also fixes the selection array.
|
||
|
||
var
|
||
Stop: PVirtualNode;
|
||
Index: Integer;
|
||
LastTotalHeight: Cardinal;
|
||
WasFullyVisible: Boolean;
|
||
|
||
begin
|
||
Assert(Node <> FRoot, 'The root node cannot be loaded from stream.');
|
||
|
||
// Keep the current total height value of Node as it has already been applied
|
||
// but might change in the load and fixup code. We have to adjust that afterwards.
|
||
LastTotalHeight := Node.TotalHeight;
|
||
WasFullyVisible := FullyVisible[Node];
|
||
|
||
// Read in the new nodes.
|
||
ReadNode(Stream, Version, Node);
|
||
|
||
// One time update of node-internal states and the global visibility counter.
|
||
// This is located here to ease and speed up the loading process.
|
||
FixupTotalCount(Node);
|
||
AdjustTotalCount(Node.Parent, Node.TotalCount - 1, True); // -1 because Node itself was already set.
|
||
FixupTotalHeight(Node);
|
||
AdjustTotalHeight(Node.Parent, Node.TotalHeight - LastTotalHeight, True);
|
||
|
||
// New nodes are always visible, so the visible node count has been increased already.
|
||
// If Node is now invisible we have to take back this increment and don't need to add any visible child node.
|
||
if not FullyVisible[Node] then
|
||
begin
|
||
if WasFullyVisible then
|
||
Dec(FVisibleCount);
|
||
end
|
||
else
|
||
// It can never happen that the node is now fully visible but was not before as this would require
|
||
// that the visibility state of one of its parents has changed, which cannot happen during loading.
|
||
Inc(FVisibleCount, CountVisibleChildren(Node));
|
||
|
||
// Fix selection array.
|
||
ClearTempCache;
|
||
if Node = FRoot then
|
||
Stop := nil
|
||
else
|
||
Stop := Node.NextSibling;
|
||
|
||
if toMultiSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
// Add all nodes which were selected before to the current selection (unless they are already there).
|
||
while Node <> Stop do
|
||
begin
|
||
if (vsSelected in Node.States) and not FindNodeInSelection(Node, Index, 0, High(FSelection)) then
|
||
InternalCacheNode(Node);
|
||
Node := GetNextNoInit(Node);
|
||
end;
|
||
if FTempNodeCount > 0 then
|
||
AddToSelection(FTempNodeCache, FTempNodeCount, True);
|
||
ClearTempCache;
|
||
end
|
||
else // No further selected nodes allowed so delete the corresponding flag in all new nodes.
|
||
while Node <> Stop do
|
||
begin
|
||
Exclude(Node.States, vsSelected);
|
||
Node := GetNextNoInit(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InternalAddToSelection(Node: PVirtualNode; ForceInsert: Boolean): Boolean;
|
||
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil!');
|
||
FSingletonNodeArray[0] := Node;
|
||
Result := InternalAddToSelection(FSingletonNodeArray, 1, ForceInsert);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InternalAddToSelection(const NewItems: TNodeArray; NewLength: Integer;
|
||
ForceInsert: Boolean): Boolean;
|
||
|
||
// Internal version of method AddToSelection which does not trigger OnChange events
|
||
|
||
var
|
||
I, J: Integer;
|
||
CurrentEnd: Integer;
|
||
Constrained,
|
||
SiblingConstrained: Boolean;
|
||
|
||
begin
|
||
// The idea behind this code is to use a kind of reverse merge sort. QuickSort is quite fast
|
||
// and would do the job here too but has a serious problem with already sorted lists like FSelection.
|
||
|
||
// 1) Remove already selected items, mark all other as being selected.
|
||
if ForceInsert then
|
||
begin
|
||
for I := 0 to NewLength - 1 do
|
||
Include(NewItems[I].States, vsSelected);
|
||
end
|
||
else
|
||
begin
|
||
Constrained := toLevelSelectConstraint in FOptions.FSelectionOptions;
|
||
if Constrained and (FLastSelectionLevel = -1) then
|
||
FLastSelectionLevel := GetNodeLevel(NewItems[0]);
|
||
SiblingConstrained := toSiblingSelectConstraint in FOptions.FSelectionOptions;
|
||
if SiblingConstrained and (FRangeAnchor = nil) then
|
||
FRangeAnchor := NewItems[0];
|
||
|
||
for I := 0 to NewLength - 1 do
|
||
if ([vsSelected, vsDisabled] * NewItems[I].States <> []) or
|
||
(Constrained and (Cardinal(FLastSelectionLevel) <> GetNodeLevel(NewItems[I]))) or
|
||
(SiblingConstrained and (FRangeAnchor.Parent <> NewItems[I].Parent)) then
|
||
Inc(PtrUInt(NewItems[I]))
|
||
else
|
||
Include(NewItems[I].States, vsSelected);
|
||
end;
|
||
|
||
I := PackArray(NewItems, NewLength);
|
||
if I > -1 then
|
||
NewLength := I;
|
||
|
||
Result := NewLength > 0;
|
||
if Result then
|
||
begin
|
||
// 2) Sort the new item list so we can easily traverse it.
|
||
if NewLength > 1 then
|
||
QuickSort(NewItems, 0, NewLength - 1);
|
||
// 3) Make room in FSelection for the new items.
|
||
if FSelectionCount + NewLength >= Length(FSelection) then
|
||
SetLength(FSelection, FSelectionCount + NewLength);
|
||
|
||
// 4) Merge in new items
|
||
J := NewLength - 1;
|
||
CurrentEnd := FSelectionCount - 1;
|
||
|
||
while J >= 0 do
|
||
begin
|
||
// First insert all new entries which are greater than the greatest entry in the old list.
|
||
// If the current end marker is < 0 then there's nothing more to move in the selection
|
||
// array and only the remaining new items must be inserted.
|
||
if CurrentEnd >= 0 then
|
||
begin
|
||
while (J >= 0) and (NewItems[J] > FSelection[CurrentEnd]) do
|
||
begin
|
||
FSelection[CurrentEnd + J + 1] := NewItems[J];
|
||
Dec(J);
|
||
end;
|
||
// early out if nothing more needs to be copied
|
||
if J < 0 then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// insert remaining new entries at position 0
|
||
Move(NewItems[0], FSelection[0], (J + 1) * SizeOf(Pointer));
|
||
// nothing more to do so exit main loop
|
||
Break;
|
||
end;
|
||
|
||
// find the last entry in the remaining selection list which is smaller then the largest
|
||
// entry in the remaining new items list
|
||
FindNodeInSelection(NewItems[J], I, 0, CurrentEnd);
|
||
Dec(I);
|
||
// move all entries which are greater than the greatest entry in the new items list up
|
||
// so the remaining gap travels down to where new items must be inserted
|
||
Move(FSelection[I + 1], FSelection[I + J + 2], (CurrentEnd - I) * SizeOf(Pointer));
|
||
CurrentEnd := I;
|
||
end;
|
||
|
||
Inc(FSelectionCount, NewLength);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InternalCacheNode(Node: PVirtualNode);
|
||
|
||
// Adds the given node to the temporary node cache (used when collecting possibly large amounts of nodes).
|
||
|
||
var
|
||
Len: Cardinal;
|
||
|
||
begin
|
||
Len := Length(FTempNodeCache);
|
||
if FTempNodeCount = Len then
|
||
begin
|
||
if Len < 100 then
|
||
Len := 100
|
||
else
|
||
Len := Len + Len div 10;
|
||
SetLength(FTempNodeCache, Len);
|
||
end;
|
||
FTempNodeCache[FTempNodeCount] := Node;
|
||
Inc(FTempNodeCount);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InternalClearSelection;
|
||
|
||
var
|
||
Count: Integer;
|
||
|
||
begin
|
||
// It is possible that there are invalid node references in the selection array
|
||
// if the tree update is locked and changes in the structure were made.
|
||
// Handle this potentially dangerous situation by packing the selection array explicitely.
|
||
if FUpdateCount > 0 then
|
||
begin
|
||
Count := PackArray(FSelection, FSelectionCount);
|
||
if Count > -1 then
|
||
begin
|
||
FSelectionCount := Count;
|
||
SetLength(FSelection, FSelectionCount);
|
||
end;
|
||
end;
|
||
|
||
while FSelectionCount > 0 do
|
||
begin
|
||
Dec(FSelectionCount);
|
||
Exclude(FSelection[FSelectionCount].States, vsSelected);
|
||
end;
|
||
ResetRangeAnchor;
|
||
FSelection := nil;
|
||
DoStateChange([], [tsClearPending]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InternalConnectNode(Node, Destination: PVirtualNode; Target: TBaseVirtualTree;
|
||
Mode: TVTNodeAttachMode);
|
||
|
||
// Connects Node with Destination depending on Mode.
|
||
// No error checking takes place. Node as well as Destination must be valid. Node must never be a root node and
|
||
// Destination must not be a root node if Mode is amInsertBefore or amInsertAfter.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
// Keep in mind that the destination node might belong to another tree.
|
||
with Target do
|
||
begin
|
||
case Mode of
|
||
amInsertBefore:
|
||
begin
|
||
Node.PrevSibling := Destination.PrevSibling;
|
||
Destination.PrevSibling := Node;
|
||
Node.NextSibling := Destination;
|
||
Node.Parent := Destination.Parent;
|
||
Node.Index := Destination.Index;
|
||
if Node.PrevSibling = nil then
|
||
Node.Parent.FirstChild := Node
|
||
else
|
||
Node.PrevSibling.NextSibling := Node;
|
||
|
||
// reindex all following nodes
|
||
Run := Destination;
|
||
while Assigned(Run) do
|
||
begin
|
||
Inc(Run.Index);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
|
||
Inc(Destination.Parent.ChildCount);
|
||
Include(Destination.Parent.States, vsHasChildren);
|
||
AdjustTotalCount(Destination.Parent, Node.TotalCount, True);
|
||
|
||
// Add the new node's height only if its parent is expanded.
|
||
if Destination.Parent.States * [vsExpanded, vsVisible] = [vsExpanded, vsVisible] then
|
||
AdjustTotalHeight(Destination.Parent, Node.TotalHeight, True);
|
||
if FullyVisible[Node] then
|
||
Inc(FVisibleCount, CountVisibleChildren(Node) + 1);
|
||
end;
|
||
amInsertAfter:
|
||
begin
|
||
Node.NextSibling := Destination.NextSibling;
|
||
Destination.NextSibling := Node;
|
||
Node.PrevSibling := Destination;
|
||
Node.Parent := Destination.Parent;
|
||
if Node.NextSibling = nil then
|
||
Node.Parent.LastChild := Node
|
||
else
|
||
Node.NextSibling.PrevSibling := Node;
|
||
Node.Index := Destination.Index;
|
||
|
||
// reindex all following nodes
|
||
Run := Node;
|
||
while Assigned(Run) do
|
||
begin
|
||
Inc(Run.Index);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
|
||
Inc(Destination.Parent.ChildCount);
|
||
Include(Destination.Parent.States, vsHasChildren);
|
||
AdjustTotalCount(Destination.Parent, Node.TotalCount, True);
|
||
|
||
// Add the new node's height only if its parent is expanded.
|
||
if Destination.Parent.States * [vsExpanded, vsVisible] = [vsExpanded, vsVisible] then
|
||
AdjustTotalHeight(Destination.Parent, Node.TotalHeight, True);
|
||
if FullyVisible[Node] then
|
||
Inc(FVisibleCount, CountVisibleChildren(Node) + 1);
|
||
end;
|
||
amAddChildFirst:
|
||
begin
|
||
if Assigned(Destination.FirstChild) then
|
||
begin
|
||
// If there's a first child then there must also be a last child.
|
||
Destination.FirstChild.PrevSibling := Node;
|
||
Node.NextSibling := Destination.FirstChild;
|
||
Destination.FirstChild := Node;
|
||
end
|
||
else
|
||
begin
|
||
// First child node at this location.
|
||
Destination.FirstChild := Node;
|
||
Destination.LastChild := Node;
|
||
Node.NextSibling := nil;
|
||
end;
|
||
Node.PrevSibling := nil;
|
||
Node.Parent := Destination;
|
||
Node.Index := 0;
|
||
// reindex all following nodes
|
||
Run := Node.NextSibling;
|
||
while Assigned(Run) do
|
||
begin
|
||
Inc(Run.Index);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
|
||
Inc(Destination.ChildCount);
|
||
Include(Destination.States, vsHasChildren);
|
||
AdjustTotalCount(Destination, Node.TotalCount, True);
|
||
// Add the new node's height only if its parent is expanded.
|
||
if Destination.States * [vsExpanded, vsVisible] = [vsExpanded, vsVisible] then
|
||
AdjustTotalHeight(Destination, Node.TotalHeight, True);
|
||
if FullyVisible[Node] then
|
||
Inc(FVisibleCount, CountVisibleChildren(Node) + 1);
|
||
end;
|
||
amAddChildLast:
|
||
begin
|
||
if Assigned(Destination.LastChild) then
|
||
begin
|
||
// If there's a last child then there must also be a first child.
|
||
Destination.LastChild.NextSibling := Node;
|
||
Node.PrevSibling := Destination.LastChild;
|
||
Destination.LastChild := Node;
|
||
end
|
||
else
|
||
begin
|
||
// first child node at this location
|
||
Destination.FirstChild := Node;
|
||
Destination.LastChild := Node;
|
||
Node.PrevSibling := nil;
|
||
end;
|
||
Node.NextSibling := nil;
|
||
Node.Parent := Destination;
|
||
if Assigned(Node.PrevSibling) then
|
||
Node.Index := Node.PrevSibling.Index + 1
|
||
else
|
||
Node.Index := 0;
|
||
Inc(Destination.ChildCount);
|
||
Include(Destination.States, vsHasChildren);
|
||
AdjustTotalCount(Destination, Node.TotalCount, True);
|
||
// Add the new node's height only if its parent is expanded.
|
||
if Destination.States * [vsExpanded, vsVisible] = [vsExpanded, vsVisible] then
|
||
AdjustTotalHeight(Destination, Node.TotalHeight, True);
|
||
if FullyVisible[Node] then
|
||
Inc(FVisibleCount, CountVisibleChildren(Node) + 1);
|
||
end;
|
||
else
|
||
// amNoWhere: do nothing
|
||
end;
|
||
|
||
// Remove temporary states.
|
||
Node.States := Node.States - [vsChecking, vsCutOrCopy, vsDeleting, vsClearing];
|
||
|
||
// Update the hidden children flag of the parent.
|
||
if (Mode <> amNoWhere) and (Node.Parent <> FRoot) then
|
||
begin
|
||
// If we have added a visible node then simply remove the all-children-hidden flag.
|
||
if vsVisible in Node.States then
|
||
Exclude(Node.Parent.States, vsAllChildrenHidden)
|
||
else
|
||
// If we have added an invisible node and this is the only child node then
|
||
// make sure the all-children-hidden flag is in a determined state.
|
||
// If there were child nodes before then no action is needed.
|
||
if Node.Parent.ChildCount = 1 then
|
||
Include(Node.Parent.States, vsAllChildrenHidden);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InternalData(Node: PVirtualNode): Pointer;
|
||
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True);
|
||
|
||
// Disconnects the given node from its parent and siblings. The node's pointer are not reset so they can still be used
|
||
// after return from this method (probably a very short time only!).
|
||
// If KeepFocus is True then the focused node is not reset. This is useful if the given node is reconnected to the tree
|
||
// immediately after return of this method and should stay being the focused node if it was it before.
|
||
// Note: Node must not be nil or the root node.
|
||
|
||
var
|
||
Parent,
|
||
Run: PVirtualNode;
|
||
Index: Integer;
|
||
AdjustHeight: Boolean;
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Node must neither be nil nor the root node.');
|
||
|
||
if (Node = FFocusedNode) and not KeepFocus then
|
||
begin
|
||
DoFocusNode(nil, False);
|
||
DoFocusChange(FFocusedNode, FFocusedColumn);
|
||
end;
|
||
|
||
if Node = FRangeAnchor then
|
||
ResetRangeAnchor;
|
||
|
||
// Update the hidden children flag of the parent.
|
||
if (Node.Parent <> FRoot) and not (vsClearing in Node.Parent.States) then
|
||
if FUpdateCount = 0 then
|
||
DetermineHiddenChildrenFlag(Node.Parent)
|
||
else
|
||
Include(FStates, tsUpdateHiddenChildrenNeeded);
|
||
|
||
if not (vsDeleting in Node.States) then
|
||
begin
|
||
// Some states are only temporary so take them out.
|
||
Node.States := Node.States - [vsChecking];
|
||
Parent := Node.Parent;
|
||
Dec(Parent.ChildCount);
|
||
AdjustHeight := Parent.States * [vsExpanded, vsVisible] = [vsExpanded, vsVisible];
|
||
if Parent.ChildCount = 0 then
|
||
begin
|
||
Parent.States := Parent.States - [vsAllChildrenHidden, vsHasChildren];
|
||
if (Parent <> FRoot) and (vsExpanded in Parent.States) then
|
||
Exclude(Parent.States, vsExpanded);
|
||
end;
|
||
AdjustTotalCount(Parent, -Integer(Node.TotalCount), True);
|
||
if AdjustHeight then
|
||
AdjustTotalHeight(Parent, -Integer(Node.TotalHeight), True);
|
||
if FullyVisible[Node] then
|
||
Dec(FVisibleCount, CountVisibleChildren(Node) + 1);
|
||
if Assigned(Node.PrevSibling) then
|
||
Node.PrevSibling.NextSibling := Node.NextSibling
|
||
else
|
||
Parent.FirstChild := Node.NextSibling;
|
||
|
||
if Assigned(Node.NextSibling) then
|
||
begin
|
||
Node.NextSibling.PrevSibling := Node.PrevSibling;
|
||
// Reindex all following nodes.
|
||
if Reindex then
|
||
begin
|
||
Run := Node.NextSibling;
|
||
Index := Node.Index;
|
||
while Assigned(Run) do
|
||
begin
|
||
Run.Index := Index;
|
||
Inc(Index);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
Parent.LastChild := Node.PrevSibling;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InternalGetNodeAt(X, Y: Integer): PVirtualNode;
|
||
var
|
||
Dummy: Integer;
|
||
begin
|
||
Result := InternalGetNodeAt(X, Y, True, Dummy);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InternalGetNodeAt(X, Y: Integer; Relative: Boolean; var NodeTop: Integer): PVirtualNode;
|
||
|
||
//lclheader this is the original version of GetNodeAt used internally since expects coordinates
|
||
// relative to the image tree. In LCL the image tree and control coordinates are different
|
||
// when header is visible
|
||
|
||
// This method returns the node that occupies the specified point, or nil if there's none.
|
||
// If Relative is True then X and Y are given in client coordinates otherwise they are considered as being
|
||
// absolute values into the virtual tree image (regardless of the current offsets in the tree window).
|
||
// NodeTop gets the absolute or relative top position of the node returned or is untouched if no node
|
||
// could be found.
|
||
|
||
var
|
||
AbsolutePos,
|
||
CurrentPos: Cardinal;
|
||
|
||
begin
|
||
if Y < 0 then
|
||
Y := 0;
|
||
|
||
AbsolutePos := Y;
|
||
if Relative then
|
||
Inc(AbsolutePos, -FOffsetY);
|
||
|
||
// CurrentPos tracks a running term of the current position to test for.
|
||
// It corresponds always to the top position of the currently considered node.
|
||
CurrentPos := 0;
|
||
|
||
// If the cache is available then use it.
|
||
if tsUseCache in FStates then
|
||
Result := FindInPositionCache(AbsolutePos, CurrentPos)
|
||
else
|
||
Result := GetFirstVisibleNoInit(nil, True);
|
||
|
||
// Determine node, of which position and height corresponds to the scroll position most closely.
|
||
while Assigned(Result) and (Result <> FRoot) do
|
||
begin
|
||
if AbsolutePos < (CurrentPos + NodeHeight[Result]) then
|
||
Break;
|
||
Inc(CurrentPos, NodeHeight[Result]);
|
||
Result := GetNextVisibleNoInit(Result, True);
|
||
end;
|
||
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
|
||
// Since the given vertical position is likely not the same as the top position
|
||
// of the found node this top position is returned.
|
||
if Assigned(Result) then
|
||
begin
|
||
NodeTop := CurrentPos;
|
||
if Relative then
|
||
Inc(NodeTop, FOffsetY);
|
||
//{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'GetNodeAt Result: ',Result^.Index);{$endif}
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InternalRemoveFromSelection(Node: PVirtualNode);
|
||
|
||
// Special version to mark a node to be no longer in the current selection. PackArray must
|
||
// be used to remove finally those entries.
|
||
|
||
var
|
||
Index: Integer;
|
||
|
||
begin
|
||
// Because pointers are always DWORD aligned we can simply increment all those
|
||
// which we want to have removed (see also PackArray) and still have the
|
||
// order in the list preserved.
|
||
if FindNodeInSelection(Node, Index, -1, -1) then
|
||
begin
|
||
Exclude(Node.States, vsSelected);
|
||
Inc(PtrUInt(FSelection[Index]));
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InvalidateCache;
|
||
|
||
// Marks the cache as invalid.
|
||
|
||
begin
|
||
DoStateChange([tsValidationNeeded], [tsUseCache]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.MarkCutCopyNodes;
|
||
|
||
// Sets the vsCutOrCopy style in every currently selected but not disabled node to indicate it is
|
||
// now part of a clipboard operation.
|
||
|
||
var
|
||
Nodes: TNodeArray;
|
||
I: Integer;
|
||
|
||
begin
|
||
Nodes := nil;
|
||
if FSelectionCount > 0 then
|
||
begin
|
||
// need the current selection sorted to exclude selected nodes which are children, grandchildren etc. of
|
||
// already selected nodes
|
||
Nodes := GetSortedSelection(False);
|
||
for I := 0 to High(Nodes) do
|
||
with Nodes[I]^ do
|
||
if not (vsDisabled in States) then
|
||
Include(States, vsCutOrCopy);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Loaded;
|
||
|
||
var
|
||
LastRootCount: Cardinal;
|
||
IsReadOnly: Boolean;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
// If a root node count has been set during load of the tree then update its child structure now
|
||
// as this hasn't been done yet in this case.
|
||
if (tsNeedRootCountUpdate in FStates) and (FRoot.ChildCount > 0) then
|
||
begin
|
||
DoStateChange([], [tsNeedRootCountUpdate]);
|
||
IsReadOnly := toReadOnly in FOptions.FMiscOptions;
|
||
Exclude(FOptions.FMiscOptions, toReadOnly);
|
||
LastRootCount := FRoot.ChildCount;
|
||
FRoot.ChildCount := 0;
|
||
BeginUpdate;
|
||
SetChildCount(FRoot, LastRootCount);
|
||
EndUpdate;
|
||
if IsReadOnly then
|
||
Include(FOptions.FMiscOptions, toReadOnly);
|
||
end;
|
||
|
||
// Prevent the object inspector at design time from marking the header as being modified
|
||
// when auto resize is enabled.
|
||
Updating;
|
||
try
|
||
FHeader.UpdateMainColumn;
|
||
FHeader.FColumns.FixPositions;
|
||
if toAutoBidiColumnOrdering in FOptions.FAutoOptions then
|
||
FHeader.FColumns.ReorderColumns(UseRightToLeftAlignment);
|
||
FHeader.RecalculateHeader;
|
||
//lclheader
|
||
//AdjustAutoSize is called inside CreateWnd. Don't call here
|
||
//Keep the commented code until we get sure of not being necessary
|
||
//if hoAutoResize in FHeader.FOptions then
|
||
// FHeader.FColumns.AdjustAutoSize(InvalidColumn, True);
|
||
finally
|
||
Updated;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.MainColumnChanged;
|
||
|
||
begin
|
||
DoCancelEdit;
|
||
{$ifdef EnableAccessible}
|
||
NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.MouseMove(Shift: TShiftState; X, Y: Integer);
|
||
|
||
var
|
||
R: TRect;
|
||
NewCursor: TCursor;
|
||
HitInfo: THitInfo;
|
||
P: TPoint;
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
// lcl: Adjust cursor
|
||
if ([tsWheelPanning, tsWheelScrolling] * FStates = []) then
|
||
begin
|
||
// Apply own cursors only if there is no global cursor set.
|
||
if Screen.Cursor = crDefault then
|
||
begin
|
||
NewCursor := crDefault;
|
||
if (toNodeHeightResize in FOptions.FMiscOptions) then
|
||
begin
|
||
GetCursorPos(P);
|
||
P := ScreenToClient(P);
|
||
GetHitTestInfoAt(P.X, P.Y, True, HitInfo);
|
||
if (hiOnItem in HitInfo.HitPositions) and
|
||
([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []) and
|
||
((hitInfo.HitColumn > NoColumn) and (coFixed in FHeader.FColumns[HitInfo.HitColumn].FOptions)) then
|
||
begin
|
||
if hiUpperSplitter in HitInfo.HitPositions then
|
||
Node := GetPreviousVisible(HitInfo.HitNode, True)
|
||
else
|
||
Node := HitInfo.HitNode;
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
NewCursor := crVertSplit;
|
||
end;
|
||
end;
|
||
if (NewCursor = crDefault) and (toHotTrack in FOptions.PaintOptions) and Assigned(FCurrentHotNode) then
|
||
NewCursor := FHotCursor;
|
||
|
||
DoGetCursor(NewCursor);
|
||
Cursor := NewCursor;
|
||
end;
|
||
end;
|
||
if tsNodeHeightTrackPending in FStates then
|
||
begin
|
||
// Remove hint if shown currently.
|
||
Application.CancelHint;
|
||
|
||
// Stop wheel panning if active.
|
||
StopWheelPanning;
|
||
|
||
// Stop timers
|
||
KillTimer(Handle, ExpandTimer);
|
||
KillTimer(Handle, EditTimer);
|
||
KillTimer(Handle, ScrollTimer);
|
||
KillTimer(Handle, SearchTimer);
|
||
FSearchBuffer := '';
|
||
FLastSearchNode := nil;
|
||
|
||
DoStateChange([tsNodeHeightTracking], [tsScrollPending, tsScrolling, tsEditPending, tsOLEDragPending, tsVCLDragPending,
|
||
tsIncrementalSearching, tsNodeHeightTrackPending]);
|
||
end;
|
||
|
||
if tsDrawSelPending in FStates then
|
||
begin
|
||
// Remove current selection in case the user clicked somewhere in the window (but not a node)
|
||
// and moved the mouse.
|
||
if CalculateSelectionRect(X, Y) then
|
||
begin
|
||
//lclheader
|
||
R := FNewSelRect;
|
||
if hoVisible in FHeader.Options then
|
||
OffsetRect(R, 0, FHeader.Height);
|
||
InvalidateRect(Handle, @R, False);
|
||
UpdateWindow(Handle);
|
||
if (Abs(FNewSelRect.Right - FNewSelRect.Left) > DragManager.DragThreshold) or
|
||
(Abs(FNewSelRect.Bottom - FNewSelRect.Top) > DragManager.DragThreshold) then
|
||
begin
|
||
if tsClearPending in FStates then
|
||
begin
|
||
DoStateChange([], [tsClearPending]);
|
||
ClearSelection;
|
||
end;
|
||
DoStateChange([tsDrawSelecting], [tsDrawSelPending]);
|
||
|
||
// Reset to main column for multiselection.
|
||
FocusedColumn := FHeader.MainColumn;
|
||
|
||
// The current rectangle may already include some node captions. Handle this.
|
||
if HandleDrawSelection(X, Y) then
|
||
InvalidateRect(Handle, nil, False);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
if tsNodeHeightTracking in FStates then
|
||
begin
|
||
// Handle height tracking.
|
||
if DoNodeHeightTracking(FHeightTrackNode, FHeightTrackColumn, FHeader.GetShiftState,
|
||
FHeightTrackPoint, Point(X, Y)) then
|
||
begin
|
||
// Avoid negative (or zero) node heights.
|
||
if FHeightTrackPoint.Y >= Y then
|
||
Y := FHeightTrackPoint.Y + 1;
|
||
SetNodeHeight(FHeightTrackNode, Y - FHeightTrackPoint.Y);
|
||
UpdateWindow(Handle);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
// If both wheel panning and auto scrolling are pending then the user moved the mouse while holding down the
|
||
// middle mouse button. This means panning is being used, hence remove the wheel scroll flag.
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates = [tsWheelPanning, tsWheelScrolling] then
|
||
begin
|
||
if ((Abs(FLastClickPos.X - X) >= DragManager.DragThreshold) or (Abs(FLastClickPos.Y - Y) >= DragManager.DragThreshold)) then
|
||
DoStateChange([], [tsWheelScrolling]);
|
||
end;
|
||
|
||
// Really start dragging if the mouse has been moved more than the threshold.
|
||
if (tsOLEDragPending in FStates) and ((Abs(FLastClickPos.X - X) >= FDragThreshold) or
|
||
(Abs(FLastClickPos.Y - Y) >= FDragThreshold)) then
|
||
DoDragging(FLastClickPos)
|
||
else
|
||
begin
|
||
if CanAutoScroll then
|
||
DoAutoScroll(X, Y);
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then
|
||
AdjustPanningCursor(X, Y);
|
||
if not IsMouseSelecting then
|
||
begin
|
||
HandleHotTrack(X, Y);
|
||
inherited MouseMove(Shift, X, Y);
|
||
end
|
||
else
|
||
begin
|
||
// Handle draw selection if required, but don't do the work twice if the
|
||
// auto scrolling code already cares about the selection.
|
||
if not (tsScrolling in FStates) and CalculateSelectionRect(X, Y) then
|
||
begin
|
||
// If something in the selection changed then invalidate the entire
|
||
// tree instead trying to figure out the display rects of all changed nodes.
|
||
if HandleDrawSelection(X, Y) then
|
||
InvalidateRect(Handle, nil, False)
|
||
else
|
||
begin
|
||
UnionRect(R, OrderRect(FNewSelRect), OrderRect(FLastSelRect));
|
||
OffsetRect(R, -FEffectiveOffsetX, FOffsetY);
|
||
if hoVisible in FHeader.Options then
|
||
OffsetRect(R, 0, FHeader.Height);
|
||
InvalidateRect(Handle, @R, False);
|
||
end;
|
||
UpdateWindow(Handle);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Notification(AComponent: TComponent; Operation: TOperation);
|
||
|
||
begin
|
||
if (AComponent <> Self) and (Operation = opRemove) then
|
||
begin
|
||
// Check for components linked to the tree.
|
||
if AComponent = FImages then
|
||
begin
|
||
Images := nil;
|
||
if not (csDestroying in ComponentState) then
|
||
Invalidate;
|
||
end
|
||
else
|
||
if AComponent = FStateImages then
|
||
begin
|
||
StateImages := nil;
|
||
if not (csDestroying in ComponentState) then
|
||
Invalidate;
|
||
end
|
||
else
|
||
if AComponent = PopupMenu then
|
||
PopupMenu := nil
|
||
else
|
||
// Check for components linked to the header.
|
||
if Assigned(FHeader) then
|
||
begin
|
||
if AComponent = FHeader.FImages then
|
||
FHeader.Images := nil
|
||
else
|
||
if AComponent = FHeader.PopupMenu then
|
||
FHeader.PopupMenu := nil;
|
||
end;
|
||
end;
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{$ifdef EnableNCFunctions}
|
||
procedure TBaseVirtualTree.OriginalWMNCPaint(DC: HDC);
|
||
|
||
// Unfortunately, the painting for the non-client area in TControl is not always correct and does also not consider
|
||
// existing clipping regions, so it has been modified here to take this into account.
|
||
|
||
const
|
||
InnerStyles: array[TBevelCut] of Integer = (0, BDR_SUNKENINNER, BDR_RAISEDINNER, 0);
|
||
OuterStyles: array[TBevelCut] of Integer = (0, BDR_SUNKENOUTER, BDR_RAISEDOUTER, 0);
|
||
EdgeStyles: array[TBevelKind] of Integer = (0, 0, BF_SOFT, BF_FLAT);
|
||
Ctl3DStyles: array[Boolean] of Integer = (BF_MONO, 0);
|
||
|
||
var
|
||
RC, RW: TRect;
|
||
EdgeSize: Integer;
|
||
Size: TSize;
|
||
|
||
begin
|
||
if (BevelKind <> bkNone) or (BorderWidth > 0) then
|
||
begin
|
||
RC := Rect(0, 0, Width, Height);
|
||
Size := GetBorderDimensions;
|
||
InflateRect(RC, Size.cx, Size.cy);
|
||
|
||
RW := RC;
|
||
|
||
if BevelKind <> bkNone then
|
||
begin
|
||
DrawEdge(DC, RC, InnerStyles[BevelInner] or OuterStyles[BevelOuter], Byte(BevelEdges) or EdgeStyles[BevelKind] or
|
||
Ctl3DStyles[Ctl3D]);
|
||
|
||
EdgeSize := 0;
|
||
if BevelInner <> bvNone then
|
||
Inc(EdgeSize, BevelWidth);
|
||
if BevelOuter <> bvNone then
|
||
Inc(EdgeSize, BevelWidth);
|
||
with RC do
|
||
begin
|
||
if beLeft in BevelEdges then
|
||
Inc(Left, EdgeSize);
|
||
if beTop in BevelEdges then
|
||
Inc(Top, EdgeSize);
|
||
if beRight in BevelEdges then
|
||
Dec(Right, EdgeSize);
|
||
if beBottom in BevelEdges then
|
||
Dec(Bottom, EdgeSize);
|
||
end;
|
||
end;
|
||
|
||
// Repaint only the part in the original clipping region and not yet drawn parts.
|
||
IntersectClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom);
|
||
|
||
// Determine inner rectangle to exclude (RC corresponds then to the client area).
|
||
InflateRect(RC, -BorderWidth, -BorderWidth);
|
||
|
||
// Remove the inner rectangle.
|
||
ExcludeClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom);
|
||
|
||
// Erase parts not drawn.
|
||
Brush.Color := FColors.BorderColor;
|
||
Windows.FillRect(DC, RW, Brush.Handle);
|
||
end;
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Paint;
|
||
|
||
// Window paint routine. Used when the tree window needs to be updated.
|
||
|
||
var
|
||
Window: TRect;
|
||
Target: TPoint;
|
||
Temp: Integer;
|
||
Options: TVTInternalPaintOptions;
|
||
RTLOffset: Integer;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaint],'Paint');{$endif}
|
||
Options := [poBackground, poColumnColor, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines];
|
||
if UseRightToLeftAlignment and FHeader.UseColumns then
|
||
RTLOffset := ComputeRTLOffset(True)
|
||
else
|
||
RTLOffset := 0;
|
||
|
||
// The update rect has already been filled in WMPaint, as it is the window's update rect, which gets
|
||
// reset when BeginPaint is called (in the ancestor).
|
||
// The difference to the DC's clipbox is that it is also valid with internal paint operations used
|
||
// e.g. by the Explorer while dragging, but show window content while dragging is disabled.
|
||
if not IsRectEmpty(FUpdateRect) then
|
||
begin
|
||
Temp := Header.Columns.GetVisibleFixedWidth;
|
||
if Temp = 0 then
|
||
begin
|
||
Window := FUpdateRect;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcHeaderOffset], 'FUpdateRect', FUpdateRect);{$endif}
|
||
Target := Window.TopLeft;
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
begin
|
||
if Target.Y < FHeader.Height then
|
||
begin
|
||
Window.Top := 0;
|
||
Target.Y := FHeader.Height;
|
||
end
|
||
else
|
||
begin
|
||
Dec(Window.Top, FHeader.Height);
|
||
end;
|
||
Dec(Window.Bottom, FHeader.Height);
|
||
|
||
if RectVisible(Canvas.Handle, FHeaderRect) then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'RectVisible = True');{$endif}
|
||
FHeader.FColumns.PaintHeader(Canvas.Handle, FHeaderRect, -FEffectiveOffsetX);
|
||
end;
|
||
with FHeaderRect do
|
||
ExcludeClipRect(Canvas.Handle, Left, Top, Right, Bottom);
|
||
end;
|
||
// The clipping rectangle is given in client coordinates of the window. We have to convert it into
|
||
// a sliding window of the tree image.
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'FEffectiveOffsetX: %d, RTLOffset: %d, OffsetY: %d',[FEffectiveOffsetX,RTLOffset,FOffsetY]);{$endif}
|
||
|
||
OffsetRect(Window, FEffectiveOffsetX - RTLOffset, -FOffsetY);
|
||
//{$ifdef DEBUG_VTV}Logger.Active:=Logger.CalledBy('DoDragging');{$endif}
|
||
PaintTree(Canvas, Window, Target, Options);
|
||
//{$ifdef DEBUG_VTV}Logger.Active:=True;{$endif}
|
||
end
|
||
else
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaint],'VisibleFixedWidth > 0');{$endif}
|
||
// First part, fixed columns
|
||
Window := ClientRect;
|
||
Window.Right := Temp;
|
||
Target := Window.TopLeft;
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
begin
|
||
//Target is always (0,0) due to call to ClientRect, so no need set Top to 0
|
||
//also no need to decrease bottom because ClientRect already computes header
|
||
Inc(Target.Y, FHeader.Height);
|
||
if RectVisible(Canvas.Handle, FHeaderRect) then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader], 'RectVisible = True');{$endif}
|
||
FHeader.FColumns.PaintHeader(Canvas.Handle, FHeaderRect, -FEffectiveOffsetX);
|
||
end;
|
||
with FHeaderRect do
|
||
ExcludeClipRect(Canvas.Handle,Left,Top,Right,Bottom);
|
||
end;
|
||
|
||
OffsetRect(Window, -RTLOffset, -FOffsetY);
|
||
PaintTree(Canvas, Window, Target, Options);
|
||
|
||
// Second part, other columns
|
||
Window := GetClientRect;
|
||
|
||
if Temp > Window.Right then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaint],'Paint');{$endif}
|
||
Exit;
|
||
end;
|
||
|
||
Window.Left := Temp;
|
||
Target := Window.TopLeft;
|
||
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
Inc(Target.Y, FHeader.Height);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcDrag],'FEffectiveOffsetX: %d, RTLOffset: %d, OffsetY: %d',[FEffectiveOffsetX,RTLOffset,FOffsetY]);{$endif}
|
||
OffsetRect(Window, FEffectiveOffsetX - RTLOffset, -FOffsetY);
|
||
PaintTree(Canvas, Window, Target, Options);
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaint],'Paint');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PaintCheckImage(const PaintInfo: TVTPaintInfo);
|
||
|
||
|
||
procedure DrawCheckButton(Canvas: TCanvas; Index: Integer; const R: TRect; Flat: Boolean);
|
||
|
||
var
|
||
ButtonState: Cardinal;
|
||
ButtonType: Cardinal;
|
||
|
||
begin
|
||
if Index < 8 then
|
||
ButtonType := DFCS_BUTTONRADIO
|
||
else
|
||
ButtonType := DFCS_BUTTONCHECK;
|
||
if Index >= 16 then
|
||
ButtonType := ButtonType or DFCS_BUTTON3STATE;
|
||
|
||
case Index mod 4 of
|
||
0:
|
||
ButtonState := 0;
|
||
1:
|
||
ButtonState := DFCS_HOT;
|
||
2:
|
||
ButtonState := DFCS_PUSHED;
|
||
else
|
||
ButtonState := DFCS_INACTIVE;
|
||
end;
|
||
if Index in [4..7, 12..19] then
|
||
ButtonState := ButtonState or DFCS_CHECKED;
|
||
if Flat then
|
||
ButtonState := ButtonState or DFCS_FLAT;
|
||
|
||
DrawFrameControl(Canvas.Handle, R, DFC_BUTTON, ButtonType or ButtonState);
|
||
end;
|
||
|
||
|
||
var
|
||
R: TRect;
|
||
{$ifdef ThemeSupport}
|
||
Details: TThemedElementDetails;
|
||
{$endif}
|
||
UseThemes: Boolean;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcCheck],'PaintCheckImage');{$endif}
|
||
with PaintInfo, ImageInfo[iiCheck] do
|
||
begin
|
||
{$ifdef ThemeSupport}
|
||
UseThemes := (tsUseThemes in FStates) and (FCheckImageKind = ckSystemDefault);
|
||
{$else}
|
||
UseThemes := False;
|
||
{$endif}
|
||
if UseThemes or ((FCheckImageKind in [ckSystemFlat, ckSystemDefault]) and not (Index in [21..24])) then
|
||
begin
|
||
{$ifdef ThemeSupport}
|
||
if UseThemes then
|
||
begin
|
||
R := Rect(XPos - 1, YPos, XPos + 16, YPos + 16);
|
||
Details.Element := teButton;
|
||
case Index of
|
||
0..8: // radio buttons
|
||
begin
|
||
Details.Part := BP_RADIOBUTTON;
|
||
Details.State := Index;
|
||
end;
|
||
9..20: // check boxes
|
||
begin
|
||
Details.Part := BP_CHECKBOX;
|
||
Details.State := Index - 8;
|
||
end;
|
||
21..24: // buttons
|
||
begin
|
||
Details.Part := BP_PUSHBUTTON;
|
||
Details.State := Index - 20;
|
||
end;
|
||
else
|
||
Details.Part := 0;
|
||
Details.State := 0;
|
||
end;
|
||
ThemeServices.DrawElement(Canvas.Handle, Details, R);
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
if Index in [21..24] then
|
||
with UtilityImages do
|
||
DirectMaskBlt(PaintInfo.Canvas.Handle, XPos - 1, YPos, Height, Height,
|
||
Canvas.Handle, 4 * Height, 0, MaskHandle);
|
||
{$else}
|
||
if Index in [21..24] then
|
||
with UtilityImages do
|
||
StretchMaskBlt(PaintInfo.Canvas.Handle, XPos - 1, YPos, Height, Height,
|
||
Canvas.Handle, 4 * Height, 0, Height, Height, MaskHandle, 4 * Height, 0, SRCCOPY);
|
||
{$endif}
|
||
end
|
||
else
|
||
{$endif}
|
||
begin
|
||
R := Rect(XPos + 1, YPos + 1, XPos + 14, YPos + 14);
|
||
DrawCheckButton(Canvas, Index - 1, R, FCheckImageKind = ckSystemFlat);
|
||
end;
|
||
end
|
||
else
|
||
with FCheckImages do
|
||
begin
|
||
{$ifdef USE_DELPHICOMPAT}
|
||
DirectMaskBlt(PaintInfo.Canvas.Handle, XPos, YPos, Height, Height, Canvas.Handle,
|
||
Index * Height, 0, MaskHandle);
|
||
{$else}
|
||
StretchMaskBlt(PaintInfo.Canvas.Handle, XPos, YPos, Height, Height, Canvas.Handle,
|
||
Index * Height, 0, Height, Height, MaskHandle, Index * Height, 0, SRCCOPY);
|
||
{$endif}
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcCheck],'PaintCheckImage');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean);
|
||
|
||
var
|
||
CutNode: Boolean;
|
||
PaintFocused: Boolean;
|
||
DrawEffect: TGraphicsDrawEffect;
|
||
|
||
begin
|
||
with PaintInfo do
|
||
begin
|
||
CutNode := (vsCutOrCopy in Node.States) and (tsCutPending in FStates);
|
||
PaintFocused := Focused or (toGhostedIfUnfocused in FOptions.FPaintOptions);
|
||
|
||
// Since the overlay image must be specified together with the image to draw
|
||
// it is meaningfull to retrieve it in advance.
|
||
if DoOverlay then
|
||
GetImageIndex(PaintInfo, ikOverlay, iiOverlay, Images)
|
||
else
|
||
PaintInfo.ImageInfo[iiOverlay].Index := -1;
|
||
|
||
with ImageInfo[ImageInfoIndex] do
|
||
begin
|
||
if (vsSelected in Node.States) and not (Ghosted or CutNode) then
|
||
begin
|
||
if PaintFocused or (toPopupMode in FOptions.FPaintOptions) then
|
||
Images.BlendColor := FColors.FocusedSelectionColor
|
||
else
|
||
Images.BlendColor := FColors.UnfocusedSelectionColor;
|
||
end
|
||
else
|
||
Images.BlendColor := Color;
|
||
|
||
if (vsDisabled in Node.States) or not Enabled then
|
||
DrawEffect := gdeDisabled
|
||
else
|
||
// Blend image if enabled and the tree has the focus (or ghosted images must be drawn also if unfocused) ...
|
||
if (toUseBlendedImages in FOptions.FPaintOptions) and PaintFocused
|
||
// ... and the image is ghosted...
|
||
and (Ghosted or
|
||
// ... or it is not the check image and the node is selected (but selection is not for the entire row)...
|
||
((vsSelected in Node.States) and
|
||
not (toFullRowSelect in FOptions.FSelectionOptions) and
|
||
not (toGridExtensions in FOptions.FMiscOptions)) or
|
||
// ... or the node must be shown in cut mode.
|
||
CutNode) then
|
||
DrawEffect := gdeShadowed
|
||
else
|
||
DrawEffect := gdeNormal;
|
||
|
||
if (vsSelected in Node.States) and not Ghosted then
|
||
Images.BlendColor := clDefault;
|
||
|
||
Images.Draw(Canvas, XPos, YPos, Index, DrawEffect);
|
||
|
||
// Now, draw the overlay.
|
||
// Delphi version has the ability to use the built in overlay indices of windows system image lists
|
||
// Since this is system dependent the LCL version will support only custom overlays
|
||
|
||
// Note: XPos and YPos are those of the normal images.
|
||
if PaintInfo.ImageInfo[iiOverlay].Index >= 0 then
|
||
ImageInfo[iiOverlay].Images.Draw(Canvas, XPos, YPos, ImageInfo[iiOverlay].Index);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect;
|
||
ButtonX, ButtonY: Integer; BidiMode: TBiDiMode);
|
||
|
||
var
|
||
Bitmap: TBitmap;
|
||
XPos: Integer;
|
||
IsHot: Boolean;
|
||
|
||
begin
|
||
IsHot := (toHotTrack in FOptions.FPaintOptions) and (FCurrentHotNode = Node) and FHotNodeButtonHit;
|
||
|
||
if vsExpanded in Node.States then
|
||
begin
|
||
if IsHot then
|
||
Bitmap := FHotMinusBM
|
||
else
|
||
Bitmap := FMinusBM;
|
||
end
|
||
else
|
||
begin
|
||
if IsHot then
|
||
Bitmap := FHotPlusBM
|
||
else
|
||
Bitmap := FPlusBM;
|
||
end;
|
||
|
||
// Draw the node's plus/minus button according to the directionality.
|
||
if BidiMode = bdLeftToRight then
|
||
XPos := R.Left + ButtonX
|
||
else
|
||
XPos := R.Right - ButtonX - Bitmap.Width;
|
||
|
||
// Need to draw this masked.
|
||
Canvas.Draw(XPos, R.Top + ButtonY, Bitmap);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PaintTreeLines(const PaintInfo: TVTPaintInfo; VAlignment, IndentSize: Integer;
|
||
LineImage: TLineImage);
|
||
|
||
var
|
||
I: Integer;
|
||
XPos,
|
||
Offset: Integer;
|
||
NewStyles: TLineImage;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintDetails],'PaintTreeLines');{$endif}
|
||
NewStyles := nil;
|
||
|
||
with PaintInfo do
|
||
begin
|
||
if BidiMode = bdLeftToRight then
|
||
begin
|
||
XPos := CellRect.Left;
|
||
Offset := FIndent;
|
||
end
|
||
else
|
||
begin
|
||
Offset := -Integer(FIndent);
|
||
XPos := CellRect.Right + Offset;
|
||
end;
|
||
|
||
case FLineMode of
|
||
lmBands:
|
||
if poGridLines in PaintInfo.PaintOptions then
|
||
begin
|
||
// Convert the line images in correct bands.
|
||
SetLength(NewStyles, Length(LineImage));
|
||
for I := IndentSize - 1 downto 0 do
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'FLineMode = lmBands');{$endif}
|
||
if (vsExpanded in Node.States) and not (vsAllChildrenHidden in Node.States) then
|
||
NewStyles[I] := ltLeft
|
||
else
|
||
case LineImage[I] of
|
||
ltRight,
|
||
ltBottomRight,
|
||
ltTopDownRight,
|
||
ltTopRight:
|
||
NewStyles[I] := ltLeftBottom;
|
||
ltNone:
|
||
// Have to take over the image to the right of this one. A no line entry can never appear as
|
||
// last entry so I don't need an end check here.
|
||
if LineImage[I + 1] in [ltNone, ltTopRight] then
|
||
NewStyles[I] := NewStyles[I + 1]
|
||
else
|
||
NewStyles[I] := ltLeft;
|
||
ltTopDown:
|
||
// Have to check the image to the right of this one. A top down line can never appear as
|
||
// last entry so I don't need an end check here.
|
||
if LineImage[I + 1] in [ltNone, ltTopRight] then
|
||
NewStyles[I] := NewStyles[I + 1]
|
||
else
|
||
NewStyles[I] := ltLeft;
|
||
end;
|
||
end;
|
||
|
||
PaintInfo.Canvas.Font.Color := FColors.GridLineColor;
|
||
for I := 0 to IndentSize - 1 do
|
||
begin
|
||
DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node] - 1, VAlignment, NewStyles[I],
|
||
BidiMode <> bdLeftToRight);
|
||
Inc(XPos, Offset);
|
||
end;
|
||
end;
|
||
else // lmNormal
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'FLineMode = lmNormal');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
PaintInfo.Canvas.Font.Color := FColors.TreeLineColor;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Font.Color);{$endif}
|
||
for I := 0 to IndentSize - 1 do
|
||
begin
|
||
DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node], VAlignment, LineImage[I],
|
||
BidiMode <> bdLeftToRight);
|
||
Inc(XPos, Offset);
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'PaintTreeLines');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PaintSelectionRectangle(Target: TCanvas; WindowOrgX: Integer; const SelectionRect: TRect;
|
||
TargetRect: TRect);
|
||
|
||
// Helper routine to draw a selection rectangle in the mode determined by DrawSelectionMode.
|
||
|
||
var
|
||
BlendRect: TRect;
|
||
TextColorBackup,
|
||
BackColorBackup: COLORREF; // used to restore forground and background colors when drawing a selection rectangle
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcSelection], 'SelectionRect at PaintSelection', SelectionRect);{$endif}
|
||
if ((FDrawSelectionMode = smDottedRectangle) and not (tsUseThemes in FStates)) or
|
||
not MMXAvailable then
|
||
begin
|
||
// Classical selection rectangle using dotted borderlines.
|
||
TextColorBackup := GetTextColor(Target.Handle);
|
||
SetTextColor(Target.Handle, $FFFFFF);
|
||
BackColorBackup := GetBkColor(Target.Handle);
|
||
SetBkColor(Target.Handle, 0);
|
||
Target.DrawFocusRect(SelectionRect);
|
||
SetTextColor(Target.Handle, TextColorBackup);
|
||
SetBkColor(Target.Handle, BackColorBackup);
|
||
end
|
||
else
|
||
begin
|
||
// Modern alpha blended style.
|
||
OffsetRect(TargetRect, WindowOrgX, 0);
|
||
if IntersectRect(BlendRect, OrderRect(SelectionRect), TargetRect) then
|
||
begin
|
||
OffsetRect(BlendRect, -WindowOrgX, 0);
|
||
AlphaBlend(0, Target.Handle, BlendRect, Point(0, 0), bmConstantAlphaAndColor, FSelectionBlendFactor,
|
||
ColorToRGB(FColors.SelectionRectangleBlendColor));
|
||
|
||
Target.Brush.Color := FColors.SelectionRectangleBorderColor;
|
||
Target.FrameRect(SelectionRect);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer);
|
||
|
||
// This method is called immediately before a cell's content is drawn und is responsible to paint selection colors etc.
|
||
|
||
var
|
||
TextColorBackup,
|
||
BackColorBackup: COLORREF;
|
||
FocusRect,
|
||
InnerRect: TRect;
|
||
{$ifdef ThemeSupport}
|
||
RowRect: TRect;
|
||
//Theme: HTHEME;
|
||
{$endif ThemeSupport}
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
procedure AlphaBlendSelection(Color: TColor);
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
// Take into account any window offset and size limitations in the target bitmap, as this is only as large
|
||
// as necessary and might not cover the whole node. For normal painting this does not matter (because of
|
||
// clipping) but for the MMX code there is no such check and it will crash badly when bitmap boundaries are
|
||
// crossed.
|
||
R := InnerRect;
|
||
OffsetRect(R, -WindowOrgX, 0);
|
||
if R.Left < 0 then
|
||
R.Left := 0;
|
||
if R.Right > MaxWidth then
|
||
R.Right := MaxWidth;
|
||
AlphaBlend(0, PaintInfo.Canvas.Handle, R, Point(0, 0), bmConstantAlphaAndColor,
|
||
FSelectionBlendFactor, ColorToRGB(Color));
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
procedure DrawBackground(State: Integer);
|
||
begin
|
||
with PaintInfo do
|
||
if (toGridExtensions in FOptions.FMiscOptions) or (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
DrawThemeBackground(Theme, Canvas.Handle, TVP_TREEITEM, State, RowRect, @CellRect)
|
||
else
|
||
DrawThemeBackground(Theme, Canvas.Handle, TVP_TREEITEM, State, InnerRect, nil);
|
||
end;
|
||
}
|
||
{$endif ThemeSupport}
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
begin
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions) then
|
||
begin
|
||
RowRect := Rect(0, PaintInfo.CellRect.Top, FRangeX, PaintInfo.CellRect.Bottom);
|
||
if toShowVertGridLines in FOptions.PaintOptions then
|
||
Dec(RowRect.Right);
|
||
Theme := OpenThemeData(Handle, 'TREEVIEW');
|
||
end
|
||
else
|
||
Theme := 0;
|
||
}
|
||
{$endif ThemeSupport}
|
||
|
||
with PaintInfo, Canvas do
|
||
begin
|
||
// Fill cell background if its color differs from tree background.
|
||
with FHeader.FColumns do
|
||
if poColumnColor in PaintOptions then
|
||
begin
|
||
Brush.Color := Items[Column].Color;
|
||
FillRect(CellRect);
|
||
end;
|
||
|
||
// Let the application customize the cell background and the content rectangle.
|
||
DoBeforeCellPaint(Canvas, Node, Column, cpmPaint, CellRect, ContentRect);
|
||
|
||
InnerRect := ContentRect;
|
||
|
||
// The selection rectangle depends on alignment.
|
||
if not (toGridExtensions in FOptions.FMiscOptions) then
|
||
begin
|
||
case Alignment of
|
||
taLeftJustify:
|
||
with InnerRect do
|
||
if Left + NodeWidth < Right then
|
||
Right := Left + NodeWidth;
|
||
taCenter:
|
||
with InnerRect do
|
||
if (Right - Left) > NodeWidth then
|
||
begin
|
||
Left := (Left + Right - NodeWidth) div 2;
|
||
Right := Left + NodeWidth;
|
||
end;
|
||
taRightJustify:
|
||
with InnerRect do
|
||
if (Right - Left) > NodeWidth then
|
||
Left := Right - NodeWidth;
|
||
end;
|
||
end;
|
||
|
||
if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
begin
|
||
// Fill the selection rectangle.
|
||
if poDrawSelection in PaintOptions then
|
||
begin
|
||
if Node = FDropTargetNode then
|
||
begin
|
||
if (FLastDropMode = dmOnNode) or (vsSelected in Node.States) then
|
||
begin
|
||
Brush.Color := FColors.DropTargetColor;
|
||
Pen.Color := FColors.DropTargetBorderColor;
|
||
|
||
if (toGridExtensions in FOptions.FMiscOptions) or
|
||
(toFullRowSelect in FOptions.FSelectionOptions) then
|
||
InnerRect := CellRect;
|
||
if not IsRectEmpty(InnerRect) then
|
||
if MMXAvailable and (toUseBlendedSelection in FOptions.PaintOptions) then
|
||
AlphaBlendSelection(Brush.Color)
|
||
else
|
||
with InnerRect do
|
||
RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius);
|
||
end
|
||
else
|
||
begin
|
||
//lcl: Is not necessary to set the style here
|
||
//Brush.Style := bsClear;
|
||
end;
|
||
end
|
||
else
|
||
if vsSelected in Node.States then
|
||
begin
|
||
if Focused or (toPopupMode in FOptions.FPaintOptions) then
|
||
begin
|
||
Brush.Color := FColors.FocusedSelectionColor;
|
||
Pen.Color := FColors.FocusedSelectionBorderColor;
|
||
end
|
||
else
|
||
begin
|
||
Brush.Color := FColors.UnfocusedSelectionColor;
|
||
Pen.Color := FColors.UnfocusedSelectionBorderColor;
|
||
end;
|
||
|
||
if (toGridExtensions in FOptions.FMiscOptions) or (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
InnerRect := CellRect;
|
||
if not IsRectEmpty(InnerRect) then
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if Theme <> 0 then
|
||
begin
|
||
// If the node is also hot, its background will be drawn later.
|
||
if not (toHotTrack in FOptions.FPaintOptions) or (Node <> FCurrentHotNode) or
|
||
((Column <> FCurrentHotColumn) and not (toFullRowSelect in FOptions.FSelectionOptions)) then
|
||
DrawBackground(IfThen(Self.Focused, TREIS_SELECTED, TREIS_SELECTEDNOTFOCUS));
|
||
end
|
||
else
|
||
}
|
||
{$endif ThemeSupport}
|
||
if MMXAvailable and (toUseBlendedSelection in FOptions.PaintOptions) then
|
||
AlphaBlendSelection(Brush.Color)
|
||
else
|
||
with InnerRect do
|
||
RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if (Theme <> 0) and (toHotTrack in FOptions.FPaintOptions) and (Node = FCurrentHotNode) and
|
||
((Column = FCurrentHotColumn) or (toFullRowSelect in FOptions.FSelectionOptions)) then
|
||
DrawBackground(IfThen((vsSelected in Node.States) and not (toAlwaysHideSelection in FOptions.FPaintOptions),
|
||
TREIS_HOTSELECTED, TREIS_HOT));
|
||
}
|
||
{$endif ThemeSupport}
|
||
|
||
if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
begin
|
||
// draw focus rect
|
||
if (poDrawFocusRect in PaintOptions) and
|
||
(Focused or (toPopupMode in FOptions.FPaintOptions)) and (FFocusedNode = Node) and
|
||
( (Column = FFocusedColumn)
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{ or
|
||
(not (toExtendedFocus in FOptions.FSelectionOptions) and
|
||
(toFullRowSelect in FOptions.FSelectionOptions) and
|
||
(Theme <> 0) )
|
||
}
|
||
{$endif ThemeSupport}
|
||
) then
|
||
begin
|
||
TextColorBackup := GetTextColor(Handle);
|
||
SetTextColor(Handle, $FFFFFF);
|
||
BackColorBackup := GetBkColor(Handle);
|
||
SetBkColor(Handle, 0);
|
||
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if not (toExtendedFocus in FOptions.FSelectionOptions) and (toFullRowSelect in FOptions.FSelectionOptions) and
|
||
(Theme <> 0) then
|
||
FocusRect := RowRect
|
||
else
|
||
}
|
||
{$endif ThemeSupport}
|
||
if toGridExtensions in FOptions.FMiscOptions then
|
||
FocusRect := CellRect
|
||
else
|
||
FocusRect := InnerRect;
|
||
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if Theme <> 0 then
|
||
InflateRect(FocusRect, -1, -1);
|
||
}
|
||
{$endif ThemeSupport}
|
||
|
||
LCLIntf.DrawFocusRect(Handle, FocusRect);
|
||
SetTextColor(Handle, TextColorBackup);
|
||
SetBkColor(Handle, BackColorBackup);
|
||
end;
|
||
end;
|
||
end;
|
||
{$ifdef ThemeSupport}
|
||
//todo
|
||
{
|
||
if Theme <> 0 then
|
||
CloseThemeData(Theme);
|
||
}
|
||
{$endif ThemeSupport}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType,
|
||
ChunkSize: Integer): Boolean;
|
||
|
||
// Called while loading a tree structure, Node is already valid (allocated) at this point.
|
||
// The function handles the base and user chunks, any other chunk is marked as being unknown (result becomes False)
|
||
// and skipped. descendants may handle them by overriding this method.
|
||
// Returns True if the chunk could be handled, otherwise False.
|
||
|
||
var
|
||
ChunkBody: TBaseChunkBody;
|
||
Run: PVirtualNode;
|
||
LastPosition: Integer;
|
||
|
||
begin
|
||
case ChunkType of
|
||
BaseChunk:
|
||
begin
|
||
// Load base chunk's body (chunk header has already been consumed).
|
||
Stream.Read(ChunkBody, SizeOf(ChunkBody));
|
||
with Node^ do
|
||
begin
|
||
// Set states first, in case the node is invisible.
|
||
States := ChunkBody.States;
|
||
NodeHeight := ChunkBody.NodeHeight;
|
||
TotalHeight := NodeHeight;
|
||
Align := ChunkBody.Align;
|
||
CheckState := ChunkBody.CheckState;
|
||
CheckType := ChunkBody.CheckType;
|
||
ChildCount := ChunkBody.ChildCount;
|
||
|
||
// Create and read child nodes.
|
||
while ChunkBody.ChildCount > 0 do
|
||
begin
|
||
Run := MakeNewNode;
|
||
|
||
Run.PrevSibling := Node.LastChild;
|
||
if Assigned(Run.PrevSibling) then
|
||
Run.Index := Run.PrevSibling.Index + 1;
|
||
if Assigned(Node.LastChild) then
|
||
Node.LastChild.NextSibling := Run
|
||
else
|
||
Node.FirstChild := Run;
|
||
Node.LastChild := Run;
|
||
Run.Parent := Node;
|
||
|
||
ReadNode(Stream, Version, Run);
|
||
Dec(ChunkBody.ChildCount);
|
||
end;
|
||
end;
|
||
Result := True;
|
||
end;
|
||
UserChunk:
|
||
if ChunkSize > 0 then
|
||
begin
|
||
// need to know whether the data was read
|
||
LastPosition := Stream.Position;
|
||
DoLoadUserData(Node, Stream);
|
||
// compare stream position to learn whether the data was read
|
||
Result := Stream.Position > LastPosition;
|
||
// Improve stability by advancing the stream to the chunk's real end if
|
||
// the application did not read what has been written.
|
||
if not Result or (Stream.Position <> (LastPosition + ChunkSize)) then
|
||
Stream.Position := LastPosition + ChunkSize;
|
||
end
|
||
else
|
||
Result := True;
|
||
else
|
||
// unknown chunk, skip it
|
||
Stream.Position := Stream.Position + ChunkSize;
|
||
Result := False;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ReadNode(Stream: TStream; Version: Integer; Node: PVirtualNode);
|
||
|
||
// Reads the anchor chunk of each node and initiates reading the sub chunks for this node
|
||
|
||
var
|
||
Header: TChunkHeader;
|
||
EndPosition: Integer;
|
||
|
||
begin
|
||
with Stream do
|
||
begin
|
||
// Read anchor chunk of the node.
|
||
Stream.Read(Header, SizeOf(Header));
|
||
if Header.ChunkType = NodeChunk then
|
||
begin
|
||
EndPosition := Stream.Position + Header.ChunkSize;
|
||
// Read all subchunks until the indicated chunk end position is reached in the stream.
|
||
while Position < EndPosition do
|
||
begin
|
||
// Read new chunk header.
|
||
Stream.Read(Header, SizeOf(Header));
|
||
ReadChunk(Stream, Version, Node, Header.ChunkType, Header.ChunkSize);
|
||
end;
|
||
// If the last chunk does not end at the given end position then there is something wrong.
|
||
if Position <> EndPosition then
|
||
ShowError(SCorruptStream2, hcTFCorruptStream2);
|
||
end
|
||
else
|
||
ShowError(SCorruptStream1, hcTFCorruptStream1);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.RedirectFontChangeEvent(Canvas: TCanvas);
|
||
|
||
begin
|
||
if @Canvas.Font.OnChange <> @FOldFontChange then
|
||
begin
|
||
FOldFontChange := Canvas.Font.OnChange;
|
||
Canvas.Font.OnChange := FontChanged;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.RemoveFromSelection(Node: PVirtualNode);
|
||
|
||
var
|
||
Index: Integer;
|
||
|
||
begin
|
||
if not FSelectionLocked then
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil!');
|
||
if vsSelected in Node.States then
|
||
begin
|
||
Exclude(Node.States, vsSelected);
|
||
if FindNodeInSelection(Node, Index, -1, -1) and (Index < FSelectionCount - 1) then
|
||
Move(FSelection[Index + 1], FSelection[Index], (FSelectionCount - Index - 1) * 4);
|
||
if FSelectionCount > 0 then
|
||
Dec(FSelectionCount);
|
||
SetLength(FSelection, FSelectionCount);
|
||
|
||
if FSelectionCount = 0 then
|
||
ResetRangeAnchor;
|
||
|
||
Change(Node);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ResetRangeAnchor;
|
||
|
||
// Called when there is no selected node anymore and the selection range anchor needs a new value.
|
||
|
||
begin
|
||
FRangeAnchor := FFocusedNode;
|
||
FLastSelectionLevel := -1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.RestoreFontChangeEvent(Canvas: TCanvas);
|
||
|
||
begin
|
||
Canvas.Font.OnChange := FOldFontChange;
|
||
FOldFontChange := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SelectNodes(StartNode, EndNode: PVirtualNode; AddOnly: Boolean);
|
||
|
||
// Selects a range of nodes and unselects all other eventually selected nodes which are not in this range if
|
||
// AddOnly is False.
|
||
// EndNode must be visible while StartNode does not necessarily as in the case where the last focused node is the start
|
||
// node but it is a child of a node which has been collapsed previously. In this case the first visible parent node
|
||
// is used as start node. StartNode can be nil in which case the very first node in the tree is used.
|
||
|
||
var
|
||
NodeFrom,
|
||
NodeTo,
|
||
LastAnchor: PVirtualNode;
|
||
Index: Integer;
|
||
|
||
begin
|
||
Assert(Assigned(EndNode), 'EndNode must not be nil!');
|
||
if not FSelectionLocked then
|
||
begin
|
||
ClearTempCache;
|
||
if StartNode = nil then
|
||
StartNode := GetFirstVisibleNoInit(nil, True)
|
||
else
|
||
if not FullyVisible[StartNode] then
|
||
begin
|
||
StartNode := GetPreviousVisible(StartNode, True);
|
||
if StartNode = nil then
|
||
StartNode := GetFirstVisibleNoInit(nil, True)
|
||
end;
|
||
|
||
if CompareNodePositions(StartNode, EndNode, True) < 0 then
|
||
begin
|
||
NodeFrom := StartNode;
|
||
NodeTo := EndNode;
|
||
end
|
||
else
|
||
begin
|
||
NodeFrom := EndNode;
|
||
NodeTo := StartNode;
|
||
end;
|
||
|
||
// The range anchor will be reset by the following call.
|
||
LastAnchor := FRangeAnchor;
|
||
if not AddOnly then
|
||
InternalClearSelection;
|
||
|
||
while NodeFrom <> NodeTo do
|
||
begin
|
||
InternalCacheNode(NodeFrom);
|
||
NodeFrom := GetNextVisible(NodeFrom, True);
|
||
end;
|
||
// select last node too
|
||
InternalCacheNode(NodeFrom);
|
||
// now add them all in "one" step
|
||
AddToSelection(FTempNodeCache, FTempNodeCount);
|
||
ClearTempCache;
|
||
if Assigned(LastAnchor) and FindNodeInSelection(LastAnchor, Index, -1, -1) then
|
||
FRangeAnchor := LastAnchor;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SetFocusedNodeAndColumn(Node: PVirtualNode; Column: TColumnIndex);
|
||
|
||
var
|
||
OldColumn: TColumnIndex;
|
||
WasDifferent: Boolean;
|
||
|
||
begin
|
||
if not FHeader.AllowFocus(Column) then
|
||
Column := FFocusedColumn;
|
||
|
||
WasDifferent := (Node <> FFocusedNode) or (Column <> FFocusedColumn);
|
||
|
||
OldColumn := FFocusedColumn;
|
||
FFocusedColumn := Column;
|
||
|
||
DoFocusNode(Node, True);
|
||
|
||
// Check if the change was accepted.
|
||
if FFocusedNode = Node then
|
||
begin
|
||
CancelEditNode;
|
||
if WasDifferent then
|
||
DoFocusChange(FFocusedNode, FFocusedColumn);
|
||
end
|
||
else
|
||
// If the user did not accept the new cell to focus then set also the focused column back
|
||
// to its original state.
|
||
FFocusedColumn := OldColumn;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SkipNode(Stream: TStream);
|
||
|
||
// Skips the data for the next node in the given stream (including the child nodes).
|
||
|
||
var
|
||
Header: TChunkHeader;
|
||
|
||
begin
|
||
with Stream do
|
||
begin
|
||
// read achor chunk of the node
|
||
Stream.Read(Header, SizeOf(Header));
|
||
if Header.ChunkType = NodeChunk then
|
||
Stream.Position := Stream.Position + Header.ChunkSize
|
||
else
|
||
ShowError(SCorruptStream1, hcTFCorruptStream1);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
procedure TBaseVirtualTree.StartWheelPanning(const Position: TPoint);
|
||
|
||
// Called when wheel panning should start. A little helper window is created to indicate the reference position,
|
||
// which determines in which direction and how far wheel panning/scrolling will happen.
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
function CreateClipRegion: HRGN;
|
||
|
||
// In order to avoid doing all the transparent drawing ourselves we use a
|
||
// window region for the wheel window.
|
||
// Since we only work on a very small image (32x32 pixels) this is acceptable.
|
||
|
||
var
|
||
Start, X, Y, ImageHeight, ImageWidth: Integer;
|
||
Temp: HRGN;
|
||
|
||
begin
|
||
Assert(not FPanningWindow.Image.Empty, 'Invalid wheel panning image.');
|
||
ImageWidth := FPanningWindow.Image.Width;
|
||
ImageHeight := FPanningWindow.Image.Height;
|
||
// Create an initial region on which we operate.
|
||
Result := CreateRectRgn(0, 0, 0, 0);
|
||
with FPanningWindow.Image.Canvas do
|
||
begin
|
||
for Y := 0 to ImageHeight - 1 do
|
||
begin
|
||
Start := -1;
|
||
for X := 0 to ImageWidth - 1 do
|
||
begin
|
||
// Start a new span if we found a non-transparent pixel and no span is currently started.
|
||
if (Start = -1) and (Pixels[X, Y] <> clFuchsia) then
|
||
Start := X
|
||
else
|
||
if (Start > -1) and (Pixels[X, Y] = clFuchsia) then
|
||
begin
|
||
// A non-transparent span is finished. Add it to the result region.
|
||
Temp := CreateRectRgn(Start, Y, X, Y + 1);
|
||
CombineRgn(Result, Result, Temp, RGN_OR);
|
||
DeleteObject(Temp);
|
||
Start := -1;
|
||
end;
|
||
end;
|
||
// If there is an open span then add this also to the result region.
|
||
if Start > -1 then
|
||
begin
|
||
Temp := CreateRectRgn(Start, Y, ImageWidth, Y + 1);
|
||
CombineRgn(Result, Result, Temp, RGN_OR);
|
||
DeleteObject(Temp);
|
||
end;
|
||
end;
|
||
end;
|
||
// The resulting region is used as window region so we must not delete it.
|
||
// Windows will own it after the assignment below.
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
var
|
||
ImageName: string;
|
||
|
||
begin
|
||
// Set both panning and scrolling flag. One will be removed shortly depending on whether the middle mouse button is
|
||
// released before the mouse is moved or vice versa. The first case is referred to as wheel scrolling while the
|
||
// latter is called wheel panning.
|
||
KillTimer(Handle, ScrollTimer);
|
||
DoStateChange([tsWheelPanning, tsWheelScrolling]);
|
||
|
||
if FPanningWindow = nil then
|
||
begin
|
||
FPanningWindow := TVirtualPanningWindow.Create;
|
||
LoadPanningCursors;
|
||
end;
|
||
|
||
FPanningWindow.Start(Handle, ClientToScreen(Position));
|
||
|
||
if Integer(FRangeX) > ClientWidth then
|
||
begin
|
||
if Integer(FRangeY) > ClientHeight then
|
||
ImageName := 'VT_MOVEALL_BMP'
|
||
else
|
||
ImageName := 'VT_MOVEEW_BMP'
|
||
end
|
||
else
|
||
ImageName := 'VT_MOVENS_BMP';
|
||
|
||
FPanningWindow.Image.LoadFromLazarusResource(ImageName);
|
||
|
||
FPanningWindow.Show(CreateClipRegion);
|
||
|
||
// Setup the panscroll timer and capture all mouse input.
|
||
SetFocus;
|
||
SetCapture(Handle);
|
||
AdjustPanningCursor(Position.X, Position.Y);
|
||
SetTimer(Handle, ScrollTimer, 20, nil);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.StopWheelPanning;
|
||
|
||
// Stops panning if currently active and destroys the helper window.
|
||
|
||
begin
|
||
if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then
|
||
begin
|
||
// Release the mouse capture and stop the panscroll timer.
|
||
KillTimer(Handle, ScrollTimer);
|
||
ReleaseCapture;
|
||
DoStateChange([], [tsWheelPanning, tsWheelScrolling]);
|
||
|
||
FPanningWindow.Stop;
|
||
{$ifndef Windows}
|
||
Cursor := crDefault;
|
||
{$endif}
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.StructureChange(Node: PVirtualNode; Reason: TChangeReason);
|
||
|
||
begin
|
||
AdviseChangeEvent(True, Node, Reason);
|
||
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
if (FChangeDelay > 0) and not (tsSynchMode in FStates) then
|
||
SetTimer(Handle, StructureChangeTimer, FChangeDelay, nil)
|
||
else
|
||
DoStructureChange(Node, Reason);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.SuggestDropEffect(Source: TObject; Shift: TShiftState; const Pt: TPoint;
|
||
AllowedEffects: LongWord): LongWord;
|
||
|
||
// determines the drop action to take if the drag'n drop operation ends on this tree
|
||
// Note: Source can be any Delphi object not just a virtual tree
|
||
|
||
begin
|
||
Result := AllowedEffects;
|
||
|
||
// prefer MOVE if source and target are the same control, otherwise whatever is allowed as initial value
|
||
if Assigned(Source) and (Source = Self) then
|
||
if (AllowedEffects and DROPEFFECT_MOVE) <> 0 then
|
||
Result := DROPEFFECT_MOVE
|
||
else // no change
|
||
else
|
||
// drag between different applicatons
|
||
if (AllowedEffects and DROPEFFECT_COPY) <> 0 then
|
||
Result := DROPEFFECT_COPY;
|
||
|
||
// consider modifier keys and what is allowed at the moment, if none of the following conditions apply then
|
||
// the initial value just set is used
|
||
if ssCtrlOS in Shift then
|
||
begin
|
||
// copy or link
|
||
if ssShift in Shift then
|
||
begin
|
||
// link
|
||
if (AllowedEffects and DROPEFFECT_LINK) <> 0 then
|
||
Result := DROPEFFECT_LINK;
|
||
end
|
||
else
|
||
begin
|
||
// copy
|
||
if (AllowedEffects and DROPEFFECT_COPY) <> 0 then
|
||
Result := DROPEFFECT_COPY;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// move, link or default
|
||
if ssShift in Shift then
|
||
begin
|
||
// move
|
||
if (AllowedEffects and DROPEFFECT_MOVE) <> 0 then
|
||
Result := DROPEFFECT_MOVE;
|
||
end
|
||
else
|
||
begin
|
||
// link or default
|
||
if ssAlt in Shift then
|
||
begin
|
||
// link
|
||
if (AllowedEffects and DROPEFFECT_LINK) <> 0 then
|
||
Result := DROPEFFECT_LINK;
|
||
end;
|
||
// else default
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ToggleSelection(StartNode, EndNode: PVirtualNode);
|
||
|
||
// Switchs the selection state of a range of nodes.
|
||
// Note: This method is specifically designed to help selecting ranges with the keyboard and considers therefore
|
||
// the range anchor.
|
||
|
||
var
|
||
NodeFrom,
|
||
NodeTo: PVirtualNode;
|
||
NewSize: Integer;
|
||
Position: Integer;
|
||
|
||
begin
|
||
if not FSelectionLocked then
|
||
begin
|
||
Assert(Assigned(EndNode), 'EndNode must not be nil!');
|
||
if StartNode = nil then
|
||
StartNode := FRoot.FirstChild
|
||
else
|
||
if not FullyVisible[StartNode] then
|
||
StartNode := GetPreviousVisible(StartNode, True);
|
||
|
||
Position := CompareNodePositions(StartNode, EndNode);
|
||
// nothing to do if start and end node are the same
|
||
if Position <> 0 then
|
||
begin
|
||
if Position < 0 then
|
||
begin
|
||
NodeFrom := StartNode;
|
||
NodeTo := EndNode;
|
||
end
|
||
else
|
||
begin
|
||
NodeFrom := EndNode;
|
||
NodeTo := StartNode;
|
||
end;
|
||
|
||
ClearTempCache;
|
||
|
||
// 1) toggle the start node if it is before the range anchor
|
||
if CompareNodePositions(NodeFrom, FRangeAnchor) < 0 then
|
||
if not (vsSelected in NodeFrom.States) then
|
||
InternalCacheNode(NodeFrom)
|
||
else
|
||
InternalRemoveFromSelection(NodeFrom);
|
||
|
||
// 2) toggle all nodes within the range
|
||
NodeFrom := GetNextVisible(NodeFrom, True);
|
||
while NodeFrom <> NodeTo do
|
||
begin
|
||
if not (vsSelected in NodeFrom.States) then
|
||
InternalCacheNode(NodeFrom)
|
||
else
|
||
InternalRemoveFromSelection(NodeFrom);
|
||
NodeFrom := GetNextVisible(NodeFrom, True);
|
||
end;
|
||
|
||
// 3) toggle end node if it is after the range anchor
|
||
if CompareNodePositions(NodeFrom, FRangeAnchor) > 0 then
|
||
if not (vsSelected in NodeFrom.States) then
|
||
InternalCacheNode(NodeFrom)
|
||
else
|
||
InternalRemoveFromSelection(NodeFrom);
|
||
|
||
// Do some housekeeping if there was a change.
|
||
NewSize := PackArray(FSelection, FSelectionCount);
|
||
if NewSize > -1 then
|
||
begin
|
||
FSelectionCount := NewSize;
|
||
SetLength(FSelection, FSelectionCount);
|
||
end;
|
||
// If the range went over the anchor then we need to reselect it.
|
||
if not (vsSelected in FRangeAnchor.States) then
|
||
InternalCacheNode(FRangeAnchor);
|
||
if FTempNodeCount > 0 then
|
||
AddToSelection(FTempNodeCache, FTempNodeCount);
|
||
ClearTempCache;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UnselectNodes(StartNode, EndNode: PVirtualNode);
|
||
|
||
// Deselects a range of nodes.
|
||
// EndNode must be visible while StartNode must not as in the case where the last focused node is the start node
|
||
// but it is a child of a node which has been collapsed previously. In this case the first visible parent node
|
||
// is used as start node. StartNode can be nil in which case the very first node in the tree is used.
|
||
|
||
var
|
||
NodeFrom,
|
||
NodeTo: PVirtualNode;
|
||
NewSize: Integer;
|
||
|
||
begin
|
||
if not FSelectionLocked then
|
||
begin
|
||
Assert(Assigned(EndNode), 'EndNode must not be nil!');
|
||
|
||
if StartNode = nil then
|
||
StartNode := FRoot.FirstChild
|
||
else
|
||
if not FullyVisible[StartNode] then
|
||
begin
|
||
StartNode := GetPreviousVisible(StartNode, True);
|
||
if StartNode = nil then
|
||
StartNode := FRoot.FirstChild
|
||
end;
|
||
|
||
if CompareNodePositions(StartNode, EndNode) < 0 then
|
||
begin
|
||
NodeFrom := StartNode;
|
||
NodeTo := EndNode;
|
||
end
|
||
else
|
||
begin
|
||
NodeFrom := EndNode;
|
||
NodeTo := StartNode;
|
||
end;
|
||
|
||
while NodeFrom <> NodeTo do
|
||
begin
|
||
InternalRemoveFromSelection(NodeFrom);
|
||
NodeFrom := GetNextVisible(NodeFrom, True);
|
||
end;
|
||
// Deselect last node too.
|
||
InternalRemoveFromSelection(NodeFrom);
|
||
|
||
// Do some housekeeping.
|
||
NewSize := PackArray(FSelection, FSelectionCount);
|
||
if NewSize > -1 then
|
||
begin
|
||
FSelectionCount := NewSize;
|
||
SetLength(FSelection, FSelectionCount);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateCheckImageList;
|
||
begin
|
||
if FCheckImages <> FCustomCheckImages then
|
||
FCheckImages.Free;
|
||
FCheckImages := nil;
|
||
CheckImageListNeeded;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateColumnCheckState(Col: TVirtualTreeColumn);
|
||
|
||
begin
|
||
Col.CheckState := DetermineNextCheckState(Col.CheckType, Col.CheckState);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateDesigner;
|
||
|
||
var
|
||
ParentForm: TCustomForm;
|
||
|
||
begin
|
||
if (csDesigning in ComponentState) and not (csUpdating in ComponentState) then
|
||
begin
|
||
ParentForm := GetParentForm(Self);
|
||
if Assigned(ParentForm) and Assigned(ParentForm.Designer) then
|
||
ParentForm.Designer.Modified;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateHeaderRect;
|
||
|
||
// Calculates the rectangle the header occupies in non-client area.
|
||
// These coordinates are in window rectangle.
|
||
|
||
var
|
||
OffsetX,
|
||
OffsetY: Integer;
|
||
//EdgeSize: Integer;
|
||
Size: TSize;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintHeader],'UpdateHeaderRect');{$endif}
|
||
FHeaderRect := Rect(0, 0, Width, Height);
|
||
|
||
// Consider borders...
|
||
Size := GetBorderDimensions;
|
||
//lclheader
|
||
//Adjust rect size
|
||
Inc(FHeaderRect.Right,Size.cx*2);
|
||
|
||
// ... and bevels.
|
||
OffsetX := BorderWidth;
|
||
OffsetY := BorderWidth;
|
||
//todo_lcl
|
||
{
|
||
if BevelKind <> bkNone then
|
||
begin
|
||
EdgeSize := 0;
|
||
if BevelInner <> bvNone then
|
||
Inc(EdgeSize, BevelWidth);
|
||
if BevelOuter <> bvNone then
|
||
Inc(EdgeSize, BevelWidth);
|
||
if beLeft in BevelEdges then
|
||
Inc(OffsetX, EdgeSize);
|
||
if beTop in BevelEdges then
|
||
Inc(OffsetY, EdgeSize);
|
||
end;
|
||
}
|
||
InflateRect(FHeaderRect, -OffsetX, -OffsetY);
|
||
|
||
if hoVisible in FHeader.FOptions then
|
||
begin
|
||
if FHeaderRect.Left <= FHeaderRect.Right then
|
||
FHeaderRect.Bottom := FHeaderRect.Top + Integer(FHeader.FHeight)
|
||
else
|
||
FHeaderRect := Rect(0, 0, 0, 0);
|
||
end
|
||
else
|
||
FHeaderRect.Bottom := FHeaderRect.Top;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'FHeaderRect',FHeaderRect);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintHeader],'UpdateHeaderRect');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateEditBounds;
|
||
|
||
// Used to update the bounds of the current node editor if editing is currently active.
|
||
|
||
var
|
||
R: TRect;
|
||
Dummy: Integer;
|
||
CurrentAlignment: TAlignment;
|
||
CurrentBidiMode: TBidiMode;
|
||
|
||
begin
|
||
if tsEditing in FStates then
|
||
begin
|
||
if vsMultiline in FFocusedNode.States then
|
||
R := GetDisplayRect(FFocusedNode, FEditColumn, True, False)
|
||
else
|
||
R := GetDisplayRect(FFocusedNode, FEditColumn, True, True);
|
||
if (toGridExtensions in FOptions.FMiscOptions) then
|
||
begin
|
||
// Adjust edit bounds depending on alignment and bidi mode.
|
||
if FEditColumn <= NoColumn then
|
||
begin
|
||
CurrentAlignment := Alignment;
|
||
CurrentBidiMode := BiDiMode;
|
||
end
|
||
else
|
||
begin
|
||
CurrentAlignment := FHeader.Columns[FEditColumn].FAlignment;
|
||
CurrentBidiMode := FHeader.Columns[FEditColumn].FBidiMode;
|
||
end;
|
||
// Consider bidi mode here. In RTL context does left alignment actually mean right alignment and vice versa.
|
||
if CurrentBidiMode <> bdLeftToRight then
|
||
ChangeBiDiModeAlignment(CurrentAlignment);
|
||
if CurrentAlignment = taLeftJustify then
|
||
FHeader.Columns.GetColumnBounds(FEditColumn, Dummy, R.Right)
|
||
else
|
||
FHeader.Columns.GetColumnBounds(FEditColumn, R.Left, Dummy);
|
||
end;
|
||
if toShowHorzGridLines in TreeOptions.PaintOptions then
|
||
Dec(R.Bottom);
|
||
FEditLink.SetBounds(R);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
const
|
||
ScrollMasks: array[Boolean] of Cardinal = (0, SIF_DISABLENOSCROLL);
|
||
|
||
const // Region identifiers for GetRandomRgn
|
||
{
|
||
CLIPRGN = 1;
|
||
METARGN = 2;
|
||
APIRGN = 3;
|
||
}
|
||
SYSRGN = 4;
|
||
|
||
procedure TBaseVirtualTree.UpdateWindowAndDragImage(const Tree: TBaseVirtualTree; TreeRect: TRect; UpdateNCArea,
|
||
ReshowDragImage: Boolean);
|
||
|
||
// Method to repaint part of the window area which is not covered by the drag image and to initiate a recapture
|
||
// of the drag image.
|
||
// Note: This method must only be called during a drag operation and the tree passed in is the one managing the current
|
||
// drag image (so it is the actual drag source).
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
var
|
||
DragRegion, // the region representing the drag image
|
||
UpdateRegion, // the unclipped region within the tree to be updated
|
||
NCRegion: HRGN; // the region representing the non-client area of the tree
|
||
DragRect,
|
||
NCRect: TRect;
|
||
RedrawFlags: Cardinal;
|
||
|
||
VisibleTreeRegion: HRGN;
|
||
|
||
DC: HDC;
|
||
{$endif}
|
||
|
||
begin
|
||
//todo: reimplement
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
if IntersectRect(TreeRect, TreeRect, ClientRect) then
|
||
begin
|
||
// Retrieve the visible region of the window. This is important to avoid overpainting parts of other windows
|
||
// which overlap this one.
|
||
VisibleTreeRegion := CreateRectRgn(0, 0, 1, 1);
|
||
DC := GetDCEx(Handle, 0, DCX_CACHE or DCX_WINDOW or DCX_CLIPSIBLINGS or DCX_CLIPCHILDREN);
|
||
GetRandomRgn(DC, VisibleTreeRegion, SYSRGN);
|
||
ReleaseDC(Handle, DC);
|
||
|
||
// In Win9x the returned visible region is given in client coordinates. We need it in screen coordinates, though.
|
||
if not IsWinNT then
|
||
with ClientToScreen(Point(0, 0)) do
|
||
OffsetRgn(VisibleTreeRegion, X, Y);
|
||
|
||
// The drag image will figure out itself what part of the rectangle can be recaptured.
|
||
// Recapturing is not done by taking a snapshot of the screen, but by letting the tree draw itself
|
||
// into the back bitmap of the drag image. So the order here is unimportant.
|
||
Tree.FDragImage.RecaptureBackground(Self, TreeRect, VisibleTreeRegion, UpdateNCArea, ReshowDragImage);
|
||
|
||
// Calculate the screen area not covered by the drag image and which needs an update.
|
||
DragRect := Tree.FDragImage.GetDragImageRect;
|
||
MapWindowPoints(0, Handle, DragRect, 2);
|
||
DragRegion := CreateRectRgnIndirect(DragRect);
|
||
|
||
// Start with non-client area if requested.
|
||
if UpdateNCArea then
|
||
begin
|
||
// Compute the part of the non-client area which must be updated.
|
||
|
||
// Determine the outer rectangle of the entire tree window.
|
||
GetWindowRect(Handle, NCRect);
|
||
// Express the tree window rectangle in client coordinates (because RedrawWindow wants them so).
|
||
MapWindowPoints(0, Handle, NCRect, 2);
|
||
NCRegion := CreateRectRgnIndirect(NCRect);
|
||
// Determine client rect in screen coordinates and create another region for it.
|
||
UpdateRegion := CreateRectRgnIndirect(ClientRect);
|
||
// Create a region which only contains the NC part by subtracting out the client area.
|
||
CombineRgn(NCRegion, NCRegion, UpdateRegion, RGN_DIFF);
|
||
// Subtract also out what is hidden by the drag image.
|
||
CombineRgn(NCRegion, NCRegion, DragRegion, RGN_DIFF);
|
||
RedrawWindow(Handle, nil, NCRegion, RDW_FRAME or RDW_NOERASE or RDW_NOCHILDREN or RDW_INVALIDATE or RDW_VALIDATE or
|
||
RDW_UPDATENOW);
|
||
DeleteObject(NCRegion);
|
||
DeleteObject(UpdateRegion);
|
||
end;
|
||
|
||
UpdateRegion := CreateRectRgnIndirect(TreeRect);
|
||
RedrawFlags := RDW_INVALIDATE or RDW_VALIDATE or RDW_UPDATENOW or RDW_NOERASE or RDW_NOCHILDREN;
|
||
// Remove the part of the update region which is covered by the drag image.
|
||
CombineRgn(UpdateRegion, UpdateRegion, DragRegion, RGN_DIFF);
|
||
RedrawWindow(Handle, nil, UpdateRegion, RedrawFlags);
|
||
DeleteObject(UpdateRegion);
|
||
DeleteObject(DragRegion);
|
||
DeleteObject(VisibleTreeRegion);
|
||
end;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ValidateCache;
|
||
|
||
// Starts cache validation if not already done by adding this instance to the worker thread's waiter list
|
||
// (if not already there) and signalling the thread it can start validating.
|
||
|
||
begin
|
||
// Wait for thread to stop validation if it is currently validating this tree's cache.
|
||
InterruptValidation;
|
||
|
||
FStartIndex := 0;
|
||
{$ifdef EnableThreadSupport}
|
||
if tsValidationNeeded in FStates then
|
||
begin
|
||
// Tell the thread this tree needs actually something to do.
|
||
WorkerThread.AddTree(Self);
|
||
WorkEvent.SetEvent;
|
||
end;
|
||
{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ValidateNodeDataSize(var Size: Integer);
|
||
|
||
begin
|
||
Size := 0;
|
||
if Assigned(FOnGetNodeDataSize) then
|
||
FOnGetNodeDataSize(Self, Size);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WndProc(var Message: TLMessage);
|
||
|
||
var
|
||
Handled: Boolean;
|
||
|
||
begin
|
||
Handled := False;
|
||
|
||
// Try the header whether it needs to take this message.
|
||
if Assigned(FHeader) and (FHeader.FStates <> []) then
|
||
Handled := FHeader.HandleMessage(Message);
|
||
if not Handled then
|
||
begin
|
||
// For auto drag mode, let tree handle itself, instead of TControl.
|
||
if not (csDesigning in ComponentState) and
|
||
((Message.Msg = LM_LBUTTONDOWN) or (Message.Msg = LM_LBUTTONDBLCLK)) then
|
||
begin
|
||
//lclheader
|
||
//when FHeader.FStates = [] it comes until here unlike Delphi (uses NC messages)
|
||
//skip this code when is clicked inside the header
|
||
if (DragMode = dmAutomatic) and (DragKind = dkDrag) and
|
||
not FHeader.InHeader(SmallPointToPoint(TLMMouse(Message).Pos)) then
|
||
begin
|
||
if IsControlMouseMsg(TLMMouse(Message)) then
|
||
Handled := True;
|
||
if not Handled then
|
||
begin
|
||
ControlState := ControlState + [csLButtonDown];
|
||
Dispatch(Message); // overrides TControl's BeginDrag
|
||
Handled := True;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if not Handled and Assigned(FHeader) then
|
||
Handled := FHeader.HandleMessage(Message);
|
||
|
||
if not Handled then
|
||
begin
|
||
//lcl: probably not necessary
|
||
//if (Message.Msg in [WM_NCLBUTTONDOWN, WM_NCRBUTTONDOWN, WM_NCMBUTTONDOWN]) and not Focused and CanFocus then
|
||
// SetFocus;
|
||
inherited;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WriteChunks(Stream: TStream; Node: PVirtualNode);
|
||
|
||
// Writes the core chunks for Node into the stream.
|
||
// Note: descendants can optionally override this method to add other node specific chunks.
|
||
// Keep in mind that this method is also called for the root node. Using this fact in descendants you can
|
||
// create a kind of "global" chunks not directly bound to a specific node.
|
||
|
||
var
|
||
Header: TChunkHeader;
|
||
LastPosition,
|
||
ChunkSize: Integer;
|
||
Chunk: TBaseChunk;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
with Stream do
|
||
begin
|
||
// 1. The base chunk...
|
||
LastPosition := Position;
|
||
Chunk.Header.ChunkType := BaseChunk;
|
||
with Node^, Chunk do
|
||
begin
|
||
Body.ChildCount := ChildCount;
|
||
Body.NodeHeight := NodeHeight;
|
||
// Some states are only temporary so take them out as they make no sense at the new location.
|
||
Body.States := States - [vsChecking, vsCutOrCopy, vsDeleting, vsInitialUserData, vsHeightMeasured];
|
||
Body.Align := Align;
|
||
Body.CheckState := CheckState;
|
||
Body.CheckType := CheckType;
|
||
Body.Reserved := 0;
|
||
end;
|
||
// write the base chunk
|
||
Write(Chunk, SizeOf(Chunk));
|
||
|
||
// 2. ... directly followed by the child node chunks (actually they are child chunks of
|
||
// the base chunk)
|
||
if vsInitialized in Node.States then
|
||
begin
|
||
Run := Node.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
WriteNode(Stream, Run);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
FinishChunkHeader(Stream, LastPosition, Position);
|
||
|
||
// 3. write user data
|
||
LastPosition := Position;
|
||
Header.ChunkType := UserChunk;
|
||
Write(Header, SizeOf(Header));
|
||
DoSaveUserData(Node, Stream);
|
||
// check if the application actually wrote data
|
||
ChunkSize := Position - LastPosition - SizeOf(TChunkHeader);
|
||
// seek back to start of chunk if nothing has been written
|
||
if ChunkSize = 0 then
|
||
begin
|
||
Position := LastPosition;
|
||
Size := Size - SizeOf(Header);
|
||
end
|
||
else
|
||
FinishChunkHeader(Stream, LastPosition, Position);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.WriteNode(Stream: TStream; Node: PVirtualNode);
|
||
|
||
// Writes the "cover" chunk for Node to Stream and initiates writing child nodes and chunks.
|
||
|
||
var
|
||
LastPosition: Integer;
|
||
Header: TChunkHeader;
|
||
|
||
begin
|
||
// Initialize the node first if necessary and wanted.
|
||
if toInitOnSave in FOptions.FMiscOptions then
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then
|
||
InitChildren(Node);
|
||
end;
|
||
|
||
with Stream do
|
||
begin
|
||
LastPosition := Position;
|
||
// Emit the anchor chunk.
|
||
Header.ChunkType := NodeChunk;
|
||
Write(Header, SizeOf(Header));
|
||
// Write other chunks to stream taking their size into this chunk's size.
|
||
WriteChunks(Stream, Node);
|
||
|
||
// Update chunk size.
|
||
FinishChunkHeader(Stream, LastPosition, Position);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.AbsoluteIndex(Node: PVirtualNode): Cardinal;
|
||
|
||
begin
|
||
Result := 0;
|
||
while Assigned(Node) and (Node <> FRoot) do
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
if Assigned(Node.PrevSibling) then
|
||
begin
|
||
// if there's a previous sibling then add its total count to the result
|
||
Node := Node.PrevSibling;
|
||
Inc(Result, Node.TotalCount);
|
||
end
|
||
else
|
||
begin
|
||
Node := Node.Parent;
|
||
if Node <> FRoot then
|
||
Inc(Result);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode;
|
||
|
||
// Adds a new node to the given parent node. This is simply done by increasing the child count of the
|
||
// parent node. If Parent is nil then the new node is added as (last) top level node.
|
||
// UserData can be used to set the first 4 bytes of the user data area to an initial value which can be used
|
||
// in OnInitNode and will also cause to trigger the OnFreeNode event (if <> nil) even if the node is not yet
|
||
// "officially" initialized.
|
||
// AddChild is a compatibility method and will implicitly validate the parent node. This is however
|
||
// against the virtual paradigm and hence I dissuade from its usage.
|
||
|
||
var
|
||
NodeData: ^Pointer;
|
||
|
||
begin
|
||
if not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
CancelEditNode;
|
||
|
||
if Parent = nil then
|
||
Parent := FRoot;
|
||
if not (vsInitialized in Parent.States) then
|
||
InitNode(Parent);
|
||
|
||
// Locally stop updates of the tree in order to avoid usage of the new node before it is correctly set up.
|
||
// If the update count was 0 on enter then there will be a correct update at the end of this method.
|
||
Inc(FUpdateCount);
|
||
try
|
||
SetChildCount(Parent, Parent.ChildCount + 1);
|
||
// Update the hidden children flag of the parent. Nodes are added as being visible by default.
|
||
Exclude(Parent.States, vsAllChildrenHidden);
|
||
finally
|
||
Dec(FUpdateCount);
|
||
end;
|
||
Result := Parent.LastChild;
|
||
|
||
// Check if there is initial user data and there is also enough user data space allocated.
|
||
if Assigned(UserData) then
|
||
if FNodeDataSize >= 4 then
|
||
begin
|
||
NodeData := Pointer(PByte(@Result.Data) + FTotalInternalDataSize);
|
||
NodeData^ := UserData;
|
||
Include(Result.States, vsInitialUserData);
|
||
end
|
||
else
|
||
ShowError(SCannotSetUserData, hcTFCannotSetUserData);
|
||
|
||
InvalidateCache;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
if tsStructureChangePending in FStates then
|
||
begin
|
||
if Parent = FRoot then
|
||
StructureChange(nil, crChildAdded)
|
||
else
|
||
StructureChange(Parent, crChildAdded);
|
||
end;
|
||
|
||
if (toAutoSort in FOptions.FAutoOptions) and (FHeader.FSortColumn > InvalidColumn) then
|
||
Sort(Parent, FHeader.FSortColumn, FHeader.FSortDirection, True);
|
||
|
||
InvalidateToBottom(Parent);
|
||
//lcl
|
||
//Calling UpdateHorizontalScrollBar without a header leads to a
|
||
//wrong NodeWidth because the node is not initialized at this time.
|
||
//As result the horizontal scrollbar is not correctly
|
||
//sized and the node can not be selected by a click.
|
||
if HandleAllocated then
|
||
UpdateVerticalScrollBar(True)
|
||
end;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AddFromStream(Stream: TStream; TargetNode: PVirtualNode);
|
||
|
||
// loads nodes from the given stream and adds them to TargetNode
|
||
// the current content is not cleared before the load process starts (see also LoadFromStream)
|
||
|
||
var
|
||
ThisID: TMagicID;
|
||
Version,
|
||
Count: Cardinal;
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
if not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
// check first whether this is a stream we can read
|
||
Stream.ReadBuffer(ThisID, SizeOf(TMagicID));
|
||
if (ThisID[0] = MagicID[0]) and
|
||
(ThisID[1] = MagicID[1]) and
|
||
(ThisID[2] = MagicID[2]) and
|
||
(ThisID[5] = MagicID[5]) then
|
||
begin
|
||
Version := Word(ThisID[3]);
|
||
if Version <= VTTreeStreamVersion then
|
||
begin
|
||
BeginUpdate;
|
||
try
|
||
if Version < 2 then
|
||
Count := MaxInt
|
||
else
|
||
Stream.ReadBuffer(Count, SizeOf(Count));
|
||
|
||
while (Stream.Position < Stream.Size) and (Count > 0) do
|
||
begin
|
||
Dec(Count);
|
||
Node := MakeNewNode;
|
||
InternalConnectNode(Node, TargetNode, Self, amAddChildLast);
|
||
InternalAddFromStream(Stream, Version, Node);
|
||
end;
|
||
if TargetNode = FRoot then
|
||
DoNodeCopied(nil)
|
||
else
|
||
DoNodeCopied(TargetNode);
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end
|
||
else
|
||
ShowError(SWrongStreamVersion, hcTFWrongStreamVersion);
|
||
end
|
||
else
|
||
ShowError(SWrongStreamVersion, hcTFWrongStreamVersion);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.AfterConstruction;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
if FRoot = nil then
|
||
InitRootNode;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Assign(Source: TPersistent);
|
||
|
||
begin
|
||
if (Source is TBaseVirtualTree) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
with Source as TBaseVirtualTree do
|
||
begin
|
||
Self.Align := Align;
|
||
Self.Anchors := Anchors;
|
||
Self.AutoScrollDelay := AutoScrollDelay;
|
||
Self.AutoScrollInterval := AutoScrollInterval;
|
||
Self.AutoSize := AutoSize;
|
||
Self.Background := Background;
|
||
//todo_lcl
|
||
{
|
||
Self.BevelEdges := BevelEdges;
|
||
Self.BevelInner := BevelInner;
|
||
Self.BevelKind := BevelKind;
|
||
Self.BevelOuter := BevelOuter;
|
||
Self.BevelWidth := BevelWidth;
|
||
}
|
||
Self.BiDiMode := BiDiMode;
|
||
Self.BorderStyle := BorderStyle;
|
||
Self.BorderWidth := BorderWidth;
|
||
Self.ChangeDelay := ChangeDelay;
|
||
Self.CheckImageKind := CheckImageKind;
|
||
Self.Color := Color;
|
||
Self.Colors.Assign(Colors);
|
||
Self.Constraints.Assign(Constraints);
|
||
Self.DefaultNodeHeight := DefaultNodeHeight;
|
||
Self.DefaultPasteMode := DefaultPasteMode;
|
||
Self.DragCursor := DragCursor;
|
||
Self.DragImageKind := DragImageKind;
|
||
Self.DragKind := DragKind;
|
||
Self.DragMode := DragMode;
|
||
Self.Enabled := Enabled;
|
||
Self.Font := Font;
|
||
Self.Header := Header;
|
||
Self.HintMode := HintMode;
|
||
Self.HotCursor := HotCursor;
|
||
Self.Images := Images;
|
||
//Self.ImeMode := ImeMode;
|
||
//Self.ImeName := ImeName;
|
||
Self.Indent := Indent;
|
||
Self.Margin := Margin;
|
||
Self.NodeAlignment := NodeAlignment;
|
||
Self.NodeDataSize := NodeDataSize;
|
||
Self.TreeOptions := TreeOptions;
|
||
//Self.ParentBiDiMode := ParentBiDiMode;
|
||
Self.ParentColor := ParentColor;
|
||
Self.ParentFont := ParentFont;
|
||
Self.ParentShowHint := ParentShowHint;
|
||
Self.PopupMenu := PopupMenu;
|
||
Self.RootNodeCount := RootNodeCount;
|
||
Self.ScrollBarOptions := ScrollBarOptions;
|
||
Self.ShowHint := ShowHint;
|
||
Self.StateImages := StateImages;
|
||
Self.TabOrder := TabOrder;
|
||
Self.TabStop := TabStop;
|
||
Self.Visible := Visible;
|
||
Self.SelectionCurveRadius := SelectionCurveRadius;
|
||
Self.SelectionBlendFactor := SelectionBlendFactor;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.BeginDrag(Immediate: Boolean; Threshold: Integer);
|
||
|
||
// Reintroduced method to allow to start OLE drag'n drop as well as VCL drag'n drop.
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcDrag],'BeginDrag');{$endif}
|
||
if FDragType = dtVCL then
|
||
begin
|
||
DoStateChange([tsVCLDragPending]);
|
||
inherited;
|
||
end
|
||
else
|
||
if (FStates * [tsOLEDragPending, tsOLEDragging]) = [] then
|
||
begin
|
||
// Drag start position has already been recorded in WMMouseDown.
|
||
if Threshold < 0 then
|
||
FDragThreshold := DragManager.DragThreshold
|
||
else
|
||
FDragThreshold := Threshold;
|
||
if Immediate then
|
||
DoDragging(FLastClickPos)
|
||
else
|
||
DoStateChange([tsOLEDragPending]);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcDrag],'BeginDrag');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.BeginSynch;
|
||
|
||
// Starts the synchronous update mode (if not already active).
|
||
|
||
begin
|
||
if not (csDestroying in ComponentState) then
|
||
begin
|
||
if FSynchUpdateCount = 0 then
|
||
begin
|
||
DoUpdating(usBeginSynch);
|
||
|
||
// Stop all timers...
|
||
KillTimer(Handle, ChangeTimer);
|
||
KillTimer(Handle, StructureChangeTimer);
|
||
KillTimer(Handle, ExpandTimer);
|
||
KillTimer(Handle, EditTimer);
|
||
KillTimer(Handle, ScrollTimer);
|
||
KillTimer(Handle, SearchTimer);
|
||
FSearchBuffer := '';
|
||
FLastSearchNode := nil;
|
||
DoStateChange([], [tsEditPending, tsScrollPending, tsScrolling, tsIncrementalSearching]);
|
||
|
||
// ...and trigger pending update states.
|
||
if tsStructureChangePending in FStates then
|
||
DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason);
|
||
if tsChangePending in FStates then
|
||
DoChange(FLastChangedNode);
|
||
end
|
||
else
|
||
DoUpdating(usSynch);
|
||
end;
|
||
Inc(FSynchUpdateCount);
|
||
DoStateChange([tsSynchMode]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.BeginUpdate;
|
||
|
||
begin
|
||
if not (csDestroying in ComponentState) then
|
||
begin
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
DoUpdating(usBegin);
|
||
SetUpdateState(True);
|
||
end
|
||
else
|
||
DoUpdating(usUpdate);
|
||
end;
|
||
Inc(FUpdateCount);
|
||
DoStateChange([tsUpdating]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CancelCutOrCopy;
|
||
|
||
// Resets nodes which are marked as being cut.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if ([tsCutPending, tsCopyPending] * FStates) <> [] then
|
||
begin
|
||
Run := FRoot.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsCutOrCopy in Run.States then
|
||
Exclude(Run.States, vsCutOrCopy);
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
end;
|
||
DoStateChange([], [tsCutPending, tsCopyPending]);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CancelEditNode: Boolean;
|
||
|
||
// Called by the application or the current edit link to cancel the edit action.
|
||
|
||
begin
|
||
if HandleAllocated and ([tsEditing, tsEditPending] * FStates <> []) then
|
||
Result := DoCancelEdit
|
||
else
|
||
Result := True;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CancelOperation;
|
||
|
||
// Called by the application to cancel a long-running operation.
|
||
|
||
begin
|
||
if FOperationCount > 0 then
|
||
FOperationCanceled := True;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CanEdit(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
||
|
||
// Returns True if the given node can be edited.
|
||
|
||
begin
|
||
Result := (toEditable in FOptions.FMiscOptions) and Enabled and not (toReadOnly in FOptions.FMiscOptions);
|
||
DoCanEdit(Node, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Clear;
|
||
|
||
begin
|
||
if not (toReadOnly in FOptions.FMiscOptions) or (csDestroying in ComponentState) then
|
||
begin
|
||
BeginUpdate;
|
||
try
|
||
InterruptValidation;
|
||
if IsEditing then
|
||
CancelEditNode;
|
||
|
||
if ClipboardStates * FStates <> [] then
|
||
begin
|
||
OleSetClipBoard(nil);
|
||
DoStateChange([], ClipboardStates);
|
||
end;
|
||
ClearSelection;
|
||
FFocusedNode := nil;
|
||
FLastSelected := nil;
|
||
FCurrentHotNode := nil;
|
||
FDropTargetNode := nil;
|
||
FLastChangedNode := nil;
|
||
FRangeAnchor := nil;
|
||
FCheckNode := nil;
|
||
FLastVCLDragTarget := nil;
|
||
FLastSearchNode := nil;
|
||
DeleteChildren(FRoot, True);
|
||
FOffsetX := 0;
|
||
FOffsetY := 0;
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ClearChecked;
|
||
|
||
var
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
Node := RootNode.FirstChild;
|
||
while Assigned(Node) do
|
||
begin
|
||
if Node.CheckState <> csUncheckedNormal then
|
||
CheckState[Node] := csUncheckedNormal;
|
||
Node := GetNextNoInit(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ClearSelection;
|
||
|
||
var
|
||
Node: PVirtualNode;
|
||
R: TRect;
|
||
Counter: Integer;
|
||
|
||
begin
|
||
if not FSelectionLocked and (FSelectionCount > 0) and not (csDestroying in ComponentState) then
|
||
begin
|
||
if (FUpdateCount = 0) and HandleAllocated and (FVisibleCount > 0) then
|
||
begin
|
||
// Iterate through nodes currently visible in the client area and invalidate them.
|
||
Node := TopNode;
|
||
if Assigned(Node) then
|
||
R := GetDisplayRect(Node, NoColumn, False);
|
||
Counter := FSelectionCount;
|
||
|
||
while Assigned(Node) do
|
||
begin
|
||
R.Bottom := R.Top + Integer(NodeHeight[Node]);
|
||
if vsSelected in Node.States then
|
||
begin
|
||
InvalidateRect(Handle, @R, False);
|
||
Dec(Counter);
|
||
// Only try as many nodes as are selected.
|
||
if Counter = 0 then
|
||
Break;
|
||
end;
|
||
R.Top := R.Bottom;
|
||
if R.Top > ClientHeight then
|
||
Break;
|
||
Node := GetNextVisibleNoInit(Node, True);
|
||
end;
|
||
end;
|
||
|
||
InternalClearSelection;
|
||
Change(nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CopyTo(Source: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode;
|
||
ChildrenOnly: Boolean): PVirtualNode;
|
||
|
||
// A simplified CopyTo method to allow to copy nodes to the root of another tree.
|
||
|
||
begin
|
||
Result := CopyTo(Source, Tree.FRoot, Mode, ChildrenOnly);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.CopyTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode;
|
||
ChildrenOnly: Boolean): PVirtualNode;
|
||
|
||
// Copies Source and all its child nodes to Target.
|
||
// Mode is used to specify further where to add the new node actually (as sibling of Target or as child of Target).
|
||
// Result is the newly created node to which source has been copied if ChildrenOnly is False or just contains Target
|
||
// in the other case.
|
||
// ChildrenOnly determines whether to copy also the source node or only its child nodes.
|
||
|
||
var
|
||
TargetTree: TBaseVirtualTree;
|
||
Stream: TMemoryStream;
|
||
|
||
begin
|
||
Assert(TreeFromNode(Source) = Self, 'The source tree must contain the source node.');
|
||
|
||
Result := nil;
|
||
if (Mode <> amNoWhere) and Assigned(Source) and (Source <> FRoot) then
|
||
begin
|
||
// Assume that an empty destination means the root in this (the source) tree.
|
||
if Target = nil then
|
||
begin
|
||
TargetTree := Self;
|
||
Target := FRoot;
|
||
Mode := amAddChildFirst;
|
||
end
|
||
else
|
||
TargetTree := TreeFromNode(Target);
|
||
|
||
if not (toReadOnly in TargetTree.FOptions.FMiscOptions) then
|
||
begin
|
||
if Target = TargetTree.FRoot then
|
||
begin
|
||
case Mode of
|
||
amInsertBefore:
|
||
Mode := amAddChildFirst;
|
||
amInsertAfter:
|
||
Mode := amAddChildLast;
|
||
end;
|
||
end;
|
||
|
||
Stream := TMemoryStream.Create;
|
||
try
|
||
// Write all nodes into a temprary stream depending on the ChildrenOnly flag.
|
||
if not ChildrenOnly then
|
||
WriteNode(Stream, Source)
|
||
else
|
||
begin
|
||
Source := Source.FirstChild;
|
||
while Assigned(Source) do
|
||
begin
|
||
WriteNode(Stream, Source);
|
||
Source := Source.NextSibling;
|
||
end;
|
||
end;
|
||
// Now load the serialized nodes into the target node (tree).
|
||
TargetTree.BeginUpdate;
|
||
try
|
||
Stream.Position := 0;
|
||
while Stream.Position < Stream.Size do
|
||
begin
|
||
Result := TargetTree.MakeNewNode;
|
||
InternalConnectNode(Result, Target, TargetTree, Mode);
|
||
TargetTree.InternalAddFromStream(Stream, VTTreeStreamVersion, Result);
|
||
if not DoNodeCopying(Result, Target) then
|
||
begin
|
||
TargetTree.DeleteNode(Result);
|
||
Result := nil;
|
||
end
|
||
else
|
||
DoNodeCopied(Result);
|
||
end;
|
||
if ChildrenOnly then
|
||
Result := Target;
|
||
finally
|
||
TargetTree.EndUpdate;
|
||
end;
|
||
finally
|
||
Stream.Free;
|
||
end;
|
||
|
||
with TargetTree do
|
||
begin
|
||
InvalidateCache;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
UpdateScrollBars(True);
|
||
Invalidate;
|
||
end;
|
||
StructureChange(Source, crNodeCopied);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CopyToClipBoard;
|
||
|
||
var
|
||
DataObject: IDataObject;
|
||
|
||
begin
|
||
if FSelectionCount > 0 then
|
||
begin
|
||
DataObject := TVTDataObject.Create(Self, True) as IDataObject;
|
||
if OleSetClipBoard(DataObject) = S_OK then
|
||
begin
|
||
MarkCutCopyNodes;
|
||
DoStateChange([tsCopyPending]);
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.CutToClipBoard;
|
||
|
||
var
|
||
DataObject: IDataObject;
|
||
|
||
begin
|
||
if (FSelectionCount > 0) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
DataObject := TVTDataObject.Create(Self, True) as IDataObject;
|
||
if OleSetClipBoard(DataObject) = S_OK then
|
||
begin
|
||
MarkCutCopyNodes;
|
||
DoStateChange([tsCutPending], [tsCopyPending]);
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DefaultHandler(var AMessage);
|
||
begin
|
||
//used to avoid default handler of LM_MOUSEWHEEL
|
||
end;
|
||
|
||
|
||
procedure TBaseVirtualTree.DeleteChildren(Node: PVirtualNode; ResetHasChildren: Boolean = False);
|
||
|
||
// Removes all children and their children from memory without changing the vsHasChildren style by default.
|
||
|
||
var
|
||
Run,
|
||
Mark: PVirtualNode;
|
||
LastTop,
|
||
LastLeft,
|
||
NewSize: Integer;
|
||
ParentVisible: Boolean;
|
||
|
||
begin
|
||
if (Node.ChildCount > 0) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
Assert(not (tsIterating in FStates), 'Deleting nodes during tree iteration leads to invalid pointers.');
|
||
|
||
// The code below uses some flags for speed improvements which may cause invalid pointers if updates of
|
||
// the tree happen. Hence switch updates off until we have finished the operation.
|
||
Inc(FUpdateCount);
|
||
try
|
||
InterruptValidation;
|
||
LastLeft := -FEffectiveOffsetX;
|
||
LastTop := FOffsetY;
|
||
|
||
// Make a local copy of the visibility state of this node to speed up
|
||
// adjusting the visible nodes count.
|
||
ParentVisible := Node = FRoot;
|
||
if not ParentVisible then
|
||
ParentVisible := FullyVisible[Node] and (vsExpanded in Node.States);
|
||
|
||
// Show that we are clearing the child list, to avoid registering structure change events.
|
||
Include(Node.States, vsClearing);
|
||
Run := Node.LastChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
if ParentVisible and (vsVisible in Run.States) then
|
||
Dec(FVisibleCount);
|
||
|
||
Include(Run.States, vsDeleting);
|
||
Mark := Run;
|
||
Run := Run.PrevSibling;
|
||
// Important, to avoid exchange of invalid pointers while disconnecting the node.
|
||
if Assigned(Run) then
|
||
Run.NextSibling := nil;
|
||
DeleteNode(Mark);
|
||
end;
|
||
Exclude(Node.States, vsClearing);
|
||
if ResetHasChildren then
|
||
Exclude(Node.States, vsHasChildren);
|
||
if Node <> FRoot then
|
||
Exclude(Node.States, vsExpanded);
|
||
Node.ChildCount := 0;
|
||
if (Node = FRoot) or (vsDeleting in Node.States) then
|
||
begin
|
||
Node.TotalHeight := FDefaultNodeHeight + NodeHeight[Node];
|
||
Node.TotalCount := 1;
|
||
end
|
||
else
|
||
begin
|
||
AdjustTotalHeight(Node, NodeHeight[Node]);
|
||
AdjustTotalCount(Node, 1);
|
||
end;
|
||
Node.FirstChild := nil;
|
||
Node.LastChild := nil;
|
||
finally
|
||
Dec(FUpdateCount);
|
||
end;
|
||
|
||
InvalidateCache;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
NewSize := PackArray(FSelection, FSelectionCount);
|
||
if NewSize > -1 then
|
||
begin
|
||
FSelectionCount := NewSize;
|
||
SetLength(FSelection, FSelectionCount);
|
||
end;
|
||
|
||
ValidateCache;
|
||
UpdateScrollbars(True);
|
||
// Invalidate entire tree if it scrolled e.g. to make the last node also the
|
||
// bottom node in the treeview.
|
||
if (LastLeft <> FOffsetX) or (LastTop <> FOffsetY) then
|
||
Invalidate
|
||
else
|
||
InvalidateToBottom(Node);
|
||
end;
|
||
StructureChange(Node, crChildDeleted);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DeleteNode(Node: PVirtualNode; Reindex: Boolean = True);
|
||
|
||
var
|
||
LastTop,
|
||
LastLeft: Integer;
|
||
LastParent: PVirtualNode;
|
||
WasInSynchMode: Boolean;
|
||
ParentClearing: Boolean;
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
Assert(not (tsIterating in FStates), 'Deleting nodes during tree iteration leads to invalid pointers.');
|
||
|
||
// Determine parent node for structure change notification.
|
||
ParentClearing := vsClearing in Node.Parent.States;
|
||
LastParent := Node.Parent;
|
||
|
||
if not ParentClearing then
|
||
begin
|
||
if LastParent = FRoot then
|
||
StructureChange(nil, crChildDeleted)
|
||
else
|
||
StructureChange(LastParent, crChildDeleted);
|
||
end;
|
||
|
||
LastLeft := -FEffectiveOffsetX;
|
||
LastTop := FOffsetY;
|
||
|
||
if vsSelected in Node.States then
|
||
begin
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
// Go temporarily into sync mode to avoid a delayed change event for the node
|
||
// when unselecting.
|
||
WasInSynchMode := tsSynchMode in FStates;
|
||
Include(FStates, tsSynchMode);
|
||
RemoveFromSelection(Node);
|
||
if not WasInSynchMode then
|
||
Exclude(FStates, tsSynchMode);
|
||
InvalidateToBottom(LastParent);
|
||
end
|
||
else
|
||
InternalRemoveFromSelection(Node);
|
||
end
|
||
else
|
||
InvalidateToBottom(LastParent);
|
||
|
||
if tsHint in FStates then
|
||
begin
|
||
Application.CancelHint;
|
||
DoStateChange([], [tsHint]);
|
||
end;
|
||
|
||
DeleteChildren(Node);
|
||
InternalDisconnectNode(Node, False, Reindex);
|
||
DoFreeNode(Node);
|
||
|
||
if not ParentClearing then
|
||
begin
|
||
DetermineHiddenChildrenFlag(LastParent);
|
||
InvalidateCache;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
UpdateScrollbars(True);
|
||
// Invalidate entire tree if it scrolled e.g. to make the last node also the
|
||
// bottom node in the treeview.
|
||
if (LastLeft <> FOffsetX) or (LastTop <> FOffsetY) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.DeleteSelectedNodes;
|
||
|
||
// Deletes all currently selected nodes (including their child nodes).
|
||
|
||
var
|
||
Nodes: TNodeArray;
|
||
I: Integer;
|
||
LevelChange: Boolean;
|
||
|
||
begin
|
||
Nodes := nil;
|
||
if (FSelectionCount > 0) and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
BeginUpdate;
|
||
try
|
||
Nodes := GetSortedSelection(True);
|
||
for I := High(Nodes) downto 1 do
|
||
begin
|
||
LevelChange := Nodes[I].Parent <> Nodes[I - 1].Parent;
|
||
DeleteNode(Nodes[I], LevelChange);
|
||
end;
|
||
DeleteNode(Nodes[0]);
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.Dragging: Boolean;
|
||
|
||
begin
|
||
// Check for both OLE drag'n drop as well as VCL drag'n drop.
|
||
Result := ([tsOLEDragPending, tsOLEDragging] * FStates <> []) or inherited Dragging;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.EditNode(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
||
|
||
// Application triggered edit event for the given node.
|
||
// Returns True if the tree started editing otherwise False.
|
||
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil.');
|
||
Assert((Column > InvalidColumn) and (Column < FHeader.Columns.Count),
|
||
'Column must be a valid column index (-1 if no header is shown).');
|
||
|
||
Result := tsEditing in FStates;
|
||
// If the tree is already editing then we don't disrupt this.
|
||
if not Result and not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
FocusedNode := Node;
|
||
if Assigned(FFocusedNode) and (Node = FFocusedNode) and CanEdit(FFocusedNode, Column) then
|
||
begin
|
||
FEditColumn := Column;
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
DoEdit;
|
||
Result := tsEditing in FStates;
|
||
end
|
||
else
|
||
Result := False;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.EndEditNode: Boolean;
|
||
|
||
// Called to finish a current edit action or stop the edit timer if an edit operation is pending.
|
||
|
||
begin
|
||
if [tsEditing, tsEditPending] * FStates <> [] then
|
||
Result := DoEndEdit
|
||
else
|
||
Result := True;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.EndSynch;
|
||
|
||
begin
|
||
if FSynchUpdateCount > 0 then
|
||
Dec(FSynchUpdateCount);
|
||
|
||
if not (csDestroying in ComponentState) then
|
||
begin
|
||
if FSynchUpdateCount = 0 then
|
||
begin
|
||
DoStateChange([], [tsSynchMode]);
|
||
DoUpdating(usEndSynch);
|
||
end
|
||
else
|
||
DoUpdating(usSynch);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.EndUpdate;
|
||
|
||
var
|
||
NewSize: Integer;
|
||
|
||
begin
|
||
if FUpdateCount > 0 then
|
||
Dec(FUpdateCount);
|
||
|
||
if not (csDestroying in ComponentState) then
|
||
begin
|
||
if (FUpdateCount = 0) and (tsUpdating in FStates) then
|
||
begin
|
||
if tsUpdateHiddenChildrenNeeded in FStates then
|
||
begin
|
||
DetermineHiddenChildrenFlagAllNodes;
|
||
Exclude(FStates, tsUpdateHiddenChildrenNeeded);
|
||
end;
|
||
|
||
DoStateChange([], [tsUpdating]);
|
||
|
||
NewSize := PackArray(FSelection, FSelectionCount);
|
||
if NewSize > -1 then
|
||
begin
|
||
FSelectionCount := NewSize;
|
||
SetLength(FSelection, FSelectionCount);
|
||
end;
|
||
|
||
InvalidateCache;
|
||
ValidateCache;
|
||
if HandleAllocated then
|
||
UpdateScrollBars(False);
|
||
|
||
if tsStructureChangePending in FStates then
|
||
DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason);
|
||
try
|
||
if tsChangePending in FStates then
|
||
DoChange(FLastChangedNode);
|
||
finally
|
||
if toAutoSort in FOptions.FAutoOptions then
|
||
SortTree(FHeader.FSortColumn, FHeader.FSortDirection, True);
|
||
|
||
SetUpdateState(False);
|
||
if HandleAllocated then
|
||
Invalidate;
|
||
UpdateDesigner;
|
||
end;
|
||
end;
|
||
|
||
if FUpdateCount = 0 then
|
||
DoUpdating(usEnd)
|
||
else
|
||
DoUpdating(usUpdate);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ExecuteAction(Action: TBasicAction): Boolean;
|
||
|
||
// Some support for standard actions.
|
||
|
||
begin
|
||
Result := inherited ExecuteAction(Action);
|
||
|
||
if not Result then
|
||
begin
|
||
Result := Action is TEditSelectAll;
|
||
if Result then
|
||
SelectAll(False)
|
||
else
|
||
begin
|
||
Result := Action is TEditCopy;
|
||
if Result then
|
||
CopyToClipboard
|
||
else
|
||
if not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
Result := Action is TEditCut;
|
||
if Result then
|
||
CutToClipBoard
|
||
else
|
||
begin
|
||
Result := Action is TEditPaste;
|
||
if Result then
|
||
PasteFromClipboard
|
||
else
|
||
begin
|
||
Result := Action is TEditDelete;
|
||
if Result then
|
||
DeleteSelectedNodes
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FinishCutOrCopy;
|
||
|
||
// Deletes nodes which are marked as being cutted.
|
||
|
||
var
|
||
Run, ToDelete: PVirtualNode;
|
||
|
||
begin
|
||
|
||
if tsCutPending in FStates then
|
||
begin
|
||
Run := FRoot.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsCutOrCopy in Run.States then
|
||
begin
|
||
ToDelete := Run;
|
||
Run := GetNextNoInit(Run);
|
||
DeleteNode(ToDelete);
|
||
end
|
||
else
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
DoStateChange([], [tsCutPending]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FlushClipboard;
|
||
|
||
// Used to render the data which is currently on the clipboard (finishes delayed rendering).
|
||
|
||
begin
|
||
if ClipboardStates * FStates <> [] then
|
||
begin
|
||
DoStateChange([tsClipboardFlushing]);
|
||
OleFlushClipboard;
|
||
CancelCutOrCopy;
|
||
DoStateChange([], [tsClipboardFlushing]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FullCollapse(Node: PVirtualNode = nil);
|
||
|
||
// This routine collapses all expanded nodes in the subtree given by Node or the whole tree if Node is FRoot or nil.
|
||
// Only nodes which are expanded will be collapsed. This excludes uninitialized nodes but nodes marked as visible
|
||
// will still be collapsed if they are expanded.
|
||
|
||
var
|
||
Stop: PVirtualNode;
|
||
|
||
begin
|
||
if FRoot.TotalCount > 1 then
|
||
begin
|
||
if Node = FRoot then
|
||
Node := nil;
|
||
|
||
DoStateChange([tsCollapsing]);
|
||
BeginUpdate;
|
||
try
|
||
Stop := Node;
|
||
Node := GetLastVisibleNoInit(Node, True);
|
||
|
||
if Assigned(Node) then
|
||
begin
|
||
repeat
|
||
if [vsHasChildren, vsExpanded] * Node.States = [vsHasChildren, vsExpanded] then
|
||
ToggleNode(Node);
|
||
Node := GetPreviousNoInit(Node);
|
||
until Node = Stop;
|
||
|
||
// Collapse the start node too.
|
||
if Assigned(Node) and ([vsHasChildren, vsExpanded] * Node.States = [vsHasChildren, vsExpanded]) then
|
||
ToggleNode(Node);
|
||
end;
|
||
finally
|
||
EndUpdate;
|
||
DoStateChange([], [tsCollapsing]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.FullExpand(Node: PVirtualNode = nil);
|
||
|
||
// This routine expands all collapsed nodes in the subtree given by Node or the whole tree if Node is FRoot or nil.
|
||
// All nodes on the way down are initialized so this procedure might take a long time.
|
||
// Since all nodes are validated, the tree cannot make use of optimatizations. Hence it is counter productive and you
|
||
// should consider avoiding its use.
|
||
|
||
var
|
||
Stop: PVirtualNode;
|
||
|
||
begin
|
||
if FRoot.TotalCount > 1 then
|
||
begin
|
||
DoStateChange([tsExpanding]);
|
||
BeginUpdate;
|
||
try
|
||
if Node = nil then
|
||
begin
|
||
Node := FRoot.FirstChild;
|
||
Stop := nil;
|
||
end
|
||
else
|
||
begin
|
||
Stop := Node.NextSibling;
|
||
if Stop = nil then
|
||
begin
|
||
Stop := Node;
|
||
repeat
|
||
Stop := Stop.Parent;
|
||
until (Stop = FRoot) or Assigned(Stop.NextSibling);
|
||
if Stop = FRoot then
|
||
Stop := nil
|
||
else
|
||
Stop := Stop.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
// Initialize the start node. Others will be initialized in GetNext.
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
|
||
repeat
|
||
if not (vsExpanded in Node.States) then
|
||
ToggleNode(Node);
|
||
Node := GetNext(Node);
|
||
until Node = Stop;
|
||
finally
|
||
EndUpdate;
|
||
DoStateChange([], [tsExpanding]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
{$ifndef fpc}
|
||
|
||
function TBaseVirtualTree.GetControlsAlignment: TAlignment;
|
||
|
||
begin
|
||
Result := FAlignment;
|
||
end;
|
||
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetDisplayRect(Node: PVirtualNode; Column: TColumnIndex; TextOnly: Boolean;
|
||
Unclipped: Boolean = False; ApplyCellContentMargin: Boolean = False): TRect;
|
||
|
||
// Determines the client coordinates the given node covers, depending on scrolling, expand state etc.
|
||
// If the given node cannot be found (because one of its parents is collapsed or it is invisible) then an empty
|
||
// rectangle is returned.
|
||
// If TextOnly is True then only the text bounds are returned, that is, the resulting rectangle's left and right border
|
||
// are updated according to bidi mode, alignment and text width of the node.
|
||
// If Unclipped is True (which only makes sense if also TextOnly is True) then the calculated text rectangle is
|
||
// not clipped if the text does not entirely fit into the text space. This is special handling needed for hints.
|
||
// If ApplyCellContentMargin is True (which only makes sense if also TextOnly is True) then the calculated text
|
||
// rectangle respects the cell content margin.
|
||
// If Column is -1 then the entire client width is used before determining the node's width otherwise the bounds of the
|
||
// particular column are used.
|
||
// Note: Column must be a valid column and is used independent of whether the header is visible or not.
|
||
|
||
var
|
||
Temp: PVirtualNode;
|
||
Offset: Cardinal;
|
||
Indent,
|
||
TextWidth: Integer;
|
||
MainColumnHit: Boolean;
|
||
CurrentBidiMode: TBidiMode;
|
||
CurrentAlignment: TAlignment;
|
||
MaxUnclippedHeight: Integer;
|
||
TM: TTextMetric;
|
||
ExtraVerticalMargin: Integer;
|
||
|
||
begin
|
||
//{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintHeader],'GetDisplayRect');{$endif}
|
||
Assert(Assigned(Node), 'Node must not be nil.');
|
||
Assert(Node <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
MainColumnHit := (Column + 1) in [0, FHeader.MainColumn + 1];
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
|
||
Result := Rect(0, 0, 0, 0);
|
||
|
||
// Check whether the node is visible (determine indentation level btw.).
|
||
Temp := Node;
|
||
if not (vsVisible in Temp.States) then
|
||
Exit;
|
||
Indent := 0;
|
||
while Temp <> FRoot do
|
||
begin
|
||
if not (vsVisible in Temp.States) or not (vsExpanded in Temp.Parent.States) then
|
||
Exit;
|
||
Temp := Temp.Parent;
|
||
if MainColumnHit and (Temp <> FRoot) then
|
||
Inc(Indent, FIndent);
|
||
end;
|
||
|
||
// Here we know the node is visible.
|
||
Offset := 0;
|
||
if tsUseCache in FStates then
|
||
begin
|
||
// If we can use the position cache then do a binary search to find a cached node which is as close as possible
|
||
// to the current node. Iterate then through all following and visible nodes and sum up their heights.
|
||
Temp := FindInPositionCache(Node, Offset);
|
||
while Assigned(Temp) and (Temp <> Node) do
|
||
begin
|
||
Inc(Offset, NodeHeight[Temp]);
|
||
Temp := GetNextVisibleNoInit(Temp, True);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// If the cache is not available then go straight through all nodes up to the root and sum up their heights.
|
||
Temp := Node;
|
||
repeat
|
||
Temp := GetPreviousVisibleNoInit(Temp, True);
|
||
if Temp = nil then
|
||
Break;
|
||
Inc(Offset, NodeHeight[Temp]);
|
||
until False;
|
||
end;
|
||
|
||
Result := Rect(0, Offset, Max(FRangeX, ClientWidth), Offset + NodeHeight[Node]);
|
||
|
||
// Limit left and right bounds to the given column (if any) and move bounds according to current scroll state.
|
||
if Column > NoColumn then
|
||
begin
|
||
FHeader.FColumns.GetColumnBounds(Column, Result.Left, Result.Right);
|
||
// The right column border is not part of this cell.
|
||
Dec(Result.Right);
|
||
OffsetRect(Result, 0, FOffsetY);
|
||
end
|
||
else
|
||
OffsetRect(Result, -FEffectiveOffsetX, FOffsetY);
|
||
|
||
// Limit left and right bounds further if only the text area is required.
|
||
if TextOnly then
|
||
begin
|
||
// Start with the offset of the text in the column and consider the indentation level too.
|
||
Offset := FMargin + Indent;
|
||
// If the text of a node is involved then we have to consider directionality and alignment too.
|
||
if Column <= NoColumn then
|
||
begin
|
||
CurrentBidiMode := BidiMode;
|
||
CurrentAlignment := Alignment;
|
||
end
|
||
else
|
||
begin
|
||
CurrentBidiMode := FHeader.FColumns[Column].BidiMode;
|
||
CurrentAlignment := FHeader.FColumns[Column].Alignment;
|
||
end;
|
||
|
||
if MainColumnHit then
|
||
begin
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
Inc(Offset, FIndent);
|
||
if (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages) and (Node.CheckType <> ctNone) then
|
||
Inc(Offset, FCheckImages.Height + 2);
|
||
end;
|
||
// Consider associated images.
|
||
if Assigned(FStateImages) and HasImage(Node, ikState, Column) then
|
||
Inc(Offset, FStateImages.Width + 2);
|
||
if Assigned(FImages) and HasImage(Node, ikNormal, Column) then
|
||
Inc(Offset, FImages.Width + 2);
|
||
|
||
// Offset contains now the distance from the left or right border of the rectangle (depending on bidi mode).
|
||
// Now consider the alignment too and calculate the final result.
|
||
if CurrentBidiMode = bdLeftToRight then
|
||
begin
|
||
Inc(Result.Left, Offset);
|
||
// Left-to-right reading does not need any special adjustment of the alignment.
|
||
end
|
||
else
|
||
begin
|
||
Dec(Result.Right, Offset);
|
||
|
||
// Consider bidi mode here. In RTL context does left alignment actually mean right alignment and vice versa.
|
||
ChangeBiDiModeAlignment(CurrentAlignment);
|
||
end;
|
||
|
||
TextWidth := DoGetNodeWidth(Node, Column);
|
||
|
||
// Keep cell height before applying cell content margin in order to increase cell height if text does not fit
|
||
// and Unclipped it true (see below).
|
||
MaxUnclippedHeight := Result.Bottom - Result.Top;
|
||
|
||
if ApplyCellContentMargin then
|
||
DoBeforeCellPaint(Self.Canvas, Node, Column, cpmGetContentMargin, Result, Result);
|
||
|
||
if Unclipped then
|
||
begin
|
||
// The caller requested the text coordinates unclipped. This means they must be calculated so as would
|
||
// there be enough space, regardless of column bounds etc.
|
||
// The layout still depends on the available space too, because this determines the position
|
||
// of the unclipped text rectangle.
|
||
if Result.Right - Result.Left < TextWidth - 1 then
|
||
if CurrentBidiMode = bdLeftToRight then
|
||
CurrentAlignment := taLeftJustify
|
||
else
|
||
CurrentAlignment := taRightJustify;
|
||
|
||
// Increase cell height (up to MaxUnclippedHeight determined above) if text does not fit.
|
||
GetTextMetrics(Self.Canvas.Handle, TM);
|
||
ExtraVerticalMargin := Math.Min(TM.tmHeight, MaxUnclippedHeight) - (Result.Bottom - Result.Top);
|
||
if ExtraVerticalMargin > 0 then
|
||
InflateRect(Result, 0, (ExtraVerticalMargin + 1) div 2);
|
||
|
||
case CurrentAlignment of
|
||
taCenter:
|
||
begin
|
||
Result.Left := (Result.Left + Result.Right - TextWidth) div 2;
|
||
Result.Right := Result.Left + TextWidth;
|
||
end;
|
||
taRightJustify:
|
||
Result.Left := Result.Right - TextWidth;
|
||
else // taLeftJustify
|
||
Result.Right := Result.Left + TextWidth - 1;
|
||
end;
|
||
end
|
||
else
|
||
// Modify rectangle only if the text fits entirely into the given room.
|
||
if Result.Right - Result.Left > TextWidth then
|
||
case CurrentAlignment of
|
||
taCenter:
|
||
begin
|
||
Result.Left := (Result.Left + Result.Right - TextWidth) div 2;
|
||
Result.Right := Result.Left + TextWidth;
|
||
end;
|
||
taRightJustify:
|
||
Result.Left := Result.Right - TextWidth;
|
||
else // taLeftJustify
|
||
Result.Right := Result.Left + TextWidth;
|
||
end;
|
||
end;
|
||
//lclheader
|
||
//todo: add a parameter to decide if the result must be returned as
|
||
//a tree offset or a control offset
|
||
if hoVisible in FHeader.FOptions then
|
||
OffsetRect(Result, 0, FHeader.Height);
|
||
//{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'DisplayRect for Node '+IntToStr(Node^.Index),Result);{$endif}
|
||
//{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintHeader],'GetDisplayRect');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the first node in the tree while optionally considering toChildrenAbove.
|
||
|
||
begin
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
if vsHasChildren in FRoot.States then
|
||
begin
|
||
Result := FRoot;
|
||
|
||
// Child nodes are the first choice if possible.
|
||
if Assigned(Result.FirstChild) then
|
||
begin
|
||
while Assigned(Result.FirstChild) do
|
||
begin
|
||
Result := Result.FirstChild;
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
|
||
if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then
|
||
InitChildren(Result);
|
||
end;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end
|
||
else
|
||
Result := FRoot.FirstChild;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstChecked(State: TCheckState = csCheckedNormal;
|
||
ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the first node in the tree with the given check state.
|
||
|
||
begin
|
||
Result := GetNextChecked(nil, State, ConsiderChildrenAbove);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstChild(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the first child of the given node. The result node is initialized before exit.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.FirstChild
|
||
else
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
if vsHasChildren in Node.States then
|
||
begin
|
||
if Node.ChildCount = 0 then
|
||
InitChildren(Node);
|
||
Result := Node.FirstChild;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstCutCopy(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the first node in the tree which is currently marked for a clipboard operation.
|
||
// See also GetNextCutCopy for comments on initialization.
|
||
|
||
begin
|
||
Result := GetNextCutCopy(nil, ConsiderChildrenAbove);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstInitialized(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the first node which is already initialized.
|
||
|
||
begin
|
||
Result := GetFirstNoInit(ConsiderChildrenAbove);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
Result := GetNextInitialized(Result, ConsiderChildrenAbove);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstLeaf: PVirtualNode;
|
||
|
||
// Returns the first node in the tree which has currently no children.
|
||
// The result is initialized if necessary.
|
||
|
||
begin
|
||
Result := GetNextLeaf(nil);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstLevel(NodeLevel: Cardinal): PVirtualNode;
|
||
|
||
// Returns the first node in the tree on a specific level.
|
||
// The result is initialized if necessary.
|
||
|
||
begin
|
||
Result := GetFirstNoInit(True);
|
||
while Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) do
|
||
Result := GetNextNoInit(Result, True);
|
||
|
||
if Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) then // i.e. there is no node with the desired level in the tree
|
||
Result := nil;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstNoInit(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the first node in the tree while optionally considering toChildrenAbove.
|
||
// No initialization is performed.
|
||
|
||
begin
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
if vsHasChildren in FRoot.States then
|
||
begin
|
||
Result := FRoot;
|
||
|
||
// Child nodes are the first choice if possible.
|
||
if Assigned(Result.FirstChild) then
|
||
begin
|
||
while Assigned(Result.FirstChild) do
|
||
Result := Result.FirstChild;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end
|
||
else
|
||
Result := FRoot.FirstChild;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the first node in the current selection while optionally considering toChildrenAbove.
|
||
|
||
begin
|
||
Result := GetNextSelected(nil, ConsiderChildrenAbove);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the first visible node in the tree while optionally considering toChildrenAbove.
|
||
// If necessary nodes are initialized on demand.
|
||
|
||
begin
|
||
Result := Node;
|
||
if not Assigned(Result) then
|
||
Result := FRoot;
|
||
|
||
if vsHasChildren in Result.States then
|
||
begin
|
||
if Result.ChildCount = 0 then
|
||
InitChildren(Result);
|
||
|
||
// Child nodes are the first choice if possible.
|
||
if Assigned(Result.FirstChild) then
|
||
begin
|
||
Result := GetFirstChild(Result);
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
repeat
|
||
// Search the first visible sibling.
|
||
while Assigned(Result.NextSibling) and not (vsVisible in Result.States) do
|
||
begin
|
||
Result := Result.NextSibling;
|
||
// Init node on demand as this might change the visibility.
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
// If there a no visible siblings take the parent.
|
||
if not (vsVisible in Result.States) then
|
||
begin
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then
|
||
InitChildren(Result);
|
||
if (not Assigned(Result.FirstChild)) or (not (vsExpanded in Result.States)) then
|
||
Break;
|
||
end;
|
||
|
||
Result := Result.FirstChild;
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
until False;
|
||
end
|
||
else
|
||
begin
|
||
// If there are no children or the first child is not visible then search the sibling nodes or traverse parents.
|
||
if not (vsVisible in Result.States) then
|
||
begin
|
||
repeat
|
||
// Is there a next sibling?
|
||
if Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.NextSibling;
|
||
// The visible state can be removed during initialization so init the node first.
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// No sibling anymore, so use the parent's next sibling.
|
||
if Result.Parent <> FRoot then
|
||
Result := Result.Parent
|
||
else
|
||
begin
|
||
// There are no further nodes to examine, hence there is no further visible node.
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstVisibleChild(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the first visible child node of Node. If necessary nodes are initialized on demand.
|
||
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
Result := GetFirstChild(Node);
|
||
|
||
if Assigned(Result) and not (vsVisible in Result.States) then
|
||
Result := GetNextVisibleSibling(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstVisibleChildNoInit(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the first visible child node of Node.
|
||
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
Result := Node.FirstChild;
|
||
if Assigned(Result) and not (vsVisible in Result.States) then
|
||
Result := GetNextVisibleSiblingNoInit(ResulT);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetFirstVisibleNoInit(Node: PVirtualNode = nil;
|
||
ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the first visible node in the tree or given subtree while optionally considering toChildrenAbove.
|
||
// No initialization is performed.
|
||
|
||
begin
|
||
Result := Node;
|
||
if not Assigned(Result) then
|
||
Result := FRoot;
|
||
|
||
if vsHasChildren in Result.States then
|
||
begin
|
||
// Child nodes are the first choice if possible.
|
||
if Assigned(Result.FirstChild) then
|
||
begin
|
||
Result := Result.FirstChild;
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
repeat
|
||
// Search the first visible sibling.
|
||
while Assigned(Result.NextSibling) and not (vsVisible in Result.States) do
|
||
Result := Result.NextSibling;
|
||
|
||
// If there a no visible siblings take the parent.
|
||
if not (vsVisible in Result.States) then
|
||
begin
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
Break;
|
||
end
|
||
else if (not Assigned(Result.FirstChild)) or (not (vsExpanded in Result.States))then
|
||
Break;
|
||
|
||
Result := Result.FirstChild;
|
||
until False;
|
||
end
|
||
else
|
||
begin
|
||
// If there are no children or the first child is not visible then search the sibling nodes or traverse parents.
|
||
if not (vsVisible in Result.States) then
|
||
begin
|
||
repeat
|
||
// Is there a next sibling?
|
||
if Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.NextSibling;
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// No sibling anymore, so use the parent's next sibling.
|
||
if Result.Parent <> FRoot then
|
||
Result := Result.Parent
|
||
else
|
||
begin
|
||
// There are no further nodes to examine, hence there is no further visible node.
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.GetHitTestInfoAt(X, Y: Integer; Relative: Boolean; var HitInfo: THitInfo);
|
||
|
||
// Determines the node that occupies the specified point or nil if there's none. The parameter Relative determines
|
||
// whether to consider X and Y as being client coordinates (if True) or as being absolute tree coordinates.
|
||
// HitInfo is filled with flags describing the hit further.
|
||
|
||
var
|
||
ColLeft,
|
||
ColRight: Integer;
|
||
NodeTop: Integer;
|
||
InitialColumn,
|
||
NextColumn: TColumnIndex;
|
||
CurrentBidiMode: TBidiMode;
|
||
CurrentAlignment: TAlignment;
|
||
NodeRect: TRect;
|
||
|
||
begin
|
||
HitInfo.HitNode := nil;
|
||
HitInfo.HitPositions := [];
|
||
HitInfo.HitColumn := NoColumn;
|
||
|
||
// Determine if point lies in the tree's client area.
|
||
if X < 0 then
|
||
Include(HitInfo.HitPositions, hiToLeft)
|
||
else
|
||
if X > Max(FRangeX, ClientWidth) then
|
||
Include(HitInfo.HitPositions, hiToRight);
|
||
|
||
//lclheader
|
||
if Y < IfThen(hoVisible in FHeader.Options, FHeader.Height) then
|
||
Include(HitInfo.HitPositions, hiAbove)
|
||
else
|
||
if Y > Max(FRangeY, inherited GetClientRect.Bottom) then
|
||
Include(HitInfo.HitPositions, hiBelow);
|
||
|
||
// If the point is in the tree area then check the nodes.
|
||
if HitInfo.HitPositions = [] then
|
||
begin
|
||
// Convert position into absolute coordinate if necessary.
|
||
if Relative then
|
||
begin
|
||
if X > Header.Columns.GetVisibleFixedWidth then
|
||
Inc(X, FEffectiveOffsetX);
|
||
//lclheader
|
||
if hoVisible in FHeader.Options then
|
||
Dec(Y, FHeader.Height);
|
||
Dec(Y, FOffsetY);
|
||
end;
|
||
|
||
HitInfo.HitNode := InternalGetNodeAt(X, Y, False, NodeTop);
|
||
if HitInfo.HitNode = nil then
|
||
Include(HitInfo.HitPositions, hiNowhere)
|
||
else
|
||
begin
|
||
// At this point we need some info about the node, so it must be initialized.
|
||
if not (vsInitialized in HitInfo.HitNode.States) then
|
||
InitNode(HitInfo.HitNode);
|
||
|
||
if FHeader.UseColumns then
|
||
begin
|
||
HitInfo.HitColumn := FHeader.Columns.GetColumnAndBounds(Point(X, Y), ColLeft, ColRight, False);
|
||
// If auto column spanning is enabled then look for the last non empty column.
|
||
if toAutoSpanColumns in FOptions.FAutoOptions then
|
||
begin
|
||
InitialColumn := HitInfo.HitColumn;
|
||
// Search to the left of the hit column for empty columns.
|
||
while (HitInfo.HitColumn > NoColumn) and ColumnIsEmpty(HitInfo.HitNode, HitInfo.HitColumn) do
|
||
begin
|
||
NextColumn := FHeader.FColumns.GetPreviousVisibleColumn(HitInfo.HitColumn);
|
||
if NextColumn = InvalidColumn then
|
||
Break;
|
||
HitInfo.HitColumn := NextColumn;
|
||
Dec(ColLeft, FHeader.FColumns[NextColumn].Width);
|
||
end;
|
||
// Search to the right of the hit column for empty columns.
|
||
repeat
|
||
InitialColumn := FHeader.FColumns.GetNextVisibleColumn(InitialColumn);
|
||
if (InitialColumn = InvalidColumn) or not ColumnIsEmpty(HitInfo.HitNode, InitialColumn) then
|
||
Break;
|
||
Inc(ColRight, FHeader.FColumns[InitialColumn].Width);
|
||
until False;
|
||
end;
|
||
// Make the X position and the right border relative to the start of the column.
|
||
Dec(X, ColLeft);
|
||
Dec(ColRight, ColLeft);
|
||
end
|
||
else
|
||
begin
|
||
HitInfo.HitColumn := NoColumn;
|
||
ColRight := Max(FRangeX, ClientWidth);
|
||
end;
|
||
ColLeft := 0;
|
||
|
||
if HitInfo.HitColumn = InvalidColumn then
|
||
Include(HitInfo.HitPositions, hiNowhere)
|
||
else
|
||
begin
|
||
// From now on X is in "column" coordinates (relative to the left column border).
|
||
HitInfo.HitPositions := [hiOnItem];
|
||
|
||
// Avoid getting the display rect if this is not necessary.
|
||
if toNodeHeightResize in FOptions.FMiscOptions then
|
||
begin
|
||
NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, False);
|
||
if Y <= (NodeRect.Top - FOffsetY + 1) then
|
||
Include(HitInfo.HitPositions, hiUpperSplitter)
|
||
else
|
||
if Y >= (NodeRect.Bottom - FOffsetY - 3) then
|
||
Include(HitInfo.HitPositions, hiLowerSplitter);
|
||
end;
|
||
|
||
if HitInfo.HitColumn <= NoColumn then
|
||
begin
|
||
CurrentBidiMode := BidiMode;
|
||
CurrentAlignment := Alignment;
|
||
end
|
||
else
|
||
begin
|
||
CurrentBidiMode := FHeader.FColumns[HitInfo.HitColumn].BidiMode;
|
||
CurrentAlignment := FHeader.FColumns[HitInfo.HitColumn].Alignment;
|
||
end;
|
||
|
||
if CurrentBidiMode = bdLeftToRight then
|
||
DetermineHitPositionLTR(HitInfo, X, ColRight, CurrentAlignment)
|
||
else
|
||
DetermineHitPositionRTL(HitInfo, X, ColRight, CurrentAlignment);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLast(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the very last node in the tree branch given by Node and initializes the nodes all the way down including the
|
||
// result. toChildrenAbove is optionally considered. By using Node = nil the very last node in the tree is returned.
|
||
|
||
var
|
||
Next: PVirtualNode;
|
||
|
||
begin
|
||
Result := GetLastChild(Node);
|
||
if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.FPaintOptions) then
|
||
while Assigned(Result) do
|
||
begin
|
||
// Test if there is a next last child. If not keep the node from the last run.
|
||
// Otherwise use the next last child.
|
||
Next := GetLastChild(Result);
|
||
if Next = nil then
|
||
Break;
|
||
Result := Next;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastInitialized(Node: PVirtualNode = nil;
|
||
ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the very last initialized child node in the tree branch given by Node.
|
||
|
||
begin
|
||
Result := GetLastNoInit(Node, ConsiderChildrenAbove);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
Result := GetPreviousInitialized(Result, ConsiderChildrenAbove);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the very last node in the tree branch given by Node without initialization.
|
||
|
||
var
|
||
Next: PVirtualNode;
|
||
|
||
begin
|
||
Result := GetLastChildNoInit(Node);
|
||
if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.FPaintOptions) then
|
||
while Assigned(Result) do
|
||
begin
|
||
// Test if there is a next last child. If not keep the node from the last run.
|
||
// Otherwise use the next last child.
|
||
Next := GetLastChildNoInit(Result);
|
||
if Next = nil then
|
||
Break;
|
||
Result := Next;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastChild(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Determines the last child of the given node and initializes it if there is one.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.LastChild
|
||
else
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
if vsHasChildren in Node.States then
|
||
begin
|
||
if Node.ChildCount = 0 then
|
||
InitChildren(Node);
|
||
Result := Node.LastChild;
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastChildNoInit(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Determines the last child of the given node but does not initialize it.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.LastChild
|
||
else
|
||
begin
|
||
if vsHasChildren in Node.States then
|
||
Result := Node.LastChild
|
||
else
|
||
Result := nil;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the very last visible node in the tree while optionally considering toChildrenAbove.
|
||
// The nodes are intialized all the way down including the result node.
|
||
|
||
var
|
||
Next: PVirtualNode;
|
||
|
||
begin
|
||
Result := GetLastVisibleChild(Node);
|
||
if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.FPaintOptions) then
|
||
while Assigned(Result) do
|
||
begin
|
||
// Test if there is a next last visible child. If not keep the node from the last run.
|
||
// Otherwise use the next last visible child.
|
||
Next := GetLastVisibleChild(Result);
|
||
if Next = nil then
|
||
Break;
|
||
Result := Next;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastVisibleChild(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Determines the last visible child of the given node and initializes it if necessary.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetLastChild(FRoot)
|
||
else
|
||
if FullyVisible[Node] and (vsExpanded in Node.States) then
|
||
Result := GetLastChild(Node)
|
||
else
|
||
Result := nil;
|
||
|
||
if Assigned(Result) and not (vsVisible in Result.States) then
|
||
Result := GetPreviousVisibleSibling(Result);
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastVisibleChildNoInit(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Determines the last visible child of the given node without initialization.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetLastChildNoInit(FRoot)
|
||
else
|
||
if FullyVisible[Node] and (vsExpanded in Node.States) then
|
||
Result := GetLastChildNoInit(Node)
|
||
else
|
||
Result := nil;
|
||
|
||
if Assigned(Result) and not (vsVisible in Result.States) then
|
||
Result := GetPreviousVisibleSiblingNoInit(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetLastVisibleNoInit(Node: PVirtualNode = nil;
|
||
ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the very last visible node in the tree while optionally considering toChildrenAbove.
|
||
// No initialization is performed.
|
||
|
||
var
|
||
Next: PVirtualNode;
|
||
|
||
begin
|
||
Result := GetLastVisibleChildNoInit(Node);
|
||
if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.FPaintOptions) then
|
||
while Assigned(Result) do
|
||
begin
|
||
// Test if there is a next last visible child. If not keep the node from the last run.
|
||
// Otherwise use the next last visible child.
|
||
Next := GetLastVisibleChildNoInit(Result);
|
||
if Next = nil then
|
||
Break;
|
||
Result := Next;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): Integer;
|
||
|
||
// This method determines the width of the largest node in the given column.
|
||
// If UseSmartColumnWidth is True then only the visible nodes which are in view will be considered
|
||
// Note: If UseSmartColumnWidth is False then every visible node in the tree will be initialized contradicting so
|
||
// the virtual paradigm.
|
||
|
||
var
|
||
Run,
|
||
LastNode,
|
||
NextNode: PVirtualNode;
|
||
NodeLeft,
|
||
TextLeft,
|
||
CurrentWidth: Integer;
|
||
WithCheck,
|
||
WithImages,
|
||
WithStateImages: Boolean;
|
||
CheckOffset,
|
||
ImageOffset,
|
||
StateImageOffset: Integer;
|
||
|
||
begin
|
||
if OperationCanceled then
|
||
begin
|
||
// Behave non-destructive.
|
||
Result := FHeader.FColumns[Column].Width;
|
||
Exit;
|
||
end
|
||
else
|
||
Result := 0;
|
||
|
||
BeginOperation;
|
||
if Assigned(FOnBeforeGetMaxColumnWidth) then
|
||
FOnBeforeGetMaxColumnWidth(FHeader, Column, UseSmartColumnWidth);
|
||
|
||
// Don't check the event here as descendant trees might have overriden the DoGetImageIndex method.
|
||
WithImages := Assigned(FImages);
|
||
if WithImages then
|
||
ImageOffset := FImages.Width + 2
|
||
else
|
||
ImageOffset := 0;
|
||
WithStateImages := Assigned(FStateImages);
|
||
if WithStateImages then
|
||
StateImageOffset := FStateImages.Width + 2
|
||
else
|
||
StateImageOffset := 0;
|
||
if Assigned(FCheckImages) then
|
||
CheckOffset := FCheckImages.Height + 2
|
||
else
|
||
CheckOffset := 0;
|
||
|
||
if UseSmartColumnWidth then // Get first visible node which is in view.
|
||
Run := GetTopNode
|
||
else
|
||
Run := GetFirstVisible(nil, True);
|
||
|
||
if Column = FHeader.MainColumn then
|
||
begin
|
||
if toFixedIndent in FOptions.FPaintOptions then
|
||
NodeLeft := FIndent
|
||
else
|
||
begin
|
||
if toShowRoot in FOptions.FPaintOptions then
|
||
NodeLeft := Integer((GetNodeLevel(Run) + 1) * FIndent)
|
||
else
|
||
NodeLeft := Integer(GetNodeLevel(Run) * FIndent);
|
||
end;
|
||
|
||
WithCheck := (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages);
|
||
end
|
||
else
|
||
begin
|
||
NodeLeft := 0;
|
||
WithCheck := False;
|
||
end;
|
||
|
||
// Consider node margin at the left of the nodes.
|
||
Inc(NodeLeft, FMargin);
|
||
|
||
// Decide where to stop.
|
||
if UseSmartColumnWidth then
|
||
LastNode := GetNextVisible(BottomNode)
|
||
else
|
||
LastNode := nil;
|
||
|
||
while Assigned(Run) and not OperationCanceled do
|
||
begin
|
||
TextLeft := NodeLeft;
|
||
if WithCheck and (Run.CheckType <> ctNone) then
|
||
Inc(TextLeft, CheckOffset);
|
||
if WithImages and HasImage(Run, ikNormal, Column) then
|
||
Inc(TextLeft, ImageOffset);
|
||
if WithStateImages and HasImage(Run, ikState, Column) then
|
||
Inc(TextLeft, StateImageOffset);
|
||
|
||
CurrentWidth := DoGetNodeWidth(Run, Column);
|
||
Inc(CurrentWidth, DoGetCellContentMargin(Run, Column).X);
|
||
|
||
if Result < (TextLeft + CurrentWidth) then
|
||
Result := TextLeft + CurrentWidth;
|
||
|
||
// Get next visible node and update left node position if needed.
|
||
NextNode := GetNextVisible(Run, True);
|
||
if NextNode = LastNode then
|
||
Break;
|
||
if (Column = Header.MainColumn) and not (toFixedIndent in FOptions.FPaintOptions) then
|
||
Inc(NodeLeft, CountLevelDifference(Run, NextNode) * Integer(FIndent));
|
||
Run := NextNode;
|
||
end;
|
||
if toShowVertGridLines in FOptions.FPaintOptions then
|
||
Inc(Result);
|
||
|
||
if Assigned(FOnAfterGetMaxColumnWidth) then
|
||
FOnAfterGetMaxColumnWidth(FHeader, Column, Result);
|
||
EndOperation;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns next node in tree while optionally considering toChildrenAbove. The Result will be initialized if needed.
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
// If this node has no siblings use the parent.
|
||
if not Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// There is at least one sibling so take it.
|
||
Result := Result.NextSibling;
|
||
|
||
// Has this node got children? Initialize them if necessary.
|
||
if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then
|
||
InitChildren(Result);
|
||
|
||
// Now take a look at the children.
|
||
while Assigned(Result.FirstChild) do
|
||
begin
|
||
Result := Result.FirstChild;
|
||
if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then
|
||
InitChildren(Result);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// Has this node got children?
|
||
if vsHasChildren in Result.States then
|
||
begin
|
||
// Yes, there are child nodes. Initialize them if necessary.
|
||
if Result.ChildCount = 0 then
|
||
InitChildren(Result);
|
||
end;
|
||
|
||
// if there is no child node try siblings
|
||
if Assigned(Result.FirstChild) then
|
||
Result := Result.FirstChild
|
||
else
|
||
begin
|
||
repeat
|
||
// Is there a next sibling?
|
||
if Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.NextSibling;
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// No sibling anymore, so use the parent's next sibling.
|
||
if Result.Parent <> FRoot then
|
||
Result := Result.Parent
|
||
else
|
||
begin
|
||
// There are no further nodes to examine, hence there is no further visible node.
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal;
|
||
ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetFirstNoInit(ConsiderChildrenAbove)
|
||
else
|
||
Result := GetNextNoInit(Node, ConsiderChildrenAbove);
|
||
|
||
while Assigned(Result) and (Result.CheckState <> State) do
|
||
Result := GetNextNoInit(Result, ConsiderChildrenAbove);
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the next node in the tree which is currently marked for a clipboard operation. Since only visible nodes can
|
||
// be marked (or they are hidden after they have been marked) it is not necessary to initialize nodes to check for
|
||
// child nodes. The result, however, is initialized if necessary.
|
||
|
||
begin
|
||
if ClipboardStates * FStates <> [] then
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetFirstNoInit(ConsiderChildrenAbove)
|
||
else
|
||
Result := GetNextNoInit(Node, ConsiderChildrenAbove);
|
||
while Assigned(Result) and not (vsCutOrCopy in Result.States) do
|
||
Result := GetNextNoInit(Result, ConsiderChildrenAbove);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the next node in tree which is initialized.
|
||
|
||
begin
|
||
Result := Node;
|
||
repeat
|
||
Result := GetNextNoInit(Result, ConsiderChildrenAbove);
|
||
until (Result = nil) or (vsInitialized in Result.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextLeaf(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the next node in the tree which has currently no children.
|
||
// The result is initialized if necessary.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.FirstChild
|
||
else
|
||
Result := GetNext(Node);
|
||
while Assigned(Result) and (vsHasChildren in Result.States) do
|
||
Result := GetNext(Result);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode;
|
||
|
||
// Returns the next node in the tree on a specific level.
|
||
// The result is initialized if necessary.
|
||
|
||
var
|
||
StartNodeLevel: Cardinal;
|
||
|
||
begin
|
||
Result := nil;
|
||
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
StartNodeLevel := GetNodeLevel(Node);
|
||
|
||
if StartNodeLevel < NodeLevel then
|
||
begin
|
||
Result := GetNext(Node);
|
||
if Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) then
|
||
Result := GetNextLevel(Result, NodeLevel);
|
||
end
|
||
else if StartNodeLevel = NodeLevel then
|
||
begin
|
||
Result := Node.NextSibling;
|
||
if not Assigned(Result) then // i.e. start node was a last sibling
|
||
begin
|
||
Result := Node.Parent;
|
||
if Assigned(Result) then
|
||
begin
|
||
// go to next anchestor of the start node which has a next sibling (if exists)
|
||
while Assigned(Result) and not Assigned(Result.NextSibling) do
|
||
Result := Result.Parent;
|
||
if Assigned(Result) then
|
||
Result := GetNextLevel(Result.NextSibling, NodeLevel);
|
||
end;
|
||
end;
|
||
end
|
||
else // i.e. StartNodeLevel > NodeLevel
|
||
Result := GetNextLevel(Node.Parent, NodeLevel);
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode;
|
||
|
||
// Optimized version of GetNext performing no initialization, but optionally considering toChildrenAbove.
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
// If this node has no siblings use the parent.
|
||
if not Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// There is at least one sibling so take it.
|
||
Result := Result.NextSibling;
|
||
|
||
// Now take a look at the children.
|
||
while Assigned(Result.FirstChild) do
|
||
begin
|
||
Result := Result.FirstChild;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// If there is no child node try siblings.
|
||
if Assigned(Result.FirstChild) then
|
||
Result := Result.FirstChild
|
||
else
|
||
begin
|
||
repeat
|
||
// Is there a next sibling?
|
||
if Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.NextSibling;
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// No sibling anymore, so use the parent's next sibling.
|
||
if Result.Parent <> FRoot then
|
||
Result := Result.Parent
|
||
else
|
||
begin
|
||
// There are no further nodes to examine, hence there is no further visible node.
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the next node in the tree which is currently selected. Since children of unitialized nodes cannot be
|
||
// in the current selection (because they simply do not exist yet) it is not necessary to initialize nodes here.
|
||
// The result however is initialized if necessary.
|
||
|
||
begin
|
||
if FSelectionCount > 0 then
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetFirstNoInit(ConsiderChildrenAbove)
|
||
else
|
||
Result := GetNextNoInit(Node, ConsiderChildrenAbove);
|
||
while Assigned(Result) and not (vsSelected in Result.States) do
|
||
Result := GetNextNoInit(Result, ConsiderChildrenAbove);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextSibling(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the next sibling of Node and initializes it if necessary.
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
Result := Result.NextSibling;
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns next node in tree, with regard to Node, which is visible.
|
||
// Nodes which need an initialization (including the result) are initialized.
|
||
// toChildrenAbove is optionally considered which is the default here.
|
||
|
||
var
|
||
ForceSearch: Boolean;
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
// If the given node is not visible then look for a parent node which is visible, otherwise we will
|
||
// likely go unnecessarily through a whole bunch of invisible nodes.
|
||
if not FullyVisible[Result] then
|
||
Result := GetVisibleParent(Result);
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
repeat
|
||
// If there a no siblings anymore, go up one level.
|
||
if not Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
begin
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// There is at least one sibling so take it.
|
||
Result := Result.NextSibling;
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
if not (vsVisible in Result.States) then
|
||
Continue;
|
||
|
||
// Now take a look at the children.
|
||
// As the children are initialized while toggling, we don't need to do this here.
|
||
while (vsExpanded in Result.States) and Assigned(Result.FirstChild) do
|
||
begin
|
||
Result := Result.FirstChild;
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
if not (vsVisible in Result.States) then
|
||
Break;
|
||
end;
|
||
|
||
// If we found a visible node we don't need to search any longer.
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end;
|
||
until False;
|
||
end
|
||
else
|
||
begin
|
||
// Has this node got children?
|
||
if [vsHasChildren, vsExpanded] * Result.States = [vsHasChildren, vsExpanded] then
|
||
begin
|
||
// Yes, there are child nodes. Initialize them if necessary.
|
||
if Result.ChildCount = 0 then
|
||
InitChildren(Result);
|
||
end;
|
||
|
||
// Child nodes are the first choice if possible.
|
||
if (vsExpanded in Result.States) and Assigned(Result.FirstChild) then
|
||
begin
|
||
Result := GetFirstChild(Result);
|
||
ForceSearch := False;
|
||
end
|
||
else
|
||
ForceSearch := True;
|
||
|
||
// If there are no children or the first child is not visible then search the sibling nodes or traverse parents.
|
||
if Assigned(Result) and (ForceSearch or not (vsVisible in Result.States)) then
|
||
begin
|
||
repeat
|
||
// Is there a next sibling?
|
||
if Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.NextSibling;
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// No sibling anymore, so use the parent's next sibling.
|
||
if Result.Parent <> FRoot then
|
||
Result := Result.Parent
|
||
else
|
||
begin
|
||
// There are no further nodes to examine, hence there is no further visible node.
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the next node in tree, with regard to Node, which is visible.
|
||
// toChildrenAbove is optionally considered (which is the default). No initialization is done.
|
||
|
||
var
|
||
ForceSearch: Boolean;
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
repeat
|
||
// If there a no siblings anymore, go up one level.
|
||
if not Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
begin
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// There is at least one sibling so take it.
|
||
Result := Result.NextSibling;
|
||
if not (vsVisible in Result.States) then
|
||
Continue;
|
||
|
||
// Now take a look at the children.
|
||
while (vsExpanded in Result.States) and Assigned(Result.FirstChild) do
|
||
begin
|
||
Result := Result.FirstChild;
|
||
if not (vsVisible in Result.States) then
|
||
Break;
|
||
end;
|
||
|
||
// If we found a visible node we don't need to search any longer.
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end;
|
||
until False;
|
||
end
|
||
else
|
||
begin
|
||
// If the given node is not visible then look for a parent node which is visible, otherwise we will
|
||
// likely go unnecessarily through a whole bunch of invisible nodes.
|
||
if not FullyVisible[Result] then
|
||
Result := GetVisibleParent(Result);
|
||
|
||
// Child nodes are the first choice if possible.
|
||
if (vsExpanded in Result.States) and Assigned(Result.FirstChild) then
|
||
begin
|
||
Result := Result.FirstChild;
|
||
ForceSearch := False;
|
||
end
|
||
else
|
||
ForceSearch := True;
|
||
|
||
// If there are no children or the first child is not visible then search the sibling nodes or traverse parents.
|
||
if ForceSearch or not (vsVisible in Result.States) then
|
||
begin
|
||
repeat
|
||
// Is there a next sibling?
|
||
if Assigned(Result.NextSibling) then
|
||
begin
|
||
Result := Result.NextSibling;
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else
|
||
begin
|
||
// No sibling anymore, so use the parent's next sibling.
|
||
if Result.Parent <> FRoot then
|
||
Result := Result.Parent
|
||
else
|
||
begin
|
||
// There are no further nodes to examine, hence there is no further visible node.
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextVisibleSibling(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the next visible sibling after Node. Initialization is done implicitly.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
||
|
||
Result := Node;
|
||
repeat
|
||
Result := GetNextSibling(Result);
|
||
until not Assigned(Result) or (vsVisible in Result.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNextVisibleSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the next visible sibling after Node.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
||
|
||
Result := Node;
|
||
repeat
|
||
Result := Result.NextSibling;
|
||
until not Assigned(Result) or (vsVisible in Result.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNodeAt(X, Y: Integer): PVirtualNode;
|
||
|
||
// Overloaded variant of GetNodeAt to easy life of application developers which do not need to have the exact
|
||
// top position returned and always use client coordinates.
|
||
|
||
var
|
||
Dummy: Integer;
|
||
|
||
begin
|
||
Result := GetNodeAt(X, Y, True, Dummy);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNodeAt(X, Y: Integer; Relative: Boolean; var NodeTop: Integer): PVirtualNode;
|
||
var
|
||
OffsetByHeader: Boolean;
|
||
begin
|
||
//lclheader
|
||
OffsetByHeader := Relative and (hoVisible in FHeader.Options);
|
||
if OffsetByHeader then
|
||
Dec(Y, FHeader.Height);
|
||
Result := InternalGetNodeAt(X, Y, Relative, NodeTop);
|
||
//lclheader
|
||
if OffsetByHeader then
|
||
Inc(NodeTop, FHeader.Height);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
|
||
|
||
// Returns the address of the user defined data area in the node.
|
||
|
||
begin
|
||
Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
|
||
|
||
if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
|
||
Result := nil
|
||
else
|
||
Result := PByte(@Node.Data) + FTotalInternalDataSize;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetNodeLevel(Node: PVirtualNode): Cardinal;
|
||
|
||
// returns the level of the given node
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Result := 0;
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
Run := Node.Parent;
|
||
while Run <> FRoot do
|
||
begin
|
||
Run := Run.Parent;
|
||
Inc(Result);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPrevious(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns previous node in tree. If ConsiderChildrenAbove is True the function considers
|
||
// whether toChildrenAbove is currently set, otherwise the result will always be the previous
|
||
// node in top-down order regardless of the current PaintOptions.
|
||
// The Result will be initialized if needed.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
// Has this node got children? Initialize them if necessary.
|
||
if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then
|
||
InitChildren(Result);
|
||
|
||
// If there is a last child, take it; if not try the previous sibling.
|
||
if Assigned(Result.LastChild) then
|
||
Result := Result.LastChild
|
||
else if Assigned(Result.PrevSibling) then
|
||
Result := Result.PrevSibling
|
||
else
|
||
begin
|
||
// If neither a last child nor a previous sibling exist, go the tree upwards and
|
||
// look, wether one of the parent nodes have a previous sibling. If not the result
|
||
// will ne nil.
|
||
repeat
|
||
Result := Result.Parent;
|
||
Run := nil;
|
||
if Result <> FRoot then
|
||
Run := Result.PrevSibling
|
||
else
|
||
Result := nil;
|
||
until Assigned(Run) or (Result = nil);
|
||
|
||
if Assigned(Run) then
|
||
Result := Run;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// Is there a previous sibling?
|
||
if Assigned(Node.PrevSibling) then
|
||
begin
|
||
// Go down and find the last child node.
|
||
Result := GetLast(Node.PrevSibling);
|
||
if Result = nil then
|
||
Result := Node.PrevSibling;
|
||
end
|
||
else
|
||
// no previous sibling so the parent of the node is the previous visible node
|
||
if Node.Parent <> FRoot then
|
||
Result := Node.Parent
|
||
else
|
||
Result := nil;
|
||
end;
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal;
|
||
ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetLastNoInit(nil, ConsiderChildrenAbove)
|
||
else
|
||
Result := GetPreviousNoInit(Node, ConsiderChildrenAbove);
|
||
|
||
while Assigned(Result) and (Result.CheckState <> State) do
|
||
Result := GetPreviousNoInit(Result, ConsiderChildrenAbove);
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the previous node in the tree which is currently marked for a clipboard operation. Since only visible nodes can
|
||
// be marked (or they are hidden after they have been marked) it is not necessary to initialize nodes to check for
|
||
// child nodes. The result, however, is initialized if necessary.
|
||
|
||
begin
|
||
if ClipboardStates * FStates <> [] then
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := GetLastNoInit(nil, ConsiderChildrenAbove)
|
||
else
|
||
Result := GetPreviousNoInit(Node, ConsiderChildrenAbove);
|
||
while Assigned(Result) and not (vsCutOrCopy in Result.States) do
|
||
Result := GetPreviousNoInit(Result, ConsiderChildrenAbove);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the previous node in tree which is initialized.
|
||
|
||
begin
|
||
Result := Node;
|
||
repeat
|
||
Result := GetPreviousNoInit(Result, ConsiderChildrenAbove);
|
||
until (Result = nil) or (vsInitialized in Result.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousLeaf(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the previous node in the tree which has currently no children.
|
||
// The result is initialized if necessary.
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.LastChild
|
||
else
|
||
Result := GetPrevious(Node);
|
||
while Assigned(Result) and (vsHasChildren in Result.States) do
|
||
Result := GetPrevious(Result);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode;
|
||
|
||
// Returns the previous node in the tree on a specific level.
|
||
// The result is initialized if necessary.
|
||
|
||
var
|
||
StartNodeLevel: Cardinal;
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Result := nil;
|
||
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
StartNodeLevel := GetNodeLevel(Node);
|
||
|
||
if StartNodeLevel < NodeLevel then
|
||
begin
|
||
Result := Node.PrevSibling;
|
||
if Assigned(Result) then
|
||
begin
|
||
// go to last descendant of previous sibling with desired node level (if exists)
|
||
Run := Result;
|
||
while Assigned(Run) and (GetNodeLevel(Run) < NodeLevel) do
|
||
begin
|
||
Result := Run;
|
||
Run := GetLastChild(Run);
|
||
end;
|
||
if Assigned(Run) and (GetNodeLevel(Run) = NodeLevel) then
|
||
Result := Run
|
||
else
|
||
begin
|
||
if Assigned(Result.PrevSibling) then
|
||
Result := GetPreviousLevel(Result, NodeLevel)
|
||
else if Assigned(Result) and (Result.Parent <> FRoot) then
|
||
Result := GetPreviousLevel(Result.Parent, NodeLevel)
|
||
else
|
||
Result := nil;
|
||
end;
|
||
end
|
||
else
|
||
Result := GetPreviousLevel(Node.Parent, NodeLevel);
|
||
end
|
||
else if StartNodeLevel = NodeLevel then
|
||
begin
|
||
Result := Node.PrevSibling;
|
||
if not Assigned(Result) then // i.e. start node was a first sibling
|
||
begin
|
||
Result := Node.Parent;
|
||
if Assigned(Result) then
|
||
Result := GetPreviousLevel(Result, NodeLevel);
|
||
end;
|
||
end
|
||
else // i.e. StartNodeLevel > NodeLevel
|
||
Result := GetPreviousLevel(Node.Parent, NodeLevel);
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns previous node in tree, optionally considering toChildrenAbove. No initialization is performed.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
// If there is a last child, take it; if not try the previous sibling.
|
||
if Assigned(Result.LastChild) then
|
||
Result := Result.LastChild
|
||
else if Assigned(Result.PrevSibling) then
|
||
Result := Result.PrevSibling
|
||
else
|
||
begin
|
||
// If neither a last child nor a previous sibling exist, go the tree upwards and
|
||
// look, wether one of the parent nodes have a previous sibling. If not the result
|
||
// will ne nil.
|
||
repeat
|
||
Result := Result.Parent;
|
||
Run := nil;
|
||
if Result <> FRoot then
|
||
Run := Result.PrevSibling
|
||
else
|
||
Result := nil;
|
||
until Assigned(Run) or (Result = nil);
|
||
|
||
if Assigned(Run) then
|
||
Result := Run;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// Is there a previous sibling?
|
||
if Assigned(Node.PrevSibling) then
|
||
begin
|
||
// Go down and find the last child node.
|
||
Result := GetLastNoInit(Node.PrevSibling);
|
||
if Result = nil then
|
||
Result := Node.PrevSibling;
|
||
end
|
||
else
|
||
// No previous sibling so the parent of the node is the previous node.
|
||
if Node.Parent <> FRoot then
|
||
Result := Node.Parent
|
||
else
|
||
Result := nil
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
||
|
||
// Returns the previous node in the tree which is currently selected. Since children of unitialized nodes cannot be
|
||
// in the current selection (because they simply do not exist yet) it is not necessary to initialize nodes here.
|
||
// The result however is initialized if necessary.
|
||
|
||
begin
|
||
if FSelectionCount > 0 then
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := FRoot.LastChild
|
||
else
|
||
Result := GetPreviousNoInit(Node, ConsiderChildrenAbove);
|
||
while Assigned(Result) and not (vsSelected in Result.States) do
|
||
Result := GetPreviousNoInit(Result, ConsiderChildrenAbove);
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousSibling(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Get next sibling of Node, initialize it if necessary.
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
Result := Result.PrevSibling;
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the previous node in tree, with regard to Node, which is visible.
|
||
// Nodes which need an initialization (including the result) are initialized.
|
||
// toChildrenAbove is optionally considered which is the default here.
|
||
|
||
var
|
||
Marker: PVirtualNode;
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
repeat
|
||
// If the given node is not visible then look for a parent node which is visible and use its last visible
|
||
// child or the parent node (if there is no visible child) as result.
|
||
if not FullyVisible[Result] then
|
||
begin
|
||
Result := GetVisibleParent(Result);
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
Marker := GetLastVisible(Result, True);
|
||
if Assigned(Marker) then
|
||
Result := Marker;
|
||
end
|
||
else
|
||
begin
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
repeat
|
||
if Assigned(Result.LastChild) and (vsExpanded in Result.States) then
|
||
begin
|
||
Result := Result.LastChild;
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else if Assigned(Result.PrevSibling) then
|
||
begin
|
||
if not (vsInitialized in Result.PrevSibling.States) then
|
||
InitNode(Result.PrevSibling);
|
||
|
||
if vsVisible in Result.PrevSibling.States then
|
||
begin
|
||
Result := Result.PrevSibling;
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
Marker := nil;
|
||
repeat
|
||
Result := Result.Parent;
|
||
if Result <> FRoot then
|
||
Marker := GetPreviousVisibleSibling(Result)
|
||
else
|
||
Result := nil;
|
||
until Assigned(Marker) or (Result = nil);
|
||
if Assigned(Marker) then
|
||
Result := Marker;
|
||
|
||
Break;
|
||
end;
|
||
until False;
|
||
end
|
||
else
|
||
begin
|
||
repeat
|
||
// Is there a previous sibling node?
|
||
if Assigned(Result.PrevSibling) then
|
||
begin
|
||
Result := Result.PrevSibling;
|
||
// Initialize the new node and check its visibility.
|
||
if not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
if vsVisible in Result.States then
|
||
begin
|
||
// If there are visible child nodes then use the last one.
|
||
Marker := GetLastVisible(Result, True);
|
||
if Assigned(Marker) then
|
||
Result := Marker;
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// No previous sibling there so the parent node is the nearest previous node.
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
until False;
|
||
end;
|
||
|
||
if Assigned(Result) and not (vsInitialized in Result.States) then
|
||
InitNode(Result);
|
||
end;
|
||
until not Assigned(Result) or (vsVisible in Result.States);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousVisibleNoInit(Node: PVirtualNode;
|
||
ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
||
|
||
// Returns the previous node in tree, with regard to Node, which is visible.
|
||
// toChildrenAbove is optionally considered which is the default here.
|
||
|
||
var
|
||
Marker: PVirtualNode;
|
||
|
||
begin
|
||
Result := Node;
|
||
if Assigned(Result) then
|
||
begin
|
||
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
repeat
|
||
// If the given node is not visible then look for a parent node which is visible and use its last visible
|
||
// child or the parent node (if there is no visible child) as result.
|
||
if not FullyVisible[Result] then
|
||
begin
|
||
Result := GetVisibleParent(Result);
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
Marker := GetLastVisibleNoInit(Result, True);
|
||
if Assigned(Marker) then
|
||
Result := Marker;
|
||
end
|
||
else
|
||
begin
|
||
if ConsiderChildrenAbove and (toChildrenAbove in FOptions.FPaintOptions) then
|
||
begin
|
||
repeat
|
||
// Is the current node expanded and has children?
|
||
if (vsExpanded in Result.States) and Assigned(Result.LastChild) then
|
||
begin
|
||
Result := Result.LastChild;
|
||
if vsVisible in Result.States then
|
||
Break;
|
||
end
|
||
else if Assigned(Result.PrevSibling) then
|
||
begin
|
||
// No children anymore, so take the previous sibling.
|
||
if vsVisible in Result.PrevSibling.States then
|
||
begin
|
||
Result := Result.PrevSibling;
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// No children and no previous siblings, so walk up the tree and look wether
|
||
// a parent has a previous visible sibling. If that is the case take it,
|
||
// otherwise there is no previous visible node.
|
||
Marker := nil;
|
||
repeat
|
||
Result := Result.Parent;
|
||
if Result <> FRoot then
|
||
Marker := GetPreviousVisibleSiblingNoInit(Result)
|
||
else
|
||
Result := nil;
|
||
until Assigned(Marker) or (Result = nil);
|
||
if Assigned(Marker) then
|
||
Result := Marker;
|
||
Break;
|
||
end;
|
||
until False;
|
||
end
|
||
else
|
||
begin
|
||
repeat
|
||
// Is there a previous sibling node?
|
||
if Assigned(Result.PrevSibling) then
|
||
begin
|
||
Result := Result.PrevSibling;
|
||
if vsVisible in Result.States then
|
||
begin
|
||
// If there are visible child nodes then use the last one.
|
||
Marker := GetLastVisibleNoInit(Result, True);
|
||
if Assigned(Marker) then
|
||
Result := Marker;
|
||
Break;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// No previous sibling there so the parent node is the nearest previous node.
|
||
Result := Result.Parent;
|
||
if Result = FRoot then
|
||
Result := nil;
|
||
Break;
|
||
end;
|
||
until False;
|
||
end;
|
||
end;
|
||
until not Assigned(Result) or (vsVisible in Result.States);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousVisibleSibling(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the previous visible sibling before Node. Initialization is done implicitly.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
||
|
||
Result := Node;
|
||
repeat
|
||
Result := GetPreviousSibling(Result);
|
||
until not Assigned(Result) or (vsVisible in Result.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetPreviousVisibleSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the previous visible sibling before Node.
|
||
|
||
begin
|
||
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
||
|
||
Result := Node;
|
||
repeat
|
||
Result := Result.PrevSibling;
|
||
until not Assigned(Result) or (vsVisible in Result.States);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetSortedCutCopySet(Resolve: Boolean): TNodeArray;
|
||
|
||
// Same as GetSortedSelection but with nodes marked as being part in the current cut/copy set (e.g. for clipboard).
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
Counter: Cardinal;
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
procedure IncludeThisNode(Node: PVirtualNode);
|
||
|
||
// adds the given node to the result
|
||
|
||
var
|
||
Len: Cardinal;
|
||
|
||
begin
|
||
Len := Length(Result);
|
||
if Counter = Len then
|
||
begin
|
||
if Len < 100 then
|
||
Len := 100
|
||
else
|
||
Len := Len + Len div 10;
|
||
SetLength(Result, Len);
|
||
end;
|
||
Result[Counter] := Node;
|
||
Inc(Counter);
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
begin
|
||
Run := FRoot.FirstChild;
|
||
Counter := 0;
|
||
if Resolve then
|
||
begin
|
||
// Resolving is actually easy: just find the first cutted node in logical order
|
||
// and then never go deeper in level than this node as long as there's a sibling node.
|
||
// Restart the search for a cutted node (at any level) if there are no further siblings.
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsCutOrCopy in Run.States then
|
||
begin
|
||
IncludeThisNode(Run);
|
||
if Assigned(Run.NextSibling) then
|
||
Run := Run.NextSibling
|
||
else
|
||
begin
|
||
// If there are no further siblings then go up one or more levels until a node is
|
||
// found or all nodes have been processed. Although we consider here only initialized
|
||
// nodes we don't need to make any special checks as only initialized nodes can also be selected.
|
||
repeat
|
||
Run := Run.Parent;
|
||
until (Run = FRoot) or Assigned(Run.NextSibling);
|
||
if Run = FRoot then
|
||
Break
|
||
else
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end
|
||
else
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
end
|
||
else
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsCutOrCopy in Run.States then
|
||
IncludeThisNode(Run);
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
|
||
// set the resulting array to its real length
|
||
SetLength(Result, Counter);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetSortedSelection(Resolve: Boolean): TNodeArray;
|
||
|
||
// Returns a list of selected nodes sorted in logical order, that is, as they appear in the tree.
|
||
// If Resolve is True then nodes which are children of other selected nodes are not put into the new array.
|
||
// This feature is in particuar important when doing drag'n drop as in this case all selected node plus their children
|
||
// need to be considered. A selected node which is child (grand child etc.) of another selected node is then
|
||
// automatically included and doesn't need to be explicitely mentioned in the returned selection array.
|
||
//
|
||
// Note: The caller is responsible for freeing the array. Allocation is done here. Usually, though, freeing the array
|
||
// doesn't need additional attention as it is automatically freed by Delphi when it gets out of scope.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
Counter: Cardinal;
|
||
|
||
begin
|
||
SetLength(Result, FSelectionCount);
|
||
if FSelectionCount > 0 then
|
||
begin
|
||
Run := FRoot.FirstChild;
|
||
Counter := 0;
|
||
if Resolve then
|
||
begin
|
||
// Resolving is actually easy: just find the first selected node in logical order
|
||
// and then never go deeper in level than this node as long as there's a sibling node.
|
||
// Restart the search for a selected node (at any level) if there are no further siblings.
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsSelected in Run.States then
|
||
begin
|
||
Result[Counter] := Run;
|
||
Inc(Counter);
|
||
if Assigned(Run.NextSibling) then
|
||
Run := Run.NextSibling
|
||
else
|
||
begin
|
||
// If there are no further siblings then go up one or more levels until a node is
|
||
// found or all nodes have been processed. Although we consider here only initialized
|
||
// nodes we don't need to make any special checks as only initialized nodes can also be selected.
|
||
repeat
|
||
Run := Run.Parent;
|
||
until (Run = FRoot) or Assigned(Run.NextSibling);
|
||
if Run = FRoot then
|
||
Break
|
||
else
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end
|
||
else
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
end
|
||
else
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsSelected in Run.States then
|
||
begin
|
||
Result[Counter] := Run;
|
||
Inc(Counter);
|
||
end;
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
|
||
// Since we may have skipped some nodes the result array is likely to be smaller than the
|
||
// selection array, hence shorten the result to true length.
|
||
if Integer(Counter) < Length(Result) then
|
||
SetLength(Result, Counter);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect;
|
||
out Text: String);
|
||
|
||
// Generic base method for editors, hint windows etc. to get some info about a node.
|
||
|
||
begin
|
||
R := Rect(0, 0, 0, 0);
|
||
Text := '';
|
||
AFont.Assign(Font);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetTreeRect: TRect;
|
||
|
||
// Returns the true size of the tree in pixels. This size is at least ClientHeight x ClientWidth and depends on
|
||
// the expand state, header size etc.
|
||
// Note: if no columns are used then the width of the tree is determined by the largest node which is currently in the
|
||
// client area. This might however not be the largest node in the entire tree.
|
||
|
||
begin
|
||
Result := Rect(0, 0, Max(FRangeX, ClientWidth), Max(FRangeY, ClientHeight));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.GetVisibleParent(Node: PVirtualNode): PVirtualNode;
|
||
|
||
// Returns the first (nearest) parent node of Node which is visible.
|
||
// This method is one of the seldom cases where the hidden root node could be returned.
|
||
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil.');
|
||
Assert(Node <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
Result := Node.Parent;
|
||
while (Result <> FRoot) and not FullyVisible[Result] do
|
||
Result := Result.Parent;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.HasAsParent(Node, PotentialParent: PVirtualNode): Boolean;
|
||
|
||
// Determines whether Node has got PotentialParent as one of its parents.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Result := Assigned(Node) and Assigned(PotentialParent) and (Node <> PotentialParent);
|
||
if Result then
|
||
begin
|
||
Run := Node;
|
||
while (Run <> FRoot) and (Run <> PotentialParent) do
|
||
Run := Run.Parent;
|
||
Result := Run = PotentialParent;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InsertNode(Node: PVirtualNode; Mode: TVTNodeAttachMode; UserData: Pointer = nil): PVirtualNode;
|
||
|
||
// Adds a new node relative to Node. The final position is determined by Mode.
|
||
// UserData can be used to set the first 4 bytes of the user data area to an initial value which can be used
|
||
// in OnInitNode and will also cause to trigger the OnFreeNode event (if <> nil) even if the node is not yet
|
||
// "officially" initialized.
|
||
// InsertNode is a compatibility method and will implicitly validate the given node if the new node
|
||
// is to be added as child node. This is however against the virtual paradigm and hence I dissuade from its usage.
|
||
|
||
var
|
||
NodeData: ^Pointer;
|
||
|
||
begin
|
||
if Mode <> amNoWhere then
|
||
begin
|
||
CancelEditNode;
|
||
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
// we need a new node...
|
||
Result := MakeNewNode;
|
||
// avoid erronous attach modes
|
||
if Node = FRoot then
|
||
begin
|
||
case Mode of
|
||
amInsertBefore:
|
||
Mode := amAddChildFirst;
|
||
amInsertAfter:
|
||
Mode := amAddChildLast;
|
||
end;
|
||
end;
|
||
|
||
// Validate given node in case the new node becomes its child.
|
||
if (Mode in [amAddChildFirst, amAddChildLast]) and not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
InternalConnectNode(Result, Node, Self, Mode);
|
||
|
||
// Check if there is initial user data and there is also enough user data space allocated.
|
||
if Assigned(UserData) then
|
||
if FNodeDataSize >= 4 then
|
||
begin
|
||
NodeData := Pointer(PByte(@Result.Data) + FTotalInternalDataSize);
|
||
NodeData^ := UserData;
|
||
Include(Result.States, vsInitialUserData);
|
||
end
|
||
else
|
||
ShowError(SCannotSetUserData, hcTFCannotSetUserData);
|
||
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
// If auto sort is enabled then sort the node or its parent (depending on the insert mode).
|
||
if (toAutoSort in FOptions.FAutoOptions) and (FHeader.FSortColumn > InvalidColumn) then
|
||
case Mode of
|
||
amInsertBefore,
|
||
amInsertAfter:
|
||
// Here no initialization is necessary because *if* a node has already got children then it
|
||
// must also be initialized.
|
||
// Note: Node can never be FRoot at this point.
|
||
Sort(Node.Parent, FHeader.FSortColumn, FHeader.FSortDirection, True);
|
||
amAddChildFirst,
|
||
amAddChildLast:
|
||
Sort(Node, FHeader.FSortColumn, FHeader.FSortDirection, True);
|
||
end;
|
||
|
||
UpdateScrollbars(True);
|
||
if Mode = amInsertBefore then
|
||
InvalidateToBottom(Result)
|
||
else
|
||
InvalidateToBottom(Node);
|
||
end;
|
||
StructureChange(Result, crNodeAdded);
|
||
end
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InvalidateChildren(Node: PVirtualNode; Recursive: Boolean);
|
||
|
||
// Invalidates Node and its immediate children.
|
||
// If Recursive is True then all grandchildren are invalidated as well.
|
||
// The node itself is initialized if necessary and its child nodes are created (and initialized too if
|
||
// Recursive is True).
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if Assigned(Node) then
|
||
begin
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
InvalidateNode(Node);
|
||
if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then
|
||
InitChildren(Node);
|
||
Run := Node.FirstChild;
|
||
end
|
||
else
|
||
Run := FRoot.FirstChild;
|
||
|
||
while Assigned(Run) do
|
||
begin
|
||
InvalidateNode(Run);
|
||
if Recursive then
|
||
InvalidateChildren(Run, True);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex);
|
||
|
||
// Invalidates the client area part of a column.
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
if (FUpdateCount = 0) and HandleAllocated and FHeader.FColumns.IsValidColumn(Column) then
|
||
begin
|
||
R := ClientRect;
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
OffsetRect(R, 0, FHeader.Height);
|
||
FHeader.Columns.GetColumnBounds(Column, R.Left, R.Right);
|
||
InvalidateRect(Handle, @R, False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.InvalidateNode(Node: PVirtualNode): TRect;
|
||
|
||
// Initiates repaint of the given node and returns the just invalidated rectangle.
|
||
|
||
begin
|
||
if (FUpdateCount = 0) and HandleAllocated then
|
||
begin
|
||
Result := GetDisplayRect(Node, NoColumn, False);
|
||
InvalidateRect(Handle, @Result, False);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InvalidateToBottom(Node: PVirtualNode);
|
||
|
||
// Initiates repaint of client area starting at given node. If this node is not visible or not yet initialized
|
||
// then nothing happens.
|
||
|
||
var
|
||
R: TRect;
|
||
|
||
begin
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Invalidate
|
||
else
|
||
if (vsInitialized in Node.States) and (vsVisible in Node.States) then
|
||
begin
|
||
R := GetDisplayRect(Node, -1, False);
|
||
if R.Top < ClientHeight then
|
||
begin
|
||
if (toChildrenAbove in FOptions.FPaintOptions) and (vsExpanded in Node.States) then
|
||
Dec(R.Top, Node.TotalHeight + NodeHeight[Node]);
|
||
R.Bottom := ClientHeight;
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
Inc(R.Bottom, FHeader.Height);
|
||
InvalidateRect(Handle, @R, False);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.InvertSelection(VisibleOnly: Boolean);
|
||
|
||
// Inverts the current selection (so nodes which are selected become unselected and vice versa).
|
||
// If VisibleOnly is True then only visible nodes are considered.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
NewSize: Integer;
|
||
NextFunction: TGetNextNodeProc;
|
||
TriggerChange: Boolean;
|
||
|
||
begin
|
||
if not FSelectionLocked and (toMultiSelect in FOptions.FSelectionOptions) then
|
||
begin
|
||
Run := FRoot.FirstChild;
|
||
ClearTempCache;
|
||
if VisibleOnly then
|
||
NextFunction := GetNextVisibleNoInit
|
||
else
|
||
NextFunction := GetNextNoInit;
|
||
while Assigned(Run) do
|
||
begin
|
||
if vsSelected in Run.States then
|
||
InternalRemoveFromSelection(Run)
|
||
else
|
||
InternalCacheNode(Run);
|
||
Run := NextFunction(Run);
|
||
end;
|
||
|
||
// do some housekeeping
|
||
// Need to trigger the OnChange event from here if nodes were only deleted but not added.
|
||
TriggerChange := False;
|
||
NewSize := PackArray(FSelection, FSelectionCount);
|
||
if NewSize > -1 then
|
||
begin
|
||
FSelectionCount := NewSize;
|
||
SetLength(FSelection, FSelectionCount);
|
||
TriggerChange := True;
|
||
end;
|
||
if FTempNodeCount > 0 then
|
||
begin
|
||
AddToSelection(FTempNodeCache, FTempNodeCount);
|
||
ClearTempCache;
|
||
TriggerChange := False;
|
||
end;
|
||
Invalidate;
|
||
if TriggerChange then
|
||
Change(nil);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.IsEditing: Boolean;
|
||
|
||
begin
|
||
Result := tsEditing in FStates;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.IsMouseSelecting: Boolean;
|
||
|
||
begin
|
||
Result := (tsDrawSelPending in FStates) or (tsDrawSelecting in FStates);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.IterateSubtree(Node: PVirtualNode; Callback: TVTGetNodeProc; Data: Pointer;
|
||
Filter: TVirtualNodeStates = []; DoInit: Boolean = False; ChildNodesOnly: Boolean = False): PVirtualNode;
|
||
|
||
// Iterates through the all children and grandchildren etc. of Node (or the entire tree if Node = nil)
|
||
// and calls for each node the provided callback method (which must not be empty).
|
||
// Filter determines which nodes to consider (an empty set denotes all nodes).
|
||
// If DoInit is True then nodes which aren't initialized yet will be initialized.
|
||
// Note: During execution of the callback the application can set Abort to True. In this case the iteration is stopped
|
||
// and the last accessed node (the one on which the callback set Abort to True) is returned to the caller.
|
||
// Otherwise (no abort) nil is returned.
|
||
|
||
var
|
||
Stop: PVirtualNode;
|
||
Abort: Boolean;
|
||
GetNextNode: TGetNextNodeProc;
|
||
WasIterating: Boolean;
|
||
|
||
begin
|
||
Assert(Node <> FRoot, 'Node must not be the hidden root node.');
|
||
|
||
WasIterating := tsIterating in FStates;
|
||
DoStateChange([tsIterating]);
|
||
try
|
||
// prepare function to be used when advancing
|
||
if DoInit then
|
||
GetNextNode := GetNext
|
||
else
|
||
GetNextNode := GetNextNoInit;
|
||
|
||
Abort := False;
|
||
if Node = nil then
|
||
Stop := nil
|
||
else
|
||
begin
|
||
if not (vsInitialized in Node.States) and DoInit then
|
||
InitNode(Node);
|
||
|
||
// The stopper does not need to be initialized since it is not taken into the enumeration.
|
||
Stop := Node.NextSibling;
|
||
if Stop = nil then
|
||
begin
|
||
Stop := Node;
|
||
repeat
|
||
Stop := Stop.Parent;
|
||
until (Stop = FRoot) or Assigned(Stop.NextSibling);
|
||
if Stop = FRoot then
|
||
Stop := nil
|
||
else
|
||
Stop := Stop.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
// Use first node if we start with the root.
|
||
if Node = nil then
|
||
Node := GetFirstNoInit;
|
||
|
||
if Assigned(Node) then
|
||
begin
|
||
if not (vsInitialized in Node.States) and DoInit then
|
||
InitNode(Node);
|
||
|
||
// Skip given node if only the child nodes are requested.
|
||
if ChildNodesOnly then
|
||
begin
|
||
if Node.ChildCount = 0 then
|
||
Node := nil
|
||
else
|
||
Node := GetNextNode(Node);
|
||
end;
|
||
|
||
if Filter = [] then
|
||
begin
|
||
// unfiltered loop
|
||
while Assigned(Node) and (Node <> Stop) do
|
||
begin
|
||
Callback(Self, Node, Data, Abort);
|
||
if Abort then
|
||
Break;
|
||
Node := GetNextNode(Node);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// filtered loop
|
||
while Assigned(Node) and (Node <> Stop) do
|
||
begin
|
||
if Node.States * Filter = Filter then
|
||
Callback(Self, Node, Data, Abort);
|
||
if Abort then
|
||
Break;
|
||
Node := GetNextNode(Node)
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if Abort then
|
||
Result := Node
|
||
else
|
||
Result := nil;
|
||
finally
|
||
if not WasIterating then
|
||
DoStateChange([], [tsIterating]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.LoadFromFile(const FileName: TFileName);
|
||
|
||
var
|
||
FileStream: TFileStream;
|
||
|
||
begin
|
||
FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
||
try
|
||
LoadFromStream(FileStream);
|
||
finally
|
||
FileStream.Free;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.LoadFromStream(Stream: TStream);
|
||
|
||
// Clears the current content of the tree and loads a new structure from the given stream.
|
||
|
||
var
|
||
ThisID: TMagicID;
|
||
Version,
|
||
Count: Cardinal;
|
||
Node: PVirtualNode;
|
||
|
||
begin
|
||
if not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
Clear;
|
||
// Check first whether this is a stream we can read.
|
||
if Stream.Read(ThisID, SizeOf(TMagicID)) < SizeOf(TMagicID) then
|
||
ShowError(SStreamTooSmall, hcTFStreamTooSmall);
|
||
|
||
if (ThisID[0] = MagicID[0]) and
|
||
(ThisID[1] = MagicID[1]) and
|
||
(ThisID[2] = MagicID[2]) and
|
||
(ThisID[5] = MagicID[5]) then
|
||
begin
|
||
Version := Word(ThisID[3]);
|
||
if Version <= VTTreeStreamVersion then
|
||
begin
|
||
BeginUpdate;
|
||
try
|
||
// LCL port started with tree stream version 2 so no need to do the check here
|
||
Stream.ReadBuffer(Count, SizeOf(Count));
|
||
|
||
while (Stream.Position < Stream.Size) and (Count > 0) do
|
||
begin
|
||
Dec(Count);
|
||
Node := MakeNewNode;
|
||
InternalConnectNode(Node, FRoot, Self, amAddChildLast);
|
||
InternalAddFromStream(Stream, Version, Node);
|
||
end;
|
||
DoNodeCopied(nil);
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end
|
||
else
|
||
ShowError(SWrongStreamVersion, hcTFWrongStreamVersion);
|
||
end
|
||
else
|
||
ShowError(SWrongStreamFormat, hcTFWrongStreamFormat);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode);
|
||
|
||
// If the height of the given node has not yet been measured then do it now.
|
||
|
||
var
|
||
NewNodeHeight: Integer;
|
||
|
||
begin
|
||
if not (vsHeightMeasured in Node.States) then
|
||
begin
|
||
Include(Node.States, vsHeightMeasured);
|
||
NewNodeHeight := Node.NodeHeight;
|
||
DoMeasureItem(Canvas, Node, NewNodeHeight);
|
||
if NewNodeHeight <> Node.NodeHeight then
|
||
SetNodeHeight(Node, NewNodeHeight);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.MoveTo(Node: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode;
|
||
ChildrenOnly: Boolean);
|
||
|
||
// A simplified method to allow to move nodes to the root of another tree.
|
||
|
||
begin
|
||
MoveTo(Node, Tree.FRoot, Mode, ChildrenOnly);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.MoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean);
|
||
|
||
// Moves the given node (and all its children) to Target. Source must belong to the tree instance which calls this
|
||
// MoveTo method. Mode determines how to connect Source to Target.
|
||
// This method might involve a change of the tree if Target belongs to a different tree than Source.
|
||
|
||
var
|
||
TargetTree: TBaseVirtualTree;
|
||
Allowed: Boolean;
|
||
NewNode: PVirtualNode;
|
||
Stream: TMemoryStream;
|
||
|
||
begin
|
||
Assert(TreeFromNode(Source) = Self, 'The source tree must contain the source node.');
|
||
|
||
// When moving nodes then source and target must not be the same node unless only the source's children are
|
||
// moved and they are inserted before or after the node itself.
|
||
Allowed := (Source <> Target) or ((Mode in [amInsertBefore, amInsertAfter]) and ChildrenOnly);
|
||
|
||
if Allowed and (Mode <> amNoWhere) and Assigned(Source) and (Source <> FRoot) and
|
||
not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
// Assume that an empty destination means the root in this (the source) tree.
|
||
if Target = nil then
|
||
begin
|
||
TargetTree := Self;
|
||
Target := FRoot;
|
||
Mode := amAddChildFirst;
|
||
end
|
||
else
|
||
TargetTree := TreeFromNode(Target);
|
||
|
||
if Target = TargetTree.FRoot then
|
||
begin
|
||
case Mode of
|
||
amInsertBefore:
|
||
Mode := amAddChildFirst;
|
||
amInsertAfter:
|
||
Mode := amAddChildLast;
|
||
end;
|
||
end;
|
||
|
||
// Make sure the target node is initialized.
|
||
if not (vsInitialized in Target.States) then
|
||
InitNode(Target)
|
||
else
|
||
if (vsHasChildren in Target.States) and (Target.ChildCount = 0) then
|
||
InitChildren(Target);
|
||
|
||
if TargetTree = Self then
|
||
begin
|
||
// Simple case: move node(s) within the same tree.
|
||
if Target = FRoot then
|
||
Allowed := DoNodeMoving(Source, nil)
|
||
else
|
||
Allowed := DoNodeMoving(Source, Target);
|
||
if Allowed then
|
||
begin
|
||
// Check first that Source is not added as new child to a target node which
|
||
// is already a child of Source.
|
||
// Consider the case Source and Target are the same node, but only child nodes are moved.
|
||
if (Source <> Target) and HasAsParent(Target, Source) then
|
||
ShowError(SWrongMoveError, hcTFWrongMoveError);
|
||
|
||
if not ChildrenOnly then
|
||
begin
|
||
// Disconnect from old location.
|
||
InternalDisconnectNode(Source, True);
|
||
// Connect to new location.
|
||
InternalConnectNode(Source, Target, Self, Mode);
|
||
DoNodeMoved(Source);
|
||
end
|
||
else
|
||
begin
|
||
// Only child nodes should be moved. Insertion order depends on move mode.
|
||
if Mode = amAddChildFirst then
|
||
begin
|
||
Source := Source.LastChild;
|
||
while Assigned(Source) do
|
||
begin
|
||
NewNode := Source.PrevSibling;
|
||
// Disconnect from old location.
|
||
InternalDisconnectNode(Source, True, False);
|
||
// Connect to new location.
|
||
InternalConnectNode(Source, Target, Self, Mode);
|
||
DoNodeMoved(Source);
|
||
Source := NewNode;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
Source := Source.FirstChild;
|
||
while Assigned(Source) do
|
||
begin
|
||
NewNode := Source.NextSibling;
|
||
// Disconnect from old location.
|
||
InternalDisconnectNode(Source, True, False);
|
||
// Connect to new location.
|
||
InternalConnectNode(Source, Target, Self, Mode);
|
||
DoNodeMoved(Source);
|
||
Source := NewNode;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// Difficult case: move node(s) to another tree.
|
||
// In opposition to node copying we ask only once if moving is allowed because
|
||
// we cannot take back a move once done.
|
||
if Target = TargetTree.FRoot then
|
||
Allowed := DoNodeMoving(Source, nil)
|
||
else
|
||
Allowed := DoNodeMoving(Source, Target);
|
||
|
||
if Allowed then
|
||
begin
|
||
Stream := TMemoryStream.Create;
|
||
try
|
||
// Write all nodes into a temporary stream depending on the ChildrenOnly flag.
|
||
if not ChildrenOnly then
|
||
WriteNode(Stream, Source)
|
||
else
|
||
begin
|
||
Source := Source.FirstChild;
|
||
while Assigned(Source) do
|
||
begin
|
||
WriteNode(Stream, Source);
|
||
Source := Source.NextSibling;
|
||
end;
|
||
end;
|
||
// Now load the serialized nodes into the target node (tree).
|
||
TargetTree.BeginUpdate;
|
||
try
|
||
Stream.Position := 0;
|
||
while Stream.Position < Stream.Size do
|
||
begin
|
||
NewNode := TargetTree.MakeNewNode;
|
||
InternalConnectNode(NewNode, Target, TargetTree, Mode);
|
||
TargetTree.InternalAddFromStream(Stream, VTTreeStreamVersion, NewNode);
|
||
DoNodeMoved(NewNode);
|
||
end;
|
||
finally
|
||
TargetTree.EndUpdate;
|
||
end;
|
||
finally
|
||
Stream.Free;
|
||
end;
|
||
// finally delete original nodes
|
||
BeginUpdate;
|
||
try
|
||
if ChildrenOnly then
|
||
DeleteChildren(Source)
|
||
else
|
||
DeleteNode(Source);
|
||
finally
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
InvalidateCache;
|
||
if (FUpdateCount = 0) and Allowed then
|
||
begin
|
||
ValidateCache;
|
||
UpdateScrollBars(True);
|
||
Invalidate;
|
||
if TargetTree <> Self then
|
||
TargetTree.Invalidate;
|
||
end;
|
||
StructureChange(Source, crNodeMoved);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PaintTree(TargetCanvas: TCanvas; const Window: TRect; Target: TPoint;
|
||
PaintOptions: TVTInternalPaintOptions; PixelFormat: TPixelFormat);
|
||
|
||
// This is the core paint routine of the tree. It is responsible for maintaining the paint cycles per node as well
|
||
// as coordinating drawing of the various parts of the tree image.
|
||
// TargetCanvas is the canvas to which to draw the tree image. This is usually the tree window itself but could well
|
||
// be a bitmap or printer canvas.
|
||
// Window determines which part of the entire tree image to draw. The full size of the virtual image is determined
|
||
// by GetTreeRect.
|
||
// Target is the position in TargetCanvas where to draw the tree part specified by Window.
|
||
// PaintOptions determines what of the tree to draw. For different tasks usually different parts need to be drawn, with
|
||
// a full image in the window, selected only nodes for a drag image etc.
|
||
|
||
const
|
||
ImageKind: array[Boolean] of TVTImageKind = (ikNormal, ikSelected);
|
||
|
||
var
|
||
DrawSelectionRect,
|
||
UseBackground,
|
||
ShowImages,
|
||
ShowStateImages,
|
||
ShowCheckImages,
|
||
UseColumns,
|
||
IsMainColumn: Boolean;
|
||
{$ifdef ManualClipNeeded}
|
||
YCorrect,
|
||
{$endif}
|
||
VAlign,
|
||
IndentSize,
|
||
ButtonX,
|
||
ButtonY: Integer;
|
||
LineImage: TLineImage;
|
||
PaintInfo: TVTPaintInfo; // all necessary information about a node to pass to the paint routines
|
||
|
||
R, // the area of an entire node in its local coordinate
|
||
TargetRect, // the area of a node (part) in the target canvas
|
||
SelectionRect: TRect; // ordered rectangle used for drawing the selection focus rect
|
||
NextColumn: TColumnIndex;
|
||
BaseOffset: Integer; // top position of the top node to draw given in absolute tree coordinates
|
||
NodeBitmap: TBitmap; // small buffer to draw flicker free
|
||
MaximumRight, // maximum horizontal target position
|
||
MaximumBottom: Integer; // maximum vertical target position
|
||
SelectLevel: Integer; // > 0 if current node is selected or child/grandchild etc. of a selected node
|
||
FirstColumn: TColumnIndex; // index of first column which is at least partially visible in the given window
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaint],'PaintTree');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaint, lcHeaderOffset],'Window',Window);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaint, lcHeaderOffset],'Target',Target);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'ClientRect',ClientRect);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'TreeRect',GetTreeRect);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader],'OffsetX: %d OffsetY: %d',[OffsetX,OffsetY]);{$endif}
|
||
//lcl changes to 24bit color depth when screen depth is 32 bit
|
||
//todo: remove when this limitation is removed
|
||
{$ifdef Windows}
|
||
if (PixelFormat = pfDevice) and (ScreenInfo.ColorDepth = 32) then
|
||
PixelFormat := pf32bit;
|
||
{$endif}
|
||
if not (tsPainting in FStates) then
|
||
begin
|
||
DoStateChange([tsPainting]);
|
||
try
|
||
DoBeforePaint(TargetCanvas);
|
||
|
||
// Create small bitmaps and initialize default values.
|
||
// The bitmaps are used to paint one node at a time and to draw the result to the target (e.g. screen) in one step,
|
||
// to prevent flickering.
|
||
NodeBitmap := TBitmap.Create;
|
||
// For alpha blending we need the 32 bit pixel format. For other targets there might be a need for a certain
|
||
// pixel format (e.g. printing).
|
||
if MMXAvailable and ((FDrawSelectionMode = smBlendedRectangle) or (tsUseThemes in FStates) or
|
||
(toUseBlendedSelection in FOptions.PaintOptions)) then
|
||
NodeBitmap.PixelFormat := pf32Bit
|
||
else
|
||
NodeBitmap.PixelFormat := PixelFormat;
|
||
|
||
// Prepare paint info structure and lock the back bitmap canvas to avoid that it gets freed on the way.
|
||
FillChar(PaintInfo, SizeOf(PaintInfo), 0);
|
||
PaintInfo.Canvas := NodeBitmap.Canvas;
|
||
NodeBitmap.Canvas.Lock;
|
||
try
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'FNewSelRect', FNewSelRect);{$endif}
|
||
// Prepare the current selection rectangle once. The corner points are absolute tree coordinates.
|
||
SelectionRect := OrderRect(FNewSelRect);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails, lcSelection],'SelectionRect', SelectionRect);{$endif}
|
||
DrawSelectionRect := IsMouseSelecting and not IsRectEmpty(SelectionRect);
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'DrawSelectionRect',DrawSelectionRect);{$endif}
|
||
// R represents an entire node (all columns), but is a bit unprecise when it comes to
|
||
// trees without any column defined, because FRangeX only represents the maximum width of all
|
||
// nodes in the client area (not all defined nodes). There might be, however, wider nodes somewhere. Without full
|
||
// validation I cannot better determine the width, though. By using at least the control's width it is ensured
|
||
// that the tree is fully displayed on screen.
|
||
R := Rect(0, 0, Max(FRangeX, ClientWidth), 0);
|
||
NodeBitmap.Width := Window.Right - Window.Left;
|
||
|
||
// For quick checks some intermediate variables are used.
|
||
UseBackground := (toShowBackground in FOptions.FPaintOptions) and (FBackground.Graphic is TBitmap) and
|
||
(poBackground in PaintOptions);
|
||
ShowImages := Assigned(FImages);
|
||
ShowStateImages := Assigned(FStateImages);
|
||
ShowCheckImages := Assigned(FCheckImages) and (toCheckSupport in FOptions.FMiscOptions);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcCheck],'ShowCheckImages',ShowCheckImages);{$endif}
|
||
UseColumns := FHeader.UseColumns;
|
||
|
||
// Adjust paint options to tree settings. Hide selection if told so or the tree is unfocused.
|
||
if (toAlwaysHideSelection in FOptions.FPaintOptions) or
|
||
(not Focused and (toHideSelection in FOptions.FPaintOptions)) then
|
||
Exclude(PaintOptions, poDrawSelection);
|
||
if toHideFocusRect in FOptions.FPaintOptions then
|
||
Exclude(PaintOptions, poDrawFocusRect);
|
||
|
||
// Determine node to start drawing with.
|
||
BaseOffset := 0;
|
||
PaintInfo.Node := InternalGetNodeAt(0, Window.Top, False, BaseOffset);
|
||
if PaintInfo.Node = nil then
|
||
BaseOffset := Window.Top;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaint, lcHeaderOffset],'BaseOffset',BaseOffset);{$endif}
|
||
|
||
// Transform selection rectangle into node bitmap coordinates.
|
||
if DrawSelectionRect then
|
||
OffsetRect(SelectionRect, 0, -BaseOffset);
|
||
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcSelection], 'SelectionRect fixed by BaseOffset', SelectionRect);{$endif}
|
||
// The target rectangle holds the coordinates of the exact area to blit in target canvas coordinates.
|
||
// It is usually smaller than an entire node and wanders while the paint loop advances.
|
||
MaximumRight := Target.X + (Window.Right - Window.Left);
|
||
MaximumBottom := Target.Y + (Window.Bottom - Window.Top);
|
||
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintHeader, lcHeaderOffset],'MaximumRight: %d MaximumBottom: %d',[MaximumRight,MaximumBottom]);{$endif}
|
||
TargetRect := Rect(Target.X, Target.Y - (Window.Top - BaseOffset), MaximumRight, 0);
|
||
TargetRect.Bottom := TargetRect.Top;
|
||
|
||
// This marker gets the index of the first column which is visible in the given window.
|
||
// This is needed for column based background colors.
|
||
FirstColumn := InvalidColumn;
|
||
|
||
if Assigned(PaintInfo.Node) then
|
||
begin
|
||
ButtonX := Round((Integer(FIndent) - FPlusBM.Width) / 2);
|
||
|
||
// ----- main node paint loop
|
||
while Assigned(PaintInfo.Node) do
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintDetails],'PaintNode');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'NodeIndex',PaintInfo.Node^.Index);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'BaseOffset',BaseOffset);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
// Determine LineImage, SelectionLevel and IndentSize
|
||
SelectLevel := DetermineLineImageAndSelectLevel(PaintInfo.Node, LineImage);
|
||
IndentSize := Length(LineImage);
|
||
if not (toFixedIndent in FOptions.FPaintOptions) then
|
||
ButtonX := (IndentSize - 1) * FIndent + Round((FIndent - FPlusBM.Width) / 2);
|
||
|
||
// Initialize node if not already done.
|
||
if not (vsInitialized in PaintInfo.Node.States) then
|
||
InitNode(PaintInfo.Node);
|
||
if (vsSelected in PaintInfo.Node.States) and not (toChildrenAbove in FOptions.FPaintOptions) then
|
||
Inc(SelectLevel);
|
||
|
||
// Ensure the node's height is determined.
|
||
MeasureItemHeight(PaintInfo.Canvas, PaintInfo.Node);
|
||
|
||
// Adjust the brush origin for dotted lines depending on the current source position.
|
||
// It is applied some lines later, as the canvas might get reallocated, when changing the node bitmap.
|
||
PaintInfo.BrushOrigin := Point(Window.Left and 1, BaseOffset and 1);
|
||
Inc(BaseOffset, PaintInfo.Node.NodeHeight);
|
||
|
||
TargetRect.Bottom := TargetRect.Top + PaintInfo.Node.NodeHeight;
|
||
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcHeaderOffset], 'TargetRect for Node ' + IntToStr(PaintInfo.Node.Index), TargetRect);{$endif}
|
||
|
||
// If poSelectedOnly is active then do the following stuff only for selected nodes or nodes
|
||
// which are children of selected nodes.
|
||
if (SelectLevel > 0) or not (poSelectedOnly in PaintOptions) then
|
||
begin
|
||
// Adjust height of temporary node bitmap.
|
||
with NodeBitmap do
|
||
begin
|
||
if Height <> PaintInfo.Node.NodeHeight then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'Setting the Node Height');{$endif}
|
||
Height := PaintInfo.Node.NodeHeight;
|
||
// Make sure the buffer bitmap and target bitmap use the same transformation mode.
|
||
{$ifndef Gtk}
|
||
SetMapMode(Canvas.Handle, GetMapMode(TargetCanvas.Handle));
|
||
{$endif}
|
||
SetWindowOrgEx(Canvas.Handle, Window.Left, 0, nil);
|
||
R.Bottom := PaintInfo.Node.NodeHeight;
|
||
end;
|
||
// Set the origin of the canvas' brush. This depends on the node heights.
|
||
//todo: see if is necessary. According to docs is only necessary when HALFTONE is set
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
with PaintInfo do
|
||
SetBrushOrgEx(Canvas.Handle, BrushOrigin.X, BrushOrigin.Y, nil);
|
||
{$endif}
|
||
end;
|
||
CalculateVerticalAlignments(ShowImages, ShowStateImages, PaintInfo.Node, VAlign, ButtonY);
|
||
|
||
// Let application decide whether the node should normally be drawn or by the application itself.
|
||
if not DoBeforeItemPaint(PaintInfo.Canvas, PaintInfo.Node, R) then
|
||
begin
|
||
// Init paint options for the background painting.
|
||
PaintInfo.PaintOptions := PaintOptions;
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
// The node background can contain a single color, a bitmap or can be drawn by the application.
|
||
ClearNodeBackground(PaintInfo, UseBackground, True, Rect(Window.Left, TargetRect.Top, Window.Right,
|
||
TargetRect.Bottom));
|
||
{$ifdef DEBUG_VTV}Logger.SendBitmap([lcPaintBitmap],'After Clear BackGround',NodeBitmap);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
// Prepare column, position and node clipping rectangle.
|
||
PaintInfo.CellRect := R;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'PaintInfo.CellRect',PaintInfo.CellRect);{$endif}
|
||
if UseColumns then
|
||
InitializeFirstColumnValues(PaintInfo);
|
||
|
||
// Now go through all visible columns (there's still one run if columns aren't used).
|
||
with FHeader.FColumns do
|
||
begin
|
||
while ((PaintInfo.Column > InvalidColumn) or not UseColumns)
|
||
and (PaintInfo.CellRect.Left < Window.Right) do
|
||
begin
|
||
if UseColumns then
|
||
begin
|
||
PaintInfo.Column := FPositionToIndex[PaintInfo.Position];
|
||
if FirstColumn = InvalidColumn then
|
||
FirstColumn := PaintInfo.Column;
|
||
PaintInfo.BidiMode := Items[PaintInfo.Column].FBiDiMode;
|
||
PaintInfo.Alignment := Items[PaintInfo.Column].FAlignment;
|
||
end
|
||
else
|
||
begin
|
||
PaintInfo.Column := NoColumn;
|
||
PaintInfo.BidiMode := BidiMode;
|
||
PaintInfo.Alignment := FAlignment;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],
|
||
'Column Paint - PaintInfo.Position: %d PaintInfo.Column: %d',[PaintInfo.Position,PaintInfo.Column]);{$endif}
|
||
PaintInfo.PaintOptions := PaintOptions;
|
||
with PaintInfo do
|
||
begin
|
||
if (tsEditing in FStates) and (Node = FFocusedNode) and
|
||
((Column = FEditColumn) or not UseColumns) then
|
||
Exclude(PaintOptions, poDrawSelection);
|
||
if not UseColumns or
|
||
((vsSelected in Node.States) and (toFullRowSelect in FOptions.FSelectionOptions) and
|
||
(poDrawSelection in PaintOptions)) or
|
||
(coParentColor in Items[PaintInfo.Column].Options) then
|
||
Exclude(PaintOptions, poColumnColor);
|
||
end;
|
||
IsMainColumn := PaintInfo.Column = FHeader.MainColumn;
|
||
|
||
// Consider bidi mode here. In RTL context means left alignment actually right alignment and vice versa.
|
||
if PaintInfo.BidiMode <> bdLeftToRight then
|
||
ChangeBiDiModeAlignment(PaintInfo.Alignment);
|
||
|
||
// Paint the current cell if it is marked as being visible or columns aren't used and
|
||
// if this cell belongs to the main column if only the main column should be drawn.
|
||
if (not UseColumns or (coVisible in Items[PaintInfo.Column].FOptions)) and
|
||
(not (poMainOnly in PaintOptions) or IsMainColumn) then
|
||
begin
|
||
AdjustPaintCellRect(PaintInfo, NextColumn);
|
||
|
||
// Paint the cell only if it is in the current window.
|
||
if PaintInfo.CellRect.Right > Window.Left then
|
||
begin
|
||
with PaintInfo do
|
||
begin
|
||
// Fill in remaining values in the paint info structure.
|
||
NodeWidth := DoGetNodeWidth(Node, Column, Canvas);
|
||
// Not the entire cell is covered by text. Hence we need a running rectangle to follow up.
|
||
ContentRect := CellRect;
|
||
// Set up the distance from column border (margin).
|
||
if BidiMode <> bdLeftToRight then
|
||
Dec(ContentRect.Right, FMargin)
|
||
else
|
||
Inc(ContentRect.Left, FMargin);
|
||
|
||
if ShowCheckImages and IsMainColumn then
|
||
begin
|
||
ImageInfo[iiCheck].Index := GetCheckImage(Node);
|
||
if ImageInfo[iiCheck].Index > -1 then
|
||
begin
|
||
AdjustImageBorder(FCheckImages.Height, FCheckImages.Height, BidiMode, VAlign, ContentRect, ImageInfo[iiCheck]);
|
||
ImageInfo[iiCheck].Ghosted := False;
|
||
end;
|
||
end
|
||
else
|
||
ImageInfo[iiCheck].Index := -1;
|
||
if ShowStateImages then
|
||
begin
|
||
GetImageIndex(PaintInfo, ikState, iiState, FStateImages);
|
||
if ImageInfo[iiState].Index > -1 then
|
||
AdjustImageBorder(FStateImages.Width, FStateImages.Height, BidiMode, VAlign, ContentRect, ImageInfo[iiState]);
|
||
end
|
||
else
|
||
ImageInfo[iiState].Index := -1;
|
||
if ShowImages then
|
||
begin
|
||
GetImageIndex(PaintInfo, ImageKind[vsSelected in Node.States], iiNormal, FImages);
|
||
if ImageInfo[iiNormal].Index > -1 then
|
||
AdjustImageBorder(FImages.Width, FImages.Height, BidiMode, VAlign, ContentRect, ImageInfo[iiNormal]);
|
||
end
|
||
else
|
||
ImageInfo[iiNormal].Index := -1;
|
||
|
||
// Take the space for the tree lines into account.
|
||
if IsMainColumn then
|
||
AdjustCoordinatesByIndent(PaintInfo, IfThen(toFixedIndent in FOptions.FPaintOptions, 1, IndentSize));
|
||
|
||
if UseColumns then
|
||
LimitPaintingToArea(Canvas, CellRect);
|
||
|
||
// Paint the horizontal grid line.
|
||
if (poGridLines in PaintOptions) and (toShowHorzGridLines in FOptions.FPaintOptions) then
|
||
begin
|
||
Canvas.Font.Color := FColors.GridLineColor;
|
||
if IsMainColumn and (FLineMode = lmBands) then
|
||
begin
|
||
if BidiMode = bdLeftToRight then
|
||
begin
|
||
DrawDottedHLine(PaintInfo, CellRect.Left + IfThen(toFixedIndent in FOptions.FPaintOptions, 1, IndentSize) * Integer(FIndent), CellRect.Right - 1,
|
||
CellRect.Bottom - 1);
|
||
end
|
||
else
|
||
begin
|
||
DrawDottedHLine(PaintInfo, CellRect.Left, CellRect.Right - IfThen(toFixedIndent in FOptions.FPaintOptions, 1, IndentSize) * Integer(FIndent) - 1,
|
||
CellRect.Bottom - 1);
|
||
end;
|
||
end
|
||
else
|
||
DrawDottedHLine(PaintInfo, CellRect.Left, CellRect.Right, CellRect.Bottom - 1);
|
||
Dec(CellRect.Bottom);
|
||
Dec(ContentRect.Bottom);
|
||
end;
|
||
|
||
if UseColumns then
|
||
begin
|
||
// Paint vertical grid line.
|
||
// Don't draw if this is the last column and the header is in autosize mode.
|
||
if (poGridLines in PaintOptions) and (toShowVertGridLines in FOptions.FPaintOptions) and
|
||
(not (hoAutoResize in FHeader.FOptions) or (Position < TColumnPosition(Count - 1))) then
|
||
begin
|
||
if (BidiMode = bdLeftToRight) or not ColumnIsEmpty(Node, Column) then
|
||
begin
|
||
Canvas.Font.Color := FColors.GridLineColor;
|
||
DrawDottedVLine(PaintInfo, CellRect.Top, CellRect.Bottom, CellRect.Right - 1);
|
||
end;
|
||
Dec(CellRect.Right);
|
||
Dec(ContentRect.Right);
|
||
end;
|
||
end;
|
||
|
||
// Prepare background and focus rect for the current cell.
|
||
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
// Some parts are only drawn for the main column.
|
||
if IsMainColumn then
|
||
begin
|
||
if toShowTreeLines in FOptions.FPaintOptions then
|
||
PaintTreeLines(PaintInfo, VAlign, IfThen(toFixedIndent in FOptions.FPaintOptions, 1,
|
||
IndentSize), LineImage);
|
||
// Show node button if allowed, if there child nodes and at least one of the child
|
||
// nodes is visible or auto button hiding is disabled.
|
||
if (toShowButtons in FOptions.FPaintOptions) and (vsHasChildren in Node.States) and
|
||
not ((vsAllChildrenHidden in Node.States) and
|
||
(toAutoHideButtons in TreeOptions.FAutoOptions)) then
|
||
PaintNodeButton(Canvas, Node, Column, CellRect, ButtonX, ButtonY, BidiMode);
|
||
|
||
if ImageInfo[iiCheck].Index > -1 then
|
||
PaintCheckImage(PaintInfo);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
if ImageInfo[iiState].Index > -1 then
|
||
PaintImage(PaintInfo, iiState, False);
|
||
if ImageInfo[iiNormal].Index > -1 then
|
||
PaintImage(PaintInfo, iiNormal, True);
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
// Now let descendants or applications draw whatever they want,
|
||
// but don't draw the node if it is currently being edited.
|
||
if not ((tsEditing in FStates) and (Node = FFocusedNode) and
|
||
((Column = FEditColumn) or not UseColumns)) then
|
||
DoPaintNode(PaintInfo);
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'Brush.Color',PaintInfo.Canvas.Brush.Color);{$endif}
|
||
DoAfterCellPaint(Canvas, Node, Column, CellRect);
|
||
end;
|
||
end;
|
||
|
||
// leave after first run if columns aren't used
|
||
if not UseColumns then
|
||
Break;
|
||
end
|
||
else
|
||
NextColumn := GetNextVisibleColumn(PaintInfo.Column);
|
||
|
||
SelectClipRgn(PaintInfo.Canvas.Handle, 0);
|
||
// Stop column loop if there are no further columns in the given window.
|
||
if (PaintInfo.CellRect.Left >= Window.Right) or (NextColumn = InvalidColumn) then
|
||
Break;
|
||
|
||
// Move on to next column which might not be the one immediately following the current one
|
||
// because of auto span feature.
|
||
PaintInfo.Position := Items[NextColumn].Position;
|
||
|
||
// Move clip rectangle and continue.
|
||
if coVisible in Items[NextColumn].FOptions then
|
||
with PaintInfo do
|
||
begin
|
||
Items[NextColumn].GetAbsoluteBounds(CellRect.Left, CellRect.Right);
|
||
CellRect.Bottom := Node.NodeHeight;
|
||
ContentRect.Bottom := Node.NodeHeight;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
// This node is finished, notify descendants/application.
|
||
with PaintInfo do
|
||
begin
|
||
DoAfterItemPaint(Canvas, Node, R);
|
||
|
||
// Final touch for this node: mark it if it is the current drop target node.
|
||
if (Node = FDropTargetNode) and (toShowDropmark in FOptions.FPaintOptions) and
|
||
(poDrawDropMark in PaintOptions) then
|
||
DoPaintDropMark(Canvas, Node, R);
|
||
end;
|
||
end;
|
||
|
||
with PaintInfo.Canvas do
|
||
begin
|
||
if DrawSelectionRect then
|
||
begin
|
||
PaintSelectionRectangle(PaintInfo.Canvas, Window.Left, SelectionRect, Rect(0, 0, NodeBitmap.Width,
|
||
NodeBitmap.Height));
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.SendBitmap([lcPaintBitmap],'NodeBitmap ' + IntToStr(PaintInfo.Node^.Index), NodeBitmap);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.SendIf([lcPaintDetails, lcHeaderOffset],'TargetRect.Top < Target.Y '+ Logger.RectToStr(TargetRect)
|
||
+' '+Logger.PointToStr(Target),TargetRect.Top < Target.Y);{$endif}
|
||
{$ifdef Gtk}
|
||
//lclheader
|
||
// This is a brute force fix AKA hack to prevent the header being cleared
|
||
// when the tree is scrolled (YOffset < 0) and the mouse is over the header
|
||
// Other widgetsets are not affected because excludecliprect works different (better?)
|
||
// this must be removed when/if the paint coordinate is modified to be header aware
|
||
YCorrect := 0;
|
||
if hoVisible in FHeader.Options then
|
||
begin
|
||
if TargetRect.Top < FHeader.Height then
|
||
YCorrect := FHeader.Height - TargetRect.Top;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.SendIf([lcPaintDetails],'YCorrect ' + IntToStr(YCorrect), YCorrect > 0);{$endif}
|
||
{$endif}
|
||
// Put the constructed node image onto the target canvas.
|
||
with TargetRect, NodeBitmap do
|
||
BitBlt(TargetCanvas.Handle, Left,
|
||
Top {$ifdef ManualClipNeeded} + YCorrect{$endif}, Width, Height, Canvas.Handle, Window.Left,
|
||
{$ifdef ManualClipNeeded}YCorrect{$else}0{$endif}, SRCCOPY);
|
||
end;
|
||
end;
|
||
|
||
Inc(TargetRect.Top, PaintInfo.Node.NodeHeight);
|
||
{$ifdef DEBUG_VTV}Logger.SendIf([lcPaintHeader,lcDrag],'Last Node to be painted: '+ IntToStr(PaintInfo.Node^.Index)
|
||
+' (TargetRect.Top >= MaximumBottom)',TargetRect.Top >= MaximumBottom);{$endif}
|
||
if TargetRect.Top >= MaximumBottom then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'PaintNode');{$endif}
|
||
Break;
|
||
end;
|
||
|
||
// Keep selection rectangle coordinates in sync.
|
||
if DrawSelectionRect then
|
||
OffsetRect(SelectionRect, 0, -PaintInfo.Node.NodeHeight);
|
||
|
||
// Advance to next visible node.
|
||
PaintInfo.Node := GetNextVisible(PaintInfo.Node, True);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'PaintNode');{$endif}
|
||
end;
|
||
end;
|
||
|
||
// Erase rest of window not covered by a node.
|
||
if TargetRect.Top < MaximumBottom then
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'UseBackground',UseBackground);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'UseColumns',UseColumns);{$endif}
|
||
// Keep the horizontal target position to determine the selection rectangle offset later (if necessary).
|
||
BaseOffset := Target.X;
|
||
Target := TargetRect.TopLeft;
|
||
R := Rect(TargetRect.Left, 0, TargetRect.Left, MaximumBottom - Target.Y);
|
||
TargetRect := Rect(0, 0, MaximumRight - Target.X, MaximumBottom - Target.Y);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'NodeBitmap.Handle',NodeBitmap.Handle);{$endif}
|
||
|
||
NodeBitmap.PixelFormat := pf32Bit;
|
||
NodeBitmap.Width := TargetRect.Right - TargetRect.Left;
|
||
NodeBitmap.Height := TargetRect.Bottom - TargetRect.Top;
|
||
// Make sure the buffer bitmap and target bitmap use the same transformation mode.
|
||
{$ifndef Gtk}
|
||
SetMapMode(NodeBitmap.Canvas.Handle, GetMapMode(TargetCanvas.Handle));
|
||
{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'NodeBitmap.Handle after changing height to background',NodeBitmap.Handle);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'TargetRect',TargetRect);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'NodeBitmap Width: %d Height: %d',[NodeBitmap.Width,NodeBitmap.Height]);{$endif}
|
||
// Call back application/descendants whether they want to erase this area.
|
||
SetWindowOrgEx(NodeBitmap.Canvas.Handle, Target.X, 0, nil);
|
||
if not DoPaintBackground(NodeBitmap.Canvas, TargetRect) then
|
||
begin
|
||
if UseBackground then
|
||
begin
|
||
SetWindowOrgEx(NodeBitmap.Canvas.Handle, 0, 0, nil);
|
||
if toStaticBackground in TreeOptions.PaintOptions then
|
||
StaticBackground(FBackground.Bitmap, NodeBitmap.Canvas, Target, TargetRect)
|
||
else
|
||
TileBackground(FBackground.Bitmap, NodeBitmap.Canvas, Target, TargetRect);
|
||
end
|
||
else
|
||
begin
|
||
// Consider here also colors of the columns.
|
||
if UseColumns then
|
||
begin
|
||
with FHeader.FColumns do
|
||
begin
|
||
// If there is no content in the tree then the first column has not yet been determined.
|
||
if FirstColumn = InvalidColumn then
|
||
begin
|
||
FirstColumn := GetFirstVisibleColumn;
|
||
repeat
|
||
if FirstColumn <> InvalidColumn then
|
||
begin
|
||
R.Left := Items[FirstColumn].Left;
|
||
R.Right := R.Left + Items[FirstColumn].FWidth;
|
||
if R.Right > TargetRect.Left then
|
||
Break;
|
||
FirstColumn := GetNextVisibleColumn(FirstColumn);
|
||
end;
|
||
until FirstColumn = InvalidColumn;
|
||
end
|
||
else
|
||
begin
|
||
R.Left := Items[FirstColumn].Left;
|
||
R.Right := R.Left + Items[FirstColumn].FWidth;
|
||
end;
|
||
|
||
NodeBitmap.Canvas.Font.Color := FColors.GridLineColor;
|
||
while (FirstColumn <> InvalidColumn) and (R.Left < TargetRect.Right + Target.X) do
|
||
begin
|
||
if (poGridLines in PaintOptions) and
|
||
(toFullVertGridLines in FOptions.FPaintOptions) and
|
||
(toShowVertGridLines in FOptions.FPaintOptions) and
|
||
(not (hoAutoResize in FHeader.FOptions) or (Cardinal(FirstColumn) < TColumnPosition(Count - 1))) then
|
||
begin
|
||
DrawDottedVLine(PaintInfo, R.Top, R.Bottom, R.Right - 1);
|
||
Dec(R.Right);
|
||
end;
|
||
|
||
if not (coParentColor in Items[FirstColumn].FOptions) then
|
||
NodeBitmap.Canvas.Brush.Color := Items[FirstColumn].FColor
|
||
else
|
||
NodeBitmap.Canvas.Brush.Color := Brush.Color;
|
||
|
||
NodeBitmap.Canvas.FillRect(R);
|
||
FirstColumn := GetNextVisibleColumn(FirstColumn);
|
||
if FirstColumn <> InvalidColumn then
|
||
begin
|
||
R.Left := Items[FirstColumn].Left;
|
||
R.Right := R.Left + Items[FirstColumn].FWidth;
|
||
end;
|
||
end;
|
||
|
||
// Erase also the part of the tree not covert by a column.
|
||
if R.Right < TargetRect.Right + Target.X then
|
||
begin
|
||
R.Left := R.Right;
|
||
R.Right := TargetRect.Right + Target.X;
|
||
// Prevent erasing the last vertical grid line.
|
||
if (poGridLines in PaintOptions) and
|
||
(toFullVertGridLines in FOptions.FPaintOptions) and (toShowVertGridLines in FOptions.FPaintOptions) and
|
||
(not (hoAutoResize in FHeader.FOptions)) then
|
||
Inc(R.Left);
|
||
NodeBitmap.Canvas.Brush.Color := Brush.Color;
|
||
NodeBitmap.Canvas.FillRect(R);
|
||
end;
|
||
end;
|
||
SetWindowOrgEx(NodeBitmap.Canvas.Handle, 0, 0, nil);
|
||
end
|
||
else
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'ErasingBackGround');{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'TargetRect',TargetRect);{$endif}
|
||
// No columns nor bitmap background. Simply erase it with the tree color.
|
||
SetWindowOrgEx(NodeBitmap.Canvas.Handle, 0, 0, nil);
|
||
NodeBitmap.Canvas.Brush.Color := Brush.Color;
|
||
NodeBitmap.Canvas.FillRect(TargetRect);
|
||
end;
|
||
end;
|
||
end;
|
||
SetWindowOrgEx(NodeBitmap.Canvas.Handle, 0, 0, nil);
|
||
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'DrawSelectionRect',DrawSelectionRect);{$endif}
|
||
if DrawSelectionRect then
|
||
begin
|
||
R := OrderRect(FNewSelRect);
|
||
// Remap the selection rectangle to the current window of the tree.
|
||
// Since Target has been used for other tasks BaseOffset got the left extent of the target position here.
|
||
OffsetRect(R, -Target.X + BaseOffset - Window.Left, -Target.Y + FOffsetY);
|
||
//todo: see if is necessary
|
||
{$ifndef INCOMPLETE_WINAPI}
|
||
SetBrushOrgEx(NodeBitmap.Canvas.Handle, 0, Target.X and 1, nil);
|
||
{$endif}
|
||
PaintSelectionRectangle(NodeBitmap.Canvas, 0, R, TargetRect);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'NodeBitmap.Canvas.Height',NodeBitmap.Canvas.Height);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'NodeBitmap.Canvas.ClipRect',NodeBitmap.Canvas.ClipRect);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'Target',Target);{$endif}
|
||
{$ifdef DEBUG_VTV}Logger.SendBitmap([lcPaintBitmap],'BackGroundBitmap',NodeBitmap);{$endif}
|
||
with Target, NodeBitmap do
|
||
BitBlt(TargetCanvas.Handle, X, Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY);
|
||
end;
|
||
finally
|
||
NodeBitmap.Canvas.Unlock;
|
||
NodeBitmap.Free;
|
||
end;
|
||
DoAfterPaint(TargetCanvas);
|
||
finally
|
||
DoStateChange([], [tsPainting]);
|
||
end;
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaint],'PaintTree');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.PasteFromClipboard: Boolean;
|
||
|
||
// Reads what is currently on the clipboard into the tree (if the format is supported).
|
||
// Note: If the application wants to have text or special formats to be inserted then it must implement
|
||
// its own code (OLE). Here only the native tree format is accepted.
|
||
|
||
var
|
||
Data: IDataObject;
|
||
Source: TBaseVirtualTree;
|
||
|
||
begin
|
||
Result := False;
|
||
if not (toReadOnly in FOptions.FMiscOptions) then
|
||
begin
|
||
if OleGetClipboard(Data) <> S_OK then
|
||
ShowError(SClipboardFailed, hcTFClipboardFailed)
|
||
else
|
||
try
|
||
// Try to get the source tree of the operation to optimize the operation.
|
||
Source := GetTreeFromDataObject(Data);
|
||
Result := ProcessOLEData(Source, Data, FFocusedNode, FDefaultPasteMode, Assigned(Source) and
|
||
(tsCutPending in Source.FStates));
|
||
if Assigned(Source) then
|
||
if Source <> Self then
|
||
Source.FinishCutOrCopy
|
||
else
|
||
DoStateChange([], [tsCutPending]);
|
||
finally
|
||
Data := nil;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.PrepareDragImage(Hotspot: TPoint; const DataObject: IDataObject);
|
||
|
||
// Initiates an image drag operation. Hotspot is the position of the mouse in client coordinates.
|
||
|
||
var
|
||
PaintOptions: TVTInternalPaintOptions;
|
||
TreeRect,
|
||
PaintRect: TRect;
|
||
LocalSpot,
|
||
ImagePos,
|
||
PaintTarget: TPoint;
|
||
Image: TBitmap;
|
||
|
||
begin
|
||
if CanShowDragImage then
|
||
begin
|
||
// Determine the drag rectangle which is a square around the hot spot. Operate in virtual tree space.
|
||
LocalSpot := HotSpot;
|
||
Dec(LocalSpot.X, -FEffectiveOffsetX);
|
||
Dec(LocalSpot.Y, FOffsetY);
|
||
TreeRect := Rect(LocalSpot.X - FDragWidth div 2, LocalSpot.Y - FDragHeight div 2, LocalSpot.X + FDragWidth div 2,
|
||
LocalSpot.Y + FDragHeight div 2);
|
||
|
||
// Check that we have a valid rectangle.
|
||
with TreeRect do
|
||
begin
|
||
PaintRect := TreeRect;
|
||
//lclheader
|
||
if hoVisible in FHeader.Options then
|
||
OffsetRect(PaintRect, 0, -FHeader.Height);
|
||
if Left < 0 then
|
||
begin
|
||
PaintTarget.X := -Left;
|
||
PaintRect.Left := 0;
|
||
end
|
||
else
|
||
PaintTarget.X := 0;
|
||
if Top < 0 then
|
||
begin
|
||
PaintTarget.Y := -PaintRect.Top;
|
||
PaintRect.Top := 0;
|
||
end
|
||
else
|
||
PaintTarget.Y := 0;
|
||
end;
|
||
|
||
Image := TBitmap.Create;
|
||
with Image do
|
||
try
|
||
PixelFormat := pf32Bit;
|
||
Width := TreeRect.Right - TreeRect.Left;
|
||
Height := TreeRect.Bottom - TreeRect.Top;
|
||
// Erase the entire image with the color key value, for the case not everything
|
||
// in the image is covered by the tree image.
|
||
Canvas.Brush.Color := Brush.Color;
|
||
Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
|
||
PaintOptions := [poDrawSelection, poSelectedOnly];
|
||
if FDragImageKind = diMainColumnOnly then
|
||
Include(PaintOptions, poMainOnly);
|
||
PaintTree(Image.Canvas, PaintRect, PaintTarget, PaintOptions);
|
||
|
||
// Once we have got the drag image we can convert all necessary coordinates into screen space.
|
||
OffsetRect(TreeRect, -FEffectiveOffsetX, FOffsetY);
|
||
ImagePos := ClientToScreen(TreeRect.TopLeft);
|
||
HotSpot := ClientToScreen(HotSpot);
|
||
|
||
FDragImage.ColorKey := Brush.Color;
|
||
FDragImage.PrepareDrag(Image, ImagePos, HotSpot, DataObject);
|
||
finally
|
||
Image.Free;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{$ifdef EnablePrint}
|
||
procedure TBaseVirtualTree.Print(Printer: TPrinter; PrintHeader: Boolean);
|
||
|
||
var
|
||
SaveTreeFont: TFont; // Remembers the tree's current font.
|
||
SaveHeaderFont: TFont; // Remembers the header's current font.
|
||
ImgRect, // Describes the dimensions of Image.
|
||
TreeRect, // The total VTree dimensions.
|
||
DestRect, // Dimensions of PrinterImage.
|
||
SrcRect: TRect; // Clip dimensions from Image -> PrinterImage
|
||
P: TPoint; // Used by PaintTree.
|
||
Options: TVTInternalPaintOptions; // Used by PaintTree.
|
||
Image, // Complete Tree is drawn to this image.
|
||
PrinterImage: TBitmap; // This is the image that gets printed.
|
||
SaveColor: TColor; // Remembers the VTree Color.
|
||
pTxtHeight, // Height of font in the TPrinter.Canvas
|
||
vTxtHeight, // Height of font in the VTree Canvas
|
||
vPageWidth,
|
||
vPageHeight, // Printer height in VTree resolution
|
||
xPageNum, yPageNum, // # of pages (except the occasional last one)
|
||
xPage, yPage: Integer; // Loop counter
|
||
Scale: Extended; // Scale factor between Printer Canvas and VTree Canvas
|
||
LogFont: TLogFont;
|
||
|
||
begin
|
||
if Assigned(Printer) then
|
||
begin
|
||
BeginUpdate;
|
||
|
||
// Grid lines are the only parts which are desirable when printing.
|
||
Options := [poGridLines];
|
||
|
||
// Remember the tree font.
|
||
SaveTreeFont := TFont.Create;
|
||
SaveTreeFont.Assign(Font);
|
||
// Create a new font for printing which does not use clear type output (but is antialiased, if possible)
|
||
// and which has the highest possible quality.
|
||
GetObject(Font.Handle, SizeOf(TLogFont), @LogFont);
|
||
LogFont.lfQuality := ANTIALIASED_QUALITY;
|
||
Font.Handle := CreateFontIndirect(LogFont);
|
||
|
||
// Create an image that will hold the complete VTree
|
||
Image := TBitmap.Create;
|
||
Image.PixelFormat := pf32Bit;
|
||
PrinterImage := nil;
|
||
try
|
||
TreeRect := GetTreeRect;
|
||
|
||
Image.Width := TreeRect.Right - TreeRect.Left;
|
||
P := Point(0, 0);
|
||
if (hoVisible in FHeader.Options) and PrintHeader then
|
||
begin
|
||
Inc(TreeRect.Bottom, FHeader.Height);
|
||
Inc(P.Y, FHeader.Height);
|
||
end;
|
||
Image.Height := TreeRect.Bottom - TreeRect.Top;
|
||
|
||
ImgRect.Left := 0;
|
||
ImgRect.Top := 0;
|
||
ImgRect.Right := Image.Width;
|
||
|
||
// Force the background to white color during the rendering.
|
||
SaveColor := Color;
|
||
Color := clWhite;
|
||
// Print header if it is visible.
|
||
if (hoVisible in FHeader.Options) and PrintHeader then
|
||
begin
|
||
SaveHeaderFont := TFont.Create;
|
||
try
|
||
SaveHeaderFont.Assign(FHeader.Font);
|
||
// Create a new font for printing which does not use clear type output (but is antialiased, if possible)
|
||
// and which has the highest possible quality.
|
||
GetObject(FHeader.Font.Handle, SizeOf(TLogFont), @LogFont);
|
||
LogFont.lfQuality := ANTIALIASED_QUALITY;
|
||
FHeader.Font.Handle := CreateFontIndirect(LogFont);
|
||
ImgRect.Bottom := FHeader.Height;
|
||
FHeader.FColumns.PaintHeader(Image.Canvas.Handle, ImgRect, 0);
|
||
FHeader.Font := SaveHeaderFont;
|
||
finally
|
||
SaveHeaderFont.Free;
|
||
end;
|
||
end;
|
||
// The image's height is already adjusted for the header if it is visible.
|
||
ImgRect.Bottom := Image.Height;
|
||
|
||
PaintTree(Image.Canvas, ImgRect, P, Options, pf32Bit);
|
||
Color := SaveColor;
|
||
|
||
// Activate the printer
|
||
Printer.BeginDoc;
|
||
Printer.Canvas.Font := Font;
|
||
|
||
// Now we can calculate the scaling :
|
||
pTxtHeight := Printer.Canvas.TextHeight('Tj');
|
||
vTxtHeight := Canvas.TextHeight('Tj');
|
||
|
||
Scale := pTxtHeight / vTxtHeight;
|
||
|
||
// Create an Image that has the same dimensions as the printer canvas but
|
||
// scaled to the VTree resolution:
|
||
PrinterImage := TBitmap.Create;
|
||
|
||
vPageHeight := Round(Printer.PageHeight / Scale);
|
||
vPageWidth := Round(Printer.PageWidth / Scale);
|
||
|
||
// We do a minumum of one page.
|
||
xPageNum := Trunc(Image.Width / vPageWidth);
|
||
yPageNum := Trunc(Image.Height / vPageHeight);
|
||
|
||
PrinterImage.Width := vPageWidth;
|
||
PrinterImage.Height := vPageHeight;
|
||
|
||
// Split vertically:
|
||
for yPage := 0 to yPageNum do
|
||
begin
|
||
DestRect.Left := 0;
|
||
DestRect.Top := 0;
|
||
DestRect.Right := PrinterImage.Width;
|
||
DestRect.Bottom := PrinterImage.Height;
|
||
|
||
// Split horizontally:
|
||
for xPage := 0 to xPageNum do
|
||
begin
|
||
SrcRect.Left := vPageWidth * xPage;
|
||
SrcRect.Top := vPageHeight * yPage;
|
||
SrcRect.Right := vPageWidth * xPage + PrinterImage.Width;
|
||
SrcRect.Bottom := SrcRect.Top + vPageHeight;
|
||
|
||
// Clear the image
|
||
PrinterImage.Canvas.Brush.Color := clWhite;
|
||
PrinterImage.Canvas.FillRect(Rect(0, 0, PrinterImage.Width, PrinterImage.Height));
|
||
PrinterImage.Canvas.CopyRect(DestRect, Image.Canvas, SrcRect);
|
||
PrtStretchDrawDIB(Printer.Canvas, Rect(0, 0, Printer.PageWidth, Printer.PageHeight - 1), PrinterImage);
|
||
if xPage <> xPageNum then
|
||
Printer.NewPage;
|
||
end;
|
||
if yPage <> yPageNum then
|
||
Printer.NewPage;
|
||
end;
|
||
|
||
// Restore tree font.
|
||
Font := SaveTreeFont;
|
||
SaveTreeFont.Free;
|
||
Printer.EndDoc;
|
||
finally
|
||
PrinterImage.Free;
|
||
Image.Free;
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
end;
|
||
{$endif}
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ProcessDrop(DataObject: IDataObject; TargetNode: PVirtualNode; var Effect: LongWord;
|
||
Mode: TVTNodeAttachMode): Boolean;
|
||
|
||
// Recreates the (sub) tree structure serialized into memory and provided by DataObject. The new nodes are attached to
|
||
// the passed node or FRoot if TargetNode is nil.
|
||
// Returns True on success, i.e. the CF_VIRTUALTREE format is supported by the data object and the structure could be
|
||
// recreated, otherwise False.
|
||
|
||
var
|
||
Source: TBaseVirtualTree;
|
||
|
||
begin
|
||
Result := False;
|
||
if Mode = amNoWhere then
|
||
Effect := DROPEFFECT_NONE
|
||
else
|
||
begin
|
||
BeginUpdate;
|
||
// try to get the source tree of the operation
|
||
Source := GetTreeFromDataObject(DataObject);
|
||
if Assigned(Source) then
|
||
Source.BeginUpdate;
|
||
try
|
||
try
|
||
// Before adding the new nodes try to optimize the operation if source and target tree reside in
|
||
// the same application and operation is a move.
|
||
if ((Effect and DROPEFFECT_MOVE) <> 0) and Assigned(Source) then
|
||
begin
|
||
// If both copy and move are specified then prefer a copy because this is not destructing.
|
||
Result := ProcessOLEData(Source, DataObject, TargetNode, Mode, (Effect and DROPEFFECT_COPY) = 0);
|
||
// Since we made an optimized move or a copy there's no reason to act further after DoDragging returns.
|
||
Effect := DROPEFFECT_NONE;
|
||
end
|
||
else
|
||
// Act only if move or copy operation is requested.
|
||
if (Effect and (DROPEFFECT_MOVE or DROPEFFECT_COPY)) <> 0 then
|
||
Result := ProcessOLEData(Source, DataObject, TargetNode, Mode, False)
|
||
else
|
||
Result := False;
|
||
except
|
||
Effect := DROPEFFECT_NONE;
|
||
end;
|
||
finally
|
||
if Assigned(Source) then
|
||
Source.EndUpdate;
|
||
EndUpdate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ReinitChildren(Node: PVirtualNode; Recursive: Boolean);
|
||
|
||
// Forces all child nodes of Node to be reinitialized.
|
||
// If Recursive is True then also the grandchildren are reinitialized.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
if Assigned(Node) then
|
||
begin
|
||
InitChildren(Node);
|
||
Run := Node.FirstChild;
|
||
end
|
||
else
|
||
begin
|
||
InitChildren(FRoot);
|
||
Run := FRoot.FirstChild;
|
||
end;
|
||
|
||
while Assigned(Run) do
|
||
begin
|
||
ReinitNode(Run, Recursive);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean);
|
||
|
||
// Forces the given node and all its children (if recursive is True) to be initialized again without
|
||
// modifying any data in the nodes nor deleting children (unless the application requests a different amount).
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
// Remove dynamic styles.
|
||
Node.States := Node.States - [vsChecking, vsCutOrCopy, vsDeleting, vsHeightMeasured];
|
||
InitNode(Node);
|
||
end;
|
||
|
||
if Recursive then
|
||
ReinitChildren(Node, True);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.RepaintNode(Node: PVirtualNode);
|
||
|
||
// Causes an immediate repaint of the given node.
|
||
|
||
var
|
||
R: Trect;
|
||
|
||
begin
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
R := GetDisplayRect(Node, -1, False);
|
||
RedrawWindow(Handle, @R, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_NOERASE or RDW_VALIDATE or RDW_NOCHILDREN);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ResetNode(Node: PVirtualNode);
|
||
|
||
// Deletes all children of the given node and marks it as being uninitialized.
|
||
|
||
begin
|
||
DoCancelEdit;
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Clear
|
||
else
|
||
begin
|
||
DoReset(Node);
|
||
DeleteChildren(Node);
|
||
// Remove initialized and other dynamic styles, keep persistent styles.
|
||
Node.States := Node.States - [vsInitialized, vsChecking, vsCutOrCopy, vsDeleting, vsHasChildren, vsExpanded,
|
||
vsHeightMeasured];
|
||
InvalidateNode(Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SaveToFile(const FileName: TFileName);
|
||
|
||
// Saves the entire content of the tree into a file (see further notes in SaveToStream).
|
||
|
||
var
|
||
FileStream: TFileStream;
|
||
|
||
begin
|
||
FileStream := TFileStream.Create(FileName, fmCreate);
|
||
try
|
||
SaveToStream(FileStream);
|
||
finally
|
||
FileStream.Free;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SaveToStream(Stream: TStream; Node: PVirtualNode = nil);
|
||
|
||
// Saves Node and all its children to Stream. If Node is nil then all top level nodes will be stored.
|
||
// Note: You should be careful about assuming what is actually saved. The problem here is that we are dealing with
|
||
// virtual data. The tree can so not know what it has to save. The only fact we reliably know is the tree's
|
||
// structure. To be flexible for future enhancements as well as unknown content (unknown to the tree class which
|
||
// is saving/loading the stream) a chunk based approach is used here. Every tree class handles only those
|
||
// chunks which are not handled by an anchestor class and are known by the class.
|
||
//
|
||
// The base tree class saves only the structure of the tree along with application provided data. descendants may
|
||
// optionally add their own chunks to store additional information. See: WriteChunks.
|
||
|
||
var
|
||
Count: Cardinal;
|
||
|
||
begin
|
||
Stream.Write(MagicID, SizeOf(MagicID));
|
||
if Node = nil then
|
||
begin
|
||
// Keep number of top level nodes for easy restauration.
|
||
Count := FRoot.ChildCount;
|
||
Stream.WriteBuffer(Count, SizeOf(Count));
|
||
|
||
// Save entire tree here.
|
||
Node := FRoot.FirstChild;
|
||
while Assigned(Node) do
|
||
begin
|
||
WriteNode(Stream, Node);
|
||
Node := Node.NextSibling;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
Count := 1;
|
||
Stream.WriteBuffer(Count, SizeOf(Count));
|
||
WriteNode(Stream, Node);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean;
|
||
|
||
// Scrolls the tree so that the given node is in the client area and returns True if the tree really has been
|
||
// scrolled (e.g. to avoid further updates) else returns False. If extened focus is enabled then the tree will also
|
||
// be horizontally scrolled if needed.
|
||
// Note: All collapsed parents of the node are expanded.
|
||
|
||
var
|
||
R: TRect;
|
||
Run: PVirtualNode;
|
||
UseColumns,
|
||
HScrollBarVisible: Boolean;
|
||
ScrolledVertically,
|
||
ScrolledHorizontally: Boolean;
|
||
|
||
begin
|
||
//todo: minimize calls to ClientHeight and ClientWidth
|
||
ScrolledVertically := False;
|
||
ScrolledHorizontally := False;
|
||
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
// Make sure all parents of the node are expanded.
|
||
Run := Node.Parent;
|
||
while Run <> FRoot do
|
||
begin
|
||
if not (vsExpanded in Run.States) then
|
||
ToggleNode(Run);
|
||
Run := Run.Parent;
|
||
end;
|
||
UseColumns := FHeader.UseColumns;
|
||
if UseColumns and FHeader.FColumns.IsValidColumn(FFocusedColumn) then
|
||
R := GetDisplayRect(Node, FFocusedColumn, not (toGridExtensions in FOptions.FMiscOptions))
|
||
else
|
||
R := GetDisplayRect(Node, NoColumn, not (toGridExtensions in FOptions.FMiscOptions));
|
||
|
||
// The returned rectangle can never be empty after the expand code above.
|
||
// 1) scroll vertically
|
||
//lclheader
|
||
if hoVisible in FHeader.FOptions then
|
||
OffsetRect(R, 0, -FHeader.Height);
|
||
|
||
if R.Top < 0 then
|
||
begin
|
||
if Center then
|
||
SetOffsetY(FOffsetY - R.Top + ClientHeight div 2)
|
||
else
|
||
SetOffsetY(FOffsetY - R.Top);
|
||
ScrolledVertically := True;
|
||
end
|
||
else
|
||
if (R.Bottom > ClientHeight) or Center then
|
||
begin
|
||
HScrollBarVisible := (ScrollBarOptions.ScrollBars in [ssBoth, ssHorizontal]) and
|
||
(ScrollBarOptions.AlwaysVisible or (Integer(FRangeX) > ClientWidth));
|
||
if Center then
|
||
SetOffsetY(FOffsetY - R.Bottom + ClientHeight div 2)
|
||
else
|
||
SetOffsetY(FOffsetY - R.Bottom + ClientHeight);
|
||
// When scrolling up and the horizontal scroll appears because of the operation
|
||
// then we have to move up the node the horizontal scrollbar's height too
|
||
// in order to avoid that the scroll bar hides the node which we wanted to have in view.
|
||
if not UseColumns and not HScrollBarVisible and (Integer(FRangeX) > ClientWidth) then
|
||
SetOffsetY(FOffsetY - GetSystemMetrics(SM_CYHSCROLL));
|
||
ScrolledVertically := True;
|
||
end;
|
||
|
||
if Horizontally then
|
||
// 2) scroll horizontally
|
||
ScrolledHorizontally := ScrollIntoView(FFocusedColumn, Center);
|
||
|
||
end;
|
||
|
||
Result := ScrolledVertically or ScrolledHorizontally;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.ScrollIntoView(Column: TColumnIndex; Center: Boolean): Boolean;
|
||
|
||
// Scrolls the columns so that the given column is in the client area and returns True if the columns really have been
|
||
// scrolled (e.g. to avoid further updates) else returns False.
|
||
|
||
var
|
||
ColumnLeft,
|
||
ColumnRight: Integer;
|
||
NewOffset: Integer;
|
||
|
||
begin
|
||
Result := False;
|
||
|
||
if not FHeader.UseColumns then exit;
|
||
if not FHeader.Columns.IsValidColumn(Column) then exit; // Just in case.
|
||
|
||
ColumnLeft := Header.Columns.Items[Column].Left;
|
||
ColumnRight := ColumnLeft + Header.Columns.Items[Column].Width;
|
||
|
||
NewOffset := FEffectiveOffsetX;
|
||
if Center then
|
||
begin
|
||
NewOffset := FEffectiveOffsetX + ColumnLeft - (Header.Columns.GetVisibleFixedWidth div 2) - (ClientWidth div 2) + ((ColumnRight - ColumnLeft) div 2);
|
||
if NewOffset <> FEffectiveOffsetX then
|
||
begin
|
||
if UseRightToLeftAlignment then
|
||
SetOffsetX(-Integer(FRangeX) + ClientWidth + NewOffset)
|
||
else
|
||
SetOffsetX(-NewOffset);
|
||
end;
|
||
Result := True;
|
||
end
|
||
else
|
||
begin
|
||
if ColumnRight > ClientWidth then
|
||
NewOffset := FEffectiveOffsetX + (ColumnRight - ClientWidth)
|
||
else if ColumnLeft < Header.Columns.GetVisibleFixedWidth then
|
||
NewOffset := FEffectiveOffsetX - (Header.Columns.GetVisibleFixedWidth - ColumnLeft);
|
||
if NewOffset <> FEffectiveOffsetX then
|
||
begin
|
||
if UseRightToLeftAlignment then
|
||
SetOffsetX(-Integer(FRangeX) + ClientWidth + NewOffset)
|
||
else
|
||
SetOffsetX(-NewOffset);
|
||
end;
|
||
Result := True;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SelectAll(VisibleOnly: Boolean);
|
||
|
||
// Select all nodes in the tree.
|
||
// If VisibleOnly is True then only visible nodes are selected.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
NextFunction: TGetNextNodeProc;
|
||
|
||
begin
|
||
if not FSelectionLocked and (toMultiSelect in FOptions.FSelectionOptions) then
|
||
begin
|
||
ClearTempCache;
|
||
if VisibleOnly then
|
||
begin
|
||
Run := GetFirstVisible(nil, True);
|
||
NextFunction := GetNextVisible;
|
||
end
|
||
else
|
||
begin
|
||
Run := GetFirst;
|
||
NextFunction := GetNext;
|
||
end;
|
||
|
||
while Assigned(Run) do
|
||
begin
|
||
if not(vsSelected in Run.States) then
|
||
InternalCacheNode(Run);
|
||
Run := NextFunction(Run);
|
||
end;
|
||
if FTempNodeCount > 0 then
|
||
AddToSelection(FTempNodeCache, FTempNodeCount);
|
||
ClearTempCache;
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True);
|
||
|
||
// Sorts the given node. The application is queried about how to sort via the OnCompareNodes event.
|
||
// Column is simply passed to the the compare function so the application can also sort in a particular column.
|
||
// In order to free the application from taking care about the sort direction the parameter Direction is used.
|
||
// This way the application can always sort in increasing order, while this method reorders nodes according to this flag.
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
function MergeAscending(A, B: PVirtualNode): PVirtualNode;
|
||
|
||
// Merges A and B (which both must be sorted via Compare) into one list.
|
||
|
||
var
|
||
Dummy: TVirtualNode;
|
||
|
||
begin
|
||
// This avoids checking for Result = nil in the loops.
|
||
Result := @Dummy;
|
||
while Assigned(A) and Assigned(B) do
|
||
begin
|
||
if DoCompare(A, B, Column) <= 0 then
|
||
begin
|
||
Result.NextSibling := A;
|
||
Result := A;
|
||
A := A.NextSibling;
|
||
end
|
||
else
|
||
begin
|
||
Result.NextSibling := B;
|
||
Result := B;
|
||
B := B.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
// Just append the list which is not nil (or set end of result list to nil if both lists are nil).
|
||
if Assigned(A) then
|
||
Result.NextSibling := A
|
||
else
|
||
Result.NextSibling := B;
|
||
// return start of the new merged list
|
||
Result := Dummy.NextSibling;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
function MergeDescending(A, B: PVirtualNode): PVirtualNode;
|
||
|
||
// Merges A and B (which both must be sorted via Compare) into one list.
|
||
|
||
var
|
||
Dummy: TVirtualNode;
|
||
|
||
begin
|
||
// this avoids checking for Result = nil in the loops
|
||
Result := @Dummy;
|
||
while Assigned(A) and Assigned(B) do
|
||
begin
|
||
if DoCompare(A, B, Column) >= 0 then
|
||
begin
|
||
Result.NextSibling := A;
|
||
Result := A;
|
||
A := A.NextSibling;
|
||
end
|
||
else
|
||
begin
|
||
Result.NextSibling := B;
|
||
Result := B;
|
||
B := B.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
// Just append the list which is not nil (or set end of result list to nil if both lists are nil).
|
||
if Assigned(A) then
|
||
Result.NextSibling := A
|
||
else
|
||
Result.NextSibling := B;
|
||
// Return start of the newly merged list.
|
||
Result := Dummy.NextSibling;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
function MergeSortAscending(var Node: PVirtualNode; N: Cardinal): PVirtualNode;
|
||
|
||
// Sorts the list of nodes given by Node (which must not be nil).
|
||
|
||
var
|
||
A, B: PVirtualNode;
|
||
|
||
begin
|
||
if N > 1 then
|
||
begin
|
||
A := MergeSortAscending(Node, N div 2);
|
||
B := MergeSortAscending(Node, (N + 1) div 2);
|
||
Result := MergeAscending(A, B);
|
||
end
|
||
else
|
||
begin
|
||
Result := Node;
|
||
Node := Node.NextSibling;
|
||
Result.NextSibling := nil;
|
||
end;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
function MergeSortDescending(var Node: PVirtualNode; N: Cardinal): PVirtualNode;
|
||
|
||
// Sorts the list of nodes given by Node (which must not be nil).
|
||
|
||
var
|
||
A, B: PVirtualNode;
|
||
|
||
begin
|
||
if N > 1 then
|
||
begin
|
||
A := MergeSortDescending(Node, N div 2);
|
||
B := MergeSortDescending(Node, (N + 1) div 2);
|
||
Result := MergeDescending(A, B);
|
||
end
|
||
else
|
||
begin
|
||
Result := Node;
|
||
Node := Node.NextSibling;
|
||
Result.NextSibling := nil;
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
Index: Cardinal;
|
||
|
||
begin
|
||
InterruptValidation;
|
||
if tsEditPending in FStates then
|
||
begin
|
||
KillTimer(Handle, EditTimer);
|
||
DoStateChange([], [tsEditPending]);
|
||
end;
|
||
|
||
if not (tsEditing in FStates) or DoEndEdit then
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
if vsHasChildren in Node.States then
|
||
begin
|
||
if (Node.ChildCount = 0) and DoInit then
|
||
InitChildren(Node);
|
||
// Make sure the children are valid, so they can be sorted at all.
|
||
if DoInit and (Node.ChildCount > 0) then
|
||
ValidateChildren(Node, False);
|
||
// Child count might have changed.
|
||
if Node.ChildCount > 1 then
|
||
begin
|
||
// Sort the linked list, check direction flag only once.
|
||
if Direction = sdAscending then
|
||
Node.FirstChild := MergeSortAscending(Node.FirstChild, Node.ChildCount)
|
||
else
|
||
Node.FirstChild := MergeSortDescending(Node.FirstChild, Node.ChildCount);
|
||
// Consolidate the child list finally.
|
||
Run := Node.FirstChild;
|
||
Run.PrevSibling := nil;
|
||
Index := 0;
|
||
repeat
|
||
Run.Index := Index;
|
||
Inc(Index);
|
||
if Run.NextSibling = nil then
|
||
Break;
|
||
Run.NextSibling.PrevSibling := Run;
|
||
Run := Run.NextSibling;
|
||
until False;
|
||
Node.LastChild := Run;
|
||
|
||
InvalidateCache;
|
||
end;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.SortTree(Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True);
|
||
|
||
//--------------- local function --------------------------------------------
|
||
|
||
procedure DoSort(Node: PVirtualNode);
|
||
|
||
// Recursively sorts Node and its child nodes.
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
|
||
begin
|
||
Sort(Node, Column, Direction, DoInit);
|
||
|
||
Run := Node.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
if DoInit and not (vsInitialized in Run.States) then
|
||
InitNode(Run);
|
||
if vsInitialized in Run.States then
|
||
DoSort(Run);
|
||
Run := Run.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local function ----------------------------------------
|
||
|
||
begin
|
||
// Instead of wrapping the sort using BeginUpdate/EndUpdate simply the update counter
|
||
// is modified. Otherwise the EndUpdate call will recurse here.
|
||
Inc(FUpdateCount);
|
||
try
|
||
if Column > InvalidColumn then
|
||
DoSort(FRoot);
|
||
InvalidateCache;
|
||
finally
|
||
if FUpdateCount > 0 then
|
||
Dec(FUpdateCount);
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ToggleNode(Node: PVirtualNode);
|
||
|
||
// Changes a node's expand state to the opposite state.
|
||
|
||
var
|
||
Child: PVirtualNode;
|
||
HeightDelta,
|
||
StepsR1,
|
||
StepsR2,
|
||
Steps: Integer;
|
||
TogglingTree,
|
||
ChildrenInView,
|
||
NeedFullInvalidate,
|
||
NeedUpdate,
|
||
NodeInView,
|
||
PosHoldable,
|
||
TotalFit: Boolean;
|
||
ToggleData: TToggleAnimationData;
|
||
|
||
//--------------- local functions -------------------------------------------
|
||
|
||
procedure UpdateRanges;
|
||
|
||
// This function is used to adjust FRangeX/FRangeY in order to correctly
|
||
// reflect the tree's state after a toggle, because it is essential that
|
||
// these values are correct if we need to scroll afterwards. To avoid a
|
||
// useless call to UpdateScrollbars we do it right here.
|
||
|
||
begin
|
||
if FRoot.TotalHeight < FDefaultNodeHeight then
|
||
FRoot.TotalHeight := FDefaultNodeHeight;
|
||
FRangeY := FRoot.TotalHeight - FRoot.NodeHeight + FBottomSpace;
|
||
|
||
if FHeader.UseColumns then
|
||
FRangeX := FHeader.FColumns.TotalWidth
|
||
else
|
||
FRangeX := GetMaxRightExtend;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure PrepareAnimation;
|
||
|
||
// Prepares ToggleData.
|
||
|
||
var
|
||
R: TRect;
|
||
S: Integer;
|
||
M: TToggleAnimationMode;
|
||
|
||
begin
|
||
with ToggleData do
|
||
begin
|
||
Window := Handle;
|
||
DC := GetDC(Handle);
|
||
//lcl: setting Color to Brush seems not necessary
|
||
//Self.Brush.Color := Color;
|
||
Brush := Self.Brush.Reference.Handle;
|
||
|
||
if (Mode1 <> tamNoScroll) and (Mode2 <> tamNoScroll) then
|
||
begin
|
||
if StepsR1 < StepsR2 then
|
||
begin
|
||
// As the primary rectangle is always R1 we will get a much smoother
|
||
// animation if R1 is the one that will be scrolled more.
|
||
R := R2;
|
||
R2 := R1;
|
||
R1 := R;
|
||
|
||
M := Mode2;
|
||
Mode2 := Mode1;
|
||
Mode1 := M;
|
||
|
||
S := StepsR2;
|
||
StepsR2 := StepsR1;
|
||
StepsR1 := S;
|
||
end;
|
||
ScaleFactor := StepsR2 / StepsR1;
|
||
MissedSteps := 0;
|
||
end;
|
||
|
||
if Mode1 <> tamNoScroll then
|
||
Steps := StepsR1
|
||
else
|
||
Steps := StepsR2;
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil.');
|
||
|
||
TogglingTree := tsToggling in FStates;
|
||
ChildrenInView := False;
|
||
HeightDelta := 0;
|
||
NeedFullInvalidate := False;
|
||
NeedUpdate := False;
|
||
NodeInView := False;
|
||
PosHoldable := False;
|
||
TotalFit := False;
|
||
|
||
// We don't need to switch the expand state if the node is being deleted otherwise some
|
||
// updates (e.g. visible node count) are done twice with disasterous results).
|
||
if [vsDeleting, vsToggling] * Node.States = [] then
|
||
begin
|
||
try
|
||
DoStateChange([tsToggling]);
|
||
Include(Node.States, vsToggling);
|
||
|
||
if vsExpanded in Node.States then
|
||
begin
|
||
if DoCollapsing(Node) then
|
||
begin
|
||
NeedUpdate := True;
|
||
|
||
if (FUpdateCount = 0) and (toAnimatedToggle in FOptions.FAnimationOptions) and not
|
||
(tsCollapsing in FStates) then
|
||
begin
|
||
Application.CancelHint;
|
||
UpdateWindow(Handle);
|
||
|
||
// animated collapsing
|
||
with ToggleData do
|
||
begin
|
||
// Determine the animation behaviour and rectangle. If toChildrenAbove is set, the behaviour is depending
|
||
// on the position of the node to be collapsed.
|
||
R1 := GetDisplayRect(Node, NoColumn, False);
|
||
Mode2 := tamNoScroll;
|
||
HeightDelta := -Node.TotalHeight + NodeHeight[Node];
|
||
if toChildrenAbove in FOptions.FPaintOptions then
|
||
begin
|
||
PosHoldable := (FOffsetY + (Integer(Node.TotalHeight - NodeHeight[Node]))) <= 0;
|
||
NodeInView := R1.Top < ClientHeight;
|
||
|
||
StepsR1 := 0;
|
||
if NodeInView then
|
||
begin
|
||
if PosHoldable or not (toAdvancedAnimatedToggle in FOptions.FAnimationOptions) then
|
||
begin
|
||
// Scroll the child nodes down.
|
||
Mode1 := tamScrollDown;
|
||
R1.Bottom := R1.Top;
|
||
R1.Top := 0;
|
||
StepsR1 := Min(R1.Bottom - R1.Top + 1, Node.TotalHeight - NodeHeight[Node]);
|
||
end
|
||
else
|
||
begin
|
||
// The position cannot be kept. So scroll the node up to its future position.
|
||
Mode1 := tamScrollUp;
|
||
R1.Top := Max(0, R1.Top + HeightDelta);
|
||
R1.Bottom := ClientHeight;
|
||
StepsR1 := FOffsetY - HeightDelta;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
if (Integer(FRangeY) + FOffsetY - R1.Bottom + HeightDelta >= ClientHeight - R1.Bottom) or
|
||
(Integer(FRangeY) <= ClientHeight) or (FOffsetY = 0) or not
|
||
(toAdvancedAnimatedToggle in FOptions.FAnimationOptions) then
|
||
begin
|
||
// Do a simple scroll up over the child nodes.
|
||
Mode1 := tamScrollUp;
|
||
Inc(R1.Top, NodeHeight[Node]);
|
||
R1.Bottom := ClientHeight;
|
||
StepsR1 := Min(R1.Bottom - R1.Top + 1, -HeightDelta);
|
||
end
|
||
else
|
||
begin
|
||
// Scroll the node down to its future position. As FOffsetY will change we need to invalidate the
|
||
// whole tree.
|
||
Mode1 := tamScrollDown;
|
||
StepsR1 := Min(-FOffsetY, ClientHeight - Integer(FRangeY) -FOffsetY - HeightDelta);
|
||
R1.Top := 0;
|
||
R1.Bottom := Min(ClientHeight, R1.Bottom + Steps);
|
||
NeedFullInvalidate := True;
|
||
end;
|
||
end;
|
||
|
||
// No animation necessary if the node is below the current client height.
|
||
if R1.Top < ClientHeight then
|
||
begin
|
||
PrepareAnimation;
|
||
try
|
||
Animate(Steps, FAnimationDuration, ToggleCallback, @ToggleData);
|
||
finally
|
||
ReleaseDC(Window, DC);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
// collapse the node
|
||
AdjustTotalHeight(Node, NodeHeight[Node]);
|
||
if FullyVisible[Node] then
|
||
Dec(FVisibleCount, CountVisibleChildren(Node));
|
||
Exclude(Node.States, vsExpanded);
|
||
DoCollapsed(Node);
|
||
|
||
// Remove child nodes now, if enabled.
|
||
if (toAutoFreeOnCollapse in FOptions.FAutoOptions) and (Node.ChildCount > 0) then
|
||
begin
|
||
DeleteChildren(Node);
|
||
Include(Node.States, vsHasChildren);
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
if DoExpanding(Node) then
|
||
begin
|
||
NeedUpdate := True;
|
||
// expand the node, need to adjust the height
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then
|
||
InitChildren(Node);
|
||
|
||
// Avoid setting the vsExpanded style if there are no child nodes.
|
||
if Node.ChildCount > 0 then
|
||
begin
|
||
// Iterate through the child nodes without initializing them. We have to determine the entire height.
|
||
Child := Node.FirstChild;
|
||
repeat
|
||
if vsVisible in Child.States then
|
||
Inc(HeightDelta, Child.TotalHeight);
|
||
Child := Child.NextSibling;
|
||
until Child = nil;
|
||
|
||
// Getting the display rectangle is already done here as it is needed for toChildrenAbove in any case.
|
||
if (toChildrenAbove in FOptions.FPaintOptions) or (FUpdateCount = 0) then
|
||
begin
|
||
with ToggleData do
|
||
begin
|
||
R1 := GetDisplayRect(Node, NoColumn, False);
|
||
Mode2 := tamNoScroll;
|
||
TotalFit := HeightDelta + Integer(NodeHeight[Node]) <= ClientHeight;
|
||
|
||
if toChildrenAbove in FOptions.FPaintOptions then
|
||
begin
|
||
// The main goal with toChildrenAbove being set is to keep the nodes visual position so the user does
|
||
// not get confused. Therefore we need to scroll the view when the expanding is done.
|
||
PosHoldable := TotalFit and (Integer(FRangeY) - ClientHeight >= 0) ;
|
||
ChildrenInView := (R1.Top - HeightDelta) >= 0;
|
||
NodeInView := R1.Bottom <= ClientHeight;
|
||
end
|
||
else
|
||
begin
|
||
PosHoldable := TotalFit;
|
||
ChildrenInView := R1.Bottom + HeightDelta <= ClientHeight;
|
||
end;
|
||
|
||
R1.Bottom := ClientHeight;
|
||
end;
|
||
end;
|
||
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
// Do animated expanding if enabled.
|
||
if (ToggleData.R1.Top < ClientHeight) and ([tsPainting, tsExpanding] * FStates = []) and
|
||
(toAnimatedToggle in FOptions.FAnimationOptions)then
|
||
begin
|
||
Application.CancelHint;
|
||
UpdateWindow(Handle);
|
||
// animated expanding
|
||
with ToggleData do
|
||
begin
|
||
if toChildrenAbove in FOptions.FPaintOptions then
|
||
begin
|
||
// At first check if we hold the position, which is the most common case.
|
||
if not (toAdvancedAnimatedToggle in FOptions.FAnimationOptions) or
|
||
(PosHoldable and ( (NodeInView and ChildrenInView) or not
|
||
(toAutoScrollOnExpand in FOptions.FAutoOptions) )) then
|
||
begin
|
||
Mode1 := tamScrollUp;
|
||
R1 := Rect(R1.Left, 0, R1.Right, R1.Top);
|
||
StepsR1 := Min(HeightDelta, R1.Bottom);
|
||
end
|
||
else
|
||
begin
|
||
// If we will not hold the node's visual position we mostly scroll in both directions.
|
||
Mode1 := tamScrollDown;
|
||
Mode2 := tamScrollUp;
|
||
R2 := Rect(R1.Left, 0, R1.Right, R1.Top);
|
||
if not (toAutoScrollOnExpand in FOptions.FAutoOptions) then
|
||
begin
|
||
// If we shall not or cannot scroll to the desired extent we calculate the new position (with
|
||
// max FOffsetY applied) and animate it that way.
|
||
StepsR1 := -FOffsetY - Max(Integer(FRangeY) + HeightDelta - ClientHeight, 0) + HeightDelta;
|
||
if (Integer(FRangeY) + HeightDelta - ClientHeight) <= 0 then
|
||
Mode2 := tamNoScroll
|
||
else
|
||
StepsR2 := Min(Integer(FRangeY) + HeightDelta - ClientHeight, R2.Bottom);
|
||
end
|
||
else
|
||
begin
|
||
if TotalFit and NodeInView and (Integer(FRangeY) + HeightDelta > ClientHeight) then
|
||
begin
|
||
// If the whole subtree will fit into the client area and the node is currently fully visible,
|
||
// the first child will be made the top node if possible.
|
||
if HeightDelta >= R1.Top then
|
||
StepsR1 := Abs(R1.Top - HeightDelta)
|
||
else
|
||
StepsR1 := ClientHeight - Integer(FRangeY);
|
||
end
|
||
else if Integer(FRangeY) + HeightDelta <= ClientHeight then
|
||
begin
|
||
// We cannot make the first child the top node as we cannot scroll to that extent,
|
||
// so we do a simple scroll down.
|
||
Mode2 := tamNoScroll;
|
||
StepsR1 := HeightDelta;
|
||
end
|
||
else
|
||
// If the subtree does not fit into the client area at once, the expanded node will
|
||
// be made the bottom node.
|
||
StepsR1 := ClientHeight - R1.Top - Integer(NodeHeight[Node]);
|
||
|
||
if Mode2 <> tamNoScroll then
|
||
begin
|
||
if StepsR1 > 0 then
|
||
StepsR2 := Min(R1.Top, HeightDelta - StepsR1)
|
||
else
|
||
begin
|
||
// If the node is already at the bottom scrolling is needed.
|
||
Mode1 := tamNoScroll;
|
||
StepsR2 := Min(HeightDelta, R1.Bottom);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// toChildrenAbove is not set.
|
||
if (PosHoldable and ChildrenInView) or not (toAutoScrollOnExpand in FOptions.FAutoOptions) or not
|
||
(toAdvancedAnimatedToggle in FOptions.FAnimationOptions) or (R1.Top <= 0) then
|
||
begin
|
||
// If the node will stay at its visual position, do a simple down-scroll.
|
||
Mode1 := tamScrollDown;
|
||
Inc(R1.Top, NodeHeight[Node]);
|
||
StepsR1 := Min(R1.Bottom - R1.Top, HeightDelta);
|
||
end
|
||
else
|
||
begin
|
||
// We will not hold the nodes visual position so perform a double scroll.
|
||
Mode1 := tamScrollUp;
|
||
Mode2 := tamScrollDown;
|
||
|
||
R1.Bottom := R1.Top + Integer(NodeHeight[Node]) + 1;
|
||
R1.Top := 0;
|
||
R2 := Rect(R1.Left, R1.Bottom, R1.Right, ClientHeight);
|
||
|
||
StepsR1 := Min(HeightDelta - (ClientHeight - R2.Top), R1.Bottom - Integer(NodeHeight[Node]));
|
||
StepsR2 := ClientHeight - R2.Top;
|
||
end;
|
||
end;
|
||
|
||
if ClientHeight >= R1.Top then
|
||
begin
|
||
PrepareAnimation;
|
||
try
|
||
Animate(Steps, FAnimationDuration, ToggleCallback, @ToggleData);
|
||
finally
|
||
ReleaseDC(Window, DC);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
Include(Node.States, vsExpanded);
|
||
AdjustTotalHeight(Node, HeightDelta, True);
|
||
if FullyVisible[Node] then
|
||
Inc(FVisibleCount, CountVisibleChildren(Node));
|
||
|
||
DoExpanded(Node);
|
||
end;
|
||
end;
|
||
|
||
if NeedUpdate then
|
||
begin
|
||
InvalidateCache;
|
||
if FUpdateCount = 0 then
|
||
begin
|
||
ValidateCache;
|
||
if Node.ChildCount > 0 then
|
||
begin
|
||
UpdateRanges;
|
||
|
||
if [tsPainting, tsExpanding] * FStates = [] then
|
||
begin
|
||
if (vsExpanded in Node.States) and ((toAutoScrollOnExpand in FOptions.FAutoOptions) or
|
||
(toChildrenAbove in FOptions.FPaintOptions)) then
|
||
begin
|
||
if toChildrenAbove in FOptions.FPaintOptions then
|
||
begin
|
||
NeedFullInvalidate := True;
|
||
if (PosHoldable and ChildrenInView and NodeInView) or not
|
||
(toAutoScrollOnExpand in FOptions.FAutoOptions) then
|
||
SetOffsetY(FOffsetY - Integer(HeightDelta))
|
||
else if TotalFit and NodeInView then
|
||
SetOffsetY(FOffsetY - GetDisplayRect(GetFirstVisible(Node, True), NoColumn, False).Top)
|
||
else
|
||
BottomNode := Node;
|
||
end
|
||
else
|
||
begin
|
||
// Scroll as much child nodes into view as possible if the node has been expanded.
|
||
if PosHoldable then
|
||
NeedFullInvalidate := ScrollIntoView(GetLastVisible(Node, True), False)
|
||
else
|
||
begin
|
||
TopNode := Node;
|
||
NeedFullInvalidate := True;
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
// If we have collapsed the node or toAutoScrollOnExpand is not set, we try to keep the nodes
|
||
// visual position.
|
||
if toChildrenAbove in FOptions.FPaintOptions then
|
||
SetOffsetY(FOffsetY - Integer(HeightDelta));
|
||
NeedFullInvalidate := True;
|
||
end;
|
||
end;
|
||
|
||
UpdateScrollbars(True);
|
||
|
||
// Check for automatically scrolled tree.
|
||
if NeedFullInvalidate then
|
||
Invalidate
|
||
else
|
||
InvalidateToBottom(Node);
|
||
end
|
||
else
|
||
InvalidateNode(Node);
|
||
end
|
||
else
|
||
UpdateRanges;
|
||
end;
|
||
|
||
finally
|
||
Exclude(Node.States, vsToggling);
|
||
if not TogglingTree then
|
||
DoStateChange([], [tsToggling]);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TBaseVirtualTree.UpdateAction(Action: TBasicAction): Boolean;
|
||
|
||
// Support for standard actions.
|
||
|
||
begin
|
||
if not Focused then
|
||
Result := inherited UpdateAction(Action)
|
||
else
|
||
begin
|
||
Result := (Action is TEditCut) or (Action is TEditCopy)
|
||
or (Action is TEditDelete);
|
||
|
||
if Result then
|
||
TAction(Action).Enabled := (FSelectionCount > 0) and
|
||
((Action is TEditDelete) or (FClipboardFormats.Count > 0))
|
||
else
|
||
begin
|
||
Result := Action is TEditPaste;
|
||
if Result then
|
||
TAction(Action).Enabled := True
|
||
else
|
||
begin
|
||
Result := Action is TEditSelectAll;
|
||
if Result then
|
||
TAction(Action).Enabled := (toMultiSelect in FOptions.FSelectionOptions) and (FVisibleCount > 0)
|
||
else
|
||
Result := inherited UpdateAction(Action);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateHorizontalScrollBar(DoRepaint: Boolean);
|
||
|
||
var
|
||
ScrollInfo: TScrollInfo;
|
||
|
||
begin
|
||
if FHeader.UseColumns then
|
||
FRangeX := FHeader.FColumns.TotalWidth
|
||
else
|
||
FRangeX := GetMaxRightExtend;
|
||
|
||
if tsUpdating in FStates then
|
||
exit;
|
||
|
||
// Adjust effect scroll offset depending on bidi mode.
|
||
if UseRightToLeftAlignment then
|
||
FEffectiveOffsetX := Integer(FRangeX) - ClientWidth + FOffsetX
|
||
else
|
||
FEffectiveOffsetX := -FOffsetX;
|
||
|
||
if FScrollBarOptions.ScrollBars in [ssHorizontal, ssBoth] then
|
||
begin
|
||
FillChar(ScrollInfo, SizeOf(ScrollInfo), 0);
|
||
//LCL automatically set cbSize field
|
||
//ScrollInfo.cbSize := SizeOf(ScrollInfo);
|
||
ScrollInfo.fMask := SIF_ALL;
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_GetScrollInfo(Handle, SB_HORZ, ScrollInfo);
|
||
{$else}
|
||
GetScrollInfo(Handle, SB_HORZ, ScrollInfo);
|
||
{$endif UseFlatScrollbars}
|
||
|
||
if (Integer(FRangeX) > ClientWidth) or FScrollBarOptions.AlwaysVisible then
|
||
begin
|
||
DoShowScrollBar(SB_HORZ, True);
|
||
|
||
ScrollInfo.nMin := 0;
|
||
ScrollInfo.nMax := FRangeX;
|
||
ScrollInfo.nPos := FEffectiveOffsetX;
|
||
ScrollInfo.nPage := Max(0, ClientWidth);
|
||
|
||
ScrollInfo.fMask := SIF_ALL or ScrollMasks[FScrollBarOptions.AlwaysVisible];
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_SetScrollInfo(Handle, SB_HORZ, ScrollInfo, DoRepaint);
|
||
{$else}
|
||
SetScrollInfo(Handle, SB_HORZ, ScrollInfo, DoRepaint);
|
||
{$endif UseFlatScrollbars}
|
||
end
|
||
else
|
||
begin
|
||
ScrollInfo.nMin := 0;
|
||
ScrollInfo.nMax := 0;
|
||
ScrollInfo.nPos := 0;
|
||
ScrollInfo.nPage := 0;
|
||
DoShowScrollBar(SB_HORZ, False);
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_SetScrollInfo(Handle, SB_HORZ, ScrollInfo, False);
|
||
{$else}
|
||
SetScrollInfo(Handle, SB_HORZ, ScrollInfo, False);
|
||
{$endif UseFlatScrollbars}
|
||
end;
|
||
|
||
// Since the position is automatically changed if it doesn't meet the range
|
||
// we better read the current position back to stay synchronized.
|
||
{$ifdef UseFlatScrollbars}
|
||
FEffectiveOffsetX := FlatSB_GetScrollPos(Handle, SB_HORZ);
|
||
{$else}
|
||
//todo: Use get scrollinfo instead of GetScrollPos??
|
||
FEffectiveOffsetX := GetScrollPos(Handle, SB_HORZ);
|
||
{$endif UseFlatScrollbars}
|
||
if UseRightToLeftAlignment then
|
||
SetOffsetX(-Integer(FRangeX) + ClientWidth + FEffectiveOffsetX)
|
||
else
|
||
SetOffsetX(-FEffectiveOffsetX);
|
||
end
|
||
else
|
||
begin
|
||
DoShowScrollBar(SB_HORZ, False);
|
||
|
||
// Reset the current horizontal offset to account for window resize etc.
|
||
SetOffsetX(FOffsetX);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'FEffectiveOffsetX after UpdateHScrollbar',FEffectiveOffsetX);{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateScrollBars(DoRepaint: Boolean);
|
||
|
||
// adjusts scrollbars to reflect current size and paint offset of the tree
|
||
|
||
begin
|
||
if HandleAllocated then
|
||
begin
|
||
UpdateVerticalScrollBar(DoRepaint);
|
||
UpdateHorizontalScrollBar(DoRepaint);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.UpdateVerticalScrollBar(DoRepaint: Boolean);
|
||
|
||
var
|
||
ScrollInfo: TScrollInfo;
|
||
|
||
begin
|
||
// Total node height includes the height of the invisible root node.
|
||
if FRoot.TotalHeight < FDefaultNodeHeight then
|
||
FRoot.TotalHeight := FDefaultNodeHeight;
|
||
FRangeY := FRoot.TotalHeight - FRoot.NodeHeight + FBottomSpace;
|
||
|
||
if tsUpdating in FStates then
|
||
exit;
|
||
|
||
if FScrollBarOptions.ScrollBars in [ssVertical, ssBoth] then
|
||
begin
|
||
//LCL automatically set cbSize field
|
||
//ScrollInfo.cbSize := SizeOf(ScrollInfo);
|
||
ScrollInfo.fMask := SIF_ALL;
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_GetScrollInfo(Handle, SB_VERT, ScrollInfo);
|
||
{$else}
|
||
GetScrollInfo(Handle, SB_VERT, ScrollInfo);
|
||
{$endif UseFlatScrollbars}
|
||
|
||
if (Integer(FRangeY) > ClientHeight) or FScrollBarOptions.AlwaysVisible then
|
||
begin
|
||
DoShowScrollBar(SB_VERT, True);
|
||
|
||
ScrollInfo.nMin := 0;
|
||
ScrollInfo.nMax := FRangeY;
|
||
ScrollInfo.nPos := -FOffsetY;
|
||
ScrollInfo.nPage := Max(0, ClientHeight);
|
||
|
||
ScrollInfo.fMask := SIF_ALL or ScrollMasks[FScrollBarOptions.AlwaysVisible];
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_SetScrollInfo(Handle, SB_VERT, ScrollInfo, DoRepaint);
|
||
{$else}
|
||
SetScrollInfo(Handle, SB_VERT, ScrollInfo, DoRepaint);
|
||
{$endif UseFlatScrollbars}
|
||
end
|
||
else
|
||
begin
|
||
ScrollInfo.nMin := 0;
|
||
ScrollInfo.nMax := 0;
|
||
ScrollInfo.nPos := 0;
|
||
ScrollInfo.nPage := 0;
|
||
DoShowScrollBar(SB_VERT, False);
|
||
{$ifdef UseFlatScrollbars}
|
||
FlatSB_SetScrollInfo(Handle, SB_VERT, ScrollInfo, False);
|
||
{$else}
|
||
SetScrollInfo(Handle, SB_VERT, ScrollInfo, False);
|
||
{$endif UseFlatScrollbars}
|
||
end;
|
||
|
||
// Since the position is automatically changed if it doesn't meet the range
|
||
// we better read the current position back to stay synchronized.
|
||
{$ifdef UseFlatScrollbars}
|
||
SetOffsetY(-FlatSB_GetScrollPos(Handle, SB_VERT));
|
||
{$else}
|
||
SetOffsetY(-GetScrollPos(Handle, SB_VERT));
|
||
{$endif UseFlatScrollBars}
|
||
end
|
||
else
|
||
begin
|
||
DoShowScrollbar(SB_VERT, False);
|
||
|
||
// Reset the current vertical offset to account for window resize etc.
|
||
SetOffsetY(FOffsetY);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
//lcl: the current implementation in TControl is exactly equal to this.
|
||
// disable for now and reenable in the case the TControl implementation change
|
||
{
|
||
function TBaseVirtualTree.UseRightToLeftReading: Boolean;
|
||
|
||
// The tree can handle right-to-left reading also on non-middle-east systems, so we cannot use the same function as
|
||
// it is implemented in TControl.
|
||
|
||
begin
|
||
Result := BiDiMode <> bdLeftToRight;
|
||
end;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ValidateChildren(Node: PVirtualNode; Recursive: Boolean);
|
||
|
||
// Ensures that the children of the given node (and all their children, if Recursive is True) are initialized.
|
||
// Node must already be initialized
|
||
|
||
var
|
||
Child: PVirtualNode;
|
||
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot;
|
||
|
||
if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then
|
||
InitChildren(Node);
|
||
Child := Node.FirstChild;
|
||
while Assigned(Child) do
|
||
begin
|
||
ValidateNode(Child, Recursive);
|
||
Child := Child.NextSibling;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TBaseVirtualTree.ValidateNode(Node: PVirtualNode; Recursive: Boolean);
|
||
|
||
// Ensures that the given node (and all its children, if Recursive is True) are initialized.
|
||
|
||
var
|
||
Child: PVirtualNode;
|
||
|
||
begin
|
||
if Node = nil then
|
||
Node := FRoot
|
||
else
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
|
||
if Recursive then
|
||
begin
|
||
if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then
|
||
InitChildren(Node);
|
||
Child := Node.FirstChild;
|
||
while Assigned(Child) do
|
||
begin
|
||
ValidateNode(Child, recursive);
|
||
Child := Child.NextSibling;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------- 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 FOwner 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 TCustomStringTreeOptions(Dest) do
|
||
StringOptions := Self.StringOptions;
|
||
end;
|
||
|
||
// Let ancestors assign their options to the destination class.
|
||
inherited;
|
||
end;
|
||
|
||
//----------------- TVTEdit --------------------------------------------------------------------------------------------
|
||
|
||
// Implementation of a generic node caption editor.
|
||
|
||
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.CNCommand(var Message: TLMCommand);
|
||
|
||
begin
|
||
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 IsWinNT then
|
||
AutoAdjustSize
|
||
else
|
||
PostMessage(Handle, CM_AUTOADJUST, 0, 0);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVTEdit.DoRelease(Data: PtrInt);
|
||
begin
|
||
Free;
|
||
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);
|
||
|
||
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
|
||
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 := ssCtrlOS 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
|
||
DC := GetDC(Handle);
|
||
LastFont := SelectObject(DC, Font.Reference.Handle);
|
||
try
|
||
// Read needed space for the current text.
|
||
GetTextExtentPoint32(DC, PChar(Text), Length(Text), Size);
|
||
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);
|
||
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
|
||
//todo: delphi uses Multiline for all
|
||
//Style := Style or ES_MULTILINE;
|
||
if vsMultiline in FLink.FNode.States then
|
||
begin
|
||
Style := Style and not (ES_AUTOHSCROLL or WS_HSCROLL) or WS_VSCROLL or ES_AUTOVSCROLL;
|
||
Style := Style or ES_MULTILINE;
|
||
end;
|
||
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
|
||
Application.QueueAsyncCall(DoRelease, 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;
|
||
|
||
// Notifies the edit link that editing can start now. descendants 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;
|
||
|
||
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;
|
||
|
||
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;
|
||
|
||
begin
|
||
Result := FEdit.BoundsRect;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TStringEditLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
||
|
||
// Retrieves the true text bounds from the owner tree.
|
||
|
||
var
|
||
Text: String;
|
||
|
||
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 := clWindowText;
|
||
FEdit.Parent := Tree;
|
||
FEdit.HandleNeeded;
|
||
FEdit.Text := Text;
|
||
|
||
if Column <= NoColumn then
|
||
begin
|
||
FEdit.BidiMode := FTree.BidiMode;
|
||
FAlignment := FTree.Alignment;
|
||
end
|
||
else
|
||
begin
|
||
FEdit.BidiMode := FTree.Header.Columns[Column].BidiMode;
|
||
FAlignment := FTree.Header.Columns[Column].Alignment;
|
||
end;
|
||
|
||
if FEdit.BidiMode <> bdLeftToRight then
|
||
ChangeBidiModeAlignment(FAlignment);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TStringEditLink.ProcessMessage(var Message: TLMessage);
|
||
|
||
begin
|
||
FEdit.WindowProc(Message);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TStringEditLink.SetBounds(R: TRect);
|
||
|
||
// 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);
|
||
|
||
SendMessage(FEdit.Handle, EM_SETRECTNP, 0, PtrUInt(@R));
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------- TCustomVirtualString -------------------------------------------------------------------------------
|
||
|
||
constructor TCustomVirtualStringTree.Create(AOwner: TComponent);
|
||
|
||
begin
|
||
inherited;
|
||
if (Owner = nil) or (([csReading, csDesigning] * Owner.ComponentState) = [csDesigning]) then
|
||
FDefaultText := 'Node';
|
||
FInternalDataOffset := AllocateInternalDataArea(SizeOf(Cardinal));
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.GetRenderStartValues(Source: TVSTTextSourceType; out Node: PVirtualNode;
|
||
out 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(nil, True);
|
||
NextNodeProc := GetNextVisible;
|
||
end;
|
||
else // tstAll
|
||
Node := GetFirst;
|
||
NextNodeProc := GetNext;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.GetImageText(Node: PVirtualNode;
|
||
Kind: TVTImageKind; Column: TColumnIndex): String;
|
||
begin
|
||
Assert(Assigned(Node), 'Node must not be nil.');
|
||
|
||
if not (vsInitialized in Node.States) then
|
||
InitNode(Node);
|
||
Result := '';
|
||
|
||
DoGetImageText(Node, Kind, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.GetOptions: TCustomStringTreeOptions;
|
||
|
||
begin
|
||
Result := FOptions as TCustomStringTreeOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.GetText(Node: PVirtualNode; Column: TColumnIndex): String;
|
||
|
||
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;
|
||
|
||
if (toHotTrack in FOptions.FPaintOptions) and (Node = FCurrentHotNode) then
|
||
begin
|
||
if not IsWinVistaOrAbove or not (tsUseThemes in FStates) or
|
||
not (toUseExplorerTheme in FOptions.FPaintOptions) then
|
||
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 = FFocusedColumn) or (toFullRowSelect in FOptions.FSelectionOptions) then
|
||
begin
|
||
if Node = FDropTargetNode then
|
||
begin
|
||
if (FLastDropMode = dmOnNode) or (vsSelected in Node.States) and
|
||
(not IsWinVistaOrAbove or not (tsUseThemes in FStates) or
|
||
not (toUseExplorerTheme in FOptions.FPaintOptions)) then
|
||
Canvas.Font.Color := clHighlightText;
|
||
end
|
||
else
|
||
if vsSelected in Node.States then
|
||
begin
|
||
if (Focused or (toPopupMode in FOptions.FPaintOptions)) and
|
||
(not IsWinVistaOrAbove or not (tsUseThemes in FStates) or
|
||
not (toUseExplorerTheme in FOptions.FPaintOptions)) then
|
||
Canvas.Font.Color := clHighlightText;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.PaintNormalText(var PaintInfo: TVTPaintInfo; TextOutFlags: Integer;
|
||
Text: String);
|
||
|
||
// This method is responsible for painting the given text 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
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintDetails],'PaintNormalText') ;{$endif}
|
||
InitializeTextProperties(PaintInfo);
|
||
with PaintInfo do
|
||
begin
|
||
R := ContentRect;
|
||
//todo_lcl See how TextStyle should be set
|
||
//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 or DT_END_ELLIPSIS or DT_EDITCONTROL or AlignmentToDrawFlag[Alignment];
|
||
if BidiMode <> bdLeftToRight then
|
||
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.
|
||
GetTextExtentPoint32(Canvas.Handle, PChar(Text), Length(Text), 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;
|
||
if BidiMode <> bdLeftToRight then
|
||
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
|
||
Text := DoShortenString(Canvas, Node, Column, Text, R.Right - R.Left, TripleWidth);
|
||
if Alignment = taRightJustify then
|
||
DrawFormat := DrawFormat or DT_RIGHT
|
||
else
|
||
DrawFormat := DrawFormat or DT_LEFT;
|
||
end
|
||
else
|
||
DrawFormat := DrawFormat or AlignmentToDrawFlag[Alignment];
|
||
end;
|
||
//todo_lcl_check
|
||
if not Canvas.TextStyle.Opaque then
|
||
SetBkMode(Canvas.Handle, TRANSPARENT)
|
||
else
|
||
SetBkMode(Canvas.Handle, OPAQUE);
|
||
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails],'Canvas.Brush.Color',Canvas.Brush.Color);{$endif}
|
||
DoTextDrawing(PaintInfo, Text, R, DrawFormat);
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'PaintNormalText');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.PaintStaticText(const PaintInfo: TVTPaintInfo; TextOutFlags: Integer;
|
||
const Text: String);
|
||
|
||
// This method retrives and draws the static text bound to a particular node.
|
||
|
||
var
|
||
R: TRect;
|
||
DrawFormat: Cardinal;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintDetails],'PaintStaticText');{$endif}
|
||
with PaintInfo do
|
||
begin
|
||
Canvas.Font := Font;
|
||
if toFullRowSelect in FOptions.FSelectionOptions then
|
||
begin
|
||
if Node = FDropTargetNode then
|
||
begin
|
||
if (FLastDropMode = dmOnNode) or (vsSelected in Node.States)then
|
||
Canvas.Font.Color := clHighlightText
|
||
else
|
||
Canvas.Font.Color := Font.Color;
|
||
end
|
||
else
|
||
if vsSelected in Node.States then
|
||
begin
|
||
if Focused or (toPopupMode in FOptions.FPaintOptions) then
|
||
Canvas.Font.Color := clHighlightText
|
||
else
|
||
Canvas.Font.Color := Font.Color;
|
||
end;
|
||
end;
|
||
|
||
DrawFormat := DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE;
|
||
//todo_lcl See how Canvas.TextStyle should be
|
||
//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);
|
||
//todo_lcl_check
|
||
if not Canvas.TextStyle.Opaque then
|
||
SetBkMode(Canvas.Handle, TRANSPARENT)
|
||
else
|
||
SetBkMode(Canvas.Handle, OPAQUE);
|
||
DrawText(Canvas.Handle, PChar(Text), Length(Text), R, DrawFormat)
|
||
end;
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'PaintStaticText');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.SetDefaultText(const Value: String);
|
||
|
||
begin
|
||
if FDefaultText <> Value then
|
||
begin
|
||
FDefaultText := Value;
|
||
if not (csLoading in ComponentState) then
|
||
Invalidate;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.SetOptions(const Value: TCustomStringTreeOptions);
|
||
|
||
begin
|
||
FOptions.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.SetText(Node: PVirtualNode; Column: TColumnIndex; const Value: String);
|
||
|
||
begin
|
||
DoNewText(Node, Column, Value);
|
||
InvalidateNode(Node);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.CMFontChanged(var Msg: TLMessage);
|
||
|
||
// 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;
|
||
Data: PInteger;
|
||
|
||
begin
|
||
inherited;
|
||
MemDC := CreateCompatibleDC(0);
|
||
try
|
||
SelectObject(MemDC, Font.Reference.Handle);
|
||
GetTextMetrics(MemDC, TM);
|
||
FTextHeight := TM.tmHeight;
|
||
|
||
GetTextExtentPoint32(MemDC, '...', 3, Size);
|
||
FEllipsisWidth := Size.cx;
|
||
finally
|
||
DeleteDC(MemDC);
|
||
end;
|
||
|
||
// Have to reset all node widths.
|
||
Run := FRoot.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
Data := InternalData(Run);
|
||
if Assigned(Data) then
|
||
Data^ := 0;
|
||
Run := GetNextNoInit(Run);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; out 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.FAutoOptions) and FHeader.UseColumns and (PaintInfo.BidiMode = bdLeftToRight) then
|
||
with FHeader.FColumns, 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
|
||
(Items[NextNonEmpty].BidiMode <> bdLeftToRight) then
|
||
Break;
|
||
Inc(CellRect.Right, Items[NextNonEmpty].Width);
|
||
NextNonEmpty := GetNextVisibleColumn(NextNonEmpty);
|
||
until False;
|
||
end
|
||
else
|
||
inherited;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.CalculateTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: String): Integer;
|
||
|
||
// Determines the width of the given text.
|
||
|
||
begin
|
||
Result := 2 * FTextMargin;
|
||
if Length(Text) > 0 then
|
||
begin
|
||
Canvas.Font := Font;
|
||
DoPaintText(Node, Canvas, Column, ttNormal);
|
||
|
||
Inc(Result, DoTextMeasuring(Canvas, Node, Column, Text));
|
||
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;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
{$ifndef LCLWin32}
|
||
procedure TCustomVirtualStringTree.CopyToClipBoard;
|
||
begin
|
||
if FSelectionCount > 0 then
|
||
begin
|
||
MarkCutCopyNodes;
|
||
DoStateChange([tsCopyPending]);
|
||
Clipboard.AsText := ContentToUTF8(tstCutCopySet, #9);
|
||
DoStateChange([], [tsCopyPending]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.CutToClipBoard;
|
||
begin
|
||
//todo: currently there's no way in LCL to know when the clipboard was used
|
||
CopyToClipBoard;
|
||
end;
|
||
{$endif}
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
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;
|
||
var LineBreakStyle: TVTTooltipLineBreakStyle): String;
|
||
|
||
begin
|
||
Result := inherited DoGetNodeHint(Node, Column, LineBreakStyle);
|
||
if Assigned(FOnGetHint) then
|
||
FOnGetHint(Self, Node, Column, LineBreakStyle, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex;
|
||
var LineBreakStyle: TVTTooltipLineBreakStyle): String;
|
||
|
||
begin
|
||
Result := inherited DoGetNodeToolTip(Node, Column, LineBreakStyle);
|
||
if Assigned(FOnGetHint) then
|
||
FOnGetHint(Self, Node, Column, LineBreakStyle, Result)
|
||
else
|
||
Result := Text[Node, Column];
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: 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 := FHeader.Columns[Column].Width
|
||
else
|
||
begin
|
||
if Canvas = nil then
|
||
Canvas := Self.Canvas;
|
||
|
||
if Column = FHeader.MainColumn then
|
||
begin
|
||
// Primary column or no columns.
|
||
Data := InternalData(Node);
|
||
if Assigned(Data) then
|
||
begin
|
||
Result := Data^;
|
||
if Result = 0 then
|
||
begin
|
||
Data^ := CalculateTextWidth(Canvas, Node, Column, Text[Node, Column]);
|
||
Result := Data^;
|
||
end;
|
||
end
|
||
else
|
||
Result := 0;
|
||
end
|
||
else
|
||
// any other column
|
||
Result := CalculateTextWidth(Canvas, Node, Column, Text[Node, Column]);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.DoGetText(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
||
var Text: String);
|
||
|
||
begin
|
||
if Assigned(FOnGetText) then
|
||
FOnGetText(Self, Node, Column, TextType, Text);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.DoIncrementalSearch(Node: PVirtualNode; const Text: String): 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, Text, Result)
|
||
else
|
||
// Default behavior is to match the search string with the start of the node text.
|
||
if Pos(Text, GetText(Node, FocusedColumn)) <> 1 then
|
||
Result := 1;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.DoNewText(Node: PVirtualNode; Column: TColumnIndex; const Text: String);
|
||
|
||
begin
|
||
if Assigned(FOnNewText) then
|
||
FOnNewText(Self, Node, Column, Text);
|
||
|
||
// The width might have changed, so update the scrollbar.
|
||
if FUpdateCount = 0 then
|
||
UpdateHorizontalScrollBar(True);
|
||
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: String;
|
||
TextOutFlags: Integer;
|
||
|
||
begin
|
||
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcPaintDetails],'TCustomVirtualStringTree.DoPaintNode');{$endif}
|
||
// 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 or 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);
|
||
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcPaintDetails],'TCustomVirtualStringTree.DoPaintNode');{$endif}
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.DoPaintText(Node: PVirtualNode; const Canvas: TCanvas; Column: TColumnIndex;
|
||
TextType: TVSTTextType);
|
||
|
||
begin
|
||
if Assigned(FOnPaintText) then
|
||
FOnPaintText(Self, Canvas, Node, Column, TextType);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.DoShortenString(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
||
const S: String; Width: Integer; EllipsisWidth: Integer = 0): String;
|
||
|
||
var
|
||
Done: Boolean;
|
||
|
||
begin
|
||
Done := False;
|
||
if Assigned(FOnShortenString) then
|
||
FOnShortenString(Self, Canvas, Node, Column, S, Width, Result, Done);
|
||
if not Done then
|
||
Result := ShortenString(Canvas.Handle, S, Width, EllipsisWidth);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.DoTextDrawing(var PaintInfo: TVTPaintInfo; const Text: String; CellRect: TRect;
|
||
DrawFormat: Cardinal);
|
||
|
||
var
|
||
DefaultDraw: Boolean;
|
||
|
||
begin
|
||
DefaultDraw := True;
|
||
if Assigned(FOnDrawText) then
|
||
FOnDrawText(Self, PaintInfo.Canvas, PaintInfo.Node, PaintInfo.Column, Text, CellRect, DefaultDraw);
|
||
if DefaultDraw then
|
||
DrawText(PaintInfo.Canvas.Handle, PChar(Text), Length(Text), CellRect, DrawFormat);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.DoTextMeasuring(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
||
const Text: String): Integer;
|
||
|
||
var
|
||
Size: TSize;
|
||
R: TRect;
|
||
DrawFormat: Integer;
|
||
|
||
begin
|
||
GetTextExtentPoint32(Canvas.Handle, PChar(Text), Length(Text), Size);
|
||
if vsMultiLine in Node.States then
|
||
begin
|
||
DrawFormat := DT_CALCRECT or DT_NOPREFIX or DT_WORDBREAK or DT_END_ELLIPSIS or DT_EDITCONTROL or AlignmentToDrawFlag[Alignment];
|
||
if BidiMode <> bdLeftToRight then
|
||
DrawFormat := DrawFormat or DT_RTLREADING;
|
||
|
||
R := Rect(0, 0, Size.cx, MaxInt);
|
||
DrawText(Canvas.Handle, PChar(Text), Length(Text), R, DrawFormat);
|
||
Size.cx := R.Right - R.Left;
|
||
end;
|
||
Result := Size.cx;
|
||
if Assigned(FOnMeasureTextWidth) then
|
||
FOnMeasureTextWidth(Self, Canvas, Node, Column, Text, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.GetOptionsClass: TTreeOptionsClass;
|
||
|
||
begin
|
||
Result := TCustomStringTreeOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
|
||
|
||
begin
|
||
if (Node = FRoot) or (Node = nil) then
|
||
Result := nil
|
||
else
|
||
Result := PByte(Node) + FInternalDataOffset;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.MainColumnChanged;
|
||
|
||
var
|
||
Run: PVirtualNode;
|
||
Data: PInteger;
|
||
|
||
begin
|
||
inherited;
|
||
|
||
// Have to reset all node widths.
|
||
Run := FRoot.FirstChild;
|
||
while Assigned(Run) do
|
||
begin
|
||
Data := InternalData(Run);
|
||
if Assigned(Data) then
|
||
Data^ := 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: String;
|
||
|
||
begin
|
||
case ChunkType of
|
||
CaptionChunk:
|
||
begin
|
||
NewText := '';
|
||
if ChunkSize > 0 then
|
||
begin
|
||
SetLength(NewText, ChunkSize);
|
||
Stream.Read(PChar(NewText)^, ChunkSize);
|
||
end;
|
||
// Do a new text event regardless of the caption content to allow removing the default string.
|
||
Text[Node, FHeader.MainColumn] := NewText;
|
||
Result := True;
|
||
end;
|
||
else
|
||
Result := inherited ReadChunk(Stream, Version, Node, ChunkType, ChunkSize);
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium;
|
||
ForClipboard: Boolean): HResult;
|
||
|
||
// Returns string expressions of all currently selected nodes in the Medium structure.
|
||
|
||
begin
|
||
Result := inherited RenderOLEData(FormatEtcIn, Medium, ForClipboard);
|
||
if Failed(Result) then
|
||
try
|
||
if ForClipboard then
|
||
Medium.hGlobal := ContentToClipboard(FormatEtcIn.cfFormat, tstCutCopySet)
|
||
else
|
||
Medium.hGlobal := ContentToClipboard(FormatEtcIn.cfFormat, tstSelected);
|
||
|
||
// Fill rest of the Medium structure if rendering went fine.
|
||
if Medium.hGlobal <> 0 then
|
||
begin
|
||
Medium.tymed := TYMED_HGLOBAL;
|
||
Medium.PunkForRelease := nil;
|
||
|
||
Result := S_OK;
|
||
end;
|
||
except
|
||
Result := E_FAIL;
|
||
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
|
||
Header: TChunkHeader;
|
||
S: String;
|
||
Len: Integer;
|
||
|
||
begin
|
||
inherited;
|
||
if (toSaveCaptions in TreeOptions.FStringOptions) and (Node <> FRoot) and
|
||
(vsInitialized in Node.States) then
|
||
with Stream do
|
||
begin
|
||
// Read the node's caption (primary column only).
|
||
S := Text[Node, FHeader.MainColumn];
|
||
Len := Length(S);
|
||
if Len > 0 then
|
||
begin
|
||
// Write a new sub chunk.
|
||
Header.ChunkType := CaptionChunk;
|
||
Header.ChunkSize := Len;
|
||
Write(Header, SizeOf(Header));
|
||
Write(PChar(S)^, Len);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.ComputeNodeHeight(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
||
S: String): Integer;
|
||
|
||
// Default node height calculation for multi line nodes. This method can be used by the application to delegate the
|
||
// computation to the string tree.
|
||
// Canvas is used to compute that value by using its current font settings.
|
||
// Node and Column describe the cell to be used for the computation.
|
||
// S is the string for which the height must be computed. If this string is empty the cell text is used instead.
|
||
|
||
var
|
||
DrawFormat: Cardinal;
|
||
BidiMode: TBidiMode;
|
||
Alignment: TAlignment;
|
||
PaintInfo: TVTPaintInfo;
|
||
Dummy: TColumnIndex;
|
||
LineImage: TLineImage;
|
||
|
||
begin
|
||
if Length(S) = 0 then
|
||
S := Text[Node, Column];
|
||
DrawFormat := DT_TOP or DT_NOPREFIX or DT_CALCRECT or DT_WORDBREAK;
|
||
if Column <= NoColumn then
|
||
begin
|
||
BidiMode := Self.BidiMode;
|
||
Alignment := Self.Alignment;
|
||
end
|
||
else
|
||
begin
|
||
BidiMode := Header.Columns[Column].BidiMode;
|
||
Alignment := Header.Columns[Column].Alignment;
|
||
end;
|
||
|
||
if BidiMode <> bdLeftToRight then
|
||
ChangeBidiModeAlignment(Alignment);
|
||
|
||
// Allow for autospanning.
|
||
PaintInfo.Node := Node;
|
||
PaintInfo.BidiMode := BidiMode;
|
||
PaintInfo.Column := Column;
|
||
PaintInfo.CellRect := Rect(0, 0, 0, 0);
|
||
if Column > NoColumn then
|
||
begin
|
||
PaintInfo.CellRect.Right := FHeader.Columns[Column].Width - FTextMargin;
|
||
PaintInfo.CellRect.Left := FTextMargin + FMargin;
|
||
if Column = Header.MainColumn then
|
||
begin
|
||
if toFixedIndent in FOptions.FPaintOptions then
|
||
SetLength(LineImage, 1)
|
||
else
|
||
DetermineLineImageAndSelectLevel(Node, LineImage);
|
||
Inc(PaintInfo.CellRect.Left, Length(LineImage) * Integer(Indent));
|
||
end;
|
||
end
|
||
else
|
||
PaintInfo.CellRect.Right := ClientWidth;
|
||
AdjustPaintCellRect(PaintInfo, Dummy);
|
||
|
||
if BidiMode <> bdLeftToRight then
|
||
DrawFormat := DrawFormat or DT_RIGHT or DT_RTLREADING
|
||
else
|
||
DrawFormat := DrawFormat or DT_LEFT;
|
||
DrawText(Canvas.Handle, PChar(S), Length(S), PaintInfo.CellRect, DrawFormat);
|
||
Result := PaintInfo.CellRect.Bottom - PaintInfo.CellRect.Top;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.ContentToHTML(Source: TVSTTextSourceType; const Caption: String = ''): String;
|
||
|
||
// Renders the current tree content (depending on Source) as HTML text encoded in UTF-8.
|
||
// If Caption is not empty then it is used to create and fill the header for the table built here.
|
||
// Based on ideas and code from Frank van den Bergh and Andreas H<>rstemeier.
|
||
|
||
var
|
||
Buffer: TBufferedUTF8String;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure WriteColorAsHex(Color: TColor);
|
||
|
||
var
|
||
WinColor: COLORREF;
|
||
I: Integer;
|
||
Component,
|
||
Value: Byte;
|
||
|
||
begin
|
||
Buffer.Add('#');
|
||
WinColor := ColorToRGB(Color);
|
||
I := 1;
|
||
while I <= 6 do
|
||
begin
|
||
Component := WinColor and $FF;
|
||
|
||
Value := 48 + (Component shr 4);
|
||
if Value > $39 then
|
||
Inc(Value, 7);
|
||
Buffer.Add(AnsiChar(Value));
|
||
Inc(I);
|
||
|
||
Value := 48 + (Component and $F);
|
||
if Value > $39 then
|
||
Inc(Value, 7);
|
||
Buffer.Add(AnsiChar(Value));
|
||
Inc(I);
|
||
|
||
WinColor := WinColor shr 8;
|
||
end;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure WriteStyle(const Name: AnsiString; 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');
|
||
if Font.Size < 0 then
|
||
Buffer.Add(Format(' %dpx "%s";', [Font.Height, Font.Name]))
|
||
else
|
||
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;
|
||
Text: String;
|
||
|
||
RenderColumns: Boolean;
|
||
Columns: TColumnsArray;
|
||
ColumnColors: array of String;
|
||
Index: Integer;
|
||
IndentWidth,
|
||
LineStyleText: String;
|
||
Alignment: TAlignment;
|
||
BidiMode: TBidiMode;
|
||
|
||
CellPadding: String;
|
||
|
||
begin
|
||
Buffer := TBufferedUTF8String.Create;
|
||
try
|
||
// For customization by the application or descendants 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(Caption) > 0 then
|
||
AddHeader := AddHeader + 'caption="' + Caption + '"';
|
||
if Borderstyle <> bsNone then
|
||
AddHeader := AddHeader + Format(' border="%d" frame=box', [BorderWidth + 1]);
|
||
|
||
Buffer.Add('<META http-equiv="Content-Type" content="text/html; charset=utf-8">');
|
||
|
||
// 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', FHeader.Font);
|
||
Buffer.AddNewLine;
|
||
|
||
// Determine grid/table lines and create CSS for it.
|
||
// Vertical and/or horizontal border to show.
|
||
if FLineStyle = lsSolid then
|
||
LineStyleText := 'solid;'
|
||
else
|
||
LineStyleText := 'dotted;';
|
||
if toShowHorzGridLines in FOptions.FPaintOptions 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; vertical-align:top;');
|
||
if toShowVertGridLines in FOptions.FPaintOptions then
|
||
Buffer.Add('border-right:1 ' + LineStyleText)
|
||
else
|
||
Buffer.Add('border-right:none;');
|
||
if toShowHorzGridLines in FOptions.FPaintOptions 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 := FHeader.UseColumns;
|
||
if RenderColumns then
|
||
begin
|
||
Columns := FHeader.FColumns.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
|
||
if (CanExportNode(Run)) then
|
||
begin
|
||
Level := GetNodeLevel(Run);
|
||
if Level > MaxLevel then
|
||
MaxLevel := Level;
|
||
end;
|
||
Run := GetNextNode(Run);
|
||
end;
|
||
|
||
if RenderColumns then
|
||
begin
|
||
if Assigned(FOnBeforeHeaderExport) then
|
||
FOnBeforeHeaderExport(Self, etHTML);
|
||
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
|
||
if Assigned(FOnBeforeColumnExport) then
|
||
FOnBeforeColumnExport(Self, etHTML, Columns[I]);
|
||
Buffer.Add('<th height="');
|
||
Buffer.Add(IntToStr(FHeader.FHeight));
|
||
Buffer.Add('px"');
|
||
Alignment := Columns[I].CaptionAlignment;
|
||
// Consider directionality.
|
||
if Columns[I].FBiDiMode <> bdLeftToRight then
|
||
begin
|
||
ChangeBidiModeAlignment(Alignment);
|
||
Buffer.Add(' dir="rtl"');
|
||
end;
|
||
|
||
// Consider aligment.
|
||
case Alignment of
|
||
taRightJustify:
|
||
Buffer.Add(' align=right');
|
||
taCenter:
|
||
Buffer.Add(' align=center');
|
||
else
|
||
Buffer.Add(' align=left');
|
||
end;
|
||
|
||
Index := Columns[I].Index;
|
||
// Merge cells of the header emulation in the main column.
|
||
if (MaxLevel > 0) and (Index = Header.MainColumn) then
|
||
begin
|
||
Buffer.Add(' colspan="');
|
||
Buffer.Add(IntToStr(MaxLevel + 1));
|
||
Buffer.Add('"');
|
||
end;
|
||
|
||
// The color of the header is usually clBtnFace.
|
||
Buffer.Add(' bgcolor=');
|
||
WriteColorAsHex(clBtnFace);
|
||
|
||
// Set column width in pixels.
|
||
Buffer.Add(' width="');
|
||
Buffer.Add(IntToStr(Columns[I].Width));
|
||
Buffer.Add('px">');
|
||
|
||
if Length(Columns[I].Text) > 0 then
|
||
Buffer.Add(Columns[I].Text);
|
||
Buffer.Add('</th>');
|
||
if Assigned(FOnAfterColumnExport) then
|
||
FOnAfterColumnExport(Self, etHTML, Columns[I]);
|
||
end;
|
||
Buffer.Add('</tr>');
|
||
Buffer.AddNewLine;
|
||
if Assigned(FOnAfterHeaderExport) then
|
||
FOnAfterHeaderExport(self, etHTML);
|
||
end;
|
||
|
||
// Now go through the tree.
|
||
Run := Save;
|
||
while Assigned(Run) do
|
||
begin
|
||
if ((not CanExportNode(Run)) or (Assigned(FonBeforeNodeExport) and (not FOnBeforeNodeExport(Self, etHTML, Run)))) then
|
||
begin
|
||
Run := GetNextNode(Run);
|
||
Continue;
|
||
end;
|
||
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].FOptions) 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].FOptions) 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].FOptions) 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
|
||
Alignment := Columns[I].Alignment;
|
||
BidiMode := Columns[I].BidiMode;
|
||
end
|
||
else
|
||
begin
|
||
Alignment := Self.Alignment;
|
||
BidiMode := Self.BidiMode;
|
||
end;
|
||
// Consider directionality.
|
||
if BiDiMode <> bdLeftToRight then
|
||
begin
|
||
ChangeBidiModeAlignment(Alignment);
|
||
Buffer.Add(' dir="rtl"');
|
||
end;
|
||
|
||
// Consider aligment.
|
||
case Alignment of
|
||
taRightJustify:
|
||
Buffer.Add(' align=right');
|
||
taCenter:
|
||
Buffer.Add(' align=center');
|
||
else
|
||
Buffer.Add(' align=left');
|
||
end;
|
||
// Merge cells in the main column.
|
||
if (MaxLevel > 0) and (Index = FHeader.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].FOptions) then
|
||
begin
|
||
Buffer.Add(' bgcolor=');
|
||
WriteColorAsHex(Columns[I].Color);
|
||
end;
|
||
Buffer.Add('>');
|
||
Text := Self.Text[Run, Index];
|
||
if Length(Text) > 0 then
|
||
Buffer.Add(Text);
|
||
Buffer.Add('</td>');
|
||
end;
|
||
|
||
if not RenderColumns then
|
||
Break;
|
||
Inc(I);
|
||
end;
|
||
if Assigned(FOnAfterNodeExport) then
|
||
FOnAfterNodeExport(Self, etHTML, Run);
|
||
Run := GetNextNode(Run);
|
||
Buffer.Add(' </tr>');
|
||
Buffer.AddNewLine;
|
||
end;
|
||
Buffer.Add('</table>');
|
||
|
||
RestoreFontChangeEvent(Canvas);
|
||
|
||
Result := Buffer.AsUTF8String;
|
||
finally
|
||
Buffer.Free;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.CanExportNode(Node: PVirtualNode ): Boolean;
|
||
|
||
begin
|
||
Result := True;
|
||
case FOptions.ExportMode of
|
||
emChecked:
|
||
Result := Node.CheckState = csCheckedNormal;
|
||
emUnchecked:
|
||
Result := Node.CheckState = csUncheckedNormal;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.ContentToRTF(Source: TVSTTextSourceType): AnsiString;
|
||
|
||
// 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<>rstemeier.
|
||
|
||
var
|
||
Fonts: TStringList;
|
||
Colors: TFpList;
|
||
CurrentFontIndex,
|
||
CurrentFontColor,
|
||
CurrentFontSize: Integer;
|
||
Buffer: TBufferedUTF8String;
|
||
|
||
//--------------- 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 := Colors.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 := Colors.Add(Pointer(Color));
|
||
Buffer.Add('\cf');
|
||
Buffer.Add(IntToStr(I + 1));
|
||
CurrentFontColor := I;
|
||
end;
|
||
end;
|
||
|
||
//---------------------------------------------------------------------------
|
||
|
||
procedure TextPlusFont(const Text: String; Font: TFont);
|
||
|
||
var
|
||
UseUnderline,
|
||
UseItalic,
|
||
UseBold: Boolean;
|
||
I: Integer;
|
||
WText: UnicodeString;
|
||
begin
|
||
if Length(Text) > 0 then
|
||
begin
|
||
WText := UTF8Decode(Text);
|
||
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(WText) do
|
||
begin
|
||
if (Text[I] = WideLF) then
|
||
Buffer.Add( '{\par}' )
|
||
else if (Text[i] <> WideCR) then
|
||
begin
|
||
Buffer.Add(Format('\u%d\''3f', [SmallInt(WText[I])]));
|
||
Continue;
|
||
end;
|
||
end;
|
||
if UseUnderline then
|
||
Buffer.Add('\ul0');
|
||
if UseItalic then
|
||
Buffer.Add('\i0');
|
||
if UseBold then
|
||
Buffer.Add('\b0');
|
||
end;
|
||
end;
|
||
|
||
//--------------- end local functions ---------------------------------------
|
||
|
||
var
|
||
Level, LastLevel: Integer;
|
||
I, J: Integer;
|
||
Save, Run: PVirtualNode;
|
||
GetNextNode: TGetNextNodeProc;
|
||
S, Tabs : String;
|
||
Text: String;
|
||
Twips: Integer;
|
||
|
||
RenderColumns: Boolean;
|
||
Columns: TColumnsArray;
|
||
Index: Integer;
|
||
Alignment: TAlignment;
|
||
BidiMode: TBidiMode;
|
||
|
||
begin
|
||
Buffer := TBufferedUTF8String.Create;
|
||
try
|
||
// For customization by the application or descendants we use again the redirected font change event.
|
||
RedirectFontChangeEvent(Canvas);
|
||
|
||
Fonts := TStringList.Create;
|
||
Colors := TFpList.Create;
|
||
CurrentFontIndex := -1;
|
||
CurrentFontColor := -1;
|
||
CurrentFontSize := -1;
|
||
|
||
Columns := nil;
|
||
Tabs := '';
|
||
LastLevel := 0;
|
||
|
||
RenderColumns := FHeader.UseColumns;
|
||
if RenderColumns then
|
||
Columns := FHeader.FColumns.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
|
||
if Assigned(FOnBeforeHeaderExport) then
|
||
FonBeforeHeaderExport(Self, etRTF);
|
||
Buffer.Add('\pard\intbl');
|
||
for I := 0 to High(Columns) do
|
||
begin
|
||
if Assigned(FOnBeforeColumnExport) then
|
||
FOnBeforeColumnExport(Self, etRTF, Columns[I]);
|
||
Alignment := Columns[I].CaptionAlignment;
|
||
BidiMode := Columns[I].BidiMode;
|
||
|
||
// Alignment is not supported with older RTF formats, however it will be ignored.
|
||
if BidiMode <> bdLeftToRight then
|
||
ChangeBidiModeAlignment(Alignment);
|
||
case Alignment of
|
||
taRightJustify:
|
||
Buffer.Add('\qr');
|
||
taCenter:
|
||
Buffer.Add('\qc');
|
||
end;
|
||
|
||
TextPlusFont(Columns[I].Text, Header.Font);
|
||
Buffer.Add('\cell');
|
||
if Assigned(FOnAfterColumnExport) then
|
||
FOnAfterColumnExport( self, etRTF, Columns[I] );
|
||
end;
|
||
Buffer.Add('\row');
|
||
if Assigned(FOnAfterHeaderExport) then
|
||
FOnAfterHeaderExport(Self, etRTF);
|
||
end;
|
||
|
||
// Now write the contents.
|
||
Run := Save;
|
||
while Assigned(Run) do
|
||
begin
|
||
if ((not CanExportNode(Run)) or
|
||
(Assigned(FOnBeforeNodeExport) and (not FOnBeforeNodeExport(Self, etRTF, Run)))) then
|
||
begin
|
||
Run := GetNextNode(Run);
|
||
Continue;
|
||
end;
|
||
I := 0;
|
||
while not RenderColumns or (I < Length(Columns)) do
|
||
begin
|
||
if RenderColumns then
|
||
begin
|
||
Index := Columns[I].Index;
|
||
Alignment := Columns[I].Alignment;
|
||
BidiMode := Columns[I].BidiMode;
|
||
end
|
||
else
|
||
begin
|
||
Index := NoColumn;
|
||
Alignment := FAlignment;
|
||
BidiMode := Self.BidiMode;
|
||
end;
|
||
|
||
if not RenderColumns or (coVisible in Columns[I].Options) then
|
||
begin
|
||
Text := Self.Text[Run, Index];
|
||
Buffer.Add('\pard\intbl');
|
||
|
||
// Alignment is not supported with older RTF formats, however it will be ignored.
|
||
if BidiMode <> bdLeftToRight then
|
||
ChangeBidiModeAlignment(Alignment);
|
||
case Alignment of
|
||
taRightJustify:
|
||
Buffer.Add('\qr');
|
||
taCenter:
|
||
Buffer.Add('\qc');
|
||
end;
|
||
|
||
// Call back the application to know about font customization.
|
||
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(Text, Canvas.Font);
|
||
Buffer.Add('\cell');
|
||
end
|
||
else
|
||
begin
|
||
TextPlusFont(Text, Canvas.Font);
|
||
Buffer.Add('\cell');
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
TextPlusFont(Text, Canvas.Font);
|
||
Buffer.Add('\cell');
|
||
end;
|
||
end;
|
||
|
||
if not RenderColumns then
|
||
Break;
|
||
Inc(I);
|
||
end;
|
||
Buffer.Add('\row');
|
||
if (Assigned(FOnAfterNodeExport)) then
|
||
FOnAfterNodeExport(Self, etRTF, Run);
|
||
Run := GetNextNode(Run);
|
||
end;
|
||
|
||
Buffer.Add('\pard\par');
|
||
|
||
// Build lists with fonts and colors. They have to be at the start of the document.
|
||
S := '{\rtf1\ansi\ansicpg1252\deff0\deflang1043{\fonttbl';
|
||
for I := 0 to Fonts.Count - 1 do
|
||
S := S + Format('{\f%d %s;}', [I, Fonts[I]]);
|
||
S := S + '}';
|
||
|
||
S := S + '{\colortbl;';
|
||
for I := 0 to Colors.Count - 1 do
|
||
begin
|
||
J := ColorToRGB(TColor(Colors[I]));
|
||
S := S + Format('\red%d\green%d\blue%d;', [J and $FF, (J shr 8) and $FF, (J shr 16) and $FF]);
|
||
end;
|
||
S := S + '}';
|
||
|
||
Result := S + Buffer.AsAnsiString + '}';
|
||
Fonts.Free;
|
||
Colors.Free;
|
||
|
||
RestoreFontChangeEvent(Canvas);
|
||
finally
|
||
Buffer.Free;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.ContentToCustom(Source: TVSTTextSourceType);
|
||
|
||
// Generic export procedure which polls the application at every stage of the export.
|
||
|
||
var
|
||
I: Integer;
|
||
Save, Run: PVirtualNode;
|
||
GetNextNode: TGetNextNodeProc;
|
||
RenderColumns: Boolean;
|
||
Columns: TColumnsArray;
|
||
|
||
begin
|
||
Columns := nil;
|
||
GetRenderStartValues(Source, Run, GetNextNode);
|
||
Save := Run;
|
||
|
||
RenderColumns := FHeader.UseColumns and ( hoVisible in FHeader.Options );
|
||
|
||
if Assigned(FOnBeforeTreeExport) then
|
||
FOnBeforeTreeExport(Self, etCustom);
|
||
|
||
// Fill table header.
|
||
if RenderColumns then
|
||
begin
|
||
if Assigned(FOnBeforeHeaderExport) then
|
||
FOnBeforeHeaderExport(Self, etCustom);
|
||
|
||
Columns := FHeader.FColumns.GetVisibleColumns;
|
||
for I := 0 to High(Columns) do
|
||
begin
|
||
if Assigned(FOnBeforeColumnExport) then
|
||
FOnBeforeColumnExport(Self, etCustom, Columns[I]);
|
||
|
||
if Assigned(FOnColumnExport) then
|
||
FOnColumnExport(Self, etCustom, Columns[I]);
|
||
|
||
if Assigned(FOnAfterColumnExport) then
|
||
FOnAfterColumnExport(Self, etCustom, Columns[I]);
|
||
end;
|
||
|
||
if Assigned(FOnAfterHeaderExport) then
|
||
FOnAfterHeaderExport(Self, etCustom);
|
||
end;
|
||
|
||
// Now write the content.
|
||
Run := Save;
|
||
while Assigned(Run) do
|
||
begin
|
||
if CanExportNode(Run) then
|
||
begin
|
||
if Assigned(FOnBeforeNodeExport) then
|
||
FOnBeforeNodeExport(Self, etCustom, Run);
|
||
|
||
if Assigned(FOnNodeExport) then
|
||
FOnNodeExport(Self, etCustom, Run);
|
||
|
||
if Assigned(FOnAfterNodeExport) then
|
||
FOnAfterNodeExport(Self, etCustom, Run);
|
||
end;
|
||
|
||
Run := GetNextNode(Run);
|
||
end;
|
||
|
||
if Assigned(FOnAfterTreeExport) then
|
||
FOnAfterTreeExport(Self, etCustom);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.ContentToAnsi(Source: TVSTTextSourceType; const Separator: String): AnsiString;
|
||
var
|
||
Buffer: TBufferedUTF8String;
|
||
begin
|
||
Buffer := TBufferedUTF8String.Create;
|
||
try
|
||
AddContentToBuffer(Buffer, Source, Separator);
|
||
finally
|
||
Result := Buffer.AsAnsiString;
|
||
Buffer.Destroy;
|
||
end;
|
||
end;
|
||
|
||
function TCustomVirtualStringTree.ContentToText(Source: TVSTTextSourceType;
|
||
const Separator: String): AnsiString;
|
||
begin
|
||
Result := ContentToAnsi(Source, Separator);
|
||
end;
|
||
|
||
function TCustomVirtualStringTree.ContentToUnicode(Source: TVSTTextSourceType;
|
||
const Separator: String): UnicodeString;
|
||
begin
|
||
Result := ContentToUTF16(Source, Separator);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.AddContentToBuffer(Buffer: TBufferedUTF8String; Source: TVSTTextSourceType; const Separator: String);
|
||
|
||
// Renders the current tree content (depending on Source) as UTF8 text.
|
||
// If an entry contains the separator char or double quotes then it is wrapped with double quotes
|
||
// and existing double quotes are duplicated.
|
||
|
||
var
|
||
RenderColumns: Boolean;
|
||
Tabs: String;
|
||
GetNextNode: TGetNextNodeProc;
|
||
Run, Save: PVirtualNode;
|
||
Level, MaxLevel: Cardinal;
|
||
Columns: TColumnsArray;
|
||
LastColumn: TVirtualTreeColumn;
|
||
Index,
|
||
I: Integer;
|
||
Text: String;
|
||
begin
|
||
Columns := nil;
|
||
RenderColumns := FHeader.UseColumns;
|
||
if RenderColumns then
|
||
Columns := FHeader.FColumns.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;
|
||
|
||
Tabs := DupeString(Separator, MaxLevel);
|
||
|
||
// First line is always the header if used.
|
||
if RenderColumns then
|
||
begin
|
||
LastColumn := Columns[High(Columns)];
|
||
for I := 0 to High(Columns) do
|
||
begin
|
||
Buffer.Add(Columns[I].Text);
|
||
if Columns[I] <> LastColumn then
|
||
begin
|
||
if Columns[I].Index = 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
|
||
if (not CanExportNode(Run) or
|
||
(Assigned(FOnBeforeNodeExport) and (not FOnBeforeNodeExport(Self, etText, Run)))) then
|
||
begin
|
||
Run := GetNextNode(Run);
|
||
Continue;
|
||
end;
|
||
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.
|
||
Text := Self.Text[Run, Index];
|
||
if Index = Header.MainColumn then
|
||
begin
|
||
Level := GetNodeLevel(Run);
|
||
Buffer.Add(Copy(Tabs, 1, Integer(Level) * Length(Separator)));
|
||
// Wrap the text with quotation marks if it contains the separator character.
|
||
if (Pos(Separator, Text) > 0) or (Pos('"', Text) > 0) then
|
||
Buffer.Add(AnsiQuotedStr(Text, '"'))
|
||
else
|
||
Buffer.Add(Text);
|
||
Buffer.Add(Copy(Tabs, 1, Integer(MaxLevel - Level) * Length(Separator)));
|
||
end
|
||
else
|
||
if (Pos(Separator, Text) > 0) or (Pos('"', Text) > 0) then
|
||
Buffer.Add(AnsiQuotedStr(Text, '"'))
|
||
else
|
||
Buffer.Add(Text);
|
||
|
||
if Columns[I] <> LastColumn then
|
||
Buffer.Add(Separator);
|
||
end;
|
||
end;
|
||
if Assigned(FOnAfterNodeExport) then
|
||
FOnAfterNodeExport(Self, etText, Run);
|
||
Run := GetNextNode(Run);
|
||
Buffer.AddNewLine;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
while Assigned(Run) do
|
||
begin
|
||
if ((not CanExportNode(Run)) or
|
||
(Assigned(FOnBeforeNodeExport) and (not FOnBeforeNodeExport(Self, etText, Run)))) then
|
||
begin
|
||
Run := GetNextNode(Run);
|
||
Continue;
|
||
end;
|
||
// This line implicitly converts the Unicode text to ANSI.
|
||
Text := Self.Text[Run, NoColumn];
|
||
Level := GetNodeLevel(Run);
|
||
Buffer.Add(Copy(Tabs, 1, Integer(Level) * Length(Separator)));
|
||
Buffer.Add(Text);
|
||
Buffer.AddNewLine;
|
||
|
||
if Assigned(FOnAfterNodeExport) then
|
||
FonAfterNodeExport(Self, etText, Run);
|
||
Run := GetNextNode(Run);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.ContentToUTF16(Source: TVSTTextSourceType; const Separator: String): UnicodeString;
|
||
var
|
||
Buffer: TBufferedUTF8String;
|
||
begin
|
||
Buffer := TBufferedUTF8String.Create;
|
||
try
|
||
AddContentToBuffer(Buffer, Source, Separator);
|
||
finally
|
||
Result := Buffer.AsUTF16String;
|
||
Buffer.Destroy;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualStringTree.ContentToUTF8(Source: TVSTTextSourceType;
|
||
const Separator: String): String;
|
||
var
|
||
Buffer: TBufferedUTF8String;
|
||
begin
|
||
Buffer := TBufferedUTF8String.Create;
|
||
try
|
||
AddContentToBuffer(Buffer, Source, Separator);
|
||
finally
|
||
Result := Buffer.AsUTF8String;
|
||
Buffer.Destroy;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect;
|
||
out Text: String);
|
||
|
||
// 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, Text);
|
||
|
||
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.
|
||
Text := 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.InvalidateNode(Node: PVirtualNode): TRect;
|
||
|
||
var
|
||
Data: PInteger;
|
||
|
||
begin
|
||
Result := inherited InvalidateNode(Node);
|
||
// Reset node width so changed text attributes are applied correctly.
|
||
if Assigned(Node) then
|
||
begin
|
||
Data := InternalData(Node);
|
||
if Assigned(Data) then
|
||
Data^ := 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: Char): String;
|
||
|
||
// Constructs a string containing the node and all its parents. The last character in the returned path is always the
|
||
// given delimiter.
|
||
|
||
var
|
||
S: String;
|
||
|
||
begin
|
||
if (Node = nil) or (Node = FRoot) then
|
||
Result := Delimiter
|
||
else
|
||
begin
|
||
Result := '';
|
||
while Node <> FRoot do
|
||
begin
|
||
DoGetText(Node, Column, TextType, S);
|
||
Result := S + Delimiter + Result;
|
||
Node := Node.Parent;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualStringTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean);
|
||
|
||
var
|
||
Data: PInteger;
|
||
|
||
begin
|
||
inherited;
|
||
// Reset node width so changed text attributes are applied correctly.
|
||
if Assigned(Node) and (Node <> FRoot) then
|
||
begin
|
||
Data := InternalData(Node);
|
||
if Assigned(Data) then
|
||
Data^ := 0;
|
||
// vsHeightMeasured is already removed in the base tree.
|
||
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;
|
||
|
||
//----------------- TCustomVirtualDrawTree -----------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualDrawTree.DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; const R: TRect; Column: TColumnIndex);
|
||
|
||
begin
|
||
if Assigned(FOnDrawHint) then
|
||
FOnDrawHint(Self, Canvas, Node, R, Column);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualDrawTree.DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex;
|
||
CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint;
|
||
|
||
begin
|
||
Result := Point(0, 0);
|
||
if Canvas = nil then
|
||
Canvas := Self.Canvas;
|
||
|
||
if Assigned(FOnGetCellContentMargin) then
|
||
FOnGetCellContentMargin(Self, Canvas, Node, Column, CellContentMarginType, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualDrawTree.DoGetHintSize(Node: PVirtualNode; Column: TColumnIndex; var R: TRect);
|
||
|
||
begin
|
||
if Assigned(FOnGetHintSize) then
|
||
FOnGetHintSize(Self, Node, Column, R);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TCustomVirtualDrawTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer;
|
||
|
||
begin
|
||
Result := 2 * FTextMargin;
|
||
if Canvas = nil then
|
||
Canvas := Self.Canvas;
|
||
|
||
if Assigned(FOnGetNodeWidth) then
|
||
FOnGetNodeWidth(Self, Canvas, Node, Column, Result);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TCustomVirtualDrawTree.DoPaintNode(var PaintInfo: TVTPaintInfo);
|
||
|
||
begin
|
||
if Assigned(FOnDrawNode) then
|
||
FOnDrawNode(Self, PaintInfo);
|
||
end;
|
||
|
||
//----------------- TVirtualDrawTree -----------------------------------------------------------------------------------
|
||
|
||
function TVirtualDrawTree.GetOptions: TVirtualTreeOptions;
|
||
|
||
begin
|
||
Result := FOptions as TVirtualTreeOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
procedure TVirtualDrawTree.SetOptions(const Value: TVirtualTreeOptions);
|
||
|
||
begin
|
||
FOptions.Assign(Value);
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
function TVirtualDrawTree.GetOptionsClass: TTreeOptionsClass;
|
||
|
||
begin
|
||
Result := TVirtualTreeOptions;
|
||
end;
|
||
|
||
//----------------------------------------------------------------------------------------------------------------------
|
||
|
||
initialization
|
||
{$I virtualtrees.lrs}
|
||
// Necessary for dynamic package loading.
|
||
Initialized := False;
|
||
NeedToUnitialize := False;
|
||
|
||
finalization
|
||
if Initialized then
|
||
FinalizeGlobalStructures;
|
||
|
||
InternalClipboardFormats.Free;
|
||
InternalClipboardFormats := nil;
|
||
{$ifdef EnableAccessible}
|
||
if VTAccessibleFactory <> nil then
|
||
begin
|
||
VTAccessibleFactory.Free;
|
||
VTAccessibleFactory := nil;
|
||
end;
|
||
{$endif}
|
||
end.
|
||
|
||
|
||
|