mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-27 02:33:38 +02:00
36880 lines
1.3 MiB
36880 lines
1.3 MiB
unit laz.VirtualTrees;
|
|
|
|
{$mode delphi}{$H+}
|
|
{$packset 1}
|
|
{$if not (defined(CPU386) or Defined(CPUX64))}
|
|
{$define PACKARRAYPASCAL}
|
|
{$endif}
|
|
|
|
{$if defined(LCLQt) or defined(LCLQt5) or defined(LCLQt6)}
|
|
{$undef windows}
|
|
{$endif}
|
|
// 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.
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//
|
|
// For a list of recent changes please see file CHANGES.TXT
|
|
//
|
|
// Credits for their valuable assistance and code donations go to:
|
|
// Freddy Ertl, Marian Aldenhövel, Thomas Bogenrieder, Jim Kuenemann, Werner Lehmann, Jens Treichler,
|
|
// Paul Gallagher (IBO tree), Ondrej Kelle, Ronaldo Melo Ferraz, Heri Bender, Roland Bedü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, Joachim Marder
|
|
// 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:
|
|
// https://code.google.com/p/virtual-treeview/source/
|
|
// Accessability implementation:
|
|
// Marco Zehe (with help from Sebastian Modersohn)
|
|
// LCL Port:
|
|
// Luiz Américo Pereira Câmara
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
interface
|
|
|
|
{$I laz.vtconfig.inc}
|
|
|
|
uses
|
|
{$ifdef LCLCocoa}
|
|
MacOSAll, // hack: low-level access to Cocoa drawins is going to be used
|
|
// in order to support Cocoa HighDPI implementation
|
|
{$endif}
|
|
{$ifdef Windows}
|
|
Windows,
|
|
ActiveX,
|
|
CommCtrl,
|
|
UxTheme,
|
|
{$else}
|
|
laz.FakeActiveX,
|
|
{$endif}
|
|
OleUtils,
|
|
LCLIntf,
|
|
{$ifdef USE_DELPHICOMPAT}
|
|
DelphiCompat,
|
|
LclExt,
|
|
{$endif}
|
|
laz.virtualpanningwindow,
|
|
laz.VTGraphics, //alpha blend functions
|
|
{$ifdef DEBUG_VTV}
|
|
laz.VTLogger,
|
|
{$endif}
|
|
LCLType, LMessages, LCLVersion, Types,
|
|
SysUtils, Classes, Graphics, Controls, Forms, ImgList, StdCtrls, Menus, Printers,
|
|
SyncObjs, // Thread support
|
|
Clipbrd, // Clipboard support
|
|
TypInfo, // for migration stuff
|
|
ActnList,
|
|
StdActns, // for standard action support
|
|
FPImage, IntfGraphics, LazCanvas, // for alpha-transparent bitmaps
|
|
GraphType
|
|
{$ifdef LCLCocoa}
|
|
,CocoaGDIObjects // hack: while using buffered drawing, multiply the context
|
|
{$endif}
|
|
{$ifdef ThemeSupport}
|
|
, Themes , TmSchema
|
|
{$endif ThemeSupport}
|
|
{$ifdef EnableAccessible}
|
|
, oleacc // for MSAA IAccessible support
|
|
{$endif};
|
|
|
|
{$ifdef LCLCocoa}
|
|
// WideChar is defined in MacOSAll. This is causing collission
|
|
// with System.WideChar, which is used by the unit below.
|
|
// Redeclearing the type.
|
|
type
|
|
WideChar = System.WideChar;
|
|
{$endif}
|
|
|
|
const
|
|
{$I laz.lclconstants.inc}
|
|
|
|
{$if defined(LCLGtk) or defined(LCLGtk2)}
|
|
{$define Gtk}
|
|
{$endif}
|
|
|
|
{$if defined(Gtk) or defined(LCLCocoa)}
|
|
{$define ManualClipNeeded}
|
|
{$endif}
|
|
|
|
{$if defined(LCLGtk2) or defined(LCLCarbon) or defined(LCLQt)}
|
|
{$define ContextMenuBeforeMouseUp}
|
|
{$endif}
|
|
|
|
VTMajorVersion = 5;
|
|
VTMinorVersion = 5;
|
|
VTReleaseVersion = 3;
|
|
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;
|
|
ThemeChangedTimer = 8;
|
|
|
|
ThemeChangedTimerDelay = 500;
|
|
|
|
// 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;
|
|
|
|
|
|
CM_UPDATE_VCLSTYLE_SCROLLBARS = CM_BASE + 2050;
|
|
|
|
// 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));
|
|
DSH_ALLOWDROPDESCRIPTIONTEXT = $1;
|
|
|
|
SID_IDropTargetHelper = '{4657278B-411B-11D2-839A-00C04FD918D0}';
|
|
SID_IDragSourceHelper = '{DE5BF786-477A-11D2-839D-00C04FD918D0}';
|
|
SID_IDragSourceHelper2 = '{83E07D0D-0C5F-4163-BF1A-60B274051E40}';
|
|
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);
|
|
|
|
{$if defined(LCLCarbon) or defined(LCLCocoa)}
|
|
ssCtrlOS = ssMeta; // Mac OS X fix
|
|
{$else}
|
|
ssCtrlOS = ssCtrl;
|
|
{$endif}
|
|
|
|
cUtilityImageSize = 16; // Needed by descendants for hittests.
|
|
|
|
DEFAULT_CHECK_WIDTH = 16;
|
|
DEFAULT_COLUMN_WIDTH = 50;
|
|
DEFAULT_DRAG_HEIGHT = 350;
|
|
DEFAULT_DRAG_WIDTH = 200;
|
|
DEFAULT_HEADER_HEIGHT = 19;
|
|
DEFAULT_INDENT = 18;
|
|
DEFAULT_MARGIN = 4;
|
|
DEFAULT_NODE_HEIGHT = 18;
|
|
DEFAULT_SPACING = 3;
|
|
DEFAULT_MIN_HEIGHT = 10;
|
|
|
|
LIS_NORMAL = 1;
|
|
{$EXTERNALSYM LIS_NORMAL}
|
|
LIS_HOT = 2;
|
|
{$EXTERNALSYM LIS_HOT}
|
|
LIS_SELECTED = 3;
|
|
{$EXTERNALSYM LIS_SELECTED}
|
|
LIS_DISABLED = 4;
|
|
{$EXTERNALSYM LIS_DISABLED}
|
|
LIS_SELECTEDNOTFOCUS = 5;
|
|
{$EXTERNALSYM LIS_SELECTEDNOTFOCUS}
|
|
|
|
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: TClipboardFormat;
|
|
|
|
MMXAvailable: Boolean; // necessary to know because the blend code uses MMX instructions
|
|
IsWinVistaOrAbove: Boolean;
|
|
|
|
UtilityImageSize: Integer = cUtilityImageSize;
|
|
|
|
{$ifdef gtk}
|
|
// Workaround LCL bug 8553
|
|
var
|
|
pf32bit: TPixelFormat = pfDevice;
|
|
{$endif}
|
|
|
|
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: UINT;
|
|
Rgn: HRGN;
|
|
lParam: LPARAM;
|
|
Result: LRESULT;
|
|
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.
|
|
vsOnFreeNodeCallRequired, // Set if 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.
|
|
vsFiltered // Indicates that the node should not be painted (without effecting its children).
|
|
);
|
|
TVirtualNodeStates = set of TVirtualNodeState;
|
|
|
|
// States used in InitNode to indicate states a node shall initially have.
|
|
TVirtualNodeInitState = (
|
|
ivsDisabled,
|
|
ivsExpanded,
|
|
ivsHasChildren,
|
|
ivsMultiline,
|
|
ivsSelected,
|
|
ivsFiltered,
|
|
ivsReInit
|
|
);
|
|
TVirtualNodeInitStates = set of TVirtualNodeInitState;
|
|
|
|
TVTScrollBarStyle = (
|
|
sbmRegular,
|
|
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.
|
|
coEditable // Column can be edited
|
|
);
|
|
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).
|
|
toHideTreeLinesIfThemed, // Do not show tree lines if theming is used.
|
|
toShowFilteredNodes // Draw nodes even if they are filtered out.
|
|
);
|
|
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.
|
|
toAlwaysSelectNode, // If this flag is set to true, the tree view tries to always have a node selected.
|
|
// This behavior is closer to the Windows TreeView and useful in Windows Explorer style applications.
|
|
toRestoreSelection // Set to true if upon refill the previously selected nodes should be selected again.
|
|
// The nodes will be identified by its caption only.
|
|
);
|
|
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
|
|
toReverseFullExpandHotKey // Used to define Ctrl+'+' instead of Ctrl+Shift+'+' for full expand (and similar for collapsing)
|
|
);
|
|
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
|
|
emVisibleDueToExpansion, //Do not export nodes that are not visible because their parent is not expanded
|
|
emSelected // export selected nodes only
|
|
);
|
|
|
|
// Kinds of operations
|
|
TVTOperationKind = (
|
|
okAutoFitColumns,
|
|
okGetMaxColumnWidth,
|
|
okSortNode,
|
|
okSortTree
|
|
);
|
|
TVTOperationKinds = set of TVTOperationKind;
|
|
|
|
const
|
|
DefaultPaintOptions = [toShowButtons, toShowDropmark, toShowTreeLines, toShowRoot, toThemeAware, toUseBlendedImages];
|
|
DefaultAnimationOptions = [];
|
|
DefaultAutoOptions = [toAutoDropExpand, toAutoTristateTracking, toAutoScrollOnExpand, toAutoDeleteMovedNodes, toAutoChangeScale, toAutoSort];
|
|
DefaultSelectionOptions = [];
|
|
DefaultMiscOptions = [toAcceptOLEDrop, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning,
|
|
toEditOnClick];
|
|
DefaultColumnOptions = [coAllowClick, coDraggable, coEnabled, coParentColor, coParentBidiMode, coResizable,
|
|
coShowDropmark, coVisible, coAllowFocus, coEditable];
|
|
|
|
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 header is needed.
|
|
TVTHeaderHitInfo = record
|
|
X,
|
|
Y: Integer;
|
|
Button: TMouseButton;
|
|
Shift: TShiftState;
|
|
Column: TColumnIndex;
|
|
HitPosition: TVTHeaderHitPositions;
|
|
end;
|
|
|
|
// Structure used when info about a certain position in the tree is needed.
|
|
THitInfo = record
|
|
HitNode: PVirtualNode;
|
|
HitPositions: THitPositions;
|
|
HitColumn: TColumnIndex;
|
|
HitPoint: TPoint;
|
|
end;
|
|
|
|
// auto scroll directions
|
|
TScrollDirections = set of (
|
|
sdLeft,
|
|
sdUp,
|
|
sdRight,
|
|
sdDown
|
|
);
|
|
|
|
// OLE drag'n drop support
|
|
TFormatEtcArray = array of TFormatEtc;
|
|
TFormatArray = array of TClipboardFormat;
|
|
|
|
// 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;
|
|
crColorKey: TColorRef;
|
|
end;
|
|
|
|
IDragSourceHelper = interface(IUnknown)
|
|
[SID_IDragSourceHelper]
|
|
function InitializeFromBitmap(SHDragImage: PSHDragImage; pDataObject: IDataObject): HRESULT; stdcall;
|
|
function InitializeFromWindow(Window: HWND; var ppt: TPoint; pDataObject: IDataObject): HRESULT; stdcall;
|
|
end;
|
|
{$EXTERNALSYM IDragSourceHelper}
|
|
|
|
IDragSourceHelper2 = interface(IDragSourceHelper)
|
|
[SID_IDragSourceHelper2]
|
|
function SetFlags(dwFlags: DWORD): HRESULT; stdcall;
|
|
end;
|
|
{$EXTERNALSYM IDragSourceHelper2}
|
|
|
|
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 }
|
|
|
|
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: TLCLHandle): TLCLHandle;
|
|
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 {%H-}pformatetcIn : FORMATETC;Out {%H-}pformatetcOut : FORMATETC):HResult; virtual; STDCALl;
|
|
function GetData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium): HResult; virtual; stdcall;
|
|
function GetDataHere(const {%H-}FormatEtc: TFormatEtc; out {%H-}Medium: TStgMedium): HResult; virtual; stdcall;
|
|
function QueryGetData(const FormatEtc: TFormatEtc): HResult; virtual; stdcall;
|
|
function SetData(const FormatEtc: TFormatEtc; var 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({%H-}Effect: LongWord): HResult; stdcall;
|
|
function QueryContinueDrag(EscapePressed: BOOL; KeyState: LongWord): HResult; stdcall;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
TVTHintKind = (vhkText, vhkOwnerDraw);
|
|
|
|
PVTHintData = ^TVTHintData;
|
|
TVTHintData = record
|
|
Tree: TBaseVirtualTree;
|
|
Node: PVirtualNode;
|
|
Column: TColumnIndex;
|
|
Kind: TVTHintKind;
|
|
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
|
|
);
|
|
|
|
TSortDirection = (
|
|
sdAscending,
|
|
sdDescending
|
|
);
|
|
|
|
TVirtualTreeColumn = class(TCollectionItem)
|
|
private
|
|
FText,
|
|
FHint: TTranslateString;
|
|
FLeft,
|
|
FWidth: Integer;
|
|
FPosition: TColumnPosition;
|
|
FMinWidth: Integer;
|
|
FMaxWidth: Integer;
|
|
FStyle: TVirtualTreeColumnStyle;
|
|
FImageIndex: TImageIndex;
|
|
FBiDiMode: TBiDiMode;
|
|
FLayout: TVTHeaderColumnLayout;
|
|
FMargin,
|
|
FSpacing: Integer;
|
|
FOptions: TVTColumnOptions;
|
|
FTag: NativeInt;
|
|
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;
|
|
FDefaultSortDirection: TSortDirection;
|
|
function GetCaptionAlignment: TAlignment;
|
|
function GetLeft: Integer;
|
|
function IsBiDiModeStored: Boolean;
|
|
function IsCaptionAlignmentStored: Boolean;
|
|
function IsColorStored: Boolean;
|
|
function IsMarginStored: Boolean;
|
|
function IsSpacingStored: Boolean;
|
|
function IsWidthStored: 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 SetWidth(Value: Integer);
|
|
protected
|
|
procedure ComputeHeaderLayout(DC: HDC; const Client: TRect; UseHeaderGlyph, UseSortGlyph: Boolean;
|
|
var HeaderGlyphPos, SortGlyphPos: TPoint; var SortGlyphSize: TSize; var TextBounds: TRect; DrawFormat: Cardinal;
|
|
{%H-}CalculateTextRect: Boolean = False);
|
|
procedure GetAbsoluteBounds(var Left, Right: Integer);
|
|
function GetDisplayName: string; override;
|
|
function GetText: String; virtual; // [IPK]
|
|
procedure SetText(const Value: TTranslateString); virtual; // [IPK] private to protected & virtual
|
|
function GetOwner: TVirtualTreeColumns; reintroduce;
|
|
property HasImage: Boolean read FHasImage;
|
|
property ImageRect: TRect read FImageRect;
|
|
public
|
|
constructor Create(Collection: TCollection); override;
|
|
destructor Destroy; override;
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
function Equals(OtherColumnObj: TObject): Boolean; override;
|
|
function GetRect: TRect; virtual;
|
|
procedure LoadFromStream(const Stream: TStream; {%H-}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;
|
|
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 DefaultSortDirection: TSortDirection read FDefaultSortDirection write FDefaultSortDirection default sdAscending;
|
|
property Hint: TTranslateString 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 stored IsMarginStored;
|
|
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 stored IsSpacingStored;
|
|
property Style: TVirtualTreeColumnStyle read FStyle write SetStyle default vsText;
|
|
property Tag: NativeInt read FTag write FTag default 0;
|
|
property Text: TTranslateString read GetText write SetText;
|
|
property Width: Integer read FWidth write SetWidth stored IsWidthStored;
|
|
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; // Index of the last clicked column.
|
|
FCheckBoxHit: Boolean; // True if the last click was on a header checkbox.
|
|
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.
|
|
|
|
function GetItem(Index: TColumnIndex): TVirtualTreeColumn;
|
|
function GetNewIndex(P: TPoint; var OldIndex: TColumnIndex): Boolean;
|
|
function IsDefaultWidthStored: Boolean;
|
|
procedure SetDefaultWidth(Value: Integer);
|
|
procedure SetItem(Index: TColumnIndex; Value: TVirtualTreeColumn);
|
|
protected
|
|
// 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
|
|
|
|
procedure AdjustAutoSize({%H-}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); virtual;
|
|
procedure DrawButtonText(DC: HDC; Caption: String; Bounds: TRect; Enabled, Hot: Boolean; DrawFormat: Cardinal;
|
|
WrapCaption: 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 Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
|
|
procedure ReorderColumns(RTL: Boolean);
|
|
procedure Update(Item: TCollectionItem); override;
|
|
procedure UpdatePositions(Force: Boolean = False);
|
|
|
|
property HeaderBitmap: TBitmap read FHeaderBitmap;
|
|
property PositionToIndex: TIndexArray read FPositionToIndex;
|
|
property HoverIndex: TColumnIndex read FHoverIndex;
|
|
property DownIndex: TColumnIndex read FDownIndex;
|
|
property CheckBoxHit: Boolean read FCheckBoxHit;
|
|
public
|
|
constructor Create(AOwner: TVTHeader); virtual;
|
|
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; override;
|
|
procedure GetColumnBounds(Column: TColumnIndex; out Left, Right: Integer);
|
|
function GetFirstVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
|
function GetLastVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex;
|
|
function GetFirstColumn: 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); overload; virtual;
|
|
procedure PaintHeader(TargetCanvas: TCanvas; R: TRect; const Target: TPoint;
|
|
RTLOffset: Integer = 0); overload; virtual;
|
|
procedure SaveToStream(const Stream: TStream);
|
|
function TotalWidth: Integer;
|
|
|
|
property ClickIndex: TColumnIndex read FClickIndex;
|
|
property DefaultWidth: Integer read FDefaultWidth write SetDefaultWidth stored IsDefaultWidthStored;
|
|
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.)
|
|
);
|
|
|
|
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.
|
|
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.
|
|
hoHeaderClickAutoSort // Clicks on the header will make the clicked column the SortColumn or toggle sort direction if
|
|
// it already was the sort column
|
|
);
|
|
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;
|
|
|
|
|
|
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
|
|
); // describes the used column resize behaviour for AutoFitColumns
|
|
|
|
|
|
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
|
|
); // desribes what made a structure change event happen
|
|
|
|
TVTHeader = class(TPersistent)
|
|
private
|
|
FOwner: TBaseVirtualTree;
|
|
FColumns: TVirtualTreeColumns;
|
|
FHeight: Integer;
|
|
FFont: TFont;
|
|
FParentFont: Boolean;
|
|
FOptions: TVTHeaderOptions;
|
|
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
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
FImagesWidth: Integer;
|
|
{$IFEND}
|
|
FSortColumn: TColumnIndex;
|
|
FSortDirection: TSortDirection;
|
|
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 IsDefaultHeightStored: Boolean;
|
|
function IsFontStored: Boolean;
|
|
function IsHeightStored: Boolean;
|
|
function IsMinHeightStored: 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);
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
procedure SetImagesWidth(const Value: Integer);
|
|
{$IFEND}
|
|
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
|
|
FStates: THeaderStates; // Used to keep track of internal states the header can enter.
|
|
FDragStart: TPoint; // initial mouse drag position
|
|
FTrackStart: TPoint; // client coordinates of the tracking start point
|
|
FTrackPoint: TPoint; // Client coordinate where the tracking started.
|
|
|
|
function CanSplitterResize(P: TPoint): Boolean;
|
|
function CanWriteColumns: Boolean; virtual;
|
|
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;
|
|
procedure DoCanSplitterResize(P: TPoint; var Allowed: Boolean); 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;
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
procedure AutoAdjustLayout(const AXProportion, AYProportion: Double); virtual;
|
|
{$IFEND}
|
|
procedure AutoFitColumns(Animated: Boolean = True; SmartAutoFitType: TSmartAutoFitType = smaUseColumnOption;
|
|
RangeStartCol: Integer = NoColumn; RangeEndCol: Integer = NoColumn); virtual;
|
|
{$IF LCL_FullVersion >= 2010000}
|
|
procedure FixDesignFontsPPI(const ADesignTimePPI: Integer); virtual;
|
|
{$IFEND}
|
|
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 stored IsDefaultHeightStored;
|
|
property Font: TFont read FFont write SetFont stored IsFontStored;
|
|
property FixedAreaConstraints: TVTFixedAreaConstraints read FFixedAreaConstraints write FFixedAreaConstraints;
|
|
property Height: Integer read FHeight write SetHeight stored IsHeightStored;
|
|
property Images: TCustomImageList read FImages write SetImages;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
property ImagesWidth: Integer read FImagesWidth write SetImagesWidth default 0;
|
|
{$IFEND}
|
|
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 stored IsMinHeightStored;
|
|
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.
|
|
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 occurred 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.
|
|
tsPreviouslySelectedLocked,// The member FPreviouslySelected should not be changed
|
|
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.
|
|
tsVCLDragFinished, // Flag to avoid triggering the OnColumnClick event twice
|
|
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.
|
|
tsUseExplorerTheme // The tree runs under WinVista+ and is using the explorer theme
|
|
);
|
|
|
|
TChangeStates = set of (
|
|
csStopValidation, // Cache validation can be stopped (usually because a change has occurred 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
|
|
poUnbuffered // draw directly onto the target canvas; especially useful when printing
|
|
);
|
|
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..16] of TColor; // [IPK] 15 -> 16
|
|
function GetColor(const Index: Integer): TColor;
|
|
procedure SetColor(const Index: Integer; const Value: TColor);
|
|
function GetBackgroundColor: TColor;
|
|
function GetHeaderFontColor: TColor;
|
|
function GetNodeFontColor: TColor;
|
|
public
|
|
constructor Create(AOwner: TBaseVirtualTree);
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
property BackGroundColor: TColor read GetBackgroundColor;
|
|
property HeaderFontColor: TColor read GetHeaderFontColor;
|
|
property NodeFontColor: TColor read GetNodeFontColor;
|
|
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 SelectionTextColor: TColor index 15 read GetColor write SetColor default clHighlightText;
|
|
property TreeLineColor: TColor index 5 read GetColor write SetColor default clBtnShadow;
|
|
property UnfocusedColor: TColor index 16 read GetColor write SetColor default clBtnFace; // [IPK] Added
|
|
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;
|
|
TVTAddToSelectionEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object;
|
|
TVTRemoveFromSelectionEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) 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;
|
|
TVTSaveTreeEvent = procedure(Sender: TBaseVirtualTree; Stream: TStream) of object;
|
|
TVTSaveNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Stream: TStream) of object;
|
|
|
|
// header/column events
|
|
TVTHeaderClickEvent = procedure(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo) 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;
|
|
TVTCanSplitterResizeHeaderEvent = procedure(Sender: TVTHeader; P: TPoint; 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;
|
|
TVTNodeClickEvent = procedure(Sender: TBaseVirtualTree; const HitInfo: THitInfo) 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;
|
|
TVTCanSplitterResizeNodeEvent = procedure(Sender: TBaseVirtualTree; P: TPoint; Node: PVirtualNode;
|
|
Column: TColumnIndex; 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;
|
|
|
|
// operations
|
|
TVTOperationEvent = procedure(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind) of object;
|
|
|
|
TVTHintKindEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Kind: TVTHintKind) of object;
|
|
TVTDrawHintEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; R: TRect; Column: TColumnIndex) of object;
|
|
TVTGetHintEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
|
var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: String) of object;
|
|
TVTGetHintSizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var R: TRect) of object;
|
|
|
|
// miscellaneous
|
|
TVTBeforeDrawLineImageEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Level: Integer; var PosX: Integer) of object;
|
|
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;
|
|
|
|
TVZVirtualNodeEnumerationMode = (
|
|
vneAll,
|
|
vneChecked,
|
|
vneChild,
|
|
vneCutCopy,
|
|
vneInitialized,
|
|
vneLeaf,
|
|
vneLevel,
|
|
vneNoInit,
|
|
vneSelected,
|
|
vneVisible,
|
|
vneVisibleChild,
|
|
vneVisibleNoInitChild,
|
|
vneVisibleNoInit
|
|
);
|
|
|
|
PVTVirtualNodeEnumeration = ^TVTVirtualNodeEnumeration;
|
|
|
|
TVTVirtualNodeEnumerator = {$ifdef COMPILER_10_UP}record{$else}class{$endif}
|
|
private
|
|
FNode: PVirtualNode;
|
|
FCanModeNext: Boolean;
|
|
FEnumeration: PVTVirtualNodeEnumeration;
|
|
function GetCurrent: PVirtualNode; {$ifdef COMPILER_10_UP}inline;{$endif}
|
|
public
|
|
function MoveNext: Boolean; {$ifdef COMPILER_10_UP}inline;{$endif}
|
|
property Current: PVirtualNode read GetCurrent;
|
|
end;
|
|
|
|
TVTVirtualNodeEnumeration = {$ifdef COMPILER_10_UP}record{$else}object{$endif}
|
|
private
|
|
FMode: TVZVirtualNodeEnumerationMode;
|
|
FTree: TBaseVirtualTree;
|
|
// GetNextXxx parameters:
|
|
FConsiderChildrenAbove: Boolean;
|
|
FNode: PVirtualNode;
|
|
FNodeLevel: Cardinal;
|
|
FState: TCheckState;
|
|
FIncludeFiltered: Boolean;
|
|
public
|
|
function GetEnumerator: TVTVirtualNodeEnumerator;
|
|
private
|
|
function GetNext(Node: PVirtualNode): PVirtualNode;
|
|
end;
|
|
|
|
|
|
// XE2+ VCL Style
|
|
{$ifdef VCLStyleSupport}
|
|
TVclStyleScrollBarsHook = class(TMouseTrackControlStyleHook)
|
|
strict private type
|
|
{$REGION 'TVclStyleScrollBarWindow'}
|
|
TVclStyleScrollBarWindow = class(TWinControl)strict private FScrollBarWindowOwner: TVclStyleScrollBarsHook;
|
|
FScrollBarVertical: Boolean;
|
|
FScrollBarVisible: Boolean;
|
|
FScrollBarEnabled: Boolean;
|
|
procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
|
|
procedure WMEraseBkgnd(var Msg: TMessage); message WM_ERASEBKGND;
|
|
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;
|
|
strict protected
|
|
procedure CreateParams(var Params: TCreateParams);
|
|
override;
|
|
public
|
|
constructor Create(AOwner: TComponent);
|
|
override;
|
|
property ScrollBarWindowOwner: TVclStyleScrollBarsHook read FScrollBarWindowOwner write FScrollBarWindowOwner;
|
|
property ScrollBarVertical: Boolean read FScrollBarVertical write FScrollBarVertical;
|
|
property ScrollBarVisible: Boolean read FScrollBarVisible write FScrollBarVisible;
|
|
property ScrollBarEnabled: Boolean read FScrollBarEnabled write FScrollBarEnabled;
|
|
end;
|
|
{$ENDREGION}
|
|
private
|
|
FHorzScrollBarDownButtonRect: TRect;
|
|
FHorzScrollBarDownButtonState: TThemedScrollBar;
|
|
FHorzScrollBarRect: TRect;
|
|
FHorzScrollBarSliderState: TThemedScrollBar;
|
|
FHorzScrollBarSliderTrackRect: TRect;
|
|
FHorzScrollBarUpButtonRect: TRect;
|
|
FHorzScrollBarUpButtonState: TThemedScrollBar;
|
|
FHorzScrollBarWindow: TVclStyleScrollBarWindow;
|
|
FLeftMouseButtonDown: Boolean;
|
|
FPrevScrollPos: Integer;
|
|
FScrollPos: Single;
|
|
FVertScrollBarDownButtonRect: TRect;
|
|
FVertScrollBarDownButtonState: TThemedScrollBar;
|
|
FVertScrollBarRect: TRect;
|
|
FVertScrollBarSliderState: TThemedScrollBar;
|
|
FVertScrollBarSliderTrackRect: TRect;
|
|
FVertScrollBarUpButtonRect: TRect;
|
|
FVertScrollBarUpButtonState: TThemedScrollBar;
|
|
FVertScrollBarWindow: TVclStyleScrollBarWindow;
|
|
|
|
procedure CMUpdateVclStyleScrollBars(var Message: TMessage); message CM_UPDATE_VCLSTYLE_SCROLLBARS;
|
|
procedure WMKeyDown(var Msg: TMessage); message WM_KEYDOWN;
|
|
procedure WMKeyUp(var Msg: TMessage); message WM_KEYUP;
|
|
procedure WMLButtonDown(var Msg: TWMMouse); message WM_LBUTTONDOWN;
|
|
procedure WMLButtonUp(var Msg: TWMMouse); message WM_LBUTTONUP;
|
|
procedure WMNCLButtonDown(var Msg: TWMMouse); message WM_NCLBUTTONDOWN;
|
|
procedure WMNCMouseMove(var Msg: TWMMouse); message WM_NCMOUSEMOVE;
|
|
procedure WMNCLButtonUp(var Msg: TWMMouse); message WM_NCLBUTTONUP;
|
|
procedure WMNCPaint(var Msg: TMessage); message WM_NCPAINT;
|
|
procedure WMMouseMove(var Msg: TWMMouse); message WM_MOUSEMOVE;
|
|
procedure WMMouseWheel(var Msg: TMessage); message WM_MOUSEWHEEL;
|
|
procedure WMVScroll(var Msg: TMessage); message WM_VSCROLL;
|
|
procedure WMHScroll(var Msg: TMessage); message WM_HSCROLL;
|
|
procedure WMCaptureChanged(var Msg: TMessage); message WM_CAPTURECHANGED;
|
|
procedure WMNCLButtonDblClk(var Msg: TWMMouse); message WM_NCLBUTTONDBLCLK;
|
|
procedure WMSize(var Msg: TMessage); message WM_SIZE;
|
|
procedure WMMove(var Msg: TMessage); message WM_MOVE;
|
|
procedure WMPosChanged(var Msg: TMessage); message WM_WINDOWPOSCHANGED;
|
|
protected
|
|
procedure CalcScrollBarsRect; virtual;
|
|
procedure DrawHorzScrollBar(DC: HDC); virtual;
|
|
procedure DrawVertScrollBar(DC: HDC); virtual;
|
|
function GetHorzScrollBarSliderRect: TRect;
|
|
function GetVertScrollBarSliderRect: TRect;
|
|
procedure MouseLeave; override;
|
|
procedure PaintScrollBars; virtual;
|
|
function PointInTreeHeader(const P: TPoint): Boolean;
|
|
procedure UpdateScrollBarWindow;
|
|
public
|
|
constructor Create(AControl: TWinControl); override;
|
|
destructor Destroy; override;
|
|
end;
|
|
{$ifend}
|
|
|
|
|
|
// ----- 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.
|
|
FCurrentHintNode: PVirtualNode; // Node which has shown the hint.
|
|
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;
|
|
FEmptyListMessage: String; // Optional message that will be displayed if no nodes exist in the control.
|
|
|
|
// 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, // state images in the tree
|
|
FCustomCheckImages: TCustomImageList; // application defined check images
|
|
FCheckImageKind: TCheckImageKind; // light or dark, cross marks or tick marks
|
|
FCheckImages: TCustomImageList; // 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
|
|
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
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
FImagesWidth: Integer; // needed for high-dpi imagelist support
|
|
FStateImagesWidth: Integer;
|
|
FCustomCheckImagesWidth: Integer;
|
|
FCheckImagesWidth: Integer;
|
|
{$IFEND}
|
|
|
|
// 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
|
|
FLastDragEffect: LongWord; // The last executed drag effect
|
|
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.
|
|
FChangingTheme: Boolean; // Used to indicate that a theme change is goi ng on
|
|
FNextNodeToSelect: PVirtualNode; // Next tree node that we would like to select if the current one gets deleted or looses selection for other reasons.
|
|
|
|
{$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 a node is copied to another parent node (probably in
|
|
// another tree, but within the same application, can be cancelled)
|
|
FOnNodeClick: TVTNodeClickEvent; // called when the user clicks on a node
|
|
FOnNodeDblClick: TVTNodeClickEvent; // called when the user double clicks on a node
|
|
FOnCanSplitterResizeNode: TVTCanSplitterResizeNodeEvent; // called to query the application wether resizing a node is allowed
|
|
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
|
|
FOnAddToSelection: TVTAddToSelectionEvent; // called when a node is added to the selection
|
|
FOnRemoveFromSelection: TVTRemoveFromSelectionEvent; // called when a node is removed from the selection
|
|
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)
|
|
FOnLoadTree, // called after the tree has been loaded from a stream to allow an
|
|
// application to load their own data saved in OnSaveTree
|
|
FOnSaveTree: TVTSaveTreeEvent; // called after the tree has been saved to a stream to allow an
|
|
// application to save its own data
|
|
|
|
// header/column mouse events
|
|
FOnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent;
|
|
FOnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent;
|
|
FOnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent;
|
|
FOnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent;
|
|
FOnHeaderClick: 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;
|
|
FOnCanSplitterResizeHeader: TVTCanSplitterResizeHeaderEvent;
|
|
|
|
// 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.
|
|
FOnBeforeDrawLineImage: TVTBeforeDrawLineImageEvent; // Called to allow adjusting the indention of treelines.
|
|
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)
|
|
|
|
// hints
|
|
FOnDrawHint: TVTDrawHintEvent;
|
|
FOnGetHint: TVTGetHintEvent; // used to retrieve the hint text to be displayed for a specific node
|
|
FOnGetHintSize: TVTGetHintSizeEvent;
|
|
FOnGetHintKind: TVTHintKindEvent;
|
|
FOnMouseEnter: TNotifyEvent;
|
|
FOnMouseLeave: TNotifyEvent;
|
|
|
|
// operations
|
|
FOnStartOperation: TVTOperationEvent; // Called when an operation starts
|
|
FOnEndOperation: TVTOperationEvent; // Called when an operation ends
|
|
|
|
FVclStyleEnabled: Boolean;
|
|
|
|
{$ifdef VCLStyleSupport}
|
|
FSavedBevelKind: TBevelKind;
|
|
FSavedBorderWidth: Integer;
|
|
FSetOrRestoreBevelKindAndBevelWidth: Boolean;
|
|
procedure CMStyleChanged(var Message: TMessage); message CM_STYLECHANGED;
|
|
procedure CMBorderChanged(var Message: TMessage); message CM_BORDERCHANGED;
|
|
procedure CMParentDoubleBufferedChange(var Message: TMessage); message CM_PARENTDOUBLEBUFFEREDCHANGED;
|
|
{$ifend}
|
|
|
|
procedure AdjustCoordinatesByIndent(var PaintInfo: TVTPaintInfo; Indent: Integer);
|
|
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;
|
|
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 GetFiltered(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({%H-}LastFocused, NewNode: PVirtualNode; Shift: TShiftState; DragPending: Boolean);
|
|
function HandleDrawSelection({%H-}X, {%H-}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 IsDefaultNodeHeightStored: Boolean;
|
|
function IsDragHeightStored: Boolean;
|
|
function IsDragWidthStored: Boolean;
|
|
function IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean;
|
|
function IsIndentStored: Boolean;
|
|
function IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean;
|
|
function IsMarginStored: Boolean;
|
|
function IsSelectionCurveRadiusStored: Boolean;
|
|
function IsTextMarginStored: Boolean;
|
|
//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: TCustomImageList);
|
|
procedure SetDefaultNodeHeight(Value: Cardinal);
|
|
procedure SetDisabled(Node: PVirtualNode; Value: Boolean);
|
|
procedure SetEmptyListMessage(const Value: String);
|
|
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 SetFiltered(Node: PVirtualNode; Value: Boolean);
|
|
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); reintroduce;
|
|
procedure SetVisiblePath(Node: PVirtualNode; Value: Boolean); reintroduce;
|
|
procedure StaticBackground(Source: TBitmap; Target: TCanvas; const OffsetPosition: TPoint; const R: TRect);
|
|
procedure SetWindowTheme(const {%H-}Theme: String);
|
|
procedure TileBackground(Source: TBitmap; Target: TCanvas; const Offset: TPoint; R: TRect);
|
|
function ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean;
|
|
protected
|
|
procedure CMColorChange(var {%H-}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 CMMouseEnter(var Message: TLMessage); message CM_MOUSEENTER;
|
|
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: TLMessage); 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 {%H-}Message: TLMNoParams); message LM_COPY;
|
|
procedure WMCut(var {%H-}Message: TLMNoParams); message LM_CUT;
|
|
procedure WMEnable(var {%H-}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 {%H-}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;
|
|
function GetRangeX: Cardinal;
|
|
function GetDoubleBuffered: Boolean;
|
|
procedure SetDoubleBuffered(const {%H-}Value: Boolean);
|
|
procedure ChangeTreeStatesAsync(EnterStates, LeaveStates: TChangeStates);
|
|
protected
|
|
FFontChanged: Boolean; // flag for keeping informed about font changes in the off screen buffer // [IPK] - private to protected
|
|
procedure AutoScale(); virtual;
|
|
procedure AddToSelection(Node: PVirtualNode); overload; virtual;
|
|
procedure AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False); overload; virtual;
|
|
procedure AdjustImageBorder(Images: TCustomImageList; BidiMode: TBidiMode; VAlign: Integer; var R: TRect;
|
|
var ImageInfo: TVTImageInfo); virtual; overload;
|
|
procedure AdjustImageBorder(ImageWidth, ImageHeight: Integer; BidiMode: TBidiMode; VAlign: Integer; var R: TRect;
|
|
var ImageInfo: TVTImageInfo); overload;
|
|
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;
|
|
function CalculateSelectionRect(X, Y: Integer): Boolean; virtual;
|
|
function CanAutoScroll: Boolean; virtual;
|
|
function CanShowDragImage: Boolean; virtual;
|
|
function CanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
procedure Change(Node: PVirtualNode); virtual;
|
|
procedure ChangeScale(M, D: Integer); override;
|
|
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;
|
|
function DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; virtual;
|
|
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 DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; 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 DoCanSplitterResizeNode(P: TPoint; 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;
|
|
procedure DoBeforeDrawLineImage(Node: PVirtualNode; Level: Integer; var XPos: Integer); 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 DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; R: TRect; Column:
|
|
TColumnIndex);
|
|
procedure DoEdit; virtual;
|
|
procedure DoEndDrag(Target: TObject; X, Y: Integer); override;
|
|
function DoEndEdit: Boolean; virtual;
|
|
procedure DoEndOperation(OperationKind: TVTOperationKind); virtual;
|
|
procedure DoEnter(); override;
|
|
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;
|
|
procedure DoGetHintSize(Node: PVirtualNode; Column: TColumnIndex; var R:
|
|
TRect); virtual;
|
|
procedure DoGetHintKind(Node: PVirtualNode; Column: TColumnIndex; var Kind:
|
|
TVTHintKind);
|
|
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({%H-}Node: PVirtualNode; {%H-}Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; virtual;
|
|
function DoGetNodeTooltip({%H-}Node: PVirtualNode; {%H-}Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; virtual;
|
|
function DoGetNodeExtraWidth({%H-}Node: PVirtualNode; {%H-}Column: TColumnIndex; {%H-}Canvas: TCanvas = nil): Integer; virtual;
|
|
function DoGetNodeWidth({%H-}Node: PVirtualNode; {%H-}Column: TColumnIndex; {%H-}Canvas: TCanvas = nil): Integer; virtual;
|
|
function DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Position: TPoint): TPopupMenu; virtual;
|
|
procedure DoGetUserClipboardFormats(var Formats: TFormatEtcArray); virtual;
|
|
procedure DoHeaderClick(HitInfo: TVTHeaderHitInfo); virtual;
|
|
procedure DoHeaderDblClick(HitInfo: TVTHeaderHitInfo); 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;
|
|
function DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal): Boolean; 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 DoMouseEnter(); virtual;
|
|
procedure DoMouseLeave(); virtual;
|
|
procedure DoNodeCopied(Node: PVirtualNode); virtual;
|
|
function DoNodeCopying(Node, NewParent: PVirtualNode): Boolean; virtual;
|
|
procedure DoNodeClick(const HitInfo: THitInfo); virtual;
|
|
procedure DoNodeDblClick(const HitInfo: THitInfo); 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; {%H-}Node: PVirtualNode; const R: TRect); virtual;
|
|
procedure DoPaintNode(var {%H-}PaintInfo: TVTPaintInfo); virtual;
|
|
procedure DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; const Position: TPoint); virtual;
|
|
procedure DoRemoveFromSelection(Node: PVirtualNode); 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 DoStartOperation(OperationKind: TVTOperationKind); virtual;
|
|
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;
|
|
var 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; UseSelectedBkColor: Boolean = False); virtual;
|
|
procedure EndOperation(OperationKind: TVTOperationKind);
|
|
procedure EnsureNodeFocused(); virtual;
|
|
function FindNodeInSelection(P: PVirtualNode; var Index: Integer; LowBound, HighBound: Integer): Boolean; virtual;
|
|
procedure FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer); virtual;
|
|
procedure FontChanged(AFont: TObject); virtual; reintroduce;
|
|
function GetBorderDimensions: TSize; virtual;
|
|
function GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone;
|
|
ImgCheckState: TCheckState = csUncheckedNormal; ImgEnabled: Boolean = True): Integer; virtual;
|
|
class function GetCheckImageListFor(Kind: TCheckImageKind): TCustomImageList; virtual;
|
|
function GetClientRect: TRect; override;
|
|
function GetColumnClass: TVirtualTreeColumnClass; virtual;
|
|
function GetDefaultHintKind: TVTHintKind; virtual;
|
|
function GetHeaderClass: TVTHeaderClass; virtual;
|
|
function GetHintWindowClass: THintWindowClass; virtual;
|
|
procedure GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex;
|
|
DefaultImages: TCustomImageList); virtual;
|
|
function GetNodeImageSize({%H-}Node: PVirtualNode): TSize; 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({%H-}Node: PVirtualNode): Pointer;
|
|
procedure InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True); virtual;
|
|
function InternalGetNodeAt(X, Y: Integer): PVirtualNode; overload;
|
|
function InternalGetNodeAt({%H-}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(Canvas: TCanvas; const ImageInfo: TVTImageInfo; {%H-}Selected: Boolean); virtual;
|
|
procedure PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean); virtual;
|
|
procedure PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; {%H-}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;
|
|
procedure UpdateNextNodeToSelect(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 StartOperation(OperationKind: TVTOperationKind);
|
|
procedure StartWheelPanning(const Position: TPoint); virtual;
|
|
procedure StopWheelPanning; virtual;
|
|
procedure StructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual;
|
|
function SuggestDropEffect(Source: TObject; Shift: TShiftState; const {%H-}Pt: TPoint; AllowedEffects: LongWord): LongWord; virtual;
|
|
procedure ToggleSelection(StartNode, EndNode: PVirtualNode); virtual;
|
|
procedure UnselectNodes(StartNode, EndNode: PVirtualNode); virtual;
|
|
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;
|
|
|
|
{$ifdef VCLStyleSupport}
|
|
procedure VclStyleChanged;
|
|
{$ifend}
|
|
property VclStyleEnabled: Boolean read FVclStyleEnabled;
|
|
|
|
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: TCustomImageList read FCustomCheckImages write SetCustomCheckImages;
|
|
property DefaultHintKind: TVTHintKind read GetDefaultHintKind;
|
|
property DefaultNodeHeight: Cardinal read FDefaultNodeHeight write SetDefaultNodeHeight stored IsDefaultNodeHeightStored;
|
|
property DefaultPasteMode: TVTNodeAttachMode read FDefaultPasteMode write FDefaultPasteMode default amAddChildLast;
|
|
property DragHeight: Integer read FDragHeight write FDragHeight stored IsDragHeightStored;
|
|
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 LastDragEffect: LongWord read FLastDragEffect;
|
|
property DragType: TVTDragType read FDragType write FDragType default dtOLE;
|
|
property DragWidth: Integer read FDragWidth write FDragWidth stored IsDragWidthStored;
|
|
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 EffectiveOffsetX: Integer read FEffectiveOffsetX;
|
|
property Header: TVTHeader read FHeader write SetHeader;
|
|
property HeaderRect: TRect read FHeaderRect;
|
|
property HintMode: TVTHintMode read FHintMode write FHintMode default hmDefault;
|
|
property HintData: TVTHintData read FHintData write FHintData;
|
|
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 stored IsIndentStored;
|
|
property LastClickPos: TPoint read FLastClickPos write FLastClickPos;
|
|
property LastDropMode: TDropMode read FLastDropMode write FLastDropMode;
|
|
property LastHintRect: TRect read FLastHintRect write FLastHintRect;
|
|
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 stored IsMarginStored;
|
|
property NextNodeToSelect: PVirtualNode read FNextNodeToSelect; // Next tree node that we would like to select if the current one gets deleted
|
|
property NodeAlignment: TVTNodeAlignment read FNodeAlignment write SetNodeAlignment default naProportional;
|
|
property NodeDataSize: Integer read FNodeDataSize write SetNodeDataSize default -1;
|
|
property OperationCanceled: Boolean read GetOperationCanceled;
|
|
property HotMinusBM: TBitmap read FHotMinusBM;
|
|
property HotPlusBM: TBitmap read FHotPlusBM;
|
|
property MinusBM: TBitmap read FMinusBM;
|
|
property PlusBM: TBitmap read FPlusBM;
|
|
property RangeX: Cardinal read GetRangeX;// Returns the width of the virtual tree in pixels, (not ClientWidth). If there are columns it returns the total width of all of them; otherwise it returns the maximum of the all the line's data widths.
|
|
property RangeY: Cardinal read FRangeY;
|
|
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 stored IsSelectionCurveRadiusStored;
|
|
property StateImages: TCustomImageList read FStateImages write SetStateImages;
|
|
property TextMargin: Integer read FTextMargin write SetTextMargin stored IsTextMarginStored;
|
|
property TotalInternalDataSize: Cardinal read FTotalInternalDataSize;
|
|
property TreeOptions: TCustomVirtualTreeOptions read FOptions write SetOptions;
|
|
property WantTabs: Boolean read FWantTabs write FWantTabs default False;
|
|
|
|
property OnAddToSelection: TVTAddToSelectionEvent read FOnAddToSelection write FOnAddToSelection;
|
|
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 FOnAfterHeaderExport write FOnAfterHeaderExport;
|
|
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 OnBeforeDrawTreeLine: TVTBeforeDrawLineImageEvent read FOnBeforeDrawLineImage write FOnBeforeDrawLineImage;
|
|
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 OnCanSplitterResizeHeader: TVTCanSplitterResizeHeaderEvent read FOnCanSplitterResizeHeader write FOnCanSplitterResizeHeader;
|
|
property OnCanSplitterResizeNode: TVTCanSplitterResizeNodeEvent read FOnCanSplitterResizeNode write FOnCanSplitterResizeNode;
|
|
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 OnDrawHint: TVTDrawHintEvent read FOnDrawHint write FOnDrawHint;
|
|
property OnEditCancelled: TVTEditCancelEvent read FOnEditCancelled write FOnEditCancelled;
|
|
property OnEditing: TVTEditChangingEvent read FOnEditing write FOnEditing;
|
|
property OnEdited: TVTEditChangeEvent read FOnEdited write FOnEdited;
|
|
property OnEndOperation: TVTOperationEvent read FOnEndOperation write FOnEndOperation;
|
|
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 OnGetHint: TVTGetHintEvent read FOnGetHint write FOnGetHint;
|
|
property OnGetHintSize: TVTGetHintSizeEvent read FOnGetHintSize write
|
|
FOnGetHintSize;
|
|
property OnGetHintKind: TVTHintKindEvent read FOnGetHintKind write
|
|
FOnGetHintKind;
|
|
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 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 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 OnLoadTree: TVTSaveTreeEvent read FOnLoadTree write FOnLoadTree;
|
|
property OnMeasureItem: TVTMeasureItemEvent read FOnMeasureItem write FOnMeasureItem;
|
|
property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
|
|
property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
|
|
property OnNodeClick: TVTNodeClickEvent read FOnNodeClick write FOnNodeClick;
|
|
property OnNodeCopied: TVTNodeCopiedEvent read FOnNodeCopied write FOnNodeCopied;
|
|
property OnNodeCopying: TVTNodeCopyingEvent read FOnNodeCopying write FOnNodeCopying;
|
|
property OnNodeDblClick: TVTNodeClickEvent read FOnNodeDblClick write FOnNodeDblClick;
|
|
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 OnRemoveFromSelection: TVTRemoveFromSelectionEvent read FOnRemoveFromSelection write FOnRemoveFromSelection;
|
|
property OnRenderOLEData: TVTRenderOLEDataEvent read FOnRenderOLEData write FOnRenderOLEData;
|
|
property OnResetNode: TVTChangeEvent read FOnResetNode write FOnResetNode;
|
|
property OnSaveNode: TVTSaveNodeEvent read FOnSaveNode write FOnSaveNode;
|
|
property OnSaveTree: TVTSaveTreeEvent read FOnSaveTree write FOnSaveTree;
|
|
property OnScroll: TVTScrollEvent read FOnScroll write FOnScroll;
|
|
property OnShowScrollBar: TVTScrollBarShowEvent read FOnShowScrollBar write FOnShowScrollBar;
|
|
property OnStartOperation: TVTOperationEvent read FOnStartOperation write FOnStartOperation;
|
|
property OnStateChange: TVTStateChangeEvent read FOnStateChange write FOnStateChange;
|
|
property OnStructureChange: TVTStructureChangeEvent read FOnStructureChange write FOnStructureChange;
|
|
property OnUpdating: TVTUpdatingEvent read FOnUpdating write FOnUpdating;
|
|
|
|
// LCL scaling support
|
|
protected
|
|
function GetRealImagesWidth: Integer;
|
|
function GetRealImagesHeight: Integer;
|
|
function GetRealStateImagesWidth: Integer;
|
|
function GetRealStateImagesHeight: Integer;
|
|
function GetRealCheckImagesWidth: Integer;
|
|
function GetRealCheckImagesHeight: Integer;
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
|
|
const AXProportion, AYProportion: Double); override;
|
|
{$IFEND}
|
|
|
|
// LCL multi-resolution imagelist support
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
private
|
|
procedure SetImagesWidth(const Value: integer);
|
|
procedure SetStateImagesWidth(const Value: Integer);
|
|
procedure SetCustomCheckImagesWidth(const Value: Integer);
|
|
protected
|
|
{ multi-resolution imagelist support }
|
|
function GetImagesWidth(Images: TCustomImageList): Integer;
|
|
property ImagesWidth: Integer read FImagesWidth write SetImagesWidth default 0;
|
|
property StateImagesWidth: Integer read FStateImagesWidth write SetStateImagesWidth default 0;
|
|
property CustomCheckImagesWidth: Integer read FCustomCheckImagesWidth write SetCustomCheckImagesWidth default 0;
|
|
{$IFEND}
|
|
|
|
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 {%H-}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;
|
|
procedure EnsureNodeSelected(); virtual;
|
|
function ExecuteAction(Action: TBasicAction): Boolean; override;
|
|
procedure FinishCutOrCopy;
|
|
{$IF LCL_FullVersion >= 2010000}
|
|
procedure FixDesignFontsPPI(const ADesignTimePPI: Integer); override;
|
|
{$IFEND}
|
|
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 GetEffectivelyFiltered(Node: PVirtualNode): Boolean;
|
|
function GetEffectivelyVisible(Node: PVirtualNode): Boolean;
|
|
function GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
|
function GetFirstChecked(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
|
function GetFirstChild(Node: PVirtualNode): PVirtualNode;
|
|
function GetFirstChildNoInit(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;
|
|
IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetFirstVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetFirstVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetFirstVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True;
|
|
IncludeFiltered: Boolean = False): 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;
|
|
{%H-}IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetLastVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetLastVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetLastVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True;
|
|
IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): Integer; virtual;
|
|
function GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode;
|
|
function GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal;
|
|
ConsiderChildrenAbove: Boolean = False): PVirtualNode; overload;
|
|
function GetNextChecked(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; overload;
|
|
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 GetNextSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
|
function GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
|
function GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
|
function GetNextVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetNextVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetNodeAt(const P: TPoint): PVirtualNode; overload; inline;
|
|
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 GetPreviousSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
|
function GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
|
function GetPreviousVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode;
|
|
function GetPreviousVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetPreviousVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
function GetSortedCutCopySet(Resolve: Boolean): TNodeArray;
|
|
function GetSortedSelection(Resolve: Boolean): TNodeArray;
|
|
procedure GetTextInfo({%H-}Node: PVirtualNode; {%H-}Column: TColumnIndex; const AFont: TFont; var R: TRect;
|
|
out Text: String); virtual;
|
|
function GetTreeRect: TRect;
|
|
function GetVisibleParent(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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 IsEmpty: 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); virtual;
|
|
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); virtual;
|
|
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 UpdateHorizontalRange;
|
|
procedure UpdateHorizontalScrollBar(DoRepaint: Boolean);
|
|
procedure UpdateRanges;
|
|
procedure UpdateScrollBars(DoRepaint: Boolean); virtual;
|
|
procedure UpdateVerticalRange;
|
|
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);
|
|
|
|
{ Enumerations }
|
|
function Nodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function CheckedNodes(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function ChildNodes(Node: PVirtualNode): TVTVirtualNodeEnumeration;
|
|
function CutCopyNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function InitializedNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function LeafNodes: TVTVirtualNodeEnumeration;
|
|
function LevelNodes(NodeLevel: Cardinal): TVTVirtualNodeEnumeration;
|
|
function NoInitNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function SelectedNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function VisibleNodes(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True;
|
|
IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function VisibleChildNodes(Node: PVirtualNode; IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function VisibleChildNoInitNodes(Node: PVirtualNode; IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration;
|
|
function VisibleNoInitNodes(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True;
|
|
IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration;
|
|
{$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: TCustomImageList 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 write FDropTargetNode;
|
|
property EditLink: IVTEditLink read FEditLink;
|
|
property EmptyListMessage: String read FEmptyListMessage write SetEmptyListMessage;
|
|
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 IsEffectivelyFiltered[Node: PVirtualNode]: Boolean read GetEffectivelyFiltered;
|
|
property IsEffectivelyVisible[Node: PVirtualNode]: Boolean read GetEffectivelyVisible;
|
|
property IsFiltered[Node: PVirtualNode]: Boolean read GetFiltered write SetFiltered;
|
|
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 OperationCount: Cardinal read FOperationCount;
|
|
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;
|
|
property DoubleBuffered: Boolean read GetDoubleBuffered write SetDoubleBuffered default True;
|
|
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 {%H-}Message: TLMessage); message CM_AUTOADJUST;
|
|
procedure CMExit(var {%H-}Message: TLMessage); message CM_EXIT;
|
|
procedure CNCommand(var Message: TLMCommand); message CN_COMMAND;
|
|
procedure DoRelease({%H-}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; virtual;
|
|
|
|
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.
|
|
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.
|
|
procedure SetEdit(const Value: TVTEdit); // Setter for the FEdit member;
|
|
public
|
|
constructor Create; virtual;
|
|
destructor Destroy; override;
|
|
property Node : PVirtualNode read FNode; // [IPK] Make FNode accessible
|
|
property Column: TColumnIndex read FColumn; // [IPK] Make Column(Index) accessible
|
|
|
|
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.
|
|
tstChecked // Only checked 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;
|
|
// 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;
|
|
TVTMeasureTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
|
Column: TColumnIndex; const CellText: String; var Extent: Integer) of object;
|
|
TVTDrawTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode;
|
|
Column: TColumnIndex; const CellText: 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;
|
|
function GetAsString: String;
|
|
public
|
|
destructor Destroy; override;
|
|
|
|
procedure Add(const S: String);
|
|
procedure AddNewLine;
|
|
|
|
property AsAnsiString: AnsiString read GetAsAnsiString;
|
|
property AsString: String read GetAsString;
|
|
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
|
|
FOnNewText: TVSTNewTextEvent; // used to notify the application about an edited node caption
|
|
FOnShortenString: TVSTShortenStringEvent; // used to allow the application a customized string shortage
|
|
FOnMeasureTextWidth: TVTMeasureTextEvent; // used to adjust the width of the cells
|
|
FOnMeasureTextHeight: TVTMeasureTextEvent;
|
|
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 GetStaticText(Node: PVirtualNode; Column: TColumnIndex): String;
|
|
function GetText(Node: PVirtualNode; Column: TColumnIndex): 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;
|
|
procedure GetDataFromGrid(const AStrings : TStringList; const IncludeHeading : Boolean = True);
|
|
protected
|
|
FPreviouslySelected: TStringList;
|
|
procedure InitializeTextProperties(var PaintInfo: TVTPaintInfo); // [IPK] - private to protected
|
|
procedure PaintNormalText(var PaintInfo: TVTPaintInfo; {%H-}TextOutFlags: Integer; Text: String); virtual; // [IPK] - private to protected
|
|
procedure PaintStaticText(const PaintInfo: TVTPaintInfo; {%H-}TextOutFlags: Integer; const Text: String); virtual; // [IPK] - private to protected
|
|
procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; out NextNonEmpty: TColumnIndex); override;
|
|
function CanExportNode(Node: PVirtualNode): Boolean;
|
|
function CalculateStaticTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: String): Integer; virtual;
|
|
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 DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; 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): TSize; 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 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: TVTMeasureTextEvent read FOnMeasureTextWidth write FOnMeasureTextWidth;
|
|
property OnMeasureTextHeight: TVTMeasureTextEvent read FOnMeasureTextHeight write FOnMeasureTextHeight;
|
|
property OnDrawText: TVTDrawTextEvent read FOnDrawText write FOnDrawText;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy(); override;
|
|
function AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; override;
|
|
function ComputeNodeHeight(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; S: String = ''): Integer; virtual;
|
|
function ContentToClipboard(Format: TClipboardFormat; 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;
|
|
procedure AddToSelection(Node: PVirtualNode); override;
|
|
procedure RemoveFromSelection(Node: PVirtualNode); override;
|
|
function SaveToCSVFile(const FileNameWithPath : TFileName; const IncludeHeading : Boolean) : Boolean;
|
|
property ImageText[Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex]: String read GetImageText;
|
|
property StaticText[Node: PVirtualNode; Column: TColumnIndex]: String read GetStaticText;
|
|
property Text[Node: PVirtualNode; Column: TColumnIndex]: String read GetText write SetText;
|
|
end;
|
|
|
|
TLazVirtualStringTree = class(TCustomVirtualStringTree)
|
|
private
|
|
function GetOptions: TStringTreeOptions;
|
|
procedure SetOptions(const Value: TStringTreeOptions);
|
|
protected
|
|
function GetOptionsClass: TTreeOptionsClass; override;
|
|
{$if CompilerVersion >= 23}
|
|
class constructor Create();
|
|
{$ifend}
|
|
public
|
|
property Canvas;
|
|
property RangeX;
|
|
property LastDragEffect;
|
|
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;
|
|
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 EmptyListMessage;
|
|
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;
|
|
{$if CompilerVersion >= 24}
|
|
property StyleElements;
|
|
{$ifend}
|
|
property TabOrder;
|
|
property TabStop default True;
|
|
property TextMargin;
|
|
property TreeOptions: TStringTreeOptions read GetOptions write SetOptions;
|
|
property Visible;
|
|
property WantTabs;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
property ImagesWidth;
|
|
property StateImagesWidth;
|
|
property CustomCheckImagesWidth;
|
|
{$IFEND}
|
|
|
|
property OnAddToSelection;
|
|
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 OnBeforeDrawTreeLine;
|
|
property OnBeforeGetMaxColumnWidth;
|
|
property OnBeforeHeaderExport;
|
|
property OnBeforeHeaderHeightTracking;
|
|
property OnBeforeItemErase;
|
|
property OnBeforeItemPaint;
|
|
property OnBeforeNodeExport;
|
|
property OnBeforePaint;
|
|
property OnBeforeTreeExport;
|
|
property OnCanSplitterResizeColumn;
|
|
property OnCanSplitterResizeHeader;
|
|
property OnCanSplitterResizeNode;
|
|
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 OnDrawText;
|
|
property OnEditCancelled;
|
|
property OnEdited;
|
|
property OnEditing;
|
|
property OnEndDock;
|
|
property OnEndDrag;
|
|
property OnEndOperation;
|
|
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 OnGetHintKind;
|
|
property OnGetHintSize;
|
|
property OnGetImageIndex;
|
|
property OnGetImageIndexEx;
|
|
property OnGetImageText;
|
|
property OnGetHint;
|
|
property OnGetLineStyle;
|
|
property OnGetNodeDataSize;
|
|
property OnGetPopupMenu;
|
|
property OnGetUserClipboardFormats;
|
|
property OnHeaderClick;
|
|
property OnHeaderDblClick;
|
|
property OnHeaderDragged;
|
|
property OnHeaderDraggedOut;
|
|
property OnHeaderDragging;
|
|
property OnHeaderDraw;
|
|
property OnHeaderDrawQueryElements;
|
|
property OnHeaderHeightDblClickResize;
|
|
property OnHeaderHeightTracking;
|
|
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 OnLoadTree;
|
|
property OnMeasureItem;
|
|
property OnMeasureTextWidth;
|
|
property OnMeasureTextHeight;
|
|
property OnMouseDown;
|
|
property OnMouseMove;
|
|
property OnMouseUp;
|
|
property OnMouseWheel;
|
|
property OnMouseEnter;
|
|
property OnMouseLeave;
|
|
property OnNewText;
|
|
property OnNodeClick;
|
|
property OnNodeCopied;
|
|
property OnNodeCopying;
|
|
property OnNodeDblClick;
|
|
property OnNodeExport;
|
|
property OnNodeHeightDblClickResize;
|
|
property OnNodeHeightTracking;
|
|
property OnNodeMoved;
|
|
property OnNodeMoving;
|
|
property OnPaintBackground;
|
|
property OnRemoveFromSelection;
|
|
property OnRenderOLEData;
|
|
property OnResetNode;
|
|
property OnResize;
|
|
property OnSaveNode;
|
|
property OnSaveTree;
|
|
property OnScroll;
|
|
property OnShortenString;
|
|
property OnShowScrollBar;
|
|
property OnStartDock;
|
|
property OnStartDrag;
|
|
property OnStartOperation;
|
|
property OnStateChange;
|
|
property OnStructureChange;
|
|
property OnUpdating;
|
|
property OnUTF8KeyPress;
|
|
//delphi only
|
|
//property OnCanResize;
|
|
//property OnGesture;
|
|
//property Touch;
|
|
end;
|
|
|
|
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;
|
|
|
|
// Tree descendant to let an application draw its stuff itself.
|
|
TCustomVirtualDrawTree = class(TBaseVirtualTree)
|
|
private
|
|
FOnDrawNode: TVTDrawNodeEvent;
|
|
FOnGetCellContentMargin: TVTGetCellContentMarginEvent;
|
|
FOnGetNodeWidth: TVTGetNodeWidthEvent;
|
|
protected
|
|
function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex;
|
|
CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; override;
|
|
function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): String; override;
|
|
function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; override;
|
|
procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override;
|
|
function GetDefaultHintKind: TVTHintKind; override;
|
|
|
|
property OnDrawNode: TVTDrawNodeEvent read FOnDrawNode write FOnDrawNode;
|
|
property OnGetCellContentMargin: TVTGetCellContentMarginEvent read FOnGetCellContentMargin write FOnGetCellContentMargin;
|
|
property OnGetNodeWidth: TVTGetNodeWidthEvent read FOnGetNodeWidth write FOnGetNodeWidth;
|
|
end;
|
|
|
|
TLazVirtualDrawTree = class(TCustomVirtualDrawTree)
|
|
private
|
|
function GetOptions: TVirtualTreeOptions;
|
|
procedure SetOptions(const Value: TVirtualTreeOptions);
|
|
protected
|
|
function GetOptionsClass: TTreeOptionsClass; override;
|
|
{$if CompilerVersion >= 23}
|
|
class constructor Create();
|
|
{$ifend}
|
|
public
|
|
property Canvas;
|
|
property LastDragEffect;
|
|
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;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
property ImagesWidth;
|
|
property StateImagesWidth;
|
|
property CustomCheckImagesWidth;
|
|
{$IFEND}
|
|
|
|
property OnAddToSelection;
|
|
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 OnBeforeDrawTreeLine;
|
|
property OnBeforeGetMaxColumnWidth;
|
|
property OnBeforeHeaderExport;
|
|
property OnBeforeHeaderHeightTracking;
|
|
property OnBeforeItemErase;
|
|
property OnBeforeItemPaint;
|
|
property OnBeforeNodeExport;
|
|
property OnBeforePaint;
|
|
property OnBeforeTreeExport;
|
|
property OnCanSplitterResizeColumn;
|
|
property OnCanSplitterResizeHeader;
|
|
property OnCanSplitterResizeNode;
|
|
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 OnEndOperation;
|
|
property OnEnter;
|
|
property OnExit;
|
|
property OnExpanded;
|
|
property OnExpanding;
|
|
property OnFocusChanged;
|
|
property OnFocusChanging;
|
|
property OnFreeNode;
|
|
property OnGetCellIsEmpty;
|
|
property OnGetCursor;
|
|
property OnGetHeaderCursor;
|
|
property OnGetHelpContext;
|
|
property OnGetHint;
|
|
property OnGetHintKind;
|
|
property OnGetHintSize;
|
|
property OnGetImageIndex;
|
|
property OnGetImageIndexEx;
|
|
property OnGetLineStyle;
|
|
property OnGetNodeDataSize;
|
|
property OnGetNodeWidth;
|
|
property OnGetPopupMenu;
|
|
property OnGetUserClipboardFormats;
|
|
property OnHeaderClick;
|
|
property OnHeaderDblClick;
|
|
property OnHeaderDragged;
|
|
property OnHeaderDraggedOut;
|
|
property OnHeaderDragging;
|
|
property OnHeaderDraw;
|
|
property OnHeaderDrawQueryElements;
|
|
property OnHeaderHeightTracking;
|
|
property OnHeaderHeightDblClickResize;
|
|
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 OnLoadTree;
|
|
property OnMeasureItem;
|
|
property OnMouseDown;
|
|
property OnMouseMove;
|
|
property OnMouseUp;
|
|
property OnMouseWheel;
|
|
property OnNodeClick;
|
|
property OnNodeCopied;
|
|
property OnNodeCopying;
|
|
property OnNodeDblClick;
|
|
property OnNodeExport;
|
|
property OnNodeHeightTracking;
|
|
property OnNodeHeightDblClickResize;
|
|
property OnNodeMoved;
|
|
property OnNodeMoving;
|
|
property OnPaintBackground;
|
|
property OnRemoveFromSelection;
|
|
property OnRenderOLEData;
|
|
property OnResetNode;
|
|
property OnResize;
|
|
property OnSaveNode;
|
|
property OnSaveTree;
|
|
property OnScroll;
|
|
property OnShowScrollBar;
|
|
property OnStartDock;
|
|
property OnStartDrag;
|
|
property OnStartOperation;
|
|
property OnStateChange;
|
|
property OnStructureChange;
|
|
property OnUpdating;
|
|
property OnUTF8KeyPress;
|
|
{$if CompilerVersion >= 24}
|
|
property StyleElements;
|
|
{$ifend}
|
|
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: TClipboardFormat): string;
|
|
procedure RegisterVTClipboardFormat(AFormat: TClipboardFormat; 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;
|
|
|
|
procedure ShowError(const Msg: String; HelpContext: Integer); // [IPK] Surface this to interface
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
implementation
|
|
|
|
{$R laz.virtualtrees.res}
|
|
|
|
uses
|
|
StrUtils, Math
|
|
{$ifdef EnableOLE}
|
|
//, AxCtrls // TOLEStream
|
|
{$endif}
|
|
{$ifdef Windows}
|
|
, MMSystem // for animation timer (does not include further resources)
|
|
{$else}
|
|
, laz.FakeMMSystem
|
|
{$endif}
|
|
{$ifdef EnableAccessible}
|
|
, laz.VTAccessibilityFactory
|
|
{$endif}; // accessibility helper class
|
|
|
|
resourcestring
|
|
// Localizable strings.
|
|
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];
|
|
TreeNodeSize = (SizeOf(TVirtualNode) + (SizeOf(Pointer) - 1)) and not (SizeOf(Pointer) - 1); // 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 © 1999, 2010 Mike Lischke';
|
|
|
|
var
|
|
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
|
|
TElementEdge = (
|
|
eeRaisedOuter
|
|
);
|
|
|
|
TElementEdges = set of TElementEdge;
|
|
|
|
TElementEdgeFlag = (
|
|
efRect
|
|
);
|
|
|
|
TElementEdgeFlags = set of TElementEdgeFlag;
|
|
//lcl: StyleServices is not implemented in LCL. If there's no plan to support it. Remove references here
|
|
// For compatibility with Delphi XE and earlier, prevents deprecated warnings in Delphi XE2 and higher
|
|
StyleServices = class
|
|
class function Enabled: Boolean;
|
|
class function DrawEdge(DC: HDC; Details: TThemedElementDetails; const R: TRect;
|
|
Edges: TElementEdges; Flags: TElementEdgeFlags; {%H-}ContentRect: PRect = nil): Boolean;
|
|
class function DrawElement(DC: HDC; Details: TThemedElementDetails; const R: TRect; ClipRect: PRect = nil): Boolean;
|
|
class function GetElementDetails(Detail: TThemedHeader): TThemedElementDetails; overload;
|
|
class function GetElementDetails(Detail: TThemedToolTip): TThemedElementDetails; overload;
|
|
class function GetElementDetails(Detail: TThemedWindow): TThemedElementDetails; overload;
|
|
class function GetElementDetails(Detail: TThemedButton): TThemedElementDetails; overload;
|
|
class procedure PaintBorder(Control: TWinControl; EraseLRCorner: Boolean);
|
|
end;
|
|
|
|
class function StyleServices.Enabled: Boolean;
|
|
begin
|
|
Result := ThemeServices.ThemesEnabled;
|
|
end;
|
|
|
|
class function StyleServices.DrawEdge(DC: HDC; Details: TThemedElementDetails; const R: TRect;
|
|
Edges: TElementEdges; Flags: TElementEdgeFlags; ContentRect: PRect = nil): Boolean;
|
|
begin
|
|
Assert((Edges = [eeRaisedOuter]) and (Flags = [efRect]));
|
|
ThemeServices.DrawEdge(DC, Details, R, BDR_RAISEDOUTER, BF_RECT);
|
|
Result := Enabled;
|
|
end;
|
|
|
|
class function StyleServices.DrawElement(DC: HDC; Details: TThemedElementDetails; const R: TRect; ClipRect: PRect = nil): Boolean;
|
|
begin
|
|
ThemeServices.DrawElement(DC, Details, R, ClipRect);
|
|
Result := Enabled;
|
|
end;
|
|
|
|
class function StyleServices.GetElementDetails(Detail: TThemedHeader): TThemedElementDetails;
|
|
begin
|
|
Result := ThemeServices.GetElementDetails(Detail);
|
|
end;
|
|
|
|
class function StyleServices.GetElementDetails(Detail: TThemedToolTip): TThemedElementDetails;
|
|
begin
|
|
Result := ThemeServices.GetElementDetails(Detail);
|
|
end;
|
|
|
|
class function StyleServices.GetElementDetails(Detail: TThemedWindow): TThemedElementDetails;
|
|
begin
|
|
Result := ThemeServices.GetElementDetails(Detail);
|
|
end;
|
|
|
|
class function StyleServices.GetElementDetails(Detail: TThemedButton): TThemedElementDetails;
|
|
begin
|
|
Result := ThemeServices.GetElementDetails(Detail);
|
|
end;
|
|
|
|
class procedure StyleServices.PaintBorder(Control: TWinControl; EraseLRCorner: Boolean);
|
|
begin
|
|
ThemeServices.PaintBorder(Control, EraseLRCorner);
|
|
end;
|
|
|
|
type
|
|
// protection against TRect record method that cause problems with with-statements
|
|
TWithSafeRect = record
|
|
case Integer of
|
|
0: (Left, Top, Right, Bottom: Longint);
|
|
1: (TopLeft, BottomRight: TPoint);
|
|
end;
|
|
|
|
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
|
|
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
|
|
|
|
{$ifndef COMPILER_11_UP}
|
|
const
|
|
{%H-}TVP_HOTGLYPH = 4;
|
|
{$endif COMPILER_11_UP}
|
|
|
|
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);
|
|
|
|
CheckImagesStrings: array [TCheckImageKind] of String = (
|
|
'LAZ_VT_CHECK_LIGHT',
|
|
'LAZ_VT_CHECK_DARK',
|
|
'LAZ_VT_TICK_LIGHT',
|
|
'LAZ_VT_TICK_DARK',
|
|
'LAZ_VT_FLAT',
|
|
'LAZ_VT_XP',
|
|
'',//ckCustom,
|
|
// Only the button images are used for ckSystem *
|
|
// The check buttons are draw at fly as requested
|
|
'LAZ_VT_FLAT',//ckSystemFlat
|
|
'LAZ_VT_CHECK_DARK' //ckSystemDefault
|
|
);
|
|
|
|
type
|
|
// internal worker thread
|
|
TWorkerThread = class(TThread)
|
|
private
|
|
FCurrentTree: TBaseVirtualTree;
|
|
FWaiterList: TThreadList;
|
|
FRefCount: Cardinal;
|
|
protected
|
|
procedure CancelValidation(Tree: TBaseVirtualTree);
|
|
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;
|
|
LightCheckImages, // global light check images
|
|
DarkCheckImages, // global heavy check images
|
|
LightTickImages, // global light tick images
|
|
DarkTickImages, // global heavy check images
|
|
FlatImages, // global flat check images
|
|
XPImages, // global XP style check images
|
|
SystemCheckImages, // global system check images
|
|
SystemFlatCheckImages: TImageList; // global flat system check images
|
|
UtilityImages: TCustomBitmap; // 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: TClipboardFormat): TVirtualTreeClass; overload;
|
|
function FindFormat(Fmt: TClipboardFormat; 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: TClipboardFormat): 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: TClipboardFormat; 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
|
|
{
|
|
,(ID: CF_DIBV5; Description: 'DIB image V5') // 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: TClipboardFormat): string;
|
|
|
|
begin
|
|
if InternalClipboardFormats = nil then
|
|
InternalClipboardFormats := TClipboardFormatList.Create;
|
|
if InternalClipboardFormats.FindFormat(AFormat, Result) = nil then
|
|
Result := '';
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure RegisterVTClipboardFormat(AFormat: TClipboardFormat; 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;
|
|
|
|
//----------------- compatibility functions ----------------------------------------------------------------------------
|
|
|
|
// ExcludeClipRect is buggy in Cocoa
|
|
// https://github.com/blikblum/VirtualTreeView-Lazarus/issues/8
|
|
// https://bugs.freepascal.org/view.php?id=34196
|
|
|
|
{$ifdef LCLCocoa}
|
|
function ExcludeClipRect(dc: hdc; Left, Top, Right, Bottom : Integer) : Integer;
|
|
begin
|
|
Result := 0;
|
|
end;
|
|
{$endif}
|
|
|
|
// LCLIntf.BitBlt is not compatible with windows.BitBlt
|
|
// The former takes into account the alpha channel while the later not
|
|
|
|
{$if not defined(USE_DELPHICOMPAT) and defined(LCLWin)}
|
|
function BitBlt(DestDC: HDC; X, Y, Width, Height: Integer; SrcDC: HDC; XSrc, YSrc: Integer; Rop: DWORD): Boolean;
|
|
begin
|
|
Result := windows.BitBlt(DestDC, X, Y, Width, Height, SrcDC, XSrc, YSrc, Rop);
|
|
end;
|
|
{$endif}
|
|
|
|
//----------------- utility functions ----------------------------------------------------------------------------------
|
|
|
|
procedure ShowError(const 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 PAnsiChar(TheArray[I]) < PAnsiChar(P) do
|
|
Inc(I);
|
|
while PAnsiChar(TheArray[J]) > PAnsiChar(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 = nil;
|
|
R: TRect;
|
|
|
|
begin
|
|
Result := '';
|
|
// Leading and trailing are ignored.
|
|
Buffer := Trim(S);
|
|
Len := Length(Buffer);
|
|
if Len < 1 then
|
|
Exit;
|
|
|
|
Width := Bounds.Right - Bounds.Left;
|
|
R := Rect(0, 0, 0, 0);
|
|
|
|
// 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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure GetStringDrawRect(DC: HDC; const S: String; var Bounds: TRect; DrawFormat: Cardinal);
|
|
|
|
// Calculates bounds of a drawing rectangle for the given string
|
|
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
function GetScalePercent: Integer;
|
|
// adapted from IDEImagesIntf
|
|
begin
|
|
if ScreenInfo.PixelsPerInchX <= 120 then
|
|
Result := 100 // 100-125% (96-120 DPI): no scaling
|
|
else
|
|
if ScreenInfo.PixelsPerInchX <= 168 then
|
|
Result := 150 // 126%-175% (144-168 DPI): 150% scaling
|
|
else
|
|
Result := Round(ScreenInfo.PixelsPerInchX/96) * 100; // 200, 300, 400, ...
|
|
end;
|
|
{$IFEND}
|
|
|
|
function BuildResourceName(AResName: String): String;
|
|
var
|
|
percent: Integer;
|
|
begin
|
|
Result := AResName;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
percent := GetScalePercent;
|
|
if percent = 150 then
|
|
Result := Result + '_150'
|
|
else if percent <> 100 then
|
|
Result := Result + '_200';
|
|
{$IFEND}
|
|
end;
|
|
|
|
// Support resources with bmp as well as png
|
|
procedure LoadBitmapFromResource(ABitmap: TBitmap; AResName: String);
|
|
var
|
|
bm: TCustomBitmap;
|
|
begin
|
|
bm := CreateBitmapFromResourceName(HINSTANCE, BuildResourceName(AResName));
|
|
try
|
|
bm.Transparent := true;
|
|
ABitmap.Assign(bm);
|
|
finally
|
|
bm.Free;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function CreateCheckImageList(CheckKind: TCheckImageKind): TImageList;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
var
|
|
bm: TCustomBitmap;
|
|
resName: String;
|
|
{$ENDIF}
|
|
begin
|
|
Result := TImageList.Create(nil);
|
|
Result.Height := 16;
|
|
Result.Width := 16;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result.RegisterResolutions([16, 24, 32]);
|
|
Result.Scaled := true;
|
|
resname := BuildResourceName(CheckImagesStrings[CheckKind]);
|
|
bm := CreateBitmapFromResourceName(HINSTANCE, resname);
|
|
try
|
|
bm.Transparent := true;
|
|
Result.AddSliced(bm, 25, 1);
|
|
finally
|
|
bm.Free;
|
|
end;
|
|
{$ELSE}
|
|
Result.AddResourceName(0, CheckImagesStrings[CheckKind], clFuchsia);
|
|
{$IFEND}
|
|
end;
|
|
|
|
function GetCheckImageList(var ImageList: TImageList; CheckKind: TCheckImageKind): TImageList;
|
|
begin
|
|
if ImageList = nil then
|
|
ImageList := CreateCheckImageList(CheckKind);
|
|
Result := ImageList;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
{$ifdef CPU64}
|
|
function HasMMX: Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
{$else}
|
|
function HasMMX: Boolean;
|
|
// Helper method to determine whether the current processor supports MMX.
|
|
{$if not (defined(CPU386) or Defined(CPUX64))}
|
|
begin
|
|
result := false;
|
|
{$else}
|
|
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
|
|
{$endif}
|
|
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 ClipCanvas(Canvas: TCanvas; ClipRect: TRect; VisibleRegion: HRGN = 0);
|
|
|
|
// Clip a given canvas to ClipRect while transforming the given rect to device coordinates.
|
|
|
|
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 SetCanvasOrigin(Canvas: TCanvas; X, Y: Integer);
|
|
|
|
// Set the coordinate space origin of a given canvas.
|
|
|
|
var
|
|
P: TPoint;
|
|
|
|
begin
|
|
// Reset origin as otherwise we would accumulate the origin shifts when calling LPtoDP.
|
|
SetWindowOrgEx(Canvas.Handle, 0, 0, nil);
|
|
|
|
// The shifting is expected in physical points, so we have to transform them accordingly.
|
|
P := Point(X, Y);
|
|
LPtoDP(Canvas.Handle, P, 1);
|
|
|
|
// Do the shift.
|
|
SetWindowOrgEx(Canvas.Handle, P.X, P.Y, nil);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure SetBrushOrigin(Canvas: TCanvas; X, Y: Integer);
|
|
|
|
// Set the brush origin of a given canvas.
|
|
|
|
var
|
|
P: TPoint;
|
|
|
|
begin
|
|
P := Point(X, Y);
|
|
LPtoDP(Canvas.Handle, P, 1);
|
|
SetBrushOrgEx(Canvas.Handle, P.X, P.Y, nil);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure InitializeGlobalStructures;
|
|
|
|
// initialization of stuff global to the unit
|
|
|
|
var
|
|
TheInstance: TLCLHandle;
|
|
|
|
begin
|
|
Initialized := True;
|
|
|
|
// For the drag image a fast MMX blend routine is used. We have to make sure MMX is available.
|
|
MMXAvailable := HasMMX;
|
|
{$ifdef Windows}
|
|
IsWinVistaOrAbove := (Win32MajorVersion >= 6);
|
|
{$else}
|
|
IsWinVistaOrAbove := False;
|
|
{$endif}
|
|
|
|
TheInstance := HINSTANCE;
|
|
|
|
{$ifdef EnableOLE}
|
|
// Initialize OLE subsystem for drag'n drop and clipboard operations.
|
|
//todo: replace by Suceeded (see in windows unit)
|
|
NeedToUnitialize := not IsLibrary and (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 := CreateBitmapFromResourceName(TheInstance, BuildResourceName('laz_vt_utilities'));
|
|
UtilityImageSize := UtilityImages.Height;
|
|
|
|
SystemCheckImages := CreateCheckImageList(ckSystemDefault);
|
|
|
|
// Delphi (at least version 6 and lower) does not provide a standard split cursor.
|
|
// Hence we have to load our own.
|
|
Screen.Cursors[crHeaderSplit] := LoadCursor(TheInstance, 'laz_VT_HEADERSPLIT');
|
|
Screen.Cursors[crVertSplit] := LoadCursor(TheInstance, 'laz_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
|
|
LightCheckImages.Free;
|
|
LightCheckImages := nil;
|
|
DarkCheckImages.Free;
|
|
DarkCheckImages := nil;
|
|
LightTickImages.Free;
|
|
LightTickImages := nil;
|
|
DarkTickImages.Free;
|
|
DarkTickImages := nil;
|
|
FlatImages.Free;
|
|
FlatImages := nil;
|
|
XPImages.Free;
|
|
XPImages := nil;
|
|
SystemCheckImages.Free;
|
|
SystemCheckImages := nil;
|
|
SystemFlatCheckImages.Free;
|
|
SystemFlatCheckImages := nil;
|
|
FreeAndNil(UtilityImages);
|
|
|
|
if NeedToUnitialize then
|
|
OleUninitialize;
|
|
|
|
end;
|
|
|
|
//----------------- TWorkerThread --------------------------------------------------------------------------------------
|
|
|
|
procedure AddThreadReference;
|
|
begin
|
|
if not Assigned(WorkerThread) 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;
|
|
|
|
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({%H-}Msg, Tree.Handle, WM_CHANGESTATE, WM_CHANGESTATE, PM_REMOVE) then
|
|
begin
|
|
//todo: see if is correct / will work
|
|
Application.ProcessMessages;
|
|
continue;
|
|
//TranslateMessage(Msg);
|
|
//DispatchMessage(Msg);
|
|
end;
|
|
if (toVariableNodeHeight in Tree.TreeOptions.MiscOptions) then
|
|
CheckSynchronize(); // We need to call CheckSynchronize here because we are using TThread.Synchronize in TBaseVirtualTree.MeasureItemHeight()
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TWorkerThread.Execute;
|
|
|
|
// Does some background tasks, like validating tree caches.
|
|
|
|
var
|
|
EnterStates,
|
|
LeaveStates: TChangeStates;
|
|
lCurrentTree: TBaseVirtualTree;
|
|
|
|
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
|
|
FCurrentTree.ChangeTreeStatesAsync([csValidating], [csUseCache, csValidationNeeded]);
|
|
EnterStates := [];
|
|
if not (tsStopValidation in FCurrentTree.FStates) and FCurrentTree.DoValidateCache then
|
|
EnterStates := [csUseCache];
|
|
|
|
finally
|
|
LeaveStates := [csValidating, csStopValidation];
|
|
FCurrentTree.ChangeTreeStatesAsync(EnterStates, LeaveStates);
|
|
lCurrentTree := FCurrentTree; // Save reference in a local variable for later use
|
|
FCurrentTree := nil; //Clear variable to prevent deadlock in CancelValidation. See #434
|
|
Synchronize(lCurrentTree.UpdateEditBounds);
|
|
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; // Seen several AVs in this line, was called from TWorkerThrea.Destroy. Joachim Marder.
|
|
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 := {%H-}AsUTF16String;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBufferedUTF8String.GetAsUTF16String: UnicodeString;
|
|
begin
|
|
//todo: optimize
|
|
Result := UTF8Decode(AsUTF8String);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBufferedUTF8String.GetAsUTF8String: String;
|
|
begin
|
|
SetString(Result, FStart, FPosition - FStart);
|
|
end;
|
|
|
|
function TBufferedUTF8String.GetAsString: 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
|
|
Invalidate;
|
|
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;
|
|
Run: PVirtualNode;
|
|
HandleWasAllocated: Boolean;
|
|
|
|
begin
|
|
if FPaintOptions <> Value then
|
|
begin
|
|
ToBeSet := Value - FPaintOptions;
|
|
ToBeCleared := FPaintOptions - Value;
|
|
FPaintOptions := Value;
|
|
if (toFixedIndent in ToBeSet) then
|
|
begin
|
|
// Fixes issue #388
|
|
Include(FPaintOptions, toShowRoot);
|
|
Include(ToBeSet, toShowRoot);
|
|
end;//if
|
|
with FOwner do
|
|
begin
|
|
HandleWasAllocated := HandleAllocated;
|
|
|
|
if not (csLoading in ComponentState) and (toShowFilteredNodes in ToBeSet + ToBeCleared) then
|
|
begin
|
|
if HandleWasAllocated then
|
|
BeginUpdate;
|
|
InterruptValidation;
|
|
Run := GetFirstNoInit;
|
|
while Assigned(Run) do
|
|
begin
|
|
if (vsFiltered in Run.States) then
|
|
begin
|
|
if FullyVisible[Run] then
|
|
begin
|
|
if toShowFilteredNodes in ToBeSet then
|
|
Inc(FVisibleCount)
|
|
else
|
|
Dec(FVisibleCount);
|
|
end;
|
|
if toShowFilteredNodes in ToBeSet then
|
|
AdjustTotalHeight(Run, Run.NodeHeight, True)
|
|
else
|
|
AdjustTotalHeight(Run, -Run.NodeHeight, True);
|
|
end;
|
|
Run := GetNextNoInit(Run);
|
|
end;
|
|
if HandleWasAllocated then
|
|
EndUpdate;
|
|
end;
|
|
|
|
if HandleAllocated then
|
|
begin
|
|
if IsWinVistaOrAbove and ((tsUseThemes in FStates) or
|
|
((toThemeAware in ToBeSet) and StyleServices.Enabled)) and
|
|
(toUseExplorerTheme in (ToBeSet + ToBeCleared)) and not VclStyleEnabled then
|
|
if (toUseExplorerTheme in ToBeSet) then
|
|
begin
|
|
SetWindowTheme('explorer');
|
|
DoStateChange([tsUseExplorerTheme]);
|
|
end
|
|
else
|
|
if toUseExplorerTheme in ToBeCleared then
|
|
begin
|
|
SetWindowTheme('');
|
|
DoStateChange([], [tsUseExplorerTheme]);
|
|
end;
|
|
|
|
if not (csLoading in ComponentState) then
|
|
begin
|
|
if ((toThemeAware in ToBeSet + ToBeCleared) or (toUseExplorerTheme in ToBeSet + ToBeCleared) or VclStyleEnabled) then
|
|
begin
|
|
if ((toThemeAware in ToBeSet) and StyleServices.Enabled) or VclStyleEnabled 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;
|
|
|
|
if toChildrenAbove in ToBeSet + ToBeCleared then
|
|
begin
|
|
InvalidateCache;
|
|
if FUpdateCount = 0 then
|
|
begin
|
|
ValidateCache;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
Invalidate;
|
|
end;
|
|
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 laz.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) and (Kind = vhkOwnerDraw) 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 or vhkText hint is about to show).
|
|
// This size has already been determined in CMHintShow.
|
|
if (Tree is TCustomVirtualDrawTree) and Assigned(Node) and (FHintData.Kind = vhkOwnerDraw) 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;
|
|
if Canvas.Font.Color = clDefault then
|
|
Canvas.Font.Color := clInfoText;
|
|
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
|
|
laz.VTGraphics.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 TWithSafeRect(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 TWithSafeRect(RDraw1) do
|
|
BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top,
|
|
SRCCOPY);
|
|
end
|
|
else
|
|
begin
|
|
with TWithSafeRect(RDraw1) do
|
|
BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top,
|
|
SRCCOPY);
|
|
with TWithSafeRect(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 TWithSafeRect(RSamp2) do
|
|
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
|
SRCCOPY);
|
|
end
|
|
else
|
|
if DeltaY = 0 then
|
|
begin
|
|
with TWithSafeRect(RSamp1) do
|
|
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
|
SRCCOPY);
|
|
end
|
|
else
|
|
begin
|
|
with TWithSafeRect(RSamp1) do
|
|
BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top,
|
|
SRCCOPY);
|
|
with TWithSafeRect(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;
|
|
lDragSourceHelper2: IDragSourceHelper2;// Needed to get Windows Vista+ style drag hints.
|
|
lNullPoint: TPoint;
|
|
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,
|
|
IDragSourceHelper, DragSourceHelper)) then
|
|
begin
|
|
Include(FStates, disSystemSupport);
|
|
lNullPoint := Point(0,0);
|
|
if Supports(DragSourceHelper, IDragSourceHelper2, lDragSourceHelper2) then
|
|
lDragSourceHelper2.SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);// Show description texts
|
|
// First let the system try to initialze the DragSourceHelper, this works fine for file system objects (CF_HDROP)
|
|
StandardOLEFormat.cfFormat := CF_HDROP;
|
|
if not Succeeded(DataObject.QueryGetData(StandardOLEFormat)) or not Succeeded(DragSourceHelper.InitializeFromWindow(0, lNullPoint, DataObject)) then
|
|
begin
|
|
// 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;
|
|
//lcl
|
|
//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.crColorKey := ColorToRGB(FColorKey);
|
|
if not Succeeded(DragSourceHelper.InitializeFromBitmap(@DragInfo, DataObject)) then
|
|
begin
|
|
DeleteObject(DragInfo.hbmpDragImage);
|
|
Exclude(FStates, disSystemSupport);
|
|
end;
|
|
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
|
|
laz.VTGraphics.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;
|
|
ClipCanvas(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);
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(Canvas, DragRect.Left - ClipRect.Left, DragRect.Top - ClipRect.Top);
|
|
{$else}
|
|
SetWindowOrgEx(Canvas.Handle, DragRect.Left - ClipRect.Left, DragRect.Top - ClipRect.Top, nil);
|
|
{$endif}
|
|
//todo: see what todo here
|
|
//Tree.Perform(WM_PRINT, WPARAM(Canvas.Handle), PRF_NONCLIENT);
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(Canvas, 0, 0);
|
|
{$else}
|
|
SetWindowOrgEx(Canvas.Handle, 0, 0, nil);
|
|
{$endif}
|
|
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.
|
|
|
|
begin
|
|
Result := Visible;
|
|
if Result then
|
|
begin
|
|
// Determine distances to move the drag image. Take care for restrictions.
|
|
case FRestriction of
|
|
dmrHorizontalOnly:
|
|
Result := FLastPosition.X <> P.X;
|
|
dmrVerticalOnly:
|
|
Result := FLastPosition.Y <> P.Y;
|
|
else // dmrNone
|
|
Result := (FLastPosition.X <> P.X) or (FLastPosition.Y <> P.Y);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//----------------- TVTVirtualNodeEnumerator ---------------------------------------------------------------------------
|
|
|
|
function TVTVirtualNodeEnumerator.GetCurrent: PVirtualNode;
|
|
|
|
begin
|
|
Result := FNode;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTVirtualNodeEnumerator.MoveNext: Boolean;
|
|
|
|
begin
|
|
Result := FCanModeNext;
|
|
if Result then
|
|
begin
|
|
FNode := FEnumeration.GetNext(FNode);
|
|
Result := FNode <> nil;
|
|
FCanModeNext := Result;
|
|
end;
|
|
end;
|
|
|
|
//----------------- TVTVirtualNodeEnumeration --------------------------------------------------------------------------
|
|
|
|
function TVTVirtualNodeEnumeration.GetEnumerator: TVTVirtualNodeEnumerator;
|
|
|
|
begin
|
|
{$ifdef COMPILER_10_UP}
|
|
{$else}
|
|
Result := TVTVirtualNodeEnumerator.Create;
|
|
{$endif COMPILER_10_UP}
|
|
Result.FNode := nil;
|
|
Result.FCanModeNext := True;
|
|
Result.FEnumeration := @Self;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTVirtualNodeEnumeration.GetNext(Node: PVirtualNode): PVirtualNode;
|
|
begin
|
|
case FMode of
|
|
vneAll:
|
|
if Node = nil then
|
|
Result := FTree.GetFirst(FConsiderChildrenAbove)
|
|
else
|
|
Result := FTree.GetNext(Node, FConsiderChildrenAbove);
|
|
|
|
vneChecked:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstChecked(FState, FConsiderChildrenAbove)
|
|
else
|
|
Result := FTree.GetNextChecked(Node, FState, FConsiderChildrenAbove);
|
|
|
|
vneChild:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstChild(FNode)
|
|
else
|
|
Result := FTree.GetNextSibling(Node);
|
|
|
|
vneCutCopy:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstCutCopy(FConsiderChildrenAbove)
|
|
else
|
|
Result := FTree.GetNextCutCopy(Node, FConsiderChildrenAbove);
|
|
|
|
vneInitialized:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstInitialized(FConsiderChildrenAbove)
|
|
else
|
|
Result := FTree.GetNextInitialized(Node, FConsiderChildrenAbove);
|
|
|
|
vneLeaf:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstLeaf
|
|
else
|
|
Result := FTree.GetNextLeaf(Node);
|
|
|
|
vneLevel:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstLevel(FNodeLevel)
|
|
else
|
|
Result := FTree.GetNextLevel(Node, FNodeLevel);
|
|
|
|
vneNoInit:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstNoInit(FConsiderChildrenAbove)
|
|
else
|
|
Result := FTree.GetNextNoInit(Node, FConsiderChildrenAbove);
|
|
|
|
vneSelected:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstSelected(FConsiderChildrenAbove)
|
|
else
|
|
Result := FTree.GetNextSelected(Node, FConsiderChildrenAbove);
|
|
|
|
vneVisible:
|
|
begin
|
|
if Node = nil then
|
|
begin
|
|
Result := FTree.GetFirstVisible(FNode, FConsiderChildrenAbove, FIncludeFiltered);
|
|
if FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result] then
|
|
Exit;
|
|
end;
|
|
repeat
|
|
Result := FTree.GetNextVisible(Node{, FConsiderChildrenAbove});
|
|
until not Assigned(Result) or FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result];
|
|
end;
|
|
|
|
vneVisibleChild:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstVisibleChild(FNode, FIncludeFiltered)
|
|
else
|
|
Result := FTree.GetNextVisibleSibling(Node, FIncludeFiltered);
|
|
|
|
vneVisibleNoInitChild:
|
|
if Node = nil then
|
|
Result := FTree.GetFirstVisibleChildNoInit(FNode, FIncludeFiltered)
|
|
else
|
|
Result := FTree.GetNextVisibleSiblingNoInit(Node, FIncludeFiltered);
|
|
|
|
vneVisibleNoInit:
|
|
begin
|
|
if Node = nil then
|
|
begin
|
|
Result := FTree.GetFirstVisibleNoInit(FNode, FConsiderChildrenAbove, FIncludeFiltered);
|
|
if FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result] then
|
|
Exit;
|
|
end;
|
|
repeat
|
|
Result := FTree.GetNextVisibleNoInit(Node, FConsiderChildrenAbove);
|
|
until not Assigned(Result) or FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result];
|
|
end;
|
|
else
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
//----------------- TVirtualTreeColumn ---------------------------------------------------------------------------------
|
|
|
|
constructor TVirtualTreeColumn.Create(Collection: TCollection);
|
|
|
|
begin
|
|
FMinWidth := 10;
|
|
FMaxWidth := 10000;
|
|
FImageIndex := -1;
|
|
//FText := '';
|
|
FOptions := DefaultColumnOptions;
|
|
FAlignment := taLeftJustify;
|
|
FBiDiMode := bdLeftToRight;
|
|
FColor := clWindow;
|
|
FLayout := blGlyphLeft;
|
|
//FBonusPixel := False;
|
|
FCaptionAlignment := taLeftJustify;
|
|
FCheckType := ctCheckBox;
|
|
FCheckState := csUncheckedNormal;
|
|
//FCheckBox := False;
|
|
//FHasImage := False;
|
|
FDefaultSortDirection := sdAscending;
|
|
|
|
inherited Create(Collection);
|
|
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
FMargin := Owner.Header.TreeView.Scale96ToFont(DEFAULT_MARGIN);
|
|
FSpacing := Owner.Header.TreeView.Scale96ToFont(DEFAULT_SPACING);
|
|
{$ELSE}
|
|
FMargin := DEFAULT_MARGIN;
|
|
FSpacing := DEFAULT_SPACING;
|
|
{$IFEND}
|
|
|
|
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
|
|
Header.Treeview.CancelEditNode;
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVirtualTreeColumn.IsMarginStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FMargin <> Owner.Header.TreeView.Scale96ToFont(DEFAULT_MARGIN);
|
|
{$ELSE}
|
|
Result := FMargin <> DEFAULT_MARGIN;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVirtualTreeColumn.IsSpacingStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FSpacing <> Owner.Header.TreeView.Scale96ToFont(DEFAULT_SPACING);
|
|
{$ELSE}
|
|
Result := FSpacing <> DEFAULT_SPACING;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVirtualTreeColumn.IsWidthStored: Boolean;
|
|
begin
|
|
Result := FWidth <> Owner.DefaultWidth;
|
|
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;
|
|
Include(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;
|
|
if Value and (csDesigning in Owner.Header.Treeview.ComponentState) then
|
|
Owner.Header.Options := Owner.Header.Options + [hoShowImages];
|
|
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;
|
|
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: TTranslateString);
|
|
|
|
begin
|
|
if FText <> Value then
|
|
begin
|
|
FText := Value;
|
|
FCaptionText := '';
|
|
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 SortGlyphSize: TSize; 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: TPoint;
|
|
CurrentAlignment: TAlignment;
|
|
MinLeft,
|
|
MaxRight,
|
|
TextSpacing: Integer;
|
|
UseText: Boolean;
|
|
R: TRect;
|
|
{$ifdef Windows}
|
|
Theme: HTHEME;
|
|
{$endif}
|
|
|
|
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 begin
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
with FImages.ResolutionForPPI[FImagesWidth, Font.PixelsPerInch, Self.Owner.Header.TreeView.GetCanvasScaleFactor] do
|
|
HeaderGlyphSize := Point(Width, Height);
|
|
{$ELSE}
|
|
HeaderGlyphSize := Point(FImages.Width, FImages.Height)
|
|
{$IFEND}
|
|
end else
|
|
with Self.Owner.Header.Treeview do
|
|
begin
|
|
if Assigned(FCheckImages) then
|
|
HeaderGlyphSize := Point(GetRealCheckImagesWidth, GetRealCheckImagesHeight);
|
|
end
|
|
else
|
|
HeaderGlyphSize := Point(0, 0);
|
|
if UseSortGlyph then
|
|
begin
|
|
if tsUseExplorerTheme in FHeader.Treeview.FStates then
|
|
begin
|
|
R := Rect(0, 0, 100, 100);
|
|
|
|
{$ifdef Windows}
|
|
Theme := OpenThemeData(FHeader.Treeview.Handle, 'HEADER');
|
|
GetThemePartSize(Theme, DC, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, SortGlyphSize);
|
|
CloseThemeData(Theme);
|
|
{$endif}
|
|
end
|
|
else
|
|
begin
|
|
SortGlyphSize.cx := UtilityImages.Height;
|
|
SortGlyphSize.cy := UtilityImages.Height;
|
|
end;
|
|
|
|
// In any case, the sort glyph is vertically centered.
|
|
SortGlyphPos.Y := (ClientSize.Y - SortGlyphSize.cy) div 2;
|
|
end
|
|
else
|
|
begin
|
|
SortGlyphSize.cx := 0;
|
|
SortGlyphSize.cy := 0;
|
|
end;
|
|
end;
|
|
|
|
if UseText then
|
|
begin
|
|
if not (coWrapCaption in FOptions) then
|
|
begin
|
|
FCaptionText := FText;
|
|
GetTextExtentPoint32(DC, PChar(FText), Length(FText), {%H-}TextSize);
|
|
Inc(TextSize.cx, 2);
|
|
TextBounds := Rect(0, 0, TextSize.cx, TextSize.cy);
|
|
end
|
|
else
|
|
begin
|
|
R := Client;
|
|
if FCaptionText = '' then
|
|
FCaptionText := WrapString(DC, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat);
|
|
|
|
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.cx) div 2, (ClientSize.Y - SortGlyphSize.cy) 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.
|
|
//Using Max() fixes badly positioned text if Extra Large fonts have been activated in the Windows display options
|
|
TextPos.Y := Max(-5, (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.cx + 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.cx 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.cx;
|
|
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.cx);
|
|
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.cx;
|
|
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.cx > MaxRight then
|
|
SortGlyphPos.X := MaxRight - SortGlyphSize.cx;
|
|
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.cx + FSpacing;
|
|
|
|
// Finally transform sort glyph to its actual position.
|
|
Inc(SortGlyphPos.X, Client.Left);
|
|
Inc(SortGlyphPos.Y, Client.Top);
|
|
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;
|
|
if FCheckBox and (Owner.Header.MainColumn = Self.Index) then
|
|
Dec(HeaderGlyphPos.X, 2)
|
|
else
|
|
if Owner.Header.MainColumn <> Self.Index then
|
|
Dec(HeaderGlyphPos.X, 2);
|
|
|
|
// Finally transform header glyph to its actual position.
|
|
Inc(HeaderGlyphPos.X, Client.Left);
|
|
Inc(HeaderGlyphPos.Y, Client.Top);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
// [IPK]
|
|
function TVirtualTreeColumn.GetText: String;
|
|
|
|
begin
|
|
Result := FText;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TVirtualTreeColumn.LoadFromStream(const Stream: TStream; Version: Integer);
|
|
|
|
var
|
|
Dummy: Integer;
|
|
S: String = '';
|
|
|
|
begin
|
|
with Stream do
|
|
begin
|
|
ReadBuffer({%H-}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;
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
FDefaultWidth := Header.TreeView.Scale96ToFont(DEFAULT_COLUMN_WIDTH);
|
|
{$ELSE}
|
|
FDefaultWidth := DEFAULT_COLUMN_WIDTH;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
destructor TVirtualTreeColumns.Destroy;
|
|
|
|
begin
|
|
FHeaderBitmap.Free;
|
|
inherited;
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVirtualTreeColumns.IsDefaultWidthStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FDefaultWidth <> Header.TreeView.Scale96ToFont(DEFAULT_COLUMN_WIDTH);
|
|
{$ELSE}
|
|
Result := FDefaultWidth <> DEFAULT_COLUMN_WIDTH;
|
|
{$IFEND}
|
|
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;
|
|
WasUpdating: Boolean;
|
|
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;
|
|
WasUpdating := csUpdating in FHeader.Treeview.ComponentState;
|
|
if not WasUpdating then
|
|
FHeader.Treeview.Updating();// Fixes #398
|
|
try
|
|
FHeader.Treeview.DoColumnResize(AutoIndex);
|
|
finally
|
|
if not WasUpdating then
|
|
FHeader.Treeview.Updated();
|
|
end;
|
|
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;
|
|
FCheckBoxHit := Items[Result].FHasImage and PtInRect(Items[Result].FImageRect, P) and Items[Result].CheckBox;
|
|
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
|
|
System.Move(FPositionToIndex[OldPosition + 1], FPositionToIndex[OldPosition], (Position - OldPosition) * SizeOf(Cardinal));
|
|
end
|
|
else
|
|
begin
|
|
// column will be moved down so move up other entries
|
|
System.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;
|
|
TextColor: TColor;
|
|
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), {%H-}Size);
|
|
TextSpace := Bounds.Right - Bounds.Left;
|
|
if TextSpace < Size.cx then
|
|
Caption := ShortenString(DC, Caption, TextSpace);
|
|
end;
|
|
|
|
SetBkMode(DC, TRANSPARENT);
|
|
if not Enabled then
|
|
if FHeader.Treeview.VclStyleEnabled then
|
|
begin
|
|
TextColor := FHeader.Treeview.FColors.HeaderFontColor;
|
|
if TextColor = clDefault then
|
|
TextColor := clBtnText;
|
|
SetTextColor(DC, ColorToRGB(TextColor));
|
|
DrawText(DC, PChar(Caption), Length(Caption), Bounds, DrawFormat);
|
|
end
|
|
else
|
|
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
|
|
TextColor := FHeader.Treeview.FColors.HeaderHotColor
|
|
else
|
|
TextColor := FHeader.Treeview.FColors.HeaderFontColor;
|
|
if TextColor = clDefault then
|
|
TextColor := FHeader.Treeview.GetDefaultColor(dctFont);
|
|
SetTextColor(DC, ColorToRGB(TextColor));
|
|
DrawText(DC, PChar(Caption), Length(Caption), Bounds, DrawFormat);
|
|
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
|
|
HitInfo: TVTHeaderHitInfo;
|
|
NewClickIndex: Integer;
|
|
|
|
begin
|
|
if (csDesigning in Header.Treeview.ComponentState) then
|
|
exit;
|
|
// Convert vertical position to local coordinates.
|
|
//lclheader
|
|
//Inc(P.Y, FHeader.FHeight);
|
|
NewClickIndex := ColumnFromPosition(P);
|
|
with HitInfo do
|
|
begin
|
|
X := P.X;
|
|
Y := P.Y;
|
|
Shift := FHeader.GetShiftState;
|
|
if DblClick then
|
|
Shift := Shift + [ssDouble];
|
|
end;
|
|
HitInfo.Button := Button;
|
|
|
|
if (NewClickIndex > NoColumn) and (coAllowClick in Items[NewClickIndex].FOptions) and
|
|
((NewClickIndex = FDownIndex) or Force) then
|
|
begin
|
|
FClickIndex := NewClickIndex;
|
|
HitInfo.Column := NewClickIndex;
|
|
HitInfo.HitPosition := [hhiOnColumn];
|
|
|
|
if Items[NewClickIndex].FHasImage and PtInRect(Items[NewClickIndex].FImageRect, P) then
|
|
begin
|
|
Include(HitInfo.HitPosition, hhiOnIcon);
|
|
if Items[NewClickIndex].CheckBox then
|
|
begin
|
|
if Button = mbLeft then
|
|
FHeader.Treeview.UpdateColumnCheckState(Items[NewClickIndex]);
|
|
Include(HitInfo.HitPosition, hhiOnCheckbox);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
FClickIndex := NoColumn;
|
|
HitInfo.Column := NoColumn;
|
|
HitInfo.HitPosition := [hhiNoWhere];
|
|
end;
|
|
|
|
if (hoHeaderClickAutoSort in Header.Options) and (HitInfo.Button = mbLeft) and not DblClick and not (hhiOnCheckbox in HitInfo.HitPosition) and (HitInfo.Column >= 0) then
|
|
begin
|
|
// handle automatic setting of SortColumn and toggling of the sort order
|
|
if HitInfo.Column <> Header.SortColumn then
|
|
begin
|
|
// set sort column
|
|
Header.SortColumn := HitInfo.Column;
|
|
Header.SortDirection := Self[Header.SortColumn].DefaultSortDirection;
|
|
end//if
|
|
else
|
|
begin
|
|
// toggle sort direction
|
|
if Header.SortDirection = sdDescending then
|
|
Header.SortDirection := sdAscending
|
|
else
|
|
Header.SortDirection := sdDescending;
|
|
end;//else
|
|
end;//if
|
|
|
|
if DblClick then
|
|
FHeader.Treeview.DoHeaderDblClick(HitInfo)
|
|
else
|
|
FHeader.Treeview.DoHeaderClick(HitInfo);
|
|
|
|
if not (hhiNoWhere in HitInfo.HitPosition) then
|
|
FHeader.Invalidate(Items[NewClickIndex]);
|
|
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
|
|
System.Move(FPositionToIndex[I + 1], FPositionToIndex[I], (Upper - I) * SizeOf(TColumnIndex));
|
|
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.Notify(Item: TCollectionItem; Action: TCollectionNotification);
|
|
|
|
begin
|
|
if Action in [cnExtracting, cnDeleting] then
|
|
with Header.Treeview do
|
|
if not (csLoading in ComponentState) and (FFocusedColumn = Item.Index) then
|
|
FFocusedColumn := NoColumn;
|
|
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;
|
|
FHeader.Treeview.UpdateHorizontalScrollBar(False);
|
|
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
|
|
Header.Treeview.CancelEditNode;
|
|
|
|
// Since we're freeing all columns, the following have to be true when we're done.
|
|
FHoverIndex := NoColumn;
|
|
FDownIndex := NoColumn;
|
|
FTrackIndex := NoColumn;
|
|
FClickIndex := NoColumn;
|
|
FCheckBoxHit := False;
|
|
|
|
with Header do
|
|
if not (hsLoading in FStates) then
|
|
begin
|
|
FAutoSizeIndex := NoColumn;
|
|
FMainColumn := NoColumn;
|
|
FSortColumn := NoColumn;
|
|
end;
|
|
|
|
with Header.Treeview do
|
|
if not (csLoading in ComponentState) then
|
|
FFocusedColumn := NoColumn;
|
|
|
|
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.GetFirstColumn: TColumnIndex;
|
|
|
|
// Returns the first column in display order.
|
|
|
|
begin
|
|
if Count = 0 then
|
|
Result := InvalidColumn
|
|
else
|
|
Result := FPositionToIndex[0];
|
|
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
|
|
Result := nil;
|
|
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({%H-}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(TColumnIndex));
|
|
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);
|
|
|
|
// Backward compatible header paint method. This method takes care of visually moving floating columns
|
|
|
|
var
|
|
VisibleFixedWidth: Integer;
|
|
RTLOffset: Integer;
|
|
{$ifdef LCLCocoa}
|
|
sc : Double;
|
|
{$endif}
|
|
|
|
procedure PaintFixedArea;
|
|
|
|
begin
|
|
if VisibleFixedWidth > 0 then
|
|
PaintHeader(FHeaderBitmap.Canvas,
|
|
Rect(0, 0, Min(R.Right, VisibleFixedWidth), R.Bottom - R.Top),
|
|
Point(R.Left, R.Top), RTLOffset);
|
|
end;
|
|
|
|
begin
|
|
// Adjust size of the header bitmap
|
|
with TWithSafeRect(FHeader.Treeview.FHeaderRect) do
|
|
begin
|
|
FHeaderBitmap.Width := Max(Right, R.Right - R.Left);
|
|
FHeaderBitmap.Height := Bottom;
|
|
{$ifdef LCLCocoa}
|
|
if Assigned(Header) and Assigned(Header.TreeView) then
|
|
sc := Header.Treeview.GetCanvasScaleFactor
|
|
else
|
|
sc := 1.0;
|
|
FHeaderBitmap.Width := Round(FHeaderBitmap.Width * sc);
|
|
FHeaderBitmap.Height := Round(FHeaderBitmap.Height * sc);
|
|
CGContextScaleCTM(TCocoaBitmapContext(FHeaderBitmap.Canvas.Handle).CGContext, sc, sc);
|
|
{$endif}
|
|
end;
|
|
|
|
VisibleFixedWidth := GetVisibleFixedWidth;
|
|
|
|
// Consider right-to-left directionality.
|
|
if FHeader.TreeView.UseRightToLeftAlignment then
|
|
RTLOffset := FHeader.Treeview.ComputeRTLOffset
|
|
else
|
|
RTLOffset := 0;
|
|
|
|
if RTLOffset = 0 then
|
|
PaintFixedArea;
|
|
|
|
// Paint the floating part of the header.
|
|
PaintHeader(FHeaderBitmap.Canvas,
|
|
Rect(VisibleFixedWidth - HOffset, 0, R.Right + VisibleFixedWidth - HOffset, R.Bottom - R.Top),
|
|
Point(R.Left + VisibleFixedWidth, R.Top), RTLOffset);
|
|
|
|
// In case of right-to-left directionality we paint the fixed part last.
|
|
if RTLOffset <> 0 then
|
|
PaintFixedArea;
|
|
|
|
// Blit the result to target.
|
|
with TWithSafeRect(R) do
|
|
{$ifdef LCLCocoa}
|
|
StretchBlt(DC, Left, Top, Right - Left, Bottom - Top,
|
|
FHeaderBitmap.Canvas.Handle,
|
|
Left, Top,
|
|
FHeaderBitmap.Width, FHeaderBitmap.Height,
|
|
SRCCOPY);
|
|
{$else}
|
|
BitBlt(DC, Left, Top, Right - Left, Bottom - Top, FHeaderBitmap.Canvas.Handle, Left, Top, SRCCOPY);
|
|
{$endif}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TVirtualTreeColumns.PaintHeader(TargetCanvas: TCanvas; R: TRect; const Target: TPoint;
|
|
RTLOffset: Integer = 0);
|
|
|
|
// Main paint method to draw the header.
|
|
// This procedure will paint the a slice (given in R) out of HeaderRect into TargetCanvas starting at position Target.
|
|
// This function does not offer the option to visually move floating columns due to scrolling. To accomplish this you
|
|
// need to call this method twice.
|
|
|
|
const
|
|
SortGlyphs: array[TSortDirection, Boolean] of Integer = ( // ascending/descending, normal/XP style
|
|
(3, 5) {ascending}, (2, 4) {descending}
|
|
);
|
|
|
|
var
|
|
Run: TColumnIndex;
|
|
RightBorderFlag,
|
|
NormalButtonStyle,
|
|
NormalButtonFlags,
|
|
PressedButtonStyle,
|
|
PressedButtonFlags,
|
|
RaisedButtonStyle,
|
|
RaisedButtonFlags: Cardinal;
|
|
Images: TCustomImageList;
|
|
OwnerDraw,
|
|
AdvancedOwnerDraw: Boolean;
|
|
PaintInfo: THeaderPaintInfo;
|
|
RequestedElements,
|
|
ActualElements: THeaderPaintElements;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImagesRes: TScaledImageListResolution;
|
|
{$IFEND}
|
|
|
|
//--------------- local functions -------------------------------------------
|
|
|
|
procedure PrepareButtonStyles;
|
|
|
|
// Prepare the button styles and flags for later usage.
|
|
|
|
begin
|
|
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;
|
|
end;
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
procedure DrawBackground;
|
|
|
|
// Draw the header background.
|
|
|
|
var
|
|
BackgroundRect: TRect;
|
|
Details: TThemedElementDetails;
|
|
|
|
begin
|
|
BackgroundRect := Rect(Target.X, Target.Y, Target.X + R.Right - R.Left, Target.Y + FHeader.Height);
|
|
|
|
with TargetCanvas do
|
|
begin
|
|
if hpeBackground in RequestedElements then
|
|
begin
|
|
PaintInfo.PaintRectangle := BackgroundRect;
|
|
FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]);
|
|
end
|
|
else
|
|
begin
|
|
if tsUseThemes in FHeader.Treeview.FStates then
|
|
begin
|
|
Details := StyleServices.GetElementDetails(thHeaderItemRightNormal);
|
|
StyleServices.DrawElement(Handle, Details, BackgroundRect, @BackgroundRect);
|
|
end
|
|
else
|
|
begin
|
|
Brush.Color := FHeader.FBackground;
|
|
FillRect(BackgroundRect);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
procedure PaintColumnHeader(AColumn: TColumnIndex; ATargetRect: TRect);
|
|
|
|
// Draw a single column to TargetRect. The clipping rect needs to be set before
|
|
// this procedure is called.
|
|
|
|
var
|
|
Y: Integer;
|
|
SavedDC: Integer;
|
|
ColCaptionText: UnicodeString;
|
|
ColImageInfo: TVTImageInfo;
|
|
SortIndex: Integer;
|
|
SortGlyphSize: TSize;
|
|
Glyph: TThemedHeader;
|
|
Details: TThemedElementDetails;
|
|
WrapCaption: Boolean;
|
|
DrawFormat: Cardinal;
|
|
Pos: TRect;
|
|
DrawHot: Boolean;
|
|
ImageWidth: Integer;
|
|
w, h: Integer;
|
|
Rsrc, Rdest: TRect;
|
|
begin
|
|
ColImageInfo.Ghosted := False;
|
|
PaintInfo.Column := Items[AColumn];
|
|
with PaintInfo, Column do
|
|
begin
|
|
//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 := (AColumn = FHoverIndex) and (hoHotTrack in FHeader.FOptions) and
|
|
(coEnabled in FOptions) and not (hsDragging in FHeader.States);
|
|
IsDownIndex := (AColumn = FDownIndex) and not FCheckBoxHit;
|
|
|
|
if (coShowDropMark in FOptions) and (AColumn = FDropTarget) and (AColumn <> 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)) or FCheckBox);
|
|
ShowSortGlyph := (AColumn = FHeader.FSortColumn) and (hoShowSortGlyphs in FHeader.FOptions);
|
|
WrapCaption := coWrapCaption in FOptions;
|
|
|
|
PaintRectangle := ATargetRect;
|
|
|
|
// 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[AColumn];
|
|
FHeader.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
|
|
end;
|
|
|
|
if ShowRightBorder or (AColumn < Count - 1) then
|
|
RightBorderFlag := BF_RIGHT
|
|
else
|
|
RightBorderFlag := 0;
|
|
|
|
if hpeBackground in RequestedElements then
|
|
FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground])
|
|
else
|
|
begin
|
|
if tsUseThemes in FHeader.Treeview.FStates then
|
|
begin
|
|
if IsDownIndex then
|
|
Details := StyleServices.GetElementDetails(thHeaderItemPressed)
|
|
else
|
|
if IsHoverIndex then
|
|
Details := StyleServices.GetElementDetails(thHeaderItemHot)
|
|
else
|
|
Details := StyleServices.GetElementDetails(thHeaderItemNormal);
|
|
StyleServices.DrawElement(TargetCanvas.Handle, Details, PaintRectangle, @PaintRectangle);
|
|
end
|
|
else
|
|
begin
|
|
if IsDownIndex then
|
|
DrawEdge(TargetCanvas.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(TargetCanvas.Handle, PaintRectangle, RaisedButtonStyle,
|
|
RaisedButtonFlags or RightBorderFlag)
|
|
else
|
|
DrawEdge(TargetCanvas.Handle, PaintRectangle, NormalButtonStyle,
|
|
NormalButtonFlags or RightBorderFlag);
|
|
end;
|
|
end;
|
|
|
|
PaintRectangle := ATargetRect;
|
|
|
|
// 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(TargetCanvas.Handle, PaintRectangle, ShowHeaderGlyph, ShowSortGlyph, GlyphPos,
|
|
SortGlyphPos, {%H-}SortGlyphSize, 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 Assigned(Images) then
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImageWidth := ImagesRes.Width
|
|
{$ELSE}
|
|
ImageWidth := Images.Width
|
|
{$IFEND}
|
|
else
|
|
ImageWidth := 0;
|
|
|
|
if not (hpeHeaderGlyph in ActualElements) and ShowHeaderGlyph and
|
|
(not ShowSortGlyph or (FBiDiMode <> bdLeftToRight) or (GlyphPos.X + ImageWidth <= SortGlyphPos.X) ) then
|
|
begin
|
|
if not FCheckBox then
|
|
begin
|
|
ColImageInfo.Images := Images;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImagesRes.Draw(TargetCanvas, GlyphPos.X, GlyphPos.Y, FImageIndex, IsEnabled);
|
|
w := ImagesRes.Width;
|
|
h := ImagesRes.Height;
|
|
{$ELSE}
|
|
Images.Draw(TargetCanvas, GlyphPos.X, GlyphPos.Y, FImageIndex, IsEnabled);
|
|
w := Images.Width;
|
|
h := Images.Height;
|
|
{$IFEND}
|
|
end
|
|
else
|
|
begin
|
|
with Header.Treeview do
|
|
begin
|
|
ColImageInfo.Images := GetCheckImageListFor(CheckImageKind);
|
|
ColImageInfo.Index := GetCheckImage(nil, FCheckType, FCheckState, IsEnabled);
|
|
ColImageInfo.XPos := GlyphPos.X;
|
|
ColImageInfo.YPos := GlyphPos.Y;
|
|
if ColImageInfo.Images <> nil then begin
|
|
w := ColImageInfo.Images.Width;
|
|
h := ColImageInfo.Images.Height;
|
|
end else begin
|
|
w := 0;
|
|
h := 0;
|
|
end;
|
|
PaintCheckImage(TargetCanvas, ColImageInfo, False);
|
|
end;
|
|
end;
|
|
|
|
FHasImage := True;
|
|
with TWithSafeRect(FImageRect) do
|
|
begin
|
|
Left := GlyphPos.X;
|
|
Top := GlyphPos.Y;
|
|
Right := Left + w;
|
|
Bottom := Top + h;
|
|
end;
|
|
end;
|
|
|
|
// caption
|
|
if WrapCaption then
|
|
ColCaptionText := UnicodeString(FCaptionText)
|
|
else
|
|
ColCaptionText := UnicodeString(Text);
|
|
if IsHoverIndex and FHeader.Treeview.VclStyleEnabled then
|
|
DrawHot := True
|
|
else
|
|
DrawHot := (IsHoverIndex and (hoHotTrack in FHeader.FOptions) and not(tsUseThemes in FHeader.Treeview.FStates));
|
|
if not(hpeText in ActualElements) and (Length(Text) > 0) then
|
|
DrawButtonText(TargetCanvas.Handle, String(ColCaptionText), TextRectangle, IsEnabled, DrawHot, DrawFormat, WrapCaption);
|
|
|
|
// sort glyph
|
|
if not (hpeSortGlyph in ActualElements) and ShowSortGlyph then
|
|
begin
|
|
Rsrc := Rect(0, 0, UtilityImageSize-1, UtilityImageSize-1);
|
|
Rdest := Rsrc;
|
|
if tsUseExplorerTheme in FHeader.Treeview.FStates then
|
|
begin
|
|
Pos.TopLeft := SortGlyphPos;
|
|
Pos.Right := Pos.Left + SortGlyphSize.cx;
|
|
Pos.Bottom := Pos.Top + SortGlyphSize.cy;
|
|
if FHeader.FSortDirection = sdAscending then
|
|
Glyph := thHeaderSortArrowSortedUp
|
|
else
|
|
Glyph := thHeaderSortArrowSortedDown;
|
|
Details := StyleServices.GetElementDetails(Glyph);
|
|
StyleServices.DrawElement(TargetCanvas.Handle, Details, Pos, @Pos);
|
|
end
|
|
else
|
|
begin
|
|
SortIndex := SortGlyphs[FHeader.FSortDirection, tsUseThemes in FHeader.Treeview.FStates];
|
|
OffsetRect(Rsrc, SortIndex * UtilityImageSize, 0);
|
|
OffsetRect(Rdest, SortGlyphPos.x, SortGlyphPos.y);
|
|
FHeaderBitmap.Canvas.CopyRect(Rdest, UtilityImages.Canvas, Rsrc);
|
|
end;
|
|
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
|
|
Rsrc := Rect(0, 0, UtilityImageSize-1, UtilityImageSize-1);
|
|
Rdest := Rsrc;
|
|
Y := (PaintRectangle.Top + PaintRectangle.Bottom - UtilityImages.Height) div 2;
|
|
if DropMark = dmmLeft then begin
|
|
OffsetRect(Rdest, PaintRectangle.Left, Y);
|
|
FHeaderBitmap.Canvas.CopyRect(Rdest, UtilityImages.Canvas, Rsrc);
|
|
end else begin
|
|
OffsetRect(Rdest, PaintRectangle.Right - UtilityImageSize, Y);
|
|
OffsetRect(Rsrc, UtilityImageSize, 0);
|
|
FHeaderBitmap.Canvas.CopyRect(Rdest, UtilityImages.Canvas, Rsrc);
|
|
end;
|
|
end;
|
|
|
|
if ActualElements <> [] then
|
|
begin
|
|
SavedDC := SaveDC(TargetCanvas.Handle);
|
|
FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, ActualElements);
|
|
RestoreDC(TargetCanvas.Handle, SavedDC);
|
|
end;
|
|
end
|
|
else // Let application draw the header.
|
|
FHeader.Treeview.DoHeaderDraw(TargetCanvas, Items[AColumn], PaintRectangle, IsHoverIndex, IsDownIndex,
|
|
DropMark);
|
|
end;
|
|
end;
|
|
|
|
//--------------- end local functions ---------------------------------------
|
|
|
|
var
|
|
TargetRect: TRect;
|
|
MaxX: Integer;
|
|
|
|
begin
|
|
if IsRectEmpty(R) then
|
|
Exit;
|
|
|
|
// If both draw posibillities are specified then prefer the advanced way.
|
|
AdvancedOwnerDraw := (hoOwnerDraw in FHeader.FOptions) and Assigned(FHeader.Treeview.FOnAdvancedHeaderDraw) and
|
|
Assigned(FHeader.Treeview.FOnHeaderDrawQueryElements) and not (csDesigning in FHeader.Treeview.ComponentState);
|
|
OwnerDraw := (hoOwnerDraw in FHeader.FOptions) and Assigned(FHeader.Treeview.FOnHeaderDraw) and
|
|
not (csDesigning in FHeader.Treeview.ComponentState) and not AdvancedOwnerDraw;
|
|
|
|
FillChar(PaintInfo, SizeOf(PaintInfo), #0);
|
|
PaintInfo.TargetCanvas := TargetCanvas;
|
|
|
|
with PaintInfo, TargetCanvas do
|
|
begin
|
|
// Use shortcuts for the images and the font.
|
|
Images := FHeader.FImages;
|
|
Font := FHeader.FFont;
|
|
if Font.Color = clDefault then
|
|
Font.Color := FHeader.Treeview.GetDefaultColor(dctFont);
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
if Images <> nil then
|
|
ImagesRes := Images.ResolutionForPPI[FHeader.ImagesWidth, Font.PixelsPerInch, Header.TreeView.GetCanvasScaleFactor];
|
|
{$IFEND}
|
|
|
|
PrepareButtonStyles;
|
|
|
|
// At first, query the application which parts of the header it wants to draw on its own.
|
|
RequestedElements := [];
|
|
if AdvancedOwnerDraw then
|
|
begin
|
|
PaintRectangle := R;
|
|
Column := nil;
|
|
FHeader.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
|
|
end;
|
|
|
|
// Draw the background.
|
|
DrawBackground;
|
|
|
|
// Now that we have drawn the background, we apply the header's dimensions to R.
|
|
R := Rect(Max(R.Left, 0), Max(R.Top, 0), Min(R.Right, TotalWidth), Min(R.Bottom, Header.Height));
|
|
|
|
// Determine where to stop.
|
|
MaxX := Target.X + R.Right - R.Left;
|
|
|
|
// Determine the start column.
|
|
Run := ColumnFromPosition(Point(R.Left + RTLOffset, 0), False);
|
|
if Run <= NoColumn then
|
|
Exit;
|
|
|
|
TargetRect.Top := Target.Y;
|
|
TargetRect.Bottom := Target.Y + R.Bottom - R.Top;
|
|
TargetRect.Left := Target.X - R.Left + Items[Run].FLeft + RTLOffset;
|
|
// TargetRect.Right will be set in the loop
|
|
|
|
ShowRightBorder := (FHeader.Style = hsThickButtons) or not (hoAutoResize in FHeader.FOptions);
|
|
|
|
// Now go for each button.
|
|
while (Run > NoColumn) and (TargetRect.Left < MaxX) do
|
|
begin
|
|
TargetRect.Right := TargetRect.Left + Items[Run].FWidth;
|
|
|
|
// create a clipping rect to limit painting to button area
|
|
ClipCanvas(TargetCanvas, Rect(Max(TargetRect.Left, Target.X), Target.Y + R.Top,
|
|
Min(TargetRect.Right, MaxX), TargetRect.Bottom));
|
|
|
|
PaintColumnHeader(Run, TargetRect);
|
|
|
|
SelectClipRgn(Handle, 0);
|
|
|
|
TargetRect.Left := TargetRect.Right;
|
|
Run := GetNextVisibleColumn(Run);
|
|
end;
|
|
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(TColumnIndex));
|
|
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);
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
FHeight := FOwner.Scale96ToFont(DEFAULT_HEADER_HEIGHT);
|
|
FDefaultHeight := FOwner.Scale96ToFont(DEFAULT_HEADER_HEIGHT);
|
|
FMinHeight := FOwner.Scale96ToFont(DEFAULT_MIN_HEIGHT);
|
|
{$ELSE}
|
|
FHeight := DEFAULT_HEADER_HEIGHT;
|
|
FDefaultHeight := DEFAULT_HEADER_HEIGHT;
|
|
FMinHeight := 10;
|
|
{$IFEND}
|
|
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);
|
|
var
|
|
I: Integer;
|
|
lMaxHeight: Integer;
|
|
begin
|
|
if toAutoChangeScale in Treeview.TreeOptions.AutoOptions then
|
|
begin
|
|
// Find the largest Columns[].Spacing
|
|
lMaxHeight := 0;
|
|
for I := 0 to Self.Columns.Count - 1 do
|
|
lMaxHeight := Max(lMaxHeight, Columns[I].Spacing);
|
|
// Calculate the required size based on the font, this is important as the use migth just vave increased the size of the icon font
|
|
with TBitmap.Create do
|
|
try
|
|
Canvas.Font.Assign(FFont);
|
|
lMaxHeight := lMaxHeight {top spacing} + (lMaxHeight div 2) {minimum bottom spacing} + Canvas.TextHeight('Q');
|
|
finally
|
|
Free;
|
|
end;
|
|
// Get the maximum of the scaled original value an
|
|
lMaxHeight := Max(lMaxHeight, FHeight);
|
|
// Set the calculated size
|
|
Self.SetHeight(lMaxHeight);
|
|
end;
|
|
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.IsDefaultHeightStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FDefaultHeight <> FOwner.Scale96ToFont(DEFAULT_HEADER_HEIGHT);
|
|
{$ELSE}
|
|
Result := FDefaultHeight <> DEFAULT_HEADER_HEIGHT;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTHeader.IsFontStored: Boolean;
|
|
|
|
begin
|
|
Result := not ParentFont;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTHeader.IsHeightStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FHeight <> FOwner.Scale96ToFont(DEFAULT_HEADER_HEIGHT);
|
|
{$ELSE}
|
|
Result := FHeight <> DEFAULT_HEADER_HEIGHT;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTHeader.IsMinHeightStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FMinHeight <> FOwner.Scale96ToFont(DEFAULT_Min_HEIGHT);
|
|
{$ELSE}
|
|
Result := FMinHeight <> DEFAULT_MIN_HEIGHT;
|
|
{$IFEND}
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
procedure TVTHeader.SetImagesWidth(const Value: Integer);
|
|
begin
|
|
if Value <> FImagesWidth then begin
|
|
FImagesWidth := Value;
|
|
Invalidate(nil);
|
|
end;
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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;
|
|
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) or (hoHeaderClickAutoSort in Options)) and (Treeview.FUpdateCount = 0) then
|
|
Treeview.SortTree(FSortColumn, FSortDirection, True);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTHeader.CanSplitterResize(P: TPoint): Boolean;
|
|
|
|
begin
|
|
Result := hoHeightResize in FOptions;
|
|
DoCanSplitterResize(P, Result);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTHeader.CanWriteColumns: Boolean;
|
|
|
|
// descendants may override this to optionally prevent column writing (e.g. if they are build dynamically).
|
|
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TVTHeader.ChangeScale(M, D: Integer);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
// This method is only executed if toAutoChangeScale is set
|
|
if not ParentFont then
|
|
FFont.Size := MulDiv(FFont.Size, M, D);
|
|
Self.Height := MulDiv(FHeight, M, D);
|
|
// Scale the columns widths too
|
|
for I := 0 to FColumns.Count - 1 do
|
|
begin
|
|
Self.FColumns[I].Width := MulDiv(Self.FColumns[I].Width, M, D);
|
|
end;//for I
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TVTHeader.DoCanSplitterResize(P: TPoint; var Allowed: Boolean);
|
|
begin
|
|
if Assigned(TreeView.FOnCanSplitterResizeHeader) then
|
|
TreeView.FOnCanSplitterResizeHeader(Self, P, Allowed);
|
|
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, {%H-}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) or (hoHeaderClickAutoSort in Options)) 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
|
|
if Treeview.HandleAllocated then
|
|
RescaleHeader
|
|
else
|
|
Include(FStates, hsNeedScaling);
|
|
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;
|
|
NextColumn,
|
|
I: TColumnIndex;
|
|
NewWidth: Integer;
|
|
|
|
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
|
|
begin
|
|
if Treeview.UseRightToLeftAlignment then
|
|
begin
|
|
NewWidth := FTrackPoint.X - XPos;
|
|
NextColumn := FColumns.GetPreviousVisibleColumn(FColumns.FTrackIndex);
|
|
end
|
|
else
|
|
begin
|
|
NewWidth := XPos - FTrackPoint.X;
|
|
NextColumn := FColumns.GetNextVisibleColumn(FColumns.FTrackIndex);
|
|
end;
|
|
|
|
// The autosized column cannot be resized using the mouse normally. Instead we resize the next
|
|
// visible column, so it look as we directly resize the autosized column.
|
|
if (hoAutoResize in FOptions) and (FColumns.FTrackIndex = FAutoSizeIndex) and
|
|
(NextColumn > NoColumn) and (coResizable in FColumns[NextColumn].FOptions) and
|
|
(FColumns[FColumns.FTrackIndex].FMinWidth < NewWidth) and
|
|
(FColumns[FColumns.FTrackIndex].FMaxWidth > NewWidth) then
|
|
FColumns[NextColumn].Width := FColumns[NextColumn].Width - NewWidth
|
|
+ FColumns[FColumns.FTrackIndex].Width
|
|
else
|
|
FColumns[FColumns.FTrackIndex].Width := NewWidth; // 1 EListError seen here (List index out of bounds (-1)) since 10/2013
|
|
end;
|
|
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));
|
|
//lclheader
|
|
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);
|
|
//lclheader
|
|
FOwner.DoHeaderMouseUp(mbMiddle, GetShiftState, P.X, P.Y { + Integer(FHeight)});
|
|
FColumns.FDownIndex := NoColumn;
|
|
FColumns.FCheckBoxHit := False;
|
|
end;
|
|
end;
|
|
LM_LBUTTONDBLCLK,
|
|
LM_MBUTTONDBLCLK,
|
|
LM_RBUTTONDBLCLK:
|
|
begin
|
|
with TLMLButtonDblClk(Message) do
|
|
P := Point(XPos, YPos);
|
|
|
|
IsInHeader := InHeader(P);
|
|
Result := IsInHeader;
|
|
|
|
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, FColumns[FColumns.FTrackIndex].FPosition,
|
|
FColumns[FColumns.FTrackIndex].FPosition);
|
|
Message.Result := 0;
|
|
Result := True;
|
|
end
|
|
else
|
|
if IsInHeader 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;
|
|
if Button = mbLeft then
|
|
Columns.AdjustDownColumn(P);
|
|
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
|
|
|
|
Application.CancelHint;
|
|
|
|
if not (csDesigning in Treeview.ComponentState) then
|
|
begin
|
|
// 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]);
|
|
end;
|
|
|
|
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);
|
|
// in design-time header columns are always resizable
|
|
if (csDesigning in Treeview.ComponentState) then
|
|
IsVSplitterHit := InHeaderSplitterArea(P)
|
|
else
|
|
IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P);
|
|
IsHSplitterHit := HSplitterHit;
|
|
|
|
if IsVSplitterHit or IsHSplitterHit then
|
|
begin
|
|
FTrackStart := P;
|
|
FColumns.FHoverIndex := NoColumn;
|
|
if IsVSplitterHit then
|
|
begin
|
|
if not (csDesigning in Treeview.ComponentState) then
|
|
DoBeforeHeightTracking(GetShiftState);
|
|
Include(FStates, hsHeightTrackPending);
|
|
end
|
|
else
|
|
begin
|
|
if not (csDesigning in Treeview.ComponentState) then
|
|
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);
|
|
// in design-time header columns are always draggable
|
|
if ((csDesigning in Treeview.ComponentState) and (HitIndex > NoColumn)) or
|
|
((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);
|
|
Message.Result := 0;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
// This is a good opportunity to notify the application.
|
|
//lclheader
|
|
if not (csDesigning in Treeview.ComponentState) and 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));
|
|
//lclheader
|
|
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);
|
|
//lclheader
|
|
FOwner.DoHeaderMouseUp(mbRight, GetShiftState, P.X, P.Y { + Integer(FHeight)});
|
|
FColumns.FDownIndex := NoColumn;
|
|
FColumns.FTrackIndex := NoColumn;
|
|
FColumns.FCheckBoxHit := False;
|
|
|
|
Menu := FPopupMenu;
|
|
//lclheader
|
|
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;
|
|
Message.Result := 1;
|
|
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, {%H-}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;
|
|
FCheckBoxHit := False;
|
|
end;
|
|
//Adjust Cursor
|
|
// Feature: design-time header
|
|
if (FStates = []) then
|
|
begin
|
|
//todo: see a way to store the user defined cursor.
|
|
IsHSplitterHit := HSplitterHit;
|
|
// in design-time header columns are always resizable
|
|
if (csDesigning in Treeview.ComponentState) then
|
|
IsVSplitterHit := InHeaderSplitterArea(P)
|
|
else
|
|
IsVSplitterHit := InHeaderSplitterArea(P) and FHeader.CanSplitterResize(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
|
|
Image: TBitmap;
|
|
ImagePos: TPoint;
|
|
DragColumn: TVirtualTreeColumn;
|
|
RTLOffset: Integer;
|
|
|
|
begin
|
|
// Determine initial position of drag image (screen coordinates).
|
|
FColumns.FDropTarget := NoColumn;
|
|
Start := Treeview.ScreenToClient(Start);
|
|
//lclheader
|
|
//Inc(Start.Y, FHeight);
|
|
FColumns.FDragIndex := FColumns.ColumnFromPosition(Start);
|
|
DragColumn := FColumns[FColumns.FDragIndex];
|
|
|
|
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));
|
|
|
|
if TreeView.UseRightToLeftAlignment then
|
|
RTLOffset := Treeview.ComputeRTLOffset
|
|
else
|
|
RTLOffset := 0;
|
|
with DragColumn do
|
|
FColumns.PaintHeader(Canvas, Rect(FLeft, 0, FLeft + Width, Height), Point(-RTLOffset, 0), RTLOffset);
|
|
|
|
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;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImagesWidth := TVTHeader(Source).ImagesWidth;
|
|
{$IFEND}
|
|
|
|
RescaleHeader;
|
|
end
|
|
else
|
|
inherited;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
procedure TVTHeader.AutoAdjustLayout(const AXProportion, AYProportion: Double);
|
|
var
|
|
i: Integer;
|
|
col: TVirtualTreeColumn;
|
|
begin
|
|
if IsDefaultHeightStored then
|
|
FDefaultHeight := Round(FDefaultHeight * AYProportion);
|
|
|
|
if IsHeightStored then
|
|
FHeight := Round(FHeight * AYProportion);
|
|
|
|
if IsMinHeightStored then
|
|
FMinHeight := Round(FMinHeight * AYProportion);
|
|
|
|
if Columns.IsDefaultWidthStored then
|
|
Columns.DefaultWidth := Round(Columns.DefaultWidth * AXProportion);
|
|
|
|
for i := 0 to Columns.Count-1 do begin
|
|
col := Columns[i];
|
|
if col.IsWidthStored then
|
|
col.Width := Round(col.Width * AXProportion);
|
|
if col.IsSpacingStored then
|
|
col.Spacing := Round(col.Spacing * AXProportion);
|
|
if col.IsMarginStored then
|
|
col.Margin := Round(col.Margin * AXProportion);
|
|
end;
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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.StartOperation(okAutoFitColumns);
|
|
try
|
|
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);
|
|
|
|
finally
|
|
Treeview.EndOperation(okAutoFitColumns);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 2010000}
|
|
procedure TVTHeader.FixDesignFontsPPI(const ADesignTimePPI: Integer);
|
|
begin
|
|
TreeView.DoFixDesignFontPPI(Font, ADesignTimePPI);
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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({%H-}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(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: array of Integer = nil;
|
|
Widths: array of Integer = nil;
|
|
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 := Integer(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
|
|
if FScrollBarStyle <> Value then
|
|
begin
|
|
FScrollBarStyle := Value;
|
|
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
|
|
FColors[15] := clHighlightText; // SelectionTextColor
|
|
FColors[16] := clBtnFace; // UnfocusedColor [IPK]
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTColors.GetBackgroundColor: TColor;
|
|
begin
|
|
// XE2 VCL Style
|
|
{$IF CompilerVersion >= 23}
|
|
if FOwner.VclStyleEnabled {$IF CompilerVersion >= 24}and (seClient in FOwner.StyleElements){$IFEND} then
|
|
Result := StyleServices.GetStyleColor(scTreeView)
|
|
else
|
|
{$IFEND}
|
|
Result := FOwner.Brush.Color;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTColors.GetColor(const Index: Integer): TColor;
|
|
|
|
begin
|
|
{$IF CompilerVersion >= 23 }
|
|
if FOwner.VclStyleEnabled then
|
|
begin
|
|
case Index of
|
|
0:
|
|
StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemDisabled), ecTextColor, Result); // DisabledColor
|
|
1:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // DropMarkColor
|
|
2:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // DropTargetColor
|
|
3:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // FocusedSelectionColor
|
|
4:
|
|
Result := StyleServices.GetSystemColor(clBtnFace); // GridLineColor
|
|
5:
|
|
StyleServices.GetElementColor(StyleServices.GetElementDetails(ttBranch), ecBorderColor, Result); // TreeLineColor
|
|
6:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // UnfocusedSelectionColor
|
|
7:
|
|
Result := StyleServices.GetSystemColor(clBtnFace); // BorderColor
|
|
8:
|
|
if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemHot), ecTextColor, Result) or
|
|
(Result <> clWindowText) then
|
|
Result := NodeFontColor; // HotColor
|
|
9:
|
|
StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemSelected), ecFillColor, Result);
|
|
// FocusedSelectionBorderColor
|
|
10:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // UnfocusedSelectionBorderColor
|
|
11:
|
|
Result := StyleServices.GetSystemColor(clBtnFace); // DropTargetBorderColor
|
|
12:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // SelectionRectangleBlendColor
|
|
13:
|
|
Result := StyleServices.GetSystemColor(clHighlight); // SelectionRectangleBorderColor
|
|
14:
|
|
StyleServices.GetElementColor(StyleServices.GetElementDetails(thHeaderItemNormal), ecTextColor, Result); // HeaderHotColor
|
|
15:
|
|
if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemSelected), ecTextColor, Result) or
|
|
(Result <> clWindowText) then
|
|
Result := NodeFontColor; // SelectionTextColor
|
|
end;
|
|
end
|
|
else
|
|
{$IFEND}
|
|
Result := FColors[Index];
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTColors.GetHeaderFontColor: TColor;
|
|
begin
|
|
// XE2+ VCL Style
|
|
{$IF CompilerVersion >= 23}
|
|
if FOwner.VclStyleEnabled {$IF CompilerVersion >= 24}and (seFont in FOwner.StyleElements){$IFEND} then
|
|
StyleServices.GetElementColor(StyleServices.GetElementDetails(thHeaderItemNormal), ecTextColor, Result)
|
|
else
|
|
{$IFEND}
|
|
Result := FOwner.FHeader.Font.Color;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TVTColors.GetNodeFontColor: TColor;
|
|
begin
|
|
{$IF CompilerVersion >= 23}
|
|
if FOwner.VclStyleEnabled {$IF CompilerVersion >= 24}and (seFont in FOwner.StyleElements){$IFEND} then
|
|
StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemNormal), ecTextColor, Result)
|
|
else
|
|
{$IFEND}
|
|
Result := FOwner.Font.Color;
|
|
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: TClipboardFormat;
|
|
RegisteredClass: TVirtualTreeClass;
|
|
|
|
begin
|
|
RegisteredClass := InternalClipboardFormats.FindFormat(S, {%H-}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: TClipboardFormat;
|
|
RegisteredClass: TVirtualTreeClass;
|
|
|
|
begin
|
|
RegisteredClass := InternalClipboardFormats.FindFormat(S, {%H-}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;
|
|
FDragOperations := [doCopy, doMove];
|
|
FHotCursor := crDefault;
|
|
FScrollBarOptions := TScrollBarOptions.Create(Self);
|
|
FFocusedColumn := NoColumn;
|
|
FDragImageKind := diComplete;
|
|
FLastSelectionLevel := -1;
|
|
FSelectionBlendFactor := 128;
|
|
|
|
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
FDefaultNodeHeight := Scale96ToFont(DEFAULT_NODE_HEIGHT);
|
|
FIndent := Scale96ToFont(DEFAULT_INDENT);
|
|
FMargin := Scale96ToFont(DEFAULT_MARGIN);
|
|
FTextMargin := Scale96ToFont(DEFAULT_MARGIN);
|
|
FDragHeight := Scale96ToFont(DEFAULT_DRAG_HEIGHT);
|
|
FDragWidth := Scale96ToFont(DEFAULT_DRAG_WIDTH);
|
|
{$ELSE}
|
|
FDefaultNodeHeight := DEFAULT_NODE_HEIGHT;
|
|
FIndent := DEFAULT_INDENT;
|
|
FMargin := DEFAULT_MARGIN;
|
|
FTextMargin := DEFAULT_MARGIN;
|
|
FDragHeight := DEFAULT_DRAG_HEIGHT;
|
|
FDragWidth := DEFAULT_DRAG_WIDTH;
|
|
{$IFEND}
|
|
|
|
FPlusBM := TBitmap.Create;
|
|
FHotPlusBM := TBitmap.Create;
|
|
FMinusBM := TBitmap.Create;
|
|
FHotMinusBM := TBitmap.Create;
|
|
|
|
FPlusBM.PixelFormat := pf32Bit;
|
|
FHotPlusBM.PixelFormat := pf32Bit;
|
|
FMinusBM.PixelFormat := pf32Bit;
|
|
FHotMinusBM.PixelFormat := pf32Bit;
|
|
|
|
BorderStyle := bsSingle;
|
|
FButtonStyle := bsRectangle;
|
|
FButtonFillMode := fmTreeColor;
|
|
|
|
FHeader := GetHeaderClass.Create(Self);
|
|
|
|
// we have an own double buffer handling
|
|
inherited DoubleBuffered := False;
|
|
|
|
FCheckImageKind := ckSystemDefault;
|
|
FCheckImages := SystemCheckImages;
|
|
|
|
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;
|
|
FLastDragEffect := DROPEFFECT_NONE;
|
|
FDragType := dtOLE;
|
|
|
|
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}
|
|
|
|
//FVclStyleEnabled := False;
|
|
// XE2+ VCL Style
|
|
{$ifdef VCLStyleSupport}
|
|
FSetOrRestoreBevelKindAndBevelWidth := False;
|
|
FSavedBevelKind := bkNone;
|
|
FSavedBorderWidth := 0;
|
|
{$ifend}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
destructor TBaseVirtualTree.Destroy;
|
|
|
|
begin
|
|
InterruptValidation();
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Release FDottedBrush in case WM_NCDESTROY hasn't been triggered.
|
|
if FDottedBrush <> 0 then
|
|
DeleteObject(FDottedBrush);
|
|
FDottedBrush := 0;
|
|
|
|
FOptions.Free;
|
|
FreeAndNil(FHeader);
|
|
|
|
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.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 := 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 := 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;
|
|
|
|
UpdateVerticalRange;
|
|
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 := Integer(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 := GetNodeImageSize(Node).cy
|
|
else
|
|
VAlign := GetRealStateImagesHeight;
|
|
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(Integer(Node.NodeHeight), Node.Align, 100);
|
|
end;
|
|
|
|
VButtonAlign := VAlign - FPlusBM.Height div 2 - (FPlusBM.Height and 1);
|
|
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);
|
|
try
|
|
if not (vsInitialized in States) then
|
|
InitNode(Node)
|
|
else if CheckState = Value then
|
|
begin
|
|
// Value didn't change and node was initialized, so nothing to do
|
|
Result := False;
|
|
Exit;
|
|
end;//if
|
|
|
|
// 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
|
|
Value := csCheckedNormal;
|
|
// 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);
|
|
|
|
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]);
|
|
finally
|
|
Exclude(States, vsChecking);
|
|
end;
|
|
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;
|
|
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);
|
|
WithStateImages := Assigned(FStateImages);
|
|
if WithStateImages then
|
|
StateImageOffset := GetRealStateImagesWidth + 2
|
|
else
|
|
StateImageOffset := 0;
|
|
if WithCheck then
|
|
CheckOffset := GetRealCheckImagesWidth + 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, {%H-}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, GetNodeImageSize(Run).cx + 2);
|
|
if WithStateImages and HasImage(Run, ikState, MainColumn) then
|
|
Inc(TextLeft, StateImageOffset);
|
|
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 or (toFullRowSelect in FOptions.FSelectionOptions) then
|
|
begin
|
|
IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom) and
|
|
((FHeader.Columns.Count = 0) or (FHeader.Columns.TotalWidth > OldRect.Left)) and (NodeLeft < OldRect.Right);
|
|
IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom) and
|
|
((FHeader.Columns.Count = 0) or (FHeader.Columns.TotalWidth > NewRect.Left)) and (NodeLeft < NewRect.Right);
|
|
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;
|
|
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);
|
|
WithStateImages := Assigned(FStateImages);
|
|
if WithStateImages then
|
|
StateImageOffset := GetRealStateImagesWidth + 2
|
|
else
|
|
StateImageOffset := 0;
|
|
if WithCheck then
|
|
CheckOffset := GetRealCheckImagesWidth + 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, {%H-}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, GetNodeImageSize(Run).cx + 2);
|
|
if WithStateImages and HasImage(Run, ikState, MainColumn) then
|
|
Dec(TextRight, StateImageOffset);
|
|
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}
|
|
BackColor := FColors.BackGroundColor;
|
|
with PaintInfo do
|
|
begin
|
|
EraseAction := eaDefault;
|
|
|
|
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 := BackColor;
|
|
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
|
|
(tsUseExplorerTheme in FStates) then
|
|
begin
|
|
{$ifdef DEBUG_VTV}Logger.Send([lcPaintDetails, lcDrag], 'Draw the background of a selected node');{$endif}
|
|
if toShowHorzGridLines in FOptions.PaintOptions then
|
|
begin
|
|
Brush.Color := BackColor;
|
|
FillRect(Rect(R.Left, R.Bottom - 1, R.Right, R.Bottom));
|
|
Dec(R.Bottom);
|
|
end;
|
|
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 TWithSafeRect(R) do
|
|
RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius);
|
|
end
|
|
else
|
|
begin
|
|
//lcl
|
|
//see note above
|
|
{
|
|
Brush.Color := BackColor;
|
|
FillRect(R);
|
|
}
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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 := Round(FIndent / 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.GetFiltered(Node: PVirtualNode): Boolean;
|
|
|
|
begin
|
|
Result := vsFiltered in Node.States;
|
|
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) and not (vsDeleting in Node.States) then
|
|
begin
|
|
if not (vsInitialized in Node.States) then
|
|
InitNode(Node);
|
|
|
|
// Ensure the node's height is determined.
|
|
MeasureItemHeight(Self.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.GetRangeX: Cardinal;
|
|
begin
|
|
Result := Max(0, FRangeX);
|
|
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);
|
|
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;
|
|
Invalidate();
|
|
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 := IsEffectivelyVisible[Node];
|
|
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 := IsEffectivelyVisible[Node];
|
|
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;
|
|
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
|
|
InvalidateCache();
|
|
end;
|
|
{$endif}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsDefaultNodeHeightStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FDefaultNodeHeight <> Scale96ToFont(DEFAULT_NODE_HEIGHT);
|
|
{$ELSE}
|
|
Result := FDefaultNodeHeight <> DEFAULT_NODE_HEIGHT;
|
|
{$IFEND}
|
|
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 IsEffectivelyVisible[Run] 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 IsEffectivelyVisible[Run] do
|
|
Run := Run.PrevSibling;
|
|
|
|
Result := Assigned(Run) and (Run = Node);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsDragHeightStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FDragHeight <> Scale96ToFont(DEFAULT_DRAG_HEIGHT);
|
|
{$ELSE}
|
|
Result := FDragHeight <> DEFAULT_DRAG_HEIGHT;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsDragWidthStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FDragWidth <> Scale96ToFont(DEFAULT_DRAG_WIDTH);
|
|
{$ELSE}
|
|
Result := FDragWidth <> DEFAULT_DRAG_WIDTH;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsIndentStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FIndent <> Scale96ToFont(DEFAULT_INDENT);
|
|
{$ELSE}
|
|
Result := FIndent <> DEFAULT_INDENT;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsMarginStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FMargin <> Scale96ToFont(DEFAULT_MARGIN);
|
|
{$ELSE}
|
|
Result := FMargin <> DEFAULT_MARGIN;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsSelectionCurveRadiusStored: Boolean;
|
|
begin
|
|
Result := FSelectionCurveRadius <> 0;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsTextMarginStored: Boolean;
|
|
begin
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
Result := FTextMargin <> Scale96ToFont(DEFAULT_MARGIN);
|
|
{$ELSE}
|
|
Result := FTextMargin <> DEFAULT_MARGIN;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.LoadPanningCursors;
|
|
|
|
var
|
|
TheInstance: TLCLHandle;
|
|
|
|
begin
|
|
TheInstance := HINSTANCE;
|
|
with Screen do
|
|
begin
|
|
Cursors[crVT_MOVEALL]:=LoadCursor(TheInstance, 'VT_MOVEALL');
|
|
Cursors[crVT_MOVEEW]:=LoadCursor(TheInstance, 'VT_MOVEEW');
|
|
Cursors[crVT_MOVENS]:=LoadCursor(TheInstance, 'VT_MOVENS');
|
|
Cursors[crVT_MOVENW]:=LoadCursor(TheInstance, 'VT_MOVENW');
|
|
Cursors[crVT_MOVESW]:=LoadCursor(TheInstance, 'VT_MOVESW');
|
|
Cursors[crVT_MOVESE]:=LoadCursor(TheInstance, 'VT_MOVESE');
|
|
Cursors[crVT_MOVENE]:=LoadCursor(TheInstance, 'VT_MOVENE');
|
|
Cursors[crVT_MOVEW]:=LoadCursor(TheInstance, 'VT_MOVEW');
|
|
Cursors[crVT_MOVEE]:=LoadCursor(TheInstance, 'VT_MOVEE');
|
|
Cursors[crVT_MOVEN]:=LoadCursor(TheInstance, 'VT_MOVEN');
|
|
Cursors[crVT_MOVES]:=LoadCursor(TheInstance, '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: NativeInt;
|
|
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^))} (NativeInt(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^))} NativeInt(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 ESI, EDX
|
|
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
|
|
p9, p8, p6, p4, p2, p1: Integer;
|
|
PatternBitmap: HBITMAP;
|
|
Bits: Pointer;
|
|
Size: TSize;
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef Windows}
|
|
Theme: HTHEME;
|
|
{$endif}
|
|
{$EndIf ThemeSupport}
|
|
R: TRect;
|
|
|
|
//--------------- local functions -------------------------------------------
|
|
|
|
procedure FillBitmap (ABitmap: TBitmap);
|
|
begin
|
|
with ABitmap, Canvas do
|
|
begin
|
|
ABitmap.SetSize(Size.cx, Size.cy);
|
|
|
|
if IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.FPaintOptions) or VclStyleEnabled then
|
|
begin
|
|
if (FHeader.MainColumn > NoColumn) and not (coParentColor in FHeader.FColumns[FHeader.MainColumn].Options) then
|
|
Brush.Color := FHeader.FColumns[FHeader.MainColumn].Color
|
|
else
|
|
Brush.Color := FColors.BackGroundColor;
|
|
end
|
|
else
|
|
Brush.Color := clFuchsia;
|
|
MaskHandle := 0;
|
|
Transparent := True;
|
|
TransparentColor := Brush.Color;
|
|
|
|
FillRect(Rect(0, 0, ABitmap.Width, ABitmap.Height));
|
|
end;
|
|
end;
|
|
|
|
procedure PaintButtonBitmap(ABitmap: TBitmap; BtnStyle: TVTButtonStyle; IsPlus: Boolean);
|
|
var
|
|
img: TLazIntfImage;
|
|
canv: TLazCanvas;
|
|
m, c: Integer;
|
|
begin
|
|
img := ABitmap.CreateIntfImage;
|
|
canv := TLazCanvas.Create(img);
|
|
try
|
|
img.FillPixels(colTransparent);
|
|
c := Img.Width div 2;
|
|
case BtnStyle of
|
|
bsRectangle:
|
|
begin
|
|
if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then
|
|
begin
|
|
case FButtonFillMode of
|
|
fmTreeColor:
|
|
canv.Brush.FPColor := TColorToFPColor(ColorToRGB(FColors.BackGroundColor));
|
|
fmWindowColor:
|
|
canv.Brush.FPColor := TColorToFPColor(ColorToRGB(clWindow));
|
|
fmTransparent:
|
|
canv.Brush.Style := bsClear;
|
|
end;
|
|
canv.Pen.FPColor := TColorToFPColor(ColorToRGB(FColors.TreeLineColor)); //clWindowText));
|
|
m := c div 2;
|
|
if m = 0 then m := 1;
|
|
canv.Rectangle(0, 0, img.Width, img.Height);
|
|
canv.Pen.FPColor := TColorToFPColor(ColorToRGB(clWindowText));
|
|
canv.Line(c-m, c, c+m, c);
|
|
if IsPlus then
|
|
canv.Line(c, c-m, c, c+m);
|
|
end else
|
|
begin
|
|
if IsPlus then
|
|
LoadBitmapFromResource(FMinusBM, 'laz_vt_xpbuttonplus')
|
|
else
|
|
LoadBitmapFromResource(FMinusBM, 'laz_vt_xpbuttonminus');
|
|
end;
|
|
end;
|
|
|
|
bsTriangle:
|
|
begin
|
|
canv.Brush.FPColor := TColorToFPColor(ColorToRGB(clWindowText));
|
|
canv.Pen.FPColor := canv.Brush.FPColor;
|
|
if IsPlus then
|
|
begin
|
|
m := Img.Width * 7 div 10;
|
|
if BiDiMode = bdLeftToRight then
|
|
canv.Polygon([Point(0, 0), Point(0, Img.Height-1), Point(m, c)])
|
|
else
|
|
canv.Polygon([Point(Img.Width-1-m, c), Point(Img.Width-1, Img.Height-1), Point(Img.Width-1, 0)]);
|
|
end else
|
|
begin
|
|
m := Img.Width * 7 div 20;
|
|
if BiDiMode = bdLeftToRight then
|
|
canv.Polygon([Point(c-m, c+m), Point(c+m, c+m), Point(c+m, c-m)])
|
|
else
|
|
canv.Polygon([Point(c-m, c-m), Point(c-m, c+m), Point(c+m, c+m)]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
ABitmap.LoadFromIntfImage(img);
|
|
finally
|
|
canv.Free;
|
|
img.Free;
|
|
end;
|
|
end;
|
|
|
|
//--------------- end local function ----------------------------------------
|
|
|
|
begin
|
|
Size.cx := Scale96ToFont(9);
|
|
if not odd(Size.cx) then dec(Size.cx);
|
|
Size.cy := Size.cx;
|
|
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef LCLWin}
|
|
if tsUseThemes in FStates then
|
|
begin
|
|
{for direct OS calls we need to implement GetNativeHandle():TLCLHandle in widgetset
|
|
because eg window handle in lclqt is TQtWidget(Handle).Widget, not Handle
|
|
by itself.}
|
|
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}
|
|
{$endif ThemeSupport}
|
|
|
|
if NeedButtons then
|
|
begin
|
|
// box is always of odd size
|
|
FillBitmap(FMinusBM);
|
|
FillBitmap(FHotMinusBM);
|
|
if (not VclStyleEnabled) {or (Theme = 0)} then
|
|
begin
|
|
if not(tsUseExplorerTheme in FStates) then
|
|
begin
|
|
PaintButtonBitmap(FMinusBM, FButtonStyle, false);
|
|
FHotMinusBM.Canvas.Draw(0, 0, FMinusBM);
|
|
end;
|
|
end;
|
|
|
|
FillBitmap(FPlusBM);
|
|
FillBitmap(FHotPlusBM);
|
|
if (not VclStyleEnabled) {or (Theme = 0)} then
|
|
begin
|
|
if not(tsUseExplorerTheme in FStates) then
|
|
begin
|
|
PaintButtonBitmap(FPlusBM, FButtonstyle, true);
|
|
FHotPlusBM.Canvas.Draw(0, 0, FPlusBM);
|
|
end;
|
|
end;
|
|
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef LCLWin}
|
|
// 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 tsUseExplorerTheme in FStates 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}
|
|
{$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}
|
|
{$ifdef LCLWin}
|
|
if tsUseThemes in FStates then
|
|
CloseThemeData(Theme);
|
|
{$endif}
|
|
{$endif ThemeSupport}
|
|
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;
|
|
FCheckImages := GetCheckImageListFor(Value);
|
|
if not Assigned(FCheckImages) then
|
|
FCheckImages := FCustomCheckImages;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
FCheckImagesWidth := 0;
|
|
if FCheckImages = FCustomCheckImages then
|
|
FCheckImagesWidth := FCustomCheckImagesWidth;
|
|
{$IFEND}
|
|
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;
|
|
if (Value <> ctTriStateCheckBox) and (Node.CheckState in [csMixedNormal, csMixedPressed]) then
|
|
Node.CheckState := csUncheckedNormal;// reset check state if it doesn't fit the new check type
|
|
// 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;
|
|
lNodeHeight: 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);
|
|
|
|
if (toVariableNodeHeight in FOptions.FMiscOptions) then
|
|
begin
|
|
lNodeHeight := Child.NodeHeight;
|
|
DoMeasureItem(Canvas, Child, lNodeHeight);
|
|
Child.NodeHeight := lNodeHeight;
|
|
Child.TotalHeight := lNodeHeight;
|
|
end;
|
|
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: TCustomImageList);
|
|
|
|
begin
|
|
if FCustomCheckImages <> Value then
|
|
begin
|
|
if Assigned(FCustomCheckImages) then
|
|
begin
|
|
FCustomCheckImages.UnRegisterChanges(FCustomCheckChangeLink);
|
|
FCustomCheckImages.RemoveFreeNotification(Self);
|
|
// Reset the internal check image list reference too, if necessary.
|
|
if FCheckImages = FCustomCheckImages then
|
|
FCheckImages := nil;
|
|
end;
|
|
FCustomCheckImages := Value;
|
|
if Assigned(FCustomCheckImages) then
|
|
begin
|
|
FCustomCheckImages.RegisterChanges(FCustomCheckChangeLink);
|
|
FCustomCheckImages.FreeNotification(Self);
|
|
end;
|
|
// 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
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Value := Scale96ToFont(DEFAULT_NODE_HEIGHT);
|
|
{$ELSE}
|
|
Value := DEFAULT_NODE_HEIGHT;
|
|
{$IFEND}
|
|
if FDefaultNodeHeight <> Value then
|
|
begin
|
|
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.SetDoubleBuffered(const Value: Boolean);
|
|
begin
|
|
// empty by intention, we do our own buffering
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetDoubleBuffered: Boolean;
|
|
begin
|
|
Result := True; // we do our own buffering
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.SetEmptyListMessage(const Value: String);
|
|
|
|
begin
|
|
if Value <> EmptyListMessage then
|
|
begin
|
|
FEmptyListMessage := Value;
|
|
Invalidate;
|
|
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.SetFiltered(Node: PVirtualNode; Value: Boolean);
|
|
|
|
// Sets the 'filtered' flag of the given node according to Value and updates all dependent states.
|
|
|
|
var
|
|
NeedUpdate: Boolean;
|
|
|
|
begin
|
|
Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.');
|
|
|
|
// Initialize the node if necessary as this might change the filtered state.
|
|
if not (vsInitialized in Node.States) then
|
|
InitNode(Node);
|
|
|
|
if Value <> (vsFiltered in Node.States) then
|
|
begin
|
|
InterruptValidation;
|
|
NeedUpdate := False;
|
|
if Value then
|
|
begin
|
|
Include(Node.States, vsFiltered);
|
|
if not (toShowFilteredNodes in FOptions.FPaintOptions) then
|
|
begin
|
|
AdjustTotalHeight(Node, -Integer(NodeHeight[Node]), True);
|
|
if FullyVisible[Node] then
|
|
begin
|
|
Dec(FVisibleCount);
|
|
NeedUpdate := True;
|
|
end;
|
|
end;
|
|
|
|
if FUpdateCount = 0 then
|
|
DetermineHiddenChildrenFlag(Node.Parent)
|
|
else
|
|
Include(FStates, tsUpdateHiddenChildrenNeeded);
|
|
end
|
|
else
|
|
begin
|
|
Exclude(Node.States, vsFiltered);
|
|
if not (toShowFilteredNodes in FOptions.FPaintOptions) then
|
|
begin
|
|
AdjustTotalHeight(Node, Integer(NodeHeight[Node]), True);
|
|
if FullyVisible[Node] then
|
|
begin
|
|
Inc(FVisibleCount);
|
|
NeedUpdate := True;
|
|
end;
|
|
end;
|
|
|
|
if vsVisible in Node.States then
|
|
// 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;
|
|
|
|
InvalidateCache;
|
|
if NeedUpdate and (FUpdateCount = 0) then
|
|
begin
|
|
ValidateCache;
|
|
UpdateScrollBars(True);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
procedure TBaseVirtualTree.SetImagesWidth(const Value: Integer);
|
|
begin
|
|
if Value <> FImagesWidth then begin
|
|
FImagesWidth := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TBaseVirtualTree.SetStateImagesWidth(const Value: Integer);
|
|
begin
|
|
if Value <> FStateImagesWidth then begin
|
|
FStateImagesWidth := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TBaseVirtualTree.SetCustomCheckImagesWidth(const Value: Integer);
|
|
begin
|
|
if Value <> FCustomCheckImagesWidth then begin
|
|
FCustomCheckImagesWidth := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
// If the node is effectively filtered out, nothing else has to be done, as it is not visible anyway.
|
|
if not IsEffectivelyFiltered[Node] then
|
|
begin
|
|
AdjustTotalHeight(Node, Difference, True);
|
|
|
|
// If an edit operation is currently active then update the editors boundaries as well.
|
|
UpdateEditBounds;
|
|
|
|
InvalidateCache;
|
|
// Stay away from touching the node cache while it is being validated.
|
|
if not (tsValidating in FStates) and FullyVisible[Node] and not IsEffectivelyFiltered[Node] then
|
|
begin
|
|
if (FUpdateCount = 0) and ([tsPainting, tsSizing] * FStates = []) then
|
|
begin
|
|
ValidateCache;
|
|
InvalidateToBottom(Node);
|
|
UpdateScrollBars(True);
|
|
end;
|
|
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] and not IsEffectivelyFiltered[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] and not IsEffectivelyFiltered[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, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1)));
|
|
NeedUpdate := True;
|
|
end;
|
|
|
|
// Update the hidden children flag of the parent.
|
|
// Since this node is now visible we simply have to remove the flag.
|
|
if not IsEffectivelyFiltered[Node] then
|
|
Exclude(Node.Parent.States, vsAllChildrenHidden);
|
|
end
|
|
else
|
|
begin
|
|
if vsExpanded in Node.Parent.States then
|
|
AdjustTotalHeight(Node.Parent, -Integer(Node.TotalHeight), True);
|
|
if VisiblePath[Node] then
|
|
begin
|
|
Dec(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1)));
|
|
NeedUpdate := True;
|
|
end;
|
|
Exclude(Node.States, vsVisible);
|
|
|
|
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 OffsetPosition: 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(OffsetPosition.X + R.Left, OffsetPosition.Y + R.Top, OffsetPosition.X + R.Right, OffsetPosition.Y + R.Bottom);
|
|
|
|
// If picture falls in AreaRect, return intersection (DrawRect).
|
|
if IntersectRect({%H-}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).
|
|
MaskBlt(Target.Handle, DrawRect.Left - OffsetPosition.X, DrawRect.Top - OffsetPosition.Y, (DrawRect.Right - OffsetPosition.X) - (DrawRect.Left - OffsetPosition.X),
|
|
(DrawRect.Bottom - OffsetPosition.Y) - (DrawRect.Top - OffsetPosition.Y), Source.Canvas.Handle, DrawRect.Left - PicRect.Left, DrawRect.Top - PicRect.Top,
|
|
Source.MaskHandle, DrawRect.Left - PicRect.Left, DrawRect.Top - PicRect.Top, MakeROP4(DST, SRCCOPY));
|
|
end
|
|
else
|
|
begin
|
|
// copy image to destination
|
|
BitBlt(Target.Handle, DrawRect.Left - OffsetPosition.X, DrawRect.Top - OffsetPosition.Y, (DrawRect.Right - OffsetPosition.X) - (DrawRect.Left - OffsetPosition.X),
|
|
(DrawRect.Bottom - OffsetPosition.Y) - (DrawRect.Top - OffsetPosition.Y) + R.Top, Source.Canvas.Handle, DrawRect.Left - PicRect.Left, DrawRect.Top - PicRect.Top,
|
|
SRCCOPY);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.SetWindowTheme(const Theme: String);
|
|
|
|
begin
|
|
FChangingTheme := True;
|
|
|
|
{$ifdef Windows}
|
|
UxTheme.SetWindowTheme(Handle, PWideChar(Theme), nil);
|
|
{$endif}
|
|
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
|
|
if VclStyleEnabled then
|
|
LocalBrush := CreateSolidBrush(ColorToRGB(FColors.BackGroundColor))
|
|
else
|
|
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;
|
|
|
|
// TODO: Compilerversion Ein/Ausschalten < Ist Eingeschaltet >
|
|
{$ifdef VCLStyleSupport}
|
|
procedure TBaseVirtualTree.CMBorderChanged(var Message: TMessage);
|
|
begin
|
|
inherited;
|
|
// For XE2+ themes
|
|
if not FSetOrRestoreBevelKindAndBevelWidth then
|
|
begin
|
|
FSavedBevelKind := BevelKind;
|
|
FSavedBorderWidth := BorderWidth;
|
|
end;
|
|
end;
|
|
|
|
procedure TBaseVirtualTree.CMStyleChanged(var Message: TMessage);
|
|
begin
|
|
VclStyleChanged;
|
|
RecreateWnd;
|
|
end;
|
|
|
|
procedure TBaseVirtualTree.CMParentDoubleBufferedChange(var Message: TMessage);
|
|
begin
|
|
// empty by intention, we do our own buffering
|
|
end;
|
|
|
|
{$ifend}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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([tsVCLDragFinished], [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
|
|
begin
|
|
AutoScale();
|
|
PrepareBitmaps(True, False);
|
|
if HandleAllocated then
|
|
Invalidate;
|
|
end;
|
|
|
|
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;
|
|
DummyLineBreakStyle: 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, {%H-}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
|
|
// An owner-draw tree should only display a hint when at least
|
|
// its OnGetHintSize event handler is assigned.
|
|
DoGetHintKind(HitInfo.HitNode, HitInfo.HitColumn, FHintData.Kind);
|
|
FHintData.HintRect := Rect(0, 0, 0, 0);
|
|
if (FHintData.Kind = vhkOwnerDraw) then
|
|
begin
|
|
DoGetHintSize(HitInfo.HitNode, HitInfo.HitColumn, FHintData.HintRect);
|
|
ShowOwnHint := not IsRectEmpty(FHintData.HintRect);
|
|
end
|
|
else
|
|
// For trees displaying text hints, 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
|
|
DummyLineBreakStyle := hlbDefault;
|
|
FLastHintRect := CursorRect;
|
|
if Length(FHintData.DefaultHint) > 0 then
|
|
HintStr := FHintData.DefaultHint
|
|
else
|
|
if FHintMode = hmToolTip then
|
|
HintStr := DoGetNodeToolTip(HitInfo.HitNode, HitInfo.HitColumn, DummyLineBreakStyle)
|
|
else
|
|
HintStr := DoGetNodeHint(HitInfo.HitNode, HitInfo.HitColumn, DummyLineBreakStyle);
|
|
// Determine actual line break style depending on what was returned by the methods and what's in the node.
|
|
if (DummyLineBreakStyle = hlbDefault) and Assigned(HitInfo.HitNode)
|
|
and (vsMultiline in HitInfo.HitNode.States) then
|
|
DummyLineBreakStyle := hlbForceMultiLine;
|
|
if DummyLineBreakStyle = 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.CMMouseEnter(var Message: TLMessage);
|
|
begin
|
|
DoMouseEnter();
|
|
inherited;
|
|
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;
|
|
|
|
if Assigned(Header) then
|
|
begin
|
|
Header.FColumns.FDownIndex := NoColumn;
|
|
Header.FColumns.FHoverIndex := NoColumn;
|
|
Header.FColumns.FCheckBoxHit := False;
|
|
end;
|
|
DoMouseLeave();
|
|
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;
|
|
|
|
// Mouse stays in same position, so reset area which the mouse must leave to reshow a hint
|
|
if ShowHint and (ScrollAmount <> 0) then
|
|
FLastHintRect := Rect(0, 0, 0, 0);
|
|
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 := LRESULT(FFocusedNode);
|
|
TVGN_CHILD:
|
|
if Assigned(Node) then
|
|
Message.Result := LRESULT(GetFirstChild(Node));
|
|
TVGN_DROPHILITE:
|
|
Message.Result := LRESULT(FDropTargetNode);
|
|
TVGN_FIRSTVISIBLE:
|
|
Message.Result := LRESULT(GetFirstVisible(nil, True));
|
|
TVGN_LASTVISIBLE:
|
|
Message.Result := LRESULT(GetLastVisible(nil, True));
|
|
TVGN_NEXT:
|
|
if Assigned(Node) then
|
|
Message.Result := LRESULT(GetNextSibling(Node));
|
|
TVGN_NEXTVISIBLE:
|
|
if Assigned(Node) then
|
|
Message.Result := LRESULT(GetNextVisible(Node, True));
|
|
TVGN_PARENT:
|
|
if Assigned(Node) and (Node <> FRoot) and (Node.Parent <> FRoot) then
|
|
Message.Result := LRESULT(Node.Parent);
|
|
TVGN_PREVIOUS:
|
|
if Assigned(Node) then
|
|
Message.Result := LRESULT(GetPreviousSibling(Node));
|
|
TVGN_PREVIOUSVISIBLE:
|
|
if Assigned(Node) then
|
|
Message.Result := LRESULT(GetPreviousVisible(Node, True));
|
|
TVGN_ROOT:
|
|
Message.Result := LRESULT(GetFirst);
|
|
end;
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.WMCancelMode(var Message: TLMessage);
|
|
|
|
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);
|
|
KillTimer(Handle, ThemeChangedTimer);
|
|
FSearchBuffer := '';
|
|
FLastSearchNode := nil;
|
|
|
|
DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending, tsDrawSelecting,
|
|
tsDrawSelPending, tsIncrementalSearching]);
|
|
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
|
|
{%H-}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;
|
|
GetScrollInfo(Handle, Code, SI);
|
|
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 AnsiChar;
|
|
|
|
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]) and (RootNode.FirstChild <> nil) 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 Shift = [ssCtrl, ssShift] then
|
|
SetOffsetX(FOffsetX + ClientWidth)
|
|
else
|
|
if [ssShift, ssAlt] = Shift then
|
|
begin
|
|
if FFocusedColumn <= NoColumn then
|
|
NewColumn := FHeader.FColumns.GetFirstVisibleColumn
|
|
else
|
|
begin
|
|
Offset := FHeader.FColumns.GetVisibleFixedWidth;
|
|
NewColumn := FFocusedColumn;
|
|
while True do
|
|
begin
|
|
TempColumn := FHeader.FColumns.GetPreviousVisibleColumn(NewColumn);
|
|
NewWidth := FHeader.FColumns[NewColumn].Width;
|
|
if (TempColumn <= NoColumn) or
|
|
(Offset + NewWidth >= ClientWidth) or
|
|
(coFixed in FHeader.FColumns[TempColumn].FOptions) then
|
|
Break;
|
|
NewColumn := TempColumn;
|
|
Inc(Offset, NewWidth);
|
|
end;
|
|
end;
|
|
SetFocusedColumn(NewColumn);
|
|
end
|
|
else
|
|
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 Shift = [ssCtrl, ssShift] then
|
|
SetOffsetX(FOffsetX - ClientWidth)
|
|
else
|
|
if [ssShift, ssAlt] = Shift then
|
|
begin
|
|
if FFocusedColumn <= NoColumn then
|
|
NewColumn := FHeader.FColumns.GetFirstVisibleColumn
|
|
else
|
|
begin
|
|
Offset := FHeader.FColumns.GetVisibleFixedWidth;
|
|
NewColumn := FFocusedColumn;
|
|
while True do
|
|
begin
|
|
TempColumn := FHeader.FColumns.GetNextVisibleColumn(NewColumn);
|
|
NewWidth := FHeader.FColumns[NewColumn].Width;
|
|
if (TempColumn <= NoColumn) or
|
|
(Offset + NewWidth >= ClientWidth) or
|
|
(coFixed in FHeader.FColumns[TempColumn].FOptions) then
|
|
Break;
|
|
NewColumn := TempColumn;
|
|
Inc(Offset, NewWidth);
|
|
end;
|
|
end;
|
|
SetFocusedColumn(NewColumn);
|
|
end
|
|
else
|
|
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;
|
|
|
|
if Assigned(FFocusedNode) then
|
|
begin
|
|
// 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 (LastFocused <> FFocusedNode) or ForceSelection then
|
|
AddToSelection(FFocusedNode);
|
|
end;
|
|
|
|
// 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, PWord(@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 https://web.archive.org/web/20041129085958/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, PWord(@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 not (toReverseFullExpandHotKey in TreeOptions.MiscOptions) and (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 not (toReverseFullExpandHotKey in TreeOptions.MiscOptions) and (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
|
|
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, IfThen(FFocusedColumn > NoColumn, FFocusedColumn, 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
|
|
else
|
|
DoPopupMenu(nil, FFocusedColumn, Point(-1, -1));
|
|
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] = []) or ((Shift * [ssCtrlOS, ssAlt] = [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;
|
|
VK_TAB:
|
|
EnsureNodeFocused(); // Always select a node if the control gets the focus via TAB key, #237
|
|
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.
|
|
if tsHint in Self.FStates then
|
|
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, {%H-}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, {%H-}HitInfo);
|
|
{$ifdef DEBUG_VTV}
|
|
if HitInfo.HitNode <> nil then
|
|
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, {%H-}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, {%H-}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, {%H-}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, {%H-}HitInfo);
|
|
HandleMouseUp(Message.Keys, {%H-}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 (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) 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;
|
|
if ((tsUseThemes in FStates) or VclStyleEnabled){$IF CompilerVersion >= 24} and (seBorder in StyleElements) {$IFEND} then
|
|
StyleServices.PaintBorder(Self, False);
|
|
{$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}
|
|
|
|
if csPaintCopy in ControlState then
|
|
FUpdateRect := ClientRect
|
|
else
|
|
FUpdateRect := Message.PaintStruct^.rcPaint;
|
|
|
|
{$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, {%H-}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, {%H-}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, {%H-}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;
|
|
|
|
if StyleServices.Enabled and (toThemeAware in TreeOptions.PaintOptions) then
|
|
DoStateChange([tsUseThemes])
|
|
else
|
|
DoStateChange([], [tsUseThemes]);
|
|
|
|
// Updating the visuals here will not work correctly. Therefore we postpone
|
|
// the update by using a timer.
|
|
if not FChangingTheme then
|
|
SetTimer(Handle, ThemeChangedTimer, ThemeChangedTimerDelay, nil);
|
|
FChangingTheme := False;
|
|
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;
|
|
ThemeChangedTimer:
|
|
begin
|
|
KillTimer(Handle, ThemeChangedTimer);
|
|
RecreateWnd(Self);
|
|
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;
|
|
GetScrollInfo(Handle, Code, SI);
|
|
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.AdjustImageBorder(Images: TCustomImageList; 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.
|
|
|
|
var
|
|
W, H: Integer;
|
|
begin
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
with Images.ResolutionForPPI[GetImagesWidth(Images), Font.PixelsPerInch, GetCanvasScaleFactor] do
|
|
{$ELSE}
|
|
with Images do
|
|
{$IFEND}
|
|
begin
|
|
W := Width;
|
|
H := Height;
|
|
end;
|
|
AdjustImageBorder(W, H, BidiMode, VAlign, R, ImageInfo);
|
|
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 - ImageWidth;
|
|
Dec(R.Right, ImageWidth + 2);
|
|
end;
|
|
ImageInfo.YPos := R.Top + VAlign - ImageHeight div 2;
|
|
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 + (SizeOf(Pointer) - 1)) and not (SizeOf(Pointer) - 1));
|
|
{$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.StartOperation(OperationKind: TVTOperationKind);
|
|
|
|
// Called to indicate that a long-running operation has been started.
|
|
|
|
begin
|
|
Inc(FOperationCount);
|
|
DoStartOperation(OperationKind);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.CanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
|
|
begin
|
|
Result := (toNodeHeightResize in FOptions.FMiscOptions) and Assigned(Node) and (Node <> FRoot) and
|
|
(Column > NoColumn) and (coFixed in FHeader.FColumns[Column].FOptions);
|
|
DoCanSplitterResizeNode(P, Node, Column, Result);
|
|
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);
|
|
|
|
begin
|
|
inherited;
|
|
|
|
if (M <> D) and (toAutoChangeScale in FOptions.FAutoOptions) then
|
|
begin
|
|
SetDefaultNodeHeight(MulDiv(FDefaultNodeHeight, M, D));
|
|
FHeader.ChangeScale(M, D);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.ChangeTreeStatesAsync(EnterStates, LeaveStates: TChangeStates);
|
|
|
|
begin
|
|
if (Self.HandleAllocated) then
|
|
SendMessage(Self.Handle, WM_CHANGESTATE, Byte(EnterStates), Byte(LeaveStates));
|
|
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.
|
|
// This is already done in the function DoCheckClick() called in the above line
|
|
// We revent unnecessary upward recursion by commenting this code.
|
|
// 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) + Cardinal(IfThen(IsEffectivelyVisible[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]);
|
|
|
|
if (StyleServices.Enabled and (toThemeAware in TreeOptions.PaintOptions)) or VclStyleEnabled then
|
|
begin
|
|
DoStateChange([tsUseThemes]);
|
|
if not VclStyleEnabled then
|
|
if (toUseExplorerTheme in FOptions.FPaintOptions) and IsWinVistaOrAbove then
|
|
begin
|
|
DoStateChange([tsUseExplorerTheme]);
|
|
SetWindowTheme('explorer');
|
|
end
|
|
else
|
|
DoStateChange([], [tsUseExplorerTheme]);
|
|
end
|
|
else
|
|
DoStateChange([], [tsUseThemes, tsUseExplorerTheme]);
|
|
|
|
AutoScale();
|
|
// 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);
|
|
|
|
PrepareBitmaps(True, True);
|
|
|
|
{$ifdef Windows}
|
|
// Register tree as OLE drop target.
|
|
if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.FMiscOptions) then
|
|
if not (csLoading in ComponentState) then // will be done in Loaded after all inherited settings are loaded from the DFMs
|
|
RegisterDragDrop(Handle, VTVDragManager as IDropTarget);
|
|
{$endif}
|
|
|
|
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;
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode;
|
|
|
|
// Determine the DropMode.
|
|
|
|
var
|
|
ImageHit: Boolean;
|
|
LabelHit: Boolean;
|
|
ItemHit: Boolean;
|
|
|
|
begin
|
|
ImageHit := HitInfo.HitPositions * [hiOnNormalIcon, hiOnStateIcon] <> [];
|
|
LabelHit := hiOnItemLabel in HitInfo.HitPositions;
|
|
ItemHit := ((hiOnItem in HitInfo.HitPositions) and
|
|
((toFullRowDrag in FOptions.FMiscOptions) or (toFullRowSelect in FOptions.FSelectionOptions)));
|
|
|
|
// 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 (ItemHit or ((LabelHit or ImageHit) and
|
|
(HitInfo.HitColumn = FHeader.MainColumn))) then
|
|
HitInfo.HitNode := nil;
|
|
|
|
if Assigned(HitInfo.HitNode) then
|
|
begin
|
|
if LabelHit or ImageHit or not (toShowDropmark in FOptions.FPaintOptions) then
|
|
Result := dmOnNode
|
|
else
|
|
if ((NodeRect.Top + NodeRect.Bottom) div 2) > P.Y then
|
|
Result := dmAbove
|
|
else
|
|
Result := dmBelow;
|
|
end
|
|
else
|
|
Result := dmNowhere;
|
|
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) and not IsEffectivelyVisible[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 (MainColumnHit and (Offset < (Indent + Margin{See issue #259}))) 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, GetRealCheckImagesWidth + 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, GetRealStateImagesWidth + 2);
|
|
if Offset < ImageOffset then
|
|
Include(HitInfo.HitPositions, hiOnStateIcon)
|
|
else
|
|
begin
|
|
if Assigned(FImages) and HasImage(HitInfo.HitNode, ikNormal, HitInfo.HitColumn) then
|
|
Inc(ImageOffset, GetNodeImageSize(HitInfo.HitNode).cx + 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, GetRealCheckImagesWidth + 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, GetRealStateImagesWidth + 2);
|
|
if Offset > ImageOffset then
|
|
Include(HitInfo.HitPositions, hiOnStateIcon)
|
|
else
|
|
begin
|
|
if Assigned(FImages) and HasImage(HitInfo.HitNode, ikNormal, HitInfo.HitColumn) then
|
|
Dec(ImageOffset, GetNodeImageSize(HitInfo.HitNode).cx + 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.DetermineLineImageAndSelectLevel(Node: PVirtualNode; var 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;
|
|
Indent: 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);
|
|
Indent := X - 1;
|
|
|
|
// Only use lines if requested.
|
|
if (toShowTreeLines in FOptions.FPaintOptions) and
|
|
(not (toHideTreeLinesIfThemed in FOptions.FPaintOptions) or not (tsUseThemes in FStates)) 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
|
|
else
|
|
LineImage[X] := ltNone;
|
|
|
|
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) and
|
|
(not (toHideTreeLinesIfThemed in FOptions.FPaintOptions) or not (tsUseThemes in FStates))) 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;
|
|
|
|
if (tsUseExplorerTheme in FStates) and HasChildren[Node] and (Indent >= 0) then
|
|
LineImage[Indent] := ltNone;
|
|
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, {%H-}UpdateRect, False);
|
|
SetUpdateState(True);
|
|
end;
|
|
{$endif}
|
|
|
|
Canvas.Font := Self.Font; // Fixes issue #298
|
|
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.DoCanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex;
|
|
var Allowed: Boolean);
|
|
|
|
begin
|
|
if Assigned(FOnCanSplitterResizeNode) then
|
|
FOnCanSplitterResizeNode(Self, P, 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);
|
|
var
|
|
lFirstSelected: PVirtualNode;
|
|
lParent: PVirtualNode;
|
|
begin
|
|
if Assigned(FOnCollapsed) then
|
|
FOnCollapsed(Self, Node);
|
|
{$ifdef EnableAccessible}
|
|
if Assigned(FAccessibleItem) then
|
|
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF);
|
|
{$endif}
|
|
|
|
if (toAlwaysSelectNode in TreeOptions.SelectionOptions) then
|
|
begin
|
|
// Select the next visible parent if the currently selected node gets invisible due to a collapse
|
|
// This makes the VT behave more like the Win32 custom TreeView control
|
|
// This makes only sense no no multi selection is allowed and if there is a selected node at all
|
|
lFirstSelected := GetFirstSelected();
|
|
if Assigned(lFirstSelected) and not FullyVisible[lFirstSelected] then
|
|
begin
|
|
lParent := GetVisibleParent(lFirstSelected);
|
|
Selected[lParent] := True;
|
|
Selected[lFirstSelected] := False;
|
|
end;//if
|
|
//if there is (still) no selected node, then use FNextNodeToSelect to select one
|
|
if SelectedCount = 0 then
|
|
EnsureNodeSelected();
|
|
end;//if
|
|
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
|
|
FOnCreateEditor(Self, Node, Column, Result);
|
|
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
|
|
AllowedEffects: LongWord;
|
|
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.
|
|
FLastDragEffect := DROPEFFECT_NONE;
|
|
AllowedEffects := GetDragOperations;
|
|
try
|
|
DragAndDrop(AllowedEffects, DataObject, FLastDragEffect);
|
|
VTVDragManager.ForceDragLeave;
|
|
finally
|
|
GetCursorPos(P);
|
|
P := ScreenToClient(P);
|
|
DoEndDrag(Self, P.X, P.Y);
|
|
|
|
FDragImage.EndDrag;
|
|
|
|
// Finish the operation.
|
|
if (FLastDragEffect = DROPEFFECT_MOVE) and (toAutoDeleteMovedNodes in TreeOptions.AutoOptions) then
|
|
begin
|
|
// The operation was a move so delete the previously selected nodes.
|
|
DeleteSelectedNodes;
|
|
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.DoBeforeDrawLineImage(Node: PVirtualNode; Level: Integer; var XPos: Integer);
|
|
|
|
begin
|
|
if Assigned(FOnBeforeDrawLineImage) then
|
|
FOnBeforeDrawLineImage(Self, Node, Level, XPos);
|
|
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.DoEndOperation(OperationKind: TVTOperationKind);
|
|
|
|
begin
|
|
if Assigned(FOnEndOperation) then
|
|
FOnEndOperation(Self, OperationKind);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoEnter();
|
|
begin
|
|
inherited;
|
|
EnsureNodeSelected();
|
|
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 = []), not (toFullRowSelect in FOptions.SelectionOptions) );
|
|
end;
|
|
|
|
// Reset range anchor if necessary.
|
|
if FSelectionCount = 0 then
|
|
ResetRangeAnchor;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoFreeNode(Node: PVirtualNode);
|
|
|
|
begin
|
|
// Prevent invalid references
|
|
if Node = FLastChangedNode then
|
|
FLastChangedNode := nil;
|
|
if Node = FCurrentHotNode then
|
|
FCurrentHotNode := nil;
|
|
if Node = FDropTargetNode then
|
|
FDropTargetNode := nil;
|
|
if Node = FLastStructureChangeNode then
|
|
FLastStructureChangeNode := nil;
|
|
|
|
if Node = FNextNodeToSelect then
|
|
FNextNodeToSelect := nil;
|
|
if Self.UpdateCount = 0 then
|
|
begin
|
|
// Omit this stuff if the control is in a BeginUpdate/EndUpdate bracket to increase performance
|
|
// We now try
|
|
// Make sure that CurrentNode does not point to an invalid node
|
|
if (toAlwaysSelectNode in TreeOptions.SelectionOptions) and (Node = GetFirstSelected()) then
|
|
begin
|
|
if Assigned(FNextNodeToSelect) then
|
|
// Select a new node if the currently selected node gets freed
|
|
Selected[FNextNodeToSelect] := True
|
|
else
|
|
begin
|
|
FNextNodeToSelect := Self.NodeParent[GetFirstSelected()];
|
|
if Assigned(FNextNodeToSelect) then
|
|
Selected[FNextNodeToSelect] := True;
|
|
end;//else
|
|
end;//if
|
|
end;
|
|
|
|
// fire event
|
|
if Assigned(FOnFreeNode) and ([vsInitialized, vsOnFreeNodeCallRequired] * Node.States <> []) then
|
|
FOnFreeNode(Self, Node);
|
|
FreeMem(Node);
|
|
if Self.UpdateCount = 0 then
|
|
EnsureNodeSelected();
|
|
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.DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer;
|
|
|
|
// Returns the pixel width of extra space occupied by node contents (for example, static text).
|
|
|
|
begin
|
|
Result := 0;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer;
|
|
|
|
// Returns the pixel width of a node.
|
|
|
|
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(HitInfo: TVTHeaderHitInfo);
|
|
|
|
begin
|
|
if Assigned(FOnHeaderClick) then
|
|
FOnHeaderClick(FHeader, HitInfo);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoHeaderDblClick(HitInfo: TVTHeaderHitInfo);
|
|
|
|
begin
|
|
if Assigned(FOnHeaderDblClick) then
|
|
FOnHeaderDblClick(FHeader, HitInfo);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal): Boolean;
|
|
/// The function calls the OnInitChildren and returns True if the event was called; it returns False if the caller can expect that no changes have been made to ChildCount
|
|
begin
|
|
if Assigned(FOnInitChildren) then
|
|
begin
|
|
FOnInitChildren(Self, Node, ChildCount);
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
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.DoMouseEnter();
|
|
|
|
begin
|
|
if Assigned(FOnMouseEnter) then
|
|
FOnMouseEnter(Self);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoMouseLeave;
|
|
|
|
begin
|
|
if Assigned(FOnMouseLeave) then
|
|
FOnMouseLeave(Self);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoNodeClick(const HitInfo: THitInfo);
|
|
|
|
begin
|
|
if Assigned(FOnNodeClick) then
|
|
FOnNodeClick(Self, HitInfo);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoNodeDblClick(const HitInfo: THitInfo);
|
|
|
|
begin
|
|
if Assigned(FOnNodeDblClick) then
|
|
FOnNodeDblClick(Self, HitInfo);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoRemoveFromSelection(Node: PVirtualNode);
|
|
|
|
begin
|
|
if Assigned(FOnRemoveFromSelection) then
|
|
FOnRemoveFromSelection(Self, Node);
|
|
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: TLCLHandle;//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;
|
|
if tsHint in Self.FStates then
|
|
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({%H-}P);
|
|
P := ScreenToClient(P);
|
|
if PtInRect(ClientRect, P) then
|
|
HandleHotTrack(P.X, P.Y);
|
|
|
|
DoScroll(DeltaX, DeltaY);
|
|
Perform(CM_UPDATE_VCLSTYLE_SCROLLBARS,0,0);
|
|
end;
|
|
{$ifdef DEBUG_VTV}Logger.ExitMethod([lcScroll],'DoSetOffsetXY');{$endif}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoShowScrollBar(Bar: Integer; Show: Boolean);
|
|
|
|
begin
|
|
ShowScrollBar(Handle, Bar, Show);
|
|
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.DoStartOperation(OperationKind: TVTOperationKind);
|
|
|
|
begin
|
|
if Assigned(FOnStartOperation) then
|
|
FOnStartOperation(Self, OperationKind);
|
|
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({%H-}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;
|
|
|
|
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 the cache is full then stop the loop.
|
|
if (Integer(Index) > Length(FPositionCache)) then // ADDED: 17.09.2013 - Veit Zimmermann
|
|
Break; // ADDED: 17.09.2013 - Veit Zimmermann
|
|
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 then stop the loop.
|
|
if (Temp = nil) then // CHANGED: 17.09.2013 - Veit Zimmermann
|
|
Break; // CHANGED: 17.09.2013 - Veit Zimmermann
|
|
|
|
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;
|
|
|
|
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
|
|
begin
|
|
UpdateScrollBars(True);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DragAndDrop(AllowedEffects: LongWord;
|
|
DataObject: IDataObject; var DragEffect: LongWord);
|
|
|
|
begin
|
|
{$ifdef Windows}
|
|
//lcl
|
|
//todo
|
|
{
|
|
if IsWinVistaOrAbove then begin
|
|
SHDoDragDrop(Self.Handle, DataObject, nil, AllowedEffects, DragEffect); // supports drag hints on Windows Vista and later
|
|
end
|
|
else
|
|
}
|
|
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, {%H-}HitInfo);
|
|
if Assigned(HitInfo.HitNode) then
|
|
begin
|
|
FDropTargetNode := HitInfo.HitNode;
|
|
R := GetDisplayRect(HitInfo.HitNode, FHeader.MainColumn, False);
|
|
if (hiOnItemLabel in HitInfo.HitPositions) or ((hiOnItem in HitInfo.HitPositions) and
|
|
((toFullRowDrag in FOptions.FMiscOptions) or (toFullRowSelect in FOptions.FSelectionOptions)))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}
|
|
if [tsOLEDragging, tsVCLDragPending, tsVCLDragging, tsVCLDragFinished] * FStates = [] then
|
|
Exit;
|
|
|
|
DoStateChange([], [tsVCLDragPending, tsVCLDragging, tsUserDragObject, tsVCLDragFinished]);
|
|
|
|
GetCursorPos({%H-}P);
|
|
P := ScreenToClient(P);
|
|
if tsRightButtonDown in FStates then
|
|
Perform(LM_RBUTTONUP, 0, LPARAM(Cardinal(PointToSmallPoint(P))))
|
|
else
|
|
if tsMiddleButtonDown in FStates then
|
|
Perform(LM_MBUTTONUP, 0, LPARAM(Cardinal(PointToSmallPoint(P))))
|
|
else
|
|
Perform(LM_LBUTTONUP, 0, LPARAM(Cardinal(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;
|
|
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, {%H-}HitInfo);
|
|
|
|
if Assigned(HitInfo.HitNode) then
|
|
R := GetDisplayRect(HitInfo.HitNode, NoColumn, False)
|
|
else
|
|
R := Rect(0, 0, 0, 0);
|
|
NewDropMode := DetermineDropMode(Pt, HitInfo, R);
|
|
|
|
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 := FColors.BackGroundColor;
|
|
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; UseSelectedBkColor: Boolean = False);
|
|
|
|
// 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
|
|
if UseSelectedBkColor then
|
|
begin
|
|
if Focused or (toPopupMode in FOptions.FPaintOptions) then
|
|
Brush.Color := FColors.FocusedSelectionColor
|
|
else
|
|
Brush.Color := FColors.UnfocusedSelectionColor;
|
|
end
|
|
else
|
|
Brush.Color := FColors.BackGroundColor;
|
|
R := Rect(Left, Min(Top, Bottom), Left + 1, Max(Top, Bottom) + 1);
|
|
LCLIntf.FillRect(Handle, R, FDottedBrush);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.EndOperation(OperationKind: TVTOperationKind);
|
|
|
|
// 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);
|
|
DoEndOperation(OperationKind);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.EnsureNodeFocused();
|
|
begin
|
|
if FocusedNode = nil then
|
|
FocusedNode := Self.GetFirstVisible();
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.EnsureNodeSelected;
|
|
begin
|
|
if (toAlwaysSelectNode in TreeOptions.SelectionOptions) and (GetFirstSelected() = nil) and not SelectionLocked then
|
|
begin
|
|
if Assigned(FNextNodeToSelect) then
|
|
Selected[FNextNodeToSelect] := True
|
|
else if Self.Focused then
|
|
Selected[GetFirstVisible] := True;
|
|
end;//if
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.FindNodeInSelection(P: PVirtualNode; var 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: 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;
|
|
if PAnsiChar(FSelection[I]) < PAnsiChar(P) then
|
|
L := I + 1
|
|
else
|
|
begin
|
|
H := I - 1;
|
|
if FSelection[I] = P 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 := Int64(StartPos) + SizeOf(Size);
|
|
// 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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 2010000}
|
|
procedure TBaseVirtualTree.FixDesignFontsPPI(const ADesignTimePPI: Integer);
|
|
begin
|
|
inherited;
|
|
FHeader.FixDesignFontsPPI(ADesignTimePPI);
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.FontChanged(AFont: TObject);
|
|
|
|
// Little helper function for font changes (as they are not tracked in TBitmap/TCanvas.OnChange).
|
|
|
|
begin
|
|
FFontChanged := True;
|
|
if Assigned(FOldFontChange) then
|
|
FOldFontChange(AFont);
|
|
//if not (tsPainting in TreeStates) then AutoScale();
|
|
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 = True): 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
|
|
IsHot: Boolean;
|
|
|
|
begin
|
|
if Assigned(Node) then
|
|
begin
|
|
ImgCheckType := Node.CheckType;
|
|
ImgCheckState := Node.CheckState;
|
|
ImgEnabled := not (vsDisabled in Node.States) and Enabled;
|
|
IsHot := Node = FCurrentHotNode;
|
|
end
|
|
else
|
|
IsHot := False;
|
|
|
|
if ImgCheckType = ctTriStateCheckBox then
|
|
ImgCheckType := ctCheckBox;
|
|
|
|
if ImgCheckType = ctNone then
|
|
Result := -1
|
|
else
|
|
Result := CheckStateToCheckImage[ImgCheckType, ImgCheckState, ImgEnabled, IsHot];
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//lcl
|
|
//todo: implement GetCheckImageListFor or change the part where is called
|
|
class function TBaseVirtualTree.GetCheckImageListFor(Kind: TCheckImageKind): TCustomImageList;
|
|
|
|
begin
|
|
case Kind of
|
|
ckDarkCheck:
|
|
Result := GetCheckImageList(DarkCheckImages, ckDarkCheck);
|
|
ckLightTick:
|
|
Result := GetCheckImageList(LightTickImages, ckLightTick);
|
|
ckDarkTick:
|
|
Result := GetCheckImageList(DarkTickImages, ckDarkTick);
|
|
ckLightCheck:
|
|
Result := GetCheckImageList(LightCheckImages, ckLightCheck);
|
|
ckFlat:
|
|
Result := GetCheckImageList(FlatImages, ckFlat);
|
|
ckXP:
|
|
Result := GetCheckImageList(XPImages, ckXP);
|
|
ckSystemDefault:
|
|
Result := GetCheckImageList(SystemCheckImages, ckSystemDefault);
|
|
ckSystemFlat:
|
|
Result := GetCheckImageList(SystemFlatCheckImages, ckSystemFlat);
|
|
else
|
|
Result := nil;
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
function TBaseVirtualTree.GetImagesWidth(Images: TCustomImageList): Integer;
|
|
begin
|
|
if Images = FImages then
|
|
Result := FImagesWidth
|
|
else if Images = FStateImages then
|
|
Result := FStateImagesWidth
|
|
else if Images = FCheckImages then
|
|
Result := FCheckImagesWidth
|
|
else
|
|
Result := 0;
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.IsEmpty: Boolean;
|
|
begin
|
|
Result := (Self.ChildCount[nil] = 0);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetNodeImageSize(Node: PVirtualNode): TSize;
|
|
|
|
// Returns the size of an image
|
|
// Override if you need different sized images for certain nodes.
|
|
begin
|
|
if Assigned(FImages) then
|
|
begin
|
|
Result.cx := GetRealImagesWidth;
|
|
Result.cy := GetRealImagesHeight;
|
|
end
|
|
else
|
|
begin
|
|
Result.cx := 0;
|
|
Result.cy := 0;
|
|
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, {%H-}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, GetRealStateImagesWidth + 2);
|
|
|
|
if Assigned(FImages) then
|
|
Inc(NodeLeft, GetRealImagesWidth + 2);
|
|
|
|
WithCheck := (toCheckSupport in FOptions.FMiscOptions) and Assigned(FCheckImages);
|
|
if WithCheck then
|
|
CheckOffset := GetRealCheckImagesWidth + 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);
|
|
Inc(CurrentWidth, DoGetNodeExtraWidth(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: Boolean;
|
|
|
|
begin
|
|
Result := FOperationCanceled and (FOperationCount > 0);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetOptionsClass: TTreeOptionsClass;
|
|
|
|
begin
|
|
Result := TCustomVirtualTreeOptions;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetRealImagesWidth: Integer;
|
|
begin
|
|
if FImages = nil then
|
|
Result := 0
|
|
else
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result := FImages.ResolutionForPPI[FImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor].Width;
|
|
{$ELSE}
|
|
Result := FImages.Width;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetRealImagesHeight: Integer;
|
|
begin
|
|
if FImages = nil then
|
|
Result := 0
|
|
else
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result := FImages.ResolutionForPPI[FImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor].Height;
|
|
{$ELSE}
|
|
Result := FImages.Height;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetRealStateImagesWidth: Integer;
|
|
begin
|
|
if FStateImages = nil then
|
|
Result := 0
|
|
else
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result := FStateImages.ResolutionForPPI[FStateImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor].Width;
|
|
{$ELSE}
|
|
Result := FStateImages.Width;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetRealStateImagesHeight: Integer;
|
|
begin
|
|
if FStateImages = nil then
|
|
Result := 0
|
|
else
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result := FStateImages.ResolutionForPPI[FStateImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor].Height;
|
|
{$ELSE}
|
|
Result := FStateImages.Height;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetRealCheckImagesWidth: Integer;
|
|
begin
|
|
if FCheckImages = nil then
|
|
Result := 0
|
|
else
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result := FCheckImages.ResolutionForPPI[FCheckImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor].Width;
|
|
{$ELSE}
|
|
Result := FCheckImages.Width;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetRealCheckImagesHeight: Integer;
|
|
begin
|
|
if FCheckImages = nil then
|
|
Result := 0
|
|
else
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Result := FCheckImages.ResolutionForPPI[FCheckImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor].Height;
|
|
{$ELSE}
|
|
Result := FCheckImages.Height;
|
|
{$IFEND}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$i laz.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, {%H-}HitInfo);
|
|
|
|
// If we left the old hot node, then we should hide it's hint
|
|
if ShowHint then
|
|
if (HitInfo.HitNode <> FCurrentHintNode) or (HitInfo.HitColumn <> FCurrentHotColumn) then
|
|
begin
|
|
Application.HideHint;
|
|
FCurrentHintNode := HitInfo.HitNode;
|
|
end;
|
|
|
|
// 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 tsUseExplorerTheme in FStates then
|
|
Include(CheckPositions, hiOnItemButtonExact);
|
|
|
|
if (CheckPositions * HitInfo.HitPositions = []) and
|
|
(not (toFullRowSelect in FOptions.FSelectionOptions) or (hiNowhere in HitInfo.HitPositions)) 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] or IsEffectivelyFiltered[Run] then
|
|
Run := nil;
|
|
end;
|
|
end;
|
|
Stop := Run;
|
|
|
|
// VK_BACK temporarily changes search direction to opposite mode.
|
|
if PreviousSearch then
|
|
begin
|
|
if {%H-}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 + String(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 + string(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));
|
|
|
|
if HitInfo.HitNode <> nil then
|
|
DoNodeDblClick(HitInfo);
|
|
|
|
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 (ssLeft in KeysToShiftState(Message.Keys)) and 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
|
|
IsLabelHit, // 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 (tsEditing in FStates) then
|
|
DoEndEdit;
|
|
|
|
// 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.
|
|
IsLabelHit := not AltPressed and not (toSimpleDrawSelection in FOptions.FSelectionOptions) and
|
|
((hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions));
|
|
|
|
IsCellHit := not AltPressed and not IsLabelHit and Assigned(HitInfo.HitNode) and
|
|
([hiOnItemButton, hiOnItemCheckBox] * HitInfo.HitPositions = []) and
|
|
((toFullRowSelect in FOptions.FSelectionOptions) or
|
|
((toGridExtensions in FOptions.FMiscOptions) and (HitInfo.HitColumn > NoColumn)));
|
|
|
|
IsAnyHit := IsLabelHit or IsCellHit;
|
|
MultiSelect := toMultiSelect in FOptions.FSelectionOptions;
|
|
ShiftEmpty := ShiftState = [];
|
|
NodeSelected := IsAnyHit and (vsSelected in HitInfo.HitNode.States);
|
|
|
|
// Determine the Drag behavior.
|
|
if MultiSelect and not (toDisableDrawSelection in FOptions.FSelectionOptions) then
|
|
begin
|
|
// We have MultiSelect and want to draw a selection rectangle.
|
|
// We will start a full row drag only in case a label was hit,
|
|
// otherwise a multi selection will start.
|
|
FullRowDrag := (toFullRowDrag in FOptions.FMiscOptions) and IsCellHit and
|
|
not (hiNowhere in HitInfo.HitPositions) and
|
|
(NodeSelected or (hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions));
|
|
end
|
|
else // No MultiSelect, hence we can start a drag anywhere in the row.
|
|
FullRowDrag := toFullRowDrag in FOptions.FMiscOptions;
|
|
|
|
IsHeightTracking := (Message.Msg = WM_LBUTTONDOWN) and
|
|
(hiOnItem in HitInfo.HitPositions) and
|
|
([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []);
|
|
|
|
// 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 (FullRowDrag or IsLabelHit);
|
|
|
|
// handle node height tracking
|
|
if IsHeightTracking then
|
|
begin
|
|
if hiUpperSplitter in HitInfo.HitPositions then
|
|
FHeightTrackNode := GetPreviousVisible(HitInfo.HitNode, True)
|
|
else
|
|
FHeightTrackNode := HitInfo.HitNode;
|
|
|
|
if CanSplitterResizeNode(Point(Message.XPos, Message.YPos), FHeightTrackNode, HitInfo.HitColumn) 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 (ssLeft in KeysToShiftState(Message.Keys)) and 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)));
|
|
|
|
// User starts a selection with a selection rectangle.
|
|
if not (toDisableDrawSelection in FOptions.FSelectionOptions) and not (IsLabelHit 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);
|
|
end;
|
|
|
|
//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)) and not (hiNowhere in HitInfo.HitPositions) then
|
|
begin
|
|
NeedChange := FSelectionCount > 1;
|
|
InternalClearSelection;
|
|
InternalAddToSelection(HitInfo.HitNode, True);
|
|
if NeedChange then
|
|
begin
|
|
Invalidate;
|
|
Change(nil);
|
|
end;
|
|
end
|
|
else if not ((hiNowhere in HitInfo.HitPositions) and (toAlwaysSelectNode in Self.TreeOptions.SelectionOptions)) then // When clicking in the free space we don't want the selection to be cleared in case toAlwaysSelectNode is set
|
|
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
|
|
begin
|
|
DoStateChange([tsEditPending]);
|
|
end;
|
|
|
|
if not (toDisableDrawSelection in FOptions.FSelectionOptions)
|
|
and not (IsLabelHit or FullRowDrag) and MultiSelect then
|
|
begin
|
|
// The original code here was moved up to fix issue #187.
|
|
// In order not to break the semantics of this procedure, we are leaving these if statements here
|
|
if not IsCellHit or (hiNowhere in HitInfo.HitPositions) then
|
|
Exit;
|
|
end;
|
|
|
|
// Keep current mouse position.
|
|
FLastClickPos := Point(Message.XPos, Message.YPos);
|
|
|
|
// Handle selection and node focus change.
|
|
if (IsLabelHit or IsCellHit) and
|
|
DoFocusChanging(FFocusedNode, HitInfo.HitNode, FFocusedColumn, Column) 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)
|
|
and not (toFullRowSelect in FOptions.SelectionOptions));
|
|
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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) and Assigned(HitInfo.HitNode) then //Prevent AV when dereferencing HitInfo.HitNode below, see bug #100
|
|
begin
|
|
if vsSelected in HitInfo.HitNode.States then
|
|
begin
|
|
if not (toAlwaysSelectNode in TreeOptions.SelectionOptions) or (Self.SelectedCount > 1) then
|
|
RemoveFromSelection(HitInfo.HitNode);
|
|
end
|
|
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]);
|
|
// Need check for nil, issue #285
|
|
// because when mouse down on checkbox but not yet released
|
|
// and in this time list starts to rebuild by timer
|
|
// after this when mouse release FCheckNode equal nil
|
|
if Assigned (FCheckNode) then
|
|
begin
|
|
// 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);
|
|
end;
|
|
FCheckNode := nil;
|
|
end;
|
|
|
|
if (FHeader.FColumns.FClickIndex > NoColumn) and (FHeader.FColumns.FClickIndex = HitInfo.HitColumn) then
|
|
DoColumnClick(HitInfo.HitColumn, KeysToShiftState(Keys));
|
|
|
|
if HitInfo.HitNode <> nil then
|
|
DoNodeClick(HitInfo);
|
|
|
|
// 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
|
|
(toEditOnClick in FOptions.FMiscOptions) and (FFocusedColumn = HitInfo.HitColumn) and
|
|
CanEdit(FFocusedNode, HitInfo.HitColumn) 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.
|
|
// The given node will be implicitly initialized if needed.
|
|
|
|
var
|
|
Ghosted: Boolean;
|
|
Index: Integer;
|
|
|
|
begin
|
|
if not (vsInitialized in Node.States) then
|
|
InitNode(Node);
|
|
|
|
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;
|
|
if DoInitChildren(Node, Count) then
|
|
begin
|
|
SetChildCount(Node, Count);
|
|
if Count = 0 then
|
|
Exclude(Node.States, vsHasChildren);
|
|
end;
|
|
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
|
|
InitStates := [];
|
|
if vsInitialized in States then
|
|
Include(InitStates, ivsReInit);
|
|
Include(States, vsInitialized);
|
|
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);
|
|
if ivsFiltered in InitStates then
|
|
begin
|
|
Include(States, vsFiltered);
|
|
if not (toShowFilteredNodes in FOptions.FPaintOptions) then
|
|
begin
|
|
AdjustTotalHeight(Node, -NodeHeight, True);
|
|
if FullyVisible[Node] then
|
|
Dec(FVisibleCount);
|
|
UpdateScrollBars(True);
|
|
end;
|
|
end;
|
|
|
|
// 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] and not IsEffectivelyFiltered[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] or IsEffectivelyFiltered[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, {%H-}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
|
|
begin
|
|
Include(NewItems[I].States, vsSelected);
|
|
if Assigned(FOnAddToSelection) then
|
|
FOnAddToSelection(Self, NewItems[I]);
|
|
end;
|
|
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(PAnsiChar(NewItems[I]))
|
|
else
|
|
begin
|
|
Include(NewItems[I].States, vsSelected);
|
|
if Assigned(FOnAddToSelection) then
|
|
FOnAddToSelection(Self, NewItems[I]);
|
|
end;
|
|
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 (PAnsiChar(NewItems[J]) > PAnsiChar(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);
|
|
DoRemoveFromSelection(FSelection[FSelectionCount]);
|
|
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 (vsExpanded in Node.Parent.States) and (vsVisible in Node.States) then
|
|
begin
|
|
AdjustTotalHeight(Destination.Parent, Node.TotalHeight, True);
|
|
Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1)));
|
|
end;
|
|
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 (vsExpanded in Node.Parent.States) and (vsVisible in Node.States) then
|
|
begin
|
|
AdjustTotalHeight(Destination.Parent, Node.TotalHeight, True);
|
|
Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1)));
|
|
end;
|
|
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 (vsExpanded in Node.Parent.States) and (vsVisible in Node.States) then
|
|
begin
|
|
AdjustTotalHeight(Destination, Node.TotalHeight, True);
|
|
Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1)));
|
|
end;
|
|
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 (vsExpanded in Node.Parent.States) and (vsVisible in Node.States) then
|
|
begin
|
|
AdjustTotalHeight(Destination, Node.TotalHeight, True);
|
|
Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1)));
|
|
end;
|
|
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 IsEffectivelyVisible[Node] 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 := (vsExpanded in Parent.States) and (vsVisible in Node.States);
|
|
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) + Cardinal(IfThen(IsEffectivelyVisible[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, {%H-}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, {%H-}Index, -1, -1) then
|
|
begin
|
|
Exclude(Node.States, vsSelected);
|
|
Inc(PAnsiChar(FSelection[Index]));
|
|
DoRemoveFromSelection(Node);
|
|
AdviseChangeEvent(False, Node, crIgnore);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.InvalidateCache;
|
|
|
|
// Marks the cache as invalid.
|
|
|
|
begin
|
|
DoStateChange([tsValidationNeeded], [tsUseCache]);
|
|
//ChangeTreeStatesAsync([csValidationNeeded], [csUseCache]);
|
|
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;
|
|
|
|
// Call RegisterDragDrop after all visual inheritance changes to MiscOptions have been applied.
|
|
if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.FMiscOptions) then
|
|
if HandleAllocated then
|
|
RegisterDragDrop(Handle, VTVDragManager as IDropTarget);
|
|
|
|
{$ifdef VCLStyleSupport}
|
|
FSavedBorderWidth := BorderWidth;
|
|
FSavedBevelKind := BevelKind;
|
|
VclStyleChanged;
|
|
{$IFEND}
|
|
// 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);
|
|
// 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
|
|
else
|
|
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({%H-}P);
|
|
P := ScreenToClient(P);
|
|
GetHitTestInfoAt(P.X, P.Y, True, {%H-}HitInfo);
|
|
if (hiOnItem in HitInfo.HitPositions) and
|
|
([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []) then
|
|
begin
|
|
if hiUpperSplitter in HitInfo.HitPositions then
|
|
Node := GetPreviousVisible(HitInfo.HitNode, True)
|
|
else
|
|
Node := HitInfo.HitNode;
|
|
if CanSplitterResizeNode(P, Node, HitInfo.HitColumn) 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 = FCustomCheckImages then
|
|
begin
|
|
CustomCheckImages := nil;
|
|
FCheckImageKind := ckSystemDefault;
|
|
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 TWithSafeRect(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, -Integer(BorderWidth), -Integer(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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//lcl: implementation of PaintCheckImage differs from Delphi
|
|
procedure TBaseVirtualTree.PaintCheckImage(Canvas: TCanvas; const ImageInfo: TVTImageInfo; Selected: Boolean);
|
|
|
|
|
|
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;
|
|
DrawEffect: TGraphicsDrawEffect;
|
|
checkSize: Integer;
|
|
|
|
begin
|
|
{$ifdef DEBUG_VTV}Logger.EnterMethod([lcCheck],'PaintCheckImage');{$endif}
|
|
|
|
{$if LCL_FullVersion >= 1080000}
|
|
checkSize := Scale96ToFont(DEFAULT_CHECK_WIDTH);
|
|
{$else}
|
|
checkSize := DEFAULT_CHECK_WIDTH;
|
|
{$ifend}
|
|
|
|
with ImageInfo do
|
|
begin
|
|
UseThemes := (tsUseThemes in FStates) and (FCheckImageKind = ckSystemDefault);
|
|
if UseThemes or ((FCheckImageKind in [ckSystemFlat, ckSystemDefault]) and not (Index in [21..24])) then
|
|
begin
|
|
if UseThemes then
|
|
begin
|
|
Details := ThemeServices.GetElementDetails(tbCheckBoxCheckedNormal);
|
|
checkSize := ThemeServices.GetDetailSizeForPPI(Details, Font.PixelsPerInch).CX;
|
|
R := Rect(XPos, YPos, XPos + checkSize, YPos + checkSize);
|
|
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;
|
|
InflateRect(R, 1, 1);
|
|
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(Canvas.Handle, XPos - 1, YPos, Height, Height,
|
|
Canvas.Handle, 4 * Height, 0, MaskHandle);
|
|
{$else}
|
|
if Index in [21..24] then
|
|
with UtilityImages do
|
|
StretchMaskBlt(Canvas.Handle, XPos - 1, YPos, Height, Height,
|
|
Canvas.Handle, 4 * Height, 0, Height, Height, MaskHandle, 4 * Height, 0, SRCCOPY);
|
|
{$endif}
|
|
end
|
|
else
|
|
begin
|
|
R := Rect(XPos + 1, YPos + 1, XPos + checkSize-2, YPos + checkSize-2);
|
|
DrawCheckButton(Canvas, Index - 1, R, FCheckImageKind = ckSystemFlat);
|
|
end;
|
|
end
|
|
else
|
|
with FCheckImages do
|
|
begin
|
|
if not Ghosted then
|
|
DrawEffect := gdeNormal
|
|
else
|
|
DrawEffect := gdeShadowed;
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
DrawForPPI(Canvas, XPos-1, YPos-1, Index, 0, Font.PixelsPerInch, GetCanvasScaleFactor, DrawEffect);
|
|
{$ELSE}
|
|
Draw(Canvas, XPos, YPos, Index, DrawEffect);
|
|
{$IFEND}
|
|
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;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImageRes: TScaledImageListResolution;
|
|
{$IFEND}
|
|
|
|
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;
|
|
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImageRes := Images.ResolutionForPPI[ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
|
|
ImageRes.Draw(Canvas, XPos, YPos, Index, DrawEffect);
|
|
{$ELSE}
|
|
Images.Draw(Canvas, XPos, YPos, Index, DrawEffect);
|
|
{$IFEND}
|
|
|
|
// 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
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
ImageRes := ImageInfo[iiOverlay].Images.ResolutionForPPI[ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
|
|
ImageRes.Draw(Canvas, XPos, YPos, ImageInfo[iiOverlay].Index);
|
|
{$ELSE}
|
|
ImageInfo[iiOverlay].Images.Draw(Canvas, XPos, YPos, ImageInfo[iiOverlay].Index);
|
|
{$IFEND}
|
|
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;
|
|
{$ifdef Windows}
|
|
Theme: HTHEME;
|
|
Glyph: Integer;
|
|
State: Integer;
|
|
Pos: TRect;
|
|
{$endif}
|
|
|
|
begin
|
|
IsHot := (toHotTrack in FOptions.FPaintOptions) and (FCurrentHotNode = Node) and FHotNodeButtonHit;
|
|
|
|
// Draw the node's plus/minus button according to the directionality.
|
|
if BidiMode = bdLeftToRight then
|
|
XPos := R.Left + ButtonX
|
|
else
|
|
XPos := R.Right - ButtonX - FPlusBM.Width;
|
|
|
|
if tsUseExplorerTheme in FStates then
|
|
begin
|
|
{$ifdef Windows}
|
|
Glyph := IfThen(IsHot, TVP_HOTGLYPH, TVP_GLYPH);
|
|
State := IfThen(vsExpanded in Node.States, GLPS_OPENED, GLPS_CLOSED);
|
|
Pos := Rect(XPos, R.Top + ButtonY, XPos + FPlusBM.Width, R.Top + ButtonY + FPlusBM.Height);
|
|
|
|
Theme := OpenThemeData(Handle, 'TREEVIEW');
|
|
DrawThemeBackground(Theme, Canvas.Handle, Glyph, State, Pos, nil);
|
|
CloseThemeData(Theme);
|
|
{$endif}
|
|
end
|
|
else
|
|
begin
|
|
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;
|
|
// Need to draw this masked.
|
|
Canvas.Draw(XPos, R.Top + ButtonY, Bitmap);
|
|
end;
|
|
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
|
|
DoBeforeDrawLineImage(PaintInfo.Node, I + Ord(not (toShowRoot in TreeOptions.PaintOptions)), XPos);
|
|
DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node] - 1, VAlignment - 1, 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
|
|
DoBeforeDrawLineImage(PaintInfo.Node, I + Ord(not (toShowRoot in TreeOptions.PaintOptions)), XPos);
|
|
DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node], VAlignment - 1, 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({%H-}BlendRect, OrderRect(SelectionRect), TargetRect) then
|
|
begin
|
|
OffsetRect(BlendRect, -WindowOrgX, 0);
|
|
laz.VTGraphics.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}
|
|
{$ifdef Windows}
|
|
Theme: HTHEME;
|
|
RowRect: TRect;
|
|
{$endif}
|
|
{$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;
|
|
laz.VTGraphics.AlphaBlend(0, PaintInfo.Canvas.Handle, R, Point(0, 0), bmConstantAlphaAndColor,
|
|
FSelectionBlendFactor, ColorToRGB(Color));
|
|
end;
|
|
|
|
//---------------------------------------------------------------------------
|
|
{$ifdef Windows}
|
|
procedure DrawBackground(State: Integer);
|
|
begin
|
|
// if the full row selection is disabled or toGridExtensions is in the MiscOptions, draw the selection
|
|
// into the InnerRect, otherwise into the RowRect
|
|
if not (toFullRowSelect in FOptions.FSelectionOptions) or (toGridExtensions in FOptions.FMiscOptions) then
|
|
DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, TVP_TREEITEM, State, InnerRect, nil)
|
|
else
|
|
DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, TVP_TREEITEM, State, RowRect, nil);
|
|
end;
|
|
|
|
procedure DrawThemedFocusRect(State: Integer);
|
|
var
|
|
Theme: HTHEME;
|
|
begin
|
|
Theme := OpenThemeData(Application.{%H-}Handle, 'Explorer::ItemsView');
|
|
if not (toFullRowSelect in FOptions.FSelectionOptions) or (toGridExtensions in FOptions.FMiscOptions) then
|
|
DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, LVP_LISTDETAIL, State, InnerRect, nil)
|
|
else
|
|
DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, LVP_LISTDETAIL, State, RowRect, nil);
|
|
CloseThemeData(Theme);
|
|
end;
|
|
{$endif}
|
|
|
|
//--------------- end local functions ---------------------------------------
|
|
|
|
begin
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef Windows}
|
|
if tsUseExplorerTheme in FStates then
|
|
begin
|
|
Theme := OpenThemeData(Application.{%H-}Handle, 'Explorer::TreeView');
|
|
RowRect := Rect(0, PaintInfo.CellRect.Top, FRangeX, PaintInfo.CellRect.Bottom);
|
|
if (Header.Columns.Count = 0) and (toFullRowSelect in TreeOptions.SelectionOptions) then
|
|
RowRect.Right := Max(ClientWidth, RowRect.Right);
|
|
if toShowVertGridLines in FOptions.PaintOptions then
|
|
Dec(RowRect.Right);
|
|
end;
|
|
{$endif}
|
|
{$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
|
|
if (VclStyleEnabled and not (coParentColor in FHeader.FColumns[Column].FOptions)) then
|
|
Brush.Color := FColors.BackGroundColor
|
|
else
|
|
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 TWithSafeRect(InnerRect) do
|
|
if Left + NodeWidth < Right then
|
|
Right := Left + NodeWidth;
|
|
taCenter:
|
|
with TWithSafeRect(InnerRect) do
|
|
if (Right - Left) > NodeWidth then
|
|
begin
|
|
Left := (Left + Right - NodeWidth) div 2;
|
|
Right := Left + NodeWidth;
|
|
end;
|
|
taRightJustify:
|
|
with TWithSafeRect(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
|
|
{$ifdef Windows}
|
|
if tsUseExplorerTheme in FStates then
|
|
DrawBackground(TREIS_SELECTED)
|
|
else
|
|
{$endif}
|
|
if MMXAvailable and (toUseBlendedSelection in FOptions.PaintOptions) then
|
|
AlphaBlendSelection(Brush.Color)
|
|
else
|
|
with TWithSafeRect(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}
|
|
{$ifdef Windows}
|
|
if tsUseExplorerTheme in FStates 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}
|
|
{$endif ThemeSupport}
|
|
if MMXAvailable and (toUseBlendedSelection in FOptions.PaintOptions) then
|
|
AlphaBlendSelection(Brush.Color)
|
|
else
|
|
with TWithSafeRect(InnerRect) do
|
|
RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef Windows}
|
|
if (tsUseExplorerTheme in FStates) 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}
|
|
{$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) or
|
|
((not (toExtendedFocus in FOptions.FSelectionOptions) or IsWinVistaOrAbove) and
|
|
(toFullRowSelect in FOptions.FSelectionOptions) and
|
|
(tsUseExplorerTheme in FStates) ) ) then
|
|
begin
|
|
TextColorBackup := GetTextColor(Handle);
|
|
SetTextColor(Handle, $FFFFFF);
|
|
BackColorBackup := GetBkColor(Handle);
|
|
SetBkColor(Handle, 0);
|
|
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef Windows}
|
|
if not (toExtendedFocus in FOptions.FSelectionOptions) and (toFullRowSelect in FOptions.FSelectionOptions) and
|
|
(tsUseExplorerTheme in FStates) then
|
|
FocusRect := RowRect
|
|
else
|
|
{$endif}
|
|
{$endif ThemeSupport}
|
|
if toGridExtensions in FOptions.FMiscOptions then
|
|
FocusRect := CellRect
|
|
else
|
|
FocusRect := InnerRect;
|
|
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef Windows}
|
|
if tsUseExplorerTheme in FStates then
|
|
InflateRect(FocusRect, -1, -1);
|
|
{$endif}
|
|
{$endif ThemeSupport}
|
|
|
|
if (tsUseExplorerTheme in FStates) and IsWinVistaOrAbove then
|
|
begin
|
|
//Draw focused unselected style like Windows 7 Explorer
|
|
{$ifdef Windows}
|
|
if not (vsSelected in Node.States) then
|
|
DrawThemedFocusRect(LIS_NORMAL)
|
|
else
|
|
DrawBackground(TREIS_HOTSELECTED);
|
|
{$endif}
|
|
end
|
|
else
|
|
LCLIntf.DrawFocusRect(Handle, FocusRect);
|
|
SetTextColor(Handle, TextColorBackup);
|
|
SetBkColor(Handle, BackColorBackup);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ifdef ThemeSupport}
|
|
{$ifdef Windows}
|
|
if tsUseExplorerTheme in FStates then
|
|
CloseThemeData(Theme);
|
|
{$endif}
|
|
{$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({%H-}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 <> (Int64(LastPosition) + ChunkSize)) then
|
|
Stream.Position := Int64(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({%H-}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, {%H-}Index, -1, -1) and (Index < FSelectionCount - 1) then
|
|
Move(FSelection[Index + 1], FSelection[Index], (FSelectionCount - Index - 1) * SizeOf(Pointer));
|
|
if FSelectionCount > 0 then
|
|
Dec(FSelectionCount);
|
|
SetLength(FSelection, FSelectionCount);
|
|
|
|
if FSelectionCount = 0 then
|
|
ResetRangeAnchor;
|
|
|
|
if FSelectionCount <= 1 then
|
|
UpdateNextNodeToSelect(Node);
|
|
|
|
DoRemoveFromSelection(Node);
|
|
Change(Node);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.UpdateNextNodeToSelect(Node: PVirtualNode);
|
|
|
|
// save a potential node to select after the currently selected node will be deleted.
|
|
// This will make the VT to behave more like the Win32 TreeView, which always selecta a new node if the currently
|
|
// selected one gets deleted.
|
|
|
|
begin
|
|
if not (toAlwaysSelectNode in TreeOptions.SelectionOptions) then
|
|
Exit;
|
|
if GetNextSibling(Node) <> nil then
|
|
FNextNodeToSelect := GetNextSibling(Node)
|
|
else if GetPreviousSibling(Node) <> nil then
|
|
FNextNodeToSelect := GetPreviousSibling(Node)
|
|
else if GetNodeLevel(Node) > 0 then
|
|
FNextNodeToSelect := Node.Parent
|
|
else
|
|
FNextNodeToSelect := GetFirstChild(Node);
|
|
end;//if Assigned(Node);
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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, {%H-}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({%H-}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);
|
|
const
|
|
TRANSPARENT_COLOR = $FFFFFE;
|
|
|
|
// 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] <> TRANSPARENT_COLOR) then
|
|
Start := X
|
|
else
|
|
if (Start > -1) and (Pixels[X, Y] = TRANSPARENT_COLOR) 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;
|
|
bm: TCustomBitmap;
|
|
|
|
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 := 'LAZ_VT_MOVEALL_BMP'
|
|
else
|
|
ImageName := 'LAZ_VT_MOVEEW_BMP';
|
|
end
|
|
else
|
|
ImageName := 'LAZ_VT_MOVENS_BMP';
|
|
|
|
bm := CreateBitmapFromResourceName(HINSTANCE, BuildResourceName(ImageName)); // is png!
|
|
try
|
|
FPanningWindow.Image.SetSize(bm.Width, bm.Height);
|
|
FPanningWindow.Image.Canvas.Brush.Color := TRANSPARENT_COLOR;
|
|
FPanningWindow.Image.Canvas.FillRect(0, 0, bm.Width, bm.Height);
|
|
FPanningWindow.Image.Transparent := true;
|
|
FPanningWindow.Image.Canvas.Draw(0, 0, bm);
|
|
finally
|
|
bm.Free;
|
|
end;
|
|
|
|
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.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) and Assigned(FFocusedNode) and
|
|
(FEditColumn < FHeader.Columns.Count) then // prevent EArgumentOutOfRangeException
|
|
begin
|
|
if (GetCurrentThreadId <> MainThreadID) then
|
|
begin
|
|
// UpdateEditBounds() will be called at the end of the thread
|
|
Exit;
|
|
end;
|
|
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);
|
|
R.Bottom := R.Top + Max(R.Bottom - R.Top, FEditLink.GetBounds.Bottom - FEditLink.GetBounds.Top); // Ensure to never decrease the size of the currently active edit control. Helps to prevent issue #159
|
|
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);
|
|
|
|
// 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, {%H-}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) and (FVisibleCount > CacheThreshold) 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 := SizeOf(Pointer);
|
|
if Assigned(FOnGetNodeDataSize) then
|
|
FOnGetNodeDataSize(Self, Size);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
{$ifdef VCLStyleSupport}
|
|
procedure TBaseVirtualTree.VclStyleChanged;
|
|
begin
|
|
FSetOrRestoreBevelKindAndBevelWidth := True;
|
|
FVclStyleEnabled := StyleServices.Enabled and not StyleServices.IsSystemStyle;
|
|
if not VclStyleEnabled then
|
|
begin
|
|
if FSavedBevelKind <> BevelKind then
|
|
BevelKind := FSavedBevelKind;
|
|
if FSavedBorderWidth <> BorderWidth then
|
|
BorderWidth := FSavedBorderWidth;
|
|
end
|
|
else
|
|
begin
|
|
if BevelKind <> bkNone then
|
|
BevelKind := bkNone;
|
|
if BorderWidth <> 0 then
|
|
BorderWidth := 0;
|
|
end;
|
|
FSetOrRestoreBevelKindAndBevelWidth := False;
|
|
end;
|
|
{$endif}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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
|
|
Handled := (DragMode = dmAutomatic) and (DragKind = dkDrag);
|
|
if Handled then
|
|
begin
|
|
if not IsControlMouseMsg(TLMMouse(Message)) then
|
|
begin
|
|
//lclheader
|
|
//let the header handle the message here
|
|
//otherwise no header click event will be fired
|
|
if not FHeader.HandleMessage(Message) then
|
|
begin
|
|
ControlState := ControlState + [csLButtonDown];
|
|
Dispatch(Message); // overrides TControl's BeginDrag
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if not Handled and Assigned(FHeader) then
|
|
Handled := FHeader.HandleMessage(Message);
|
|
|
|
if not Handled then
|
|
inherited;
|
|
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, vsOnFreeNodeCallRequired, 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 SizeOf(Pointer) 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 >= SizeOf(Pointer) then
|
|
begin
|
|
NodeData := Pointer(PByte(@Result.Data) + FTotalInternalDataSize);
|
|
NodeData^ := UserData;
|
|
Include(Result.States, vsOnFreeNodeCallRequired);
|
|
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({%H-}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;
|
|
{$if CompilerVersion >= 24}
|
|
Self.StyleElements := StyleElements;
|
|
{$ifend}
|
|
Self.TabOrder := TabOrder;
|
|
Self.TabStop := TabStop;
|
|
Self.Visible := Visible;
|
|
Self.SelectionCurveRadius := SelectionCurveRadius;
|
|
Self.SelectionBlendFactor := SelectionBlendFactor;
|
|
Self.EmptyListMessage := EmptyListMessage;
|
|
{$IF LCL_FullVersion >= 2000000}
|
|
Self.ImagesWidth := ImagesWidth;
|
|
Self.StateImagesWidth := StateImagesWidth;
|
|
Self.CustomCheckImagesWidth := CustomCheckImagesWidth;
|
|
{$IFEND}
|
|
end
|
|
else
|
|
inherited;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.AutoScale();
|
|
|
|
// If toAutoChangeScale is set, this method ensures that the defaulz node height is set corectly.
|
|
|
|
var
|
|
lTextHeight: Cardinal;
|
|
begin
|
|
if HandleAllocated and (toAutoChangeScale in TreeOptions.AutoOptions) then
|
|
begin
|
|
Canvas.Font.Assign(Self.Font);
|
|
lTextHeight := Canvas.TextHeight('Tg');
|
|
if (lTextHeight > Self.DefaultNodeHeight) then
|
|
Self.DefaultNodeHeight := lTextHeight;
|
|
end;
|
|
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)
|
|
and ((Column < 0) or (coEditable in FHeader.Columns[Column].Options));
|
|
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;
|
|
begin
|
|
if (FSelectionCount > 0) and not (toReadOnly in FOptions.FMiscOptions) then
|
|
begin
|
|
if OleSetClipboard(TVTDataObject.Create(Self, True)) = 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 Assigned(Node) and (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 IsEffectivelyVisible[Run] 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
|
|
else if ResetHasChildren then
|
|
Exclude(Node.States, vsHasChildren);
|
|
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;
|
|
|
|
if not ParentClearing then
|
|
InterruptValidation;
|
|
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$IF LCL_FullVersion >= 1080000}
|
|
procedure TBaseVirtualTree.DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
|
|
const AXProportion, AYProportion: Double);
|
|
begin
|
|
inherited;
|
|
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
|
|
begin
|
|
DisableAutoSizing;
|
|
try
|
|
if IsDefaultNodeHeightStored then
|
|
FDefaultNodeHeight := Round(FDefaultNodeHeight * AYProportion);
|
|
if IsIndentStored then
|
|
FIndent := Round(FIndent * AYProportion);
|
|
if IsMarginStored then
|
|
FMargin := Round(FMargin * AXProportion);
|
|
if IsTextMarginStored then
|
|
FTextMargin := Round(FTextMargin * AXProportion);
|
|
if IsSelectionCurveRadiusStored then
|
|
FSelectionCurveRadius := Round(FSelectionCurveRadius * AXProportion);
|
|
if IsDragHeightStored then
|
|
FDragHeight := Round(FDragHeight * AYProportion);
|
|
if IsDragWidthStored then
|
|
FDragWidth := Round(FDragWidth * AXProportion);
|
|
FHeader.AutoAdjustLayout(AXProportion, AYProportion);
|
|
PrepareBitmaps(true, false);
|
|
finally
|
|
EnableAutoSizing;
|
|
end;
|
|
end;
|
|
end;
|
|
{$IFEND}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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 begin
|
|
DoUpdating(usEnd);
|
|
EnsureNodeSelected();
|
|
end
|
|
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, True);
|
|
until (Node = Stop) or not Assigned(Node);
|
|
|
|
// Collapse the start node too.
|
|
if Assigned(Stop) and ([vsHasChildren, vsExpanded] * Stop.States = [vsHasChildren, vsExpanded]) then
|
|
ToggleNode(Stop);
|
|
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;
|
|
CacheIsAvailable: Boolean;
|
|
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.).
|
|
if not IsEffectivelyVisible[Node] then
|
|
Exit;
|
|
Temp := Node;
|
|
Indent := 0;
|
|
if not (toFixedIndent in FOptions.FPaintOptions) then
|
|
begin
|
|
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;
|
|
end;//if not toFixedIndent
|
|
|
|
// Here we know the node is visible.
|
|
Offset := 0;
|
|
CacheIsAvailable := False;
|
|
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);
|
|
CacheIsAvailable := Assigned(Temp);
|
|
while Assigned(Temp) and (Temp <> Node) do
|
|
begin
|
|
Inc(Offset, NodeHeight[Temp]);
|
|
Temp := GetNextVisibleNoInit(Temp, True);
|
|
end;
|
|
end;
|
|
if not CacheIsAvailable then
|
|
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, GetRealCheckImagesWidth + 2);
|
|
end;
|
|
|
|
// Consider associated images.
|
|
if Assigned(FStateImages) and HasImage(Node, ikState, Column) then
|
|
Inc(Offset, GetRealStateImagesWidth + 2);
|
|
if Assigned(FImages) and HasImage(Node, ikNormal, Column) then
|
|
Inc(Offset, GetNodeImageSize(Node).cx + 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, {%H-}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.GetEffectivelyFiltered(Node: PVirtualNode): Boolean;
|
|
|
|
// Checks if a node is effectively filtered out. This depends on the nodes state and the paint options.
|
|
|
|
begin
|
|
if Assigned(Node) then
|
|
Result := (vsFiltered in Node.States) and not (toShowFilteredNodes in FOptions.FPaintOptions)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetEffectivelyVisible(Node: PVirtualNode): Boolean;
|
|
|
|
begin
|
|
Result := (vsVisible in Node.States) and not IsEffectivelyFiltered[Node];
|
|
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.GetFirstChildNoInit(Node: PVirtualNode): PVirtualNode;
|
|
// Determines the first child of the given node but does not initialize it.
|
|
|
|
begin
|
|
if (Node = nil) or (Node = FRoot) then
|
|
Result := FRoot.FirstChild
|
|
else
|
|
begin
|
|
if vsHasChildren in Node.States then
|
|
Result := Node.FirstChild
|
|
else
|
|
Result := nil;
|
|
end;
|
|
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;
|
|
IncludeFiltered: Boolean = False): 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 are 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;
|
|
|
|
if Assigned(Result) and not IncludeFiltered and IsEffectivelyFiltered[Result] then
|
|
Result := GetNextVisible(Result);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetFirstVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) or
|
|
(not IncludeFiltered and IsEffectivelyFiltered[Node])) then
|
|
Result := GetNextVisibleSibling(Result, IncludeFiltered);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetFirstVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) or
|
|
(not IncludeFiltered and IsEffectivelyFiltered[Node])) then
|
|
Result := GetNextVisibleSiblingNoInit(Result, IncludeFiltered);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetFirstVisibleNoInit(Node: PVirtualNode = nil;
|
|
ConsiderChildrenAbove: Boolean = True; IncludeFiltered: Boolean = False): 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;
|
|
|
|
if Assigned(Result) and not IncludeFiltered and IsEffectivelyFiltered[Result] then
|
|
Result := GetNextVisibleNoInit(Result);
|
|
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);
|
|
|
|
// 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);
|
|
Inc(Y, -FOffsetY);
|
|
end;
|
|
HitInfo.HitPoint.X := X;
|
|
HitInfo.HitPoint.Y := Y;
|
|
|
|
// If the point is in the tree area then check the nodes.
|
|
if HitInfo.HitPositions = [] then
|
|
begin
|
|
HitInfo.HitNode := InternalGetNodeAt(X, Y, False, {%H-}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), {%H-}ColLeft, {%H-}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;
|
|
IncludeFiltered: Boolean = False): PVirtualNode;
|
|
|
|
// Returns the very last visible node in the tree while optionally considering toChildrenAbove.
|
|
// The nodes are intialized all the way up including the result node.
|
|
|
|
var
|
|
Run: PVirtualNode;
|
|
|
|
begin
|
|
Result := GetLastVisibleNoInit(Node, ConsiderChildrenAbove);
|
|
|
|
Run := Result;
|
|
while Assigned(Run) and (Run <> Node) and (Run <> RootNode) do
|
|
begin
|
|
if not (vsInitialized in Run.States) then
|
|
InitNode(Run);
|
|
Run := Run.Parent;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetLastVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) or
|
|
(not IncludeFiltered and IsEffectivelyFiltered[Node])) then
|
|
Result := GetPreviousVisibleSibling(Result, IncludeFiltered);
|
|
|
|
if Assigned(Result) and not (vsInitialized in Result.States) then
|
|
InitNode(Result);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetLastVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) or
|
|
(not IncludeFiltered and IsEffectivelyFiltered[Node])) then
|
|
Result := GetPreviousVisibleSiblingNoInit(Result, IncludeFiltered);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetLastVisibleNoInit(Node: PVirtualNode = nil;
|
|
ConsiderChildrenAbove: Boolean = True; IncludeFiltered: Boolean = False): PVirtualNode;
|
|
|
|
// Returns the very last visible node in the tree while optionally considering toChildrenAbove.
|
|
// No initialization is performed.
|
|
|
|
begin
|
|
Result := GetLastNoInit(Node, ConsiderChildrenAbove);
|
|
while Assigned(Result) and (Result <> Node) do
|
|
begin
|
|
if FullyVisible[Result] and
|
|
(IncludeFiltered or not IsEffectivelyFiltered[Result]) then
|
|
Break;
|
|
Result := GetPreviousNoInit(Result, ConsiderChildrenAbove);
|
|
end;
|
|
|
|
if (Result = Node) then // i.e. there is no visible node
|
|
Result := nil;
|
|
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;
|
|
AssumeImage: Boolean;
|
|
WithCheck,
|
|
WithStateImages: Boolean;
|
|
CheckOffset,
|
|
StateImageOffset: Integer;
|
|
|
|
begin
|
|
if OperationCanceled then
|
|
begin
|
|
// Behave non-destructive.
|
|
Result := FHeader.FColumns[Column].Width;
|
|
Exit;
|
|
end
|
|
else
|
|
Result := 0;
|
|
|
|
StartOperation(okGetMaxColumnWidth);
|
|
try
|
|
if Assigned(FOnBeforeGetMaxColumnWidth) then
|
|
FOnBeforeGetMaxColumnWidth(FHeader, Column, UseSmartColumnWidth);
|
|
|
|
WithStateImages := Assigned(FStateImages);
|
|
if WithStateImages then
|
|
StateImageOffset := GetRealStateImagesWidth + 2
|
|
else
|
|
StateImageOffset := 0;
|
|
|
|
if Assigned(FCheckImages) then
|
|
CheckOffset := GetRealCheckImagesWidth + 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;
|
|
|
|
AssumeImage := False;
|
|
while Assigned(Run) and not OperationCanceled do
|
|
begin
|
|
TextLeft := NodeLeft;
|
|
if WithCheck and (Run.CheckType <> ctNone) then
|
|
Inc(TextLeft, CheckOffset);
|
|
if Assigned(FImages) and (AssumeImage or HasImage(Run, ikNormal, Column)) then
|
|
begin
|
|
TextLeft := TextLeft + GetNodeImageSize(Run).cx + 2;
|
|
AssumeImage := True;// From now on, assume that the nodes do ave an image
|
|
end;
|
|
if WithStateImages and HasImage(Run, ikState, Column) then
|
|
Inc(TextLeft, StateImageOffset);
|
|
|
|
CurrentWidth := DoGetNodeWidth(Run, Column);
|
|
Inc(CurrentWidth, DoGetNodeExtraWidth(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);
|
|
|
|
finally
|
|
EndOperation(okGetMaxColumnWidth);
|
|
end;
|
|
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.GetNextChecked(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode;
|
|
begin
|
|
Result := Self.GetNextChecked(Node, csCheckedNormal, ConsiderChildrenAbove);
|
|
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.GetNextSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
|
|
|
// Returns the next sibling of Node.
|
|
|
|
begin
|
|
Result := Node;
|
|
if Assigned(Result) then
|
|
begin
|
|
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
|
|
|
Result := Result.NextSibling;
|
|
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.');
|
|
|
|
repeat
|
|
// 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, True);
|
|
|
|
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;
|
|
until not Assigned(Result) or IsEffectivelyVisible[Result];
|
|
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.');
|
|
|
|
repeat
|
|
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, True);
|
|
|
|
// 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;
|
|
until not Assigned(Result) or IsEffectivelyVisible[Result];
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetNextVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) and
|
|
(IncludeFiltered or not IsEffectivelyFiltered[Result]));
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetNextVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) and
|
|
(IncludeFiltered or not IsEffectivelyFiltered[Result]));
|
|
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, {%H-}Dummy);
|
|
end;
|
|
|
|
function TBaseVirtualTree.GetNodeAt(const P: TPoint): PVirtualNode;
|
|
begin
|
|
Result := GetNodeAt(P.X, P.Y);
|
|
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
|
|
begin
|
|
Result := PByte(@Node.Data) + FTotalInternalDataSize;
|
|
Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
|
|
end;
|
|
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;
|
|
|
|
// Returns the previous 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.PrevSibling;
|
|
if Assigned(Result) and not (vsInitialized in Result.States) then
|
|
InitNode(Result);
|
|
end;
|
|
end;
|
|
|
|
function TBaseVirtualTree.GetPreviousSiblingNoInit(Node: PVirtualNode): PVirtualNode;
|
|
|
|
// Returns the previous sibling of Node
|
|
|
|
begin
|
|
Result := Node;
|
|
if Assigned(Result) then
|
|
begin
|
|
Assert(Result <> FRoot, 'Node must not be the hidden root node.');
|
|
|
|
Result := Result.PrevSibling;
|
|
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, True);
|
|
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, True)
|
|
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, 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 IsEffectivelyVisible[Result];
|
|
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, True);
|
|
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.
|
|
Result := Result.PrevSibling;
|
|
if vsVisible in Result.States then
|
|
Break;
|
|
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, True)
|
|
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, 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 IsEffectivelyVisible[Result];
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetPreviousVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): 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) and
|
|
(IncludeFiltered or not IsEffectivelyFiltered[Result]));
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetPreviousVisibleSiblingNoInit(Node: PVirtualNode;
|
|
IncludeFiltered: Boolean = False): 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) and
|
|
(IncludeFiltered or not IsEffectivelyFiltered[Result]));
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.Nodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for all nodes
|
|
|
|
begin
|
|
Result.FMode := vneAll;
|
|
Result.FTree := Self;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.CheckedNodes(State: TCheckState; ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for all checked nodes
|
|
|
|
begin
|
|
Result.FMode := vneChecked;
|
|
Result.FTree := Self;
|
|
Result.FState := State;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.ChildNodes(Node: PVirtualNode): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for child nodes
|
|
|
|
begin
|
|
Result.FMode := vneChild;
|
|
Result.FTree := Self;
|
|
Result.FNode := Node;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.CutCopyNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for cut copy node
|
|
|
|
begin
|
|
Result.FMode := vneCutCopy;
|
|
Result.FTree := Self;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.InitializedNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for initialized nodes
|
|
|
|
begin
|
|
Result.FMode := vneInitialized;
|
|
Result.FTree := Self;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.LeafNodes: TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for leaf nodes
|
|
|
|
begin
|
|
Result.FMode := vneLeaf;
|
|
Result.FTree := Self;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.LevelNodes(NodeLevel: Cardinal): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for level nodes
|
|
|
|
begin
|
|
Result.FMode := vneLevel;
|
|
Result.FTree := Self;
|
|
Result.FNodeLevel := NodeLevel;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.NoInitNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for no init nodes
|
|
begin
|
|
Result.FMode := vneNoInit;
|
|
Result.FTree := Self;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.SelectedNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for selected nodes
|
|
|
|
begin
|
|
Result.FMode := vneSelected;
|
|
Result.FTree := Self;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.VisibleNodes(Node: PVirtualNode; ConsiderChildrenAbove: Boolean;
|
|
IncludeFiltered: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for visible nodes
|
|
|
|
begin
|
|
Result.FMode := vneVisible;
|
|
Result.FTree := Self;
|
|
Result.FNode := Node;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
Result.FIncludeFiltered := IncludeFiltered;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.VisibleChildNodes(Node: PVirtualNode; IncludeFiltered: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for visible child nodes
|
|
|
|
begin
|
|
Result.FMode := vneVisibleChild;
|
|
Result.FTree := Self;
|
|
Result.FNode := Node;
|
|
Result.FIncludeFiltered := IncludeFiltered;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.VisibleChildNoInitNodes(Node: PVirtualNode; IncludeFiltered: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for visible child no init nodes
|
|
|
|
begin
|
|
Result.FMode := vneVisibleNoInitChild;
|
|
Result.FTree := Self;
|
|
Result.FNode := Node;
|
|
Result.FIncludeFiltered := IncludeFiltered;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.VisibleNoInitNodes(Node: PVirtualNode; ConsiderChildrenAbove: Boolean;
|
|
IncludeFiltered: Boolean): TVTVirtualNodeEnumeration;
|
|
|
|
// Enumeration for visible no init nodes
|
|
|
|
begin
|
|
Result.FMode := vneVisibleNoInit;
|
|
Result.FTree := Self;
|
|
Result.FNode := Node;
|
|
Result.FConsiderChildrenAbove := ConsiderChildrenAbove;
|
|
Result.FIncludeFiltered := IncludeFiltered;
|
|
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
|
|
Result := nil;
|
|
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; IncludeFiltered: Boolean = False): 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] or (not IncludeFiltered and IsEffectivelyFiltered[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 SizeOf(Pointer) 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 >= SizeOf(Pointer) then
|
|
begin
|
|
NodeData := Pointer(PByte(@Result.Data) + FTotalInternalDataSize);
|
|
NodeData^ := UserData;
|
|
Include(Result.States, vsOnFreeNodeCallRequired);
|
|
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) and HandleAllocated then
|
|
begin
|
|
if (Node = nil) or (Node = FRoot) then
|
|
Invalidate
|
|
else
|
|
if (vsInitialized in Node.States) and IsEffectivelyVisible[Node] 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);
|
|
if Self.SelectedCount = 0 then
|
|
FNextNodeToSelect := nil;//Ensure that no other node is selected now
|
|
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({%H-}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({%H-}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);
|
|
if Assigned(FOnLoadTree) then
|
|
FOnLoadTree(Self, Stream);
|
|
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) {$if CompilerVersion < 20}and (MainThreadId = GetCurrentThreadId){$ifend} then
|
|
begin
|
|
Include(Node.States, vsHeightMeasured);
|
|
if (toVariableNodeHeight in FOptions.FMiscOptions) then
|
|
begin
|
|
NewNodeHeight := Node.NodeHeight;
|
|
{$if CompilerVersion >= 20} // Anonymous methods help to make this thread safe easily. In Delphi 2007 and lower developers must take care themselves about thread synchronization when consuming the OnMeasureItemHeight event
|
|
if (MainThreadId <> GetCurrentThreadId) then
|
|
TThread.Synchronize(nil,
|
|
procedure
|
|
begin
|
|
DoMeasureItem(Canvas, Node, NewNodeHeight);
|
|
SetNodeHeight(Node, NewNodeHeight);
|
|
end
|
|
)
|
|
else
|
|
{$ifend}
|
|
begin
|
|
DoMeasureItem(Canvas, Node, NewNodeHeight);
|
|
SetNodeHeight(Node, NewNodeHeight);
|
|
end;
|
|
end;
|
|
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
|
|
TargetTree.InitNode(Target)
|
|
else
|
|
if (vsHasChildren in Target.States) and (Target.ChildCount = 0) then
|
|
TargetTree.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, // ordered rectangle used for drawing the selection focus rect
|
|
ClipRect: TRect; // area to which the canvas will be clipped when painting a node's content
|
|
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
|
|
|
|
MaxRight,
|
|
ColLeft,
|
|
ColRight: Integer;
|
|
|
|
SavedTargetDC: Integer;
|
|
PaintWidth: Integer;
|
|
CurrentNodeHeight: Integer;
|
|
lUseSelectedBkColor: Boolean; // determines if the dotted grid lines need to be painted in selection color of background color
|
|
|
|
CellIsTouchingClientRight: Boolean;
|
|
CellIsInLastColumn: Boolean;
|
|
ColumnIsFixed: Boolean;
|
|
|
|
{$ifdef LCLCocoa}
|
|
sc: Double; // the retina scale. 1.0 for no-retina
|
|
cg: CGContextRef; // tracking the Context of Bitmap
|
|
cglast: CGContextRef; // the last Context of Bitmap.
|
|
// The scale is applied only when the context changes
|
|
{$endif}
|
|
begin
|
|
{$ifdef LCLCocoa}
|
|
cglast := nil;
|
|
sc := GetCanvasScaleFactor;
|
|
{$endif}
|
|
{$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);
|
|
|
|
if poUnbuffered in PaintOptions then
|
|
SavedTargetDC := SaveDC(TargetCanvas.Handle)
|
|
else
|
|
SavedTargetDC := 0;
|
|
|
|
// Prepare paint info structure and lock the back bitmap canvas to avoid that it gets freed on the way.
|
|
FillChar({%H-}PaintInfo, SizeOf(PaintInfo), 0);
|
|
|
|
PaintWidth := Window.Right - Window.Left;
|
|
|
|
if not (poUnbuffered in PaintOptions) then
|
|
begin
|
|
// 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;
|
|
|
|
{$ifdef LCLCocoa}
|
|
NodeBitmap.Width := Round(PaintWidth*sc);
|
|
cg := TCocoaBitmapContext(NodeBitmap.Canvas.Handle).CGContext;
|
|
{$else}
|
|
NodeBitmap.Width := PaintWidth;
|
|
{$endif}
|
|
|
|
// Make sure the buffer bitmap and target bitmap use the same transformation mode.
|
|
{$ifndef Gtk}
|
|
SetMapMode(NodeBitmap.Canvas.Handle, GetMapMode(TargetCanvas.Handle));
|
|
{$endif}
|
|
PaintInfo.Canvas := NodeBitmap.Canvas;
|
|
end
|
|
else
|
|
begin
|
|
PaintInfo.Canvas := TargetCanvas;
|
|
NodeBitmap := nil;
|
|
end;
|
|
|
|
// Lock the canvas to avoid that it gets freed on the way.
|
|
PaintInfo.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) and (GetKeyState(VK_LBUTTON) < 0);
|
|
{$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);
|
|
|
|
// 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;
|
|
TargetCanvas.Font := Self.Font;
|
|
|
|
// 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) + 1;
|
|
|
|
// ----- 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, {%H-}LineImage);
|
|
IndentSize := Length(LineImage);
|
|
if not (toFixedIndent in FOptions.FPaintOptions) then
|
|
ButtonX := (IndentSize - 1) * Integer(FIndent) + Round((Integer(FIndent) - FPlusBM.Width) / 2) + 1;
|
|
|
|
// 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
|
|
if not (poUnbuffered in PaintOptions) then
|
|
begin
|
|
// Adjust height of temporary node bitmap.
|
|
with NodeBitmap do
|
|
begin
|
|
if Height < PaintInfo.Node.NodeHeight then
|
|
begin
|
|
// Avoid that the VCL copies the bitmap while changing its height.
|
|
{$ifdef LCLCocoa}
|
|
if Height > 0 then SetSize(1,1); // can't go to 0, must keep canvas
|
|
SetSize(Round(PaintWidth*sc), Round(PaintInfo.Node.NodeHeight * sc));
|
|
cg := TCocoaBitmapContext(NodeBitmap.Canvas.Handle).CGContext;
|
|
if cglast <> cg then
|
|
begin
|
|
CGContextScaleCTM(cg, sc, sc);
|
|
cglast := cg;
|
|
end;
|
|
{$else}
|
|
if Height > 0 then SetSize(1,1); // can't go to 0, must keep canvas
|
|
SetSize(PaintWidth, PaintInfo.Node.NodeHeight);
|
|
{$endif}
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(Canvas, Window.Left, 0);
|
|
{$else}
|
|
SetWindowOrgEx(Canvas.Handle, Window.Left, 0, nil);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
// 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
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(PaintInfo.Canvas, -TargetRect.Left + Window.Left, -TargetRect.Top);
|
|
{$else}
|
|
SetWindowOrgEx(PaintInfo.Canvas.Handle, -TargetRect.Left + Window.Left, -TargetRect.Top, nil);
|
|
{$endif}
|
|
ClipCanvas(PaintInfo.Canvas, Rect(TargetRect.Left, TargetRect.Top, TargetRect.Right,
|
|
Min(TargetRect.Bottom, MaximumBottom)));
|
|
end;
|
|
|
|
// Set the origin of the canvas' brush. This depends on the node heights.
|
|
with PaintInfo do
|
|
SetBrushOrigin(Canvas, BrushOrigin.X, BrushOrigin.Y);
|
|
|
|
CurrentNodeHeight := PaintInfo.Node.NodeHeight;
|
|
R.Bottom := CurrentNodeHeight;
|
|
|
|
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, 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(GetRealStateImagesWidth, GetRealStateImagesHeight, 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(ImageInfo[iiNormal].Images, 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
|
|
begin
|
|
ClipRect := CellRect;
|
|
if poUnbuffered in PaintOptions then
|
|
begin
|
|
ClipRect.Left := Max(ClipRect.Left, Window.Left);
|
|
ClipRect.Right := Min(ClipRect.Right, Window.Right);
|
|
ClipRect.Top := Max(ClipRect.Top, Window.Top - (BaseOffset - CurrentNodeHeight));
|
|
ClipRect.Bottom := ClipRect.Bottom - Max(TargetRect.Bottom - MaximumBottom, 0);
|
|
end;
|
|
ClipCanvas(Canvas, ClipRect);
|
|
end;
|
|
|
|
// 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.
|
|
if (poGridLines in PaintOptions) and (toShowVertGridLines in FOptions.FPaintOptions) then
|
|
begin
|
|
// These variables and the nested if conditions shall make the logic
|
|
// easier to understand.
|
|
CellIsTouchingClientRight := PaintInfo.CellRect.Right = Window.Right;
|
|
CellIsInLastColumn := Position = TColumnPosition(Count - 1);
|
|
ColumnIsFixed := coFixed in FHeader.FColumns[Column].Options;
|
|
|
|
// Don't draw if this is the last column and the header is in autosize mode.
|
|
if not ((hoAutoResize in FHeader.FOptions) and CellIsInLastColumn) then
|
|
begin
|
|
// We have to take spanned cells into account which we determine
|
|
// by checking if CellRect.Right equals the Window.Right.
|
|
// But since the PaintTree procedure is called twice in
|
|
// TBaseVirtualTree.Paint (i.e. for fixed columns and other columns.
|
|
// CellIsTouchingClientRight does not work for fixed columns.)
|
|
// we have to paint fixed column grid line anyway.
|
|
if not CellIsTouchingClientRight or ColumnIsFixed then
|
|
begin
|
|
if (BidiMode = bdLeftToRight) or not ColumnIsEmpty(Node, Column) then
|
|
begin
|
|
Canvas.Font.Color := FColors.GridLineColor;
|
|
lUseSelectedBkColor := (poDrawSelection in PaintOptions) and (toFullRowSelect in FOptions.FSelectionOptions) and
|
|
(vsSelected in Node.States) and not (toUseBlendedSelection in FOptions.PaintOptions) and not
|
|
(tsUseExplorerTheme in FStates);
|
|
DrawDottedVLine(PaintInfo, CellRect.Top, CellRect.Bottom, CellRect.Right - 1, lUseSelectedBkColor);
|
|
end;
|
|
|
|
Dec(CellRect.Right);
|
|
Dec(ContentRect.Right);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Prepare background and focus rect for the current cell.
|
|
PrepareCell(PaintInfo, Window.Left, PaintWidth);
|
|
{$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) and
|
|
(not (toHideTreeLinesIfThemed in FOptions.FPaintOptions) or
|
|
not (tsUseThemes in FStates)) 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(Canvas, PaintInfo.ImageInfo[iiCheck], vsSelected in PaintInfo.Node.States);
|
|
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, PaintWidth,
|
|
CurrentNodeHeight));
|
|
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 ManualClipNeeded}
|
|
//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.
|
|
if not (poUnbuffered in PaintOptions) then
|
|
with TWithSafeRect(TargetRect), NodeBitmap do
|
|
begin
|
|
{$ifdef LCLCocoa}
|
|
StretchBlt(
|
|
TargetCanvas.Handle,
|
|
Left,
|
|
Top + YCorrect,
|
|
PaintWidth,
|
|
PaintInfo.Node.NodeHeight - YCorrect,
|
|
Canvas.Handle,
|
|
Window.Left,
|
|
Round(YCorrect * sc),
|
|
NodeBitmap.Width,
|
|
Round(PaintInfo.Node.NodeHeight * sc) - Round(YCorrect * sc),
|
|
SRCCOPY
|
|
);
|
|
{$else}
|
|
BitBlt(TargetCanvas.Handle, Left,
|
|
Top {$ifdef ManualClipNeeded} + YCorrect{$endif}, Width, PaintInfo.Node.NodeHeight, Canvas.Handle, Window.Left,
|
|
{$ifdef ManualClipNeeded}YCorrect{$else}0{$endif}, SRCCOPY);
|
|
{$endif}
|
|
end;
|
|
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}
|
|
|
|
if not (poUnbuffered in PaintOptions) then
|
|
begin
|
|
// Avoid unnecessary copying of bitmap content. This will destroy the DC handle too.
|
|
NodeBitmap.Height := 0;
|
|
NodeBitmap.PixelFormat := pf32Bit;
|
|
NodeBitmap.Width := TargetRect.Right - TargetRect.Left;
|
|
NodeBitmap.Height := TargetRect.Bottom - TargetRect.Top;
|
|
end;
|
|
|
|
{$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.
|
|
if not DoPaintBackground(PaintInfo.Canvas, TargetRect) then
|
|
begin
|
|
if UseBackground then
|
|
begin
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(PaintInfo.Canvas, 0, 0);
|
|
{$else}
|
|
SetWindowOrgEx(PaintInfo.Canvas.Handle, 0, 0, nil);
|
|
{$endif}
|
|
if toStaticBackground in TreeOptions.PaintOptions then
|
|
StaticBackground(FBackground.Bitmap, PaintInfo.Canvas, Target, TargetRect)
|
|
else
|
|
TileBackground(FBackground.Bitmap, PaintInfo.Canvas, Target, TargetRect);
|
|
end
|
|
else
|
|
begin
|
|
// Consider here also colors of the columns.
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(PaintInfo.Canvas, Target.X, 0); // This line caused issue #313 when it was placed above the if-statement
|
|
{$else}
|
|
SetWindowOrgEx(PaintInfo.Canvas.Handle, Target.X, 0, nil);
|
|
{$endif}
|
|
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;
|
|
|
|
// Initialize MaxRight.
|
|
MaxRight := Target.X - 1;
|
|
|
|
PaintInfo.Canvas.Font.Color := FColors.GridLineColor;
|
|
while (FirstColumn <> InvalidColumn) and (MaxRight < TargetRect.Right + Target.X) do
|
|
begin
|
|
// Determine left and right coordinate of the current column
|
|
ColLeft := Items[FirstColumn].Left;
|
|
ColRight := (ColLeft + Items[FirstColumn].FWidth);
|
|
|
|
// Check wether this column needs to be painted at all.
|
|
if (ColRight >= MaxRight) then
|
|
begin
|
|
R.Left := MaxRight; // Continue where we left off
|
|
R.Right := ColRight; // Paint to the right of the column
|
|
MaxRight := ColRight; // And record were to start the next column.
|
|
|
|
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
|
|
PaintInfo.Canvas.Brush.Color := Items[FirstColumn].FColor
|
|
else
|
|
PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor;
|
|
PaintInfo.Canvas.FillRect(R);
|
|
end;
|
|
FirstColumn := GetNextVisibleColumn(FirstColumn);
|
|
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);
|
|
PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor;
|
|
PaintInfo.Canvas.FillRect(R);
|
|
end;
|
|
end;
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(PaintInfo.Canvas, 0, 0);
|
|
{$else}
|
|
SetWindowOrgEx(PaintInfo.Canvas.Handle, 0, 0, nil);
|
|
{$endif}
|
|
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.
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(PaintInfo.Canvas, 0, 0);
|
|
{$else}
|
|
SetWindowOrgEx(PaintInfo.Canvas.Handle, 0, 0, nil);
|
|
{$endif}
|
|
PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor;
|
|
PaintInfo.Canvas.FillRect(TargetRect);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ifdef UseSetCanvasOrigin}
|
|
SetCanvasOrigin(PaintInfo.Canvas, 0, 0);
|
|
{$else}
|
|
SetWindowOrgEx(PaintInfo.Canvas.Handle, 0, 0, nil);
|
|
{$endif}
|
|
{$ifdef DEBUG_VTV}Logger.Watch([lcPaintDetails],'DrawSelectionRect',DrawSelectionRect);{$endif}
|
|
if DrawSelectionRect then
|
|
begin
|
|
R := OrderRect(FNewSelRect);
|
|
//lclheader
|
|
if hoVisible in FHeader.Options then
|
|
OffsetRect(R, 0, FHeader.Height);
|
|
// 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}
|
|
SetBrushOrigin(PaintInfo.Canvas, 0, Target.X and 1);
|
|
{$endif}
|
|
PaintSelectionRectangle(PaintInfo.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}
|
|
if not (poUnBuffered in PaintOptions) then
|
|
with Target, NodeBitmap do
|
|
BitBlt(TargetCanvas.Handle, X, Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY);
|
|
end;
|
|
finally
|
|
PaintInfo.Canvas.Unlock;
|
|
if poUnbuffered in PaintOptions then
|
|
RestoreDC(TargetCanvas.Handle, SavedTargetDC)
|
|
else
|
|
NodeBitmap.Free;
|
|
end;
|
|
|
|
if (ChildCount[nil] = 0) and (FEmptyListMessage <> '') then
|
|
begin
|
|
// output a message if no items are to display
|
|
Canvas.Font := Self.Font;
|
|
SetBkMode(TargetCanvas.Handle, TRANSPARENT);
|
|
R.Left := OffSetX + 2;
|
|
R.Top := 2;
|
|
R.Right := R.Left + Width - 2;
|
|
R.Bottom := Height -2;
|
|
TargetCanvas.Font.Color := clGrayText;
|
|
//lcl: LCL has no support for tfNoClip, tfLeft
|
|
//TargetCanvas.TextRect(R, FEmptyListMessage, [tfNoClip, tfLeft]);
|
|
TextOut(TargetCanvas.Handle, 2 - Window.Left, 2 - Window.Top, PAnsiChar(FEmptyListMessage), Length(FEmptyListMessage));
|
|
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
|
|
begin
|
|
// 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
|
|
begin
|
|
if Source <> Self then
|
|
Source.FinishCutOrCopy
|
|
else
|
|
DoStateChange([], [tsCutPending]);
|
|
end;
|
|
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.
|
|
PaintRect := TreeRect;
|
|
with TWithSafeRect(TreeRect) do
|
|
begin
|
|
//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 := FColors.BackGroundColor;
|
|
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 := FColors.BackGroundColor;
|
|
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 := FColors.BackGroundColor;
|
|
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;
|
|
|
|
//----------------- TBaseVirtualTree -----------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; R:
|
|
TRect; Column: TColumnIndex);
|
|
|
|
begin
|
|
if Assigned(FOnDrawHint) then
|
|
FOnDrawHint(Self, Canvas, Node, R, Column);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoGetHintSize(Node: PVirtualNode; Column:
|
|
TColumnIndex; var R: TRect);
|
|
|
|
begin
|
|
if Assigned(FOnGetHintSize) then
|
|
FOnGetHintSize(Self, Node, Column, R);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.DoGetHintKind(Node: PVirtualNode; Column:
|
|
TColumnIndex; var Kind: TVTHintKind);
|
|
|
|
begin
|
|
Kind := DefaultHintKind;
|
|
if Assigned(FOnGetHintKind) then
|
|
FOnGetHintKind(Self, Node, Column, Kind);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.GetDefaultHintKind: TVTHintKind;
|
|
|
|
begin
|
|
Result := vhkText;
|
|
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;
|
|
if Assigned(FOnSaveTree) then
|
|
FOnSaveTree(Self, Stream);
|
|
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
|
|
if not HandleAllocated then
|
|
exit;
|
|
//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 if not (coFixed in Header.Columns[Column].Options) then
|
|
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
|
|
else
|
|
Result := True;
|
|
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;
|
|
CompareResult: Integer;
|
|
begin
|
|
// This avoids checking for Result = nil in the loops.
|
|
Result := @Dummy;
|
|
while Assigned(A) and Assigned(B) do
|
|
begin
|
|
if OperationCanceled then
|
|
CompareResult := 0
|
|
else
|
|
CompareResult := DoCompare(A, B, Column);
|
|
|
|
if CompareResult <= 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;
|
|
CompareResult: Integer;
|
|
|
|
begin
|
|
// this avoids checking for Result = nil in the loops
|
|
Result := @Dummy;
|
|
while Assigned(A) and Assigned(B) do
|
|
begin
|
|
if OperationCanceled then
|
|
CompareResult := 0
|
|
else
|
|
CompareResult := DoCompare(A, B, Column);
|
|
|
|
if CompareResult >= 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
|
|
StartOperation(okSortNode);
|
|
try
|
|
// 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);
|
|
finally
|
|
EndOperation(okSortNode);
|
|
end;
|
|
// 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);
|
|
// Recurse to next level
|
|
Run := Node.FirstChild;
|
|
while Assigned(Run) and not FOperationCanceled do
|
|
begin
|
|
if DoInit and not (vsInitialized in Run.States) then
|
|
InitNode(Run);
|
|
if (vsInitialized in Run.States) and (not (toAutoSort in TreeOptions.AutoOptions) or Expanded[Run]) then // There is no need to sort collapsed branches
|
|
DoSort(Run);
|
|
Run := Run.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
//--------------- end local function ----------------------------------------
|
|
|
|
begin
|
|
if RootNode.TotalCount <= 2 then
|
|
Exit;//Nothing to do if there are one or zero nodes. RootNode.TotalCount is 1 if there are no nodes in the treee as the root node counts too here.
|
|
// 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
|
|
begin
|
|
StartOperation(okSortTree);
|
|
try
|
|
DoSort(FRoot);
|
|
finally
|
|
EndOperation(okSortTree);
|
|
end;
|
|
end;
|
|
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,
|
|
FirstVisible: PVirtualNode;
|
|
HeightDelta,
|
|
StepsR1,
|
|
StepsR2,
|
|
Steps: Integer;
|
|
TogglingTree,
|
|
ChildrenInView,
|
|
NeedFullInvalidate,
|
|
NeedUpdate,
|
|
NodeInView,
|
|
PosHoldable,
|
|
TotalFit: Boolean;
|
|
ToggleData: TToggleAnimationData;
|
|
|
|
//--------------- local function --------------------------------------------
|
|
|
|
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 := FColors.BackGroundColor;;
|
|
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 function ----------------------------------------
|
|
|
|
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;
|
|
|
|
// Calculate the height delta right now as we need it for toChildrenAbove anyway.
|
|
HeightDelta := -Integer(Node.TotalHeight) + Integer(NodeHeight[Node]);
|
|
if (FUpdateCount = 0) and (toAnimatedToggle in FOptions.FAnimationOptions) and not
|
|
(tsCollapsing in FStates) then
|
|
begin
|
|
if tsHint in Self.FStates then
|
|
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;
|
|
if toChildrenAbove in FOptions.FPaintOptions then
|
|
begin
|
|
PosHoldable := (FOffsetY + (Integer(Node.TotalHeight) - Integer(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, Integer(Node.TotalHeight) - Integer(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, IfThen(IsEffectivelyFiltered[Node], 0, 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
|
|
begin
|
|
// Ensure the item height is measured
|
|
MeasureItemHeight(Canvas, Child);
|
|
|
|
Inc(HeightDelta, Child.TotalHeight);
|
|
end;
|
|
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
|
|
if tsHint in Self.FStates then
|
|
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;
|
|
if toAutoSort in FOptions.FAutoOptions then
|
|
Sort(Node, FHeader.FSortColumn, FHeader.FSortDirection, False);
|
|
end;// if UpdateCount = 0
|
|
|
|
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;
|
|
UpdateScrollBars(True);
|
|
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
|
|
begin
|
|
FirstVisible := GetFirstVisible(Node, True);
|
|
if Assigned(FirstVisible) then // otherwise there is no visible child at all
|
|
SetOffsetY(FOffsetY - GetDisplayRect(FirstVisible, NoColumn, False).Top);
|
|
end
|
|
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); Moved up
|
|
|
|
// 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.UpdateHorizontalRange;
|
|
|
|
begin
|
|
if not HandleAllocated then
|
|
exit;
|
|
if FHeader.UseColumns then
|
|
FRangeX := FHeader.FColumns.TotalWidth
|
|
else
|
|
FRangeX := GetMaxRightExtend;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.UpdateHorizontalScrollBar(DoRepaint: Boolean);
|
|
|
|
var
|
|
ScrollInfo: TScrollInfo;
|
|
|
|
begin
|
|
if not HandleAllocated then
|
|
exit;
|
|
UpdateHorizontalRange;
|
|
|
|
if (tsUpdating in FStates) or not HandleAllocated 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({%H-}ScrollInfo, SizeOf(ScrollInfo), 0);
|
|
//LCL automatically set cbSize field
|
|
//ScrollInfo.cbSize := SizeOf(ScrollInfo);
|
|
ScrollInfo.fMask := SIF_ALL;
|
|
GetScrollInfo(Handle, SB_HORZ, ScrollInfo);
|
|
|
|
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];
|
|
SetScrollInfo(Handle, SB_HORZ, ScrollInfo, DoRepaint);
|
|
end
|
|
else
|
|
begin
|
|
ScrollInfo.nMin := 0;
|
|
ScrollInfo.nMax := 0;
|
|
ScrollInfo.nPos := 0;
|
|
ScrollInfo.nPage := 0;
|
|
DoShowScrollBar(SB_HORZ, False);
|
|
SetScrollInfo(Handle, SB_HORZ, ScrollInfo, False);
|
|
end;
|
|
|
|
// Since the position is automatically changed if it doesn't meet the range
|
|
// we better read the current position back to stay synchronized.
|
|
FEffectiveOffsetX := GetScrollPos(Handle, SB_HORZ);
|
|
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.UpdateRanges;
|
|
|
|
begin
|
|
UpdateVerticalRange;
|
|
UpdateHorizontalRange;
|
|
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);
|
|
Perform(CM_UPDATE_VCLSTYLE_SCROLLBARS,0,0);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.UpdateVerticalRange;
|
|
|
|
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;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TBaseVirtualTree.UpdateVerticalScrollBar(DoRepaint: Boolean);
|
|
|
|
var
|
|
ScrollInfo: TScrollInfo;
|
|
|
|
begin
|
|
if not HandleAllocated then
|
|
exit;
|
|
UpdateVerticalRange;
|
|
|
|
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;
|
|
GetScrollInfo(Handle, SB_VERT, ScrollInfo);
|
|
|
|
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];
|
|
SetScrollInfo(Handle, SB_VERT, ScrollInfo, DoRepaint);
|
|
end
|
|
else
|
|
begin
|
|
ScrollInfo.nMin := 0;
|
|
ScrollInfo.nMax := 0;
|
|
ScrollInfo.nPos := 0;
|
|
ScrollInfo.nPage := 0;
|
|
DoShowScrollBar(SB_VERT, False);
|
|
SetScrollInfo(Handle, SB_VERT, ScrollInfo, False);
|
|
end;
|
|
|
|
// Since the position is automatically changed if it doesn't meet the range
|
|
// we better read the current position back to stay synchronized.
|
|
SetOffsetY(-GetScrollPos(Handle, SB_VERT));
|
|
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 (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.
|
|
AutoAdjustSize()
|
|
else
|
|
inherited;
|
|
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;
|
|
NextNode: PVirtualNode;
|
|
begin
|
|
Tree := FLink.FTree;
|
|
case Message.CharCode of
|
|
VK_ESCAPE:
|
|
begin
|
|
Tree.DoCancelEdit;
|
|
Tree.SetFocus;
|
|
end;
|
|
VK_RETURN:
|
|
begin
|
|
EndEdit := not (vsMultiline in FLink.FNode.States);
|
|
if not EndEdit then
|
|
begin
|
|
// If a multiline node is being edited the finish editing only if Ctrl+Enter was pressed,
|
|
// otherwise allow to insert line breaks into the text.
|
|
Shift := KeyDataToShiftState(Message.KeyData);
|
|
EndEdit := ssCtrl in Shift;
|
|
end;
|
|
if EndEdit then
|
|
begin
|
|
Tree := FLink.FTree;
|
|
FLink.FTree.InvalidateNode(FLink.FNode);
|
|
FLink.FTree.DoEndEdit;
|
|
Tree.SetFocus;
|
|
end;
|
|
end;
|
|
VK_UP:
|
|
begin
|
|
if not (vsMultiline in FLink.FNode.States) then
|
|
Message.CharCode := VK_LEFT;
|
|
inherited;
|
|
end;
|
|
VK_DOWN:
|
|
begin
|
|
if not (vsMultiline in FLink.FNode.States) then
|
|
Message.CharCode := VK_RIGHT;
|
|
inherited;
|
|
end;
|
|
VK_TAB:
|
|
begin
|
|
if Tree.IsEditing then
|
|
begin
|
|
Tree.InvalidateNode(FLink.FNode);
|
|
NextNode := Tree.GetNextVisible(FLink.FNode, True);
|
|
Tree.EndEditNode;
|
|
Tree.FocusedNode := NextNode;
|
|
if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then
|
|
Tree.DoEdit;
|
|
end;
|
|
end;
|
|
Ord('A'):
|
|
begin
|
|
if Tree.IsEditing and ([ssCtrl] = KeyDataToShiftState(Message.KeyData) {KeyboardStateToShiftState}) then
|
|
begin
|
|
Self.SelectAll();
|
|
Message.CharCode := 0;
|
|
end;
|
|
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: TLCLHandle;
|
|
|
|
begin
|
|
if not (vsMultiline in FLink.FNode.States) and not (toGridExtensions in FLink.FTree.FOptions.FMiscOptions{see issue #252}) 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), {%H-}Size);
|
|
Inc(Size.cx, 2 * FLink.FTree.FTextMargin);
|
|
Inc(Size.cy, 2 * FLink.FTree.FTextMargin);
|
|
Height := Max(Size.cy, Height); // Ensure a minimum height so that the edit field's content and cursor are displayed correctly. See #159
|
|
// Repaint associated node if the edit becomes smaller.
|
|
if Size.cx < Width then
|
|
FLink.FTree.Invalidate();
|
|
|
|
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
|
|
if Assigned(FEdit) then
|
|
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;
|
|
FEdit.AutoAdjustSize;
|
|
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
|
|
if not Assigned(FEdit) then
|
|
begin
|
|
FEdit := TVTEdit.Create(Self);
|
|
FEdit.Visible := False;
|
|
FEdit.BorderStyle := bsSingle;
|
|
FEdit.AutoSize := False;
|
|
end;
|
|
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
|
|
lOffset: Integer;
|
|
|
|
begin
|
|
if not FStopping then
|
|
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 R.Left < 0 then
|
|
R.Left := 0;
|
|
if R.Right - R.Left < 30 then
|
|
begin
|
|
if FAlignment = taRightJustify then
|
|
R.Left := R.Right - 30
|
|
else
|
|
R.Right := R.Left + 30;
|
|
end;
|
|
if R.Right > FTree.ClientWidth then
|
|
R.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;
|
|
lOffset := IfThen(vsMultiline in FNode.States, 0, 2);
|
|
if tsUseThemes in FTree.FStates then
|
|
Inc(lOffset);
|
|
InflateRect(R, -FTree.FTextMargin + lOffset, lOffset);
|
|
if not (vsMultiline in FNode.States) then
|
|
OffsetRect(R, 0, FTextBounds.Top - FEdit.Top);
|
|
R.Top := Max(-1, R.Top); // A value smaller than -1 will prevent the edit cursor from being shown by Windows, see issue #159
|
|
R.Left := Max(-1, R.Left);
|
|
SendMessage(FEdit.Handle, EM_SETRECTNP, 0, {%H-}LPARAM(@R));
|
|
end;
|
|
end;
|
|
|
|
//----------------- TCustomVirtualString -------------------------------------------------------------------------------
|
|
|
|
constructor TCustomVirtualStringTree.Create(AOwner: TComponent);
|
|
|
|
begin
|
|
inherited;
|
|
FPreviouslySelected := nil;
|
|
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;
|
|
tstChecked:
|
|
begin
|
|
Node := GetFirstChecked;
|
|
NextNodeProc := GetNextChecked;
|
|
end;
|
|
else // tstAll
|
|
Node := GetFirst;
|
|
NextNodeProc := GetNext;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TCustomVirtualStringTree.GetDataFromGrid(const AStrings: TStringList;
|
|
const IncludeHeading: Boolean);
|
|
var
|
|
LColIndex : Integer;
|
|
LStartIndex : Integer;
|
|
LAddString : string;
|
|
LCellText : string;
|
|
LChildNode : PVirtualNode;
|
|
begin
|
|
{ Start from the First column. }
|
|
LStartIndex := 0;
|
|
|
|
{ Do it for Header first }
|
|
if IncludeHeading then
|
|
begin
|
|
LAddString := EmptyStr;
|
|
for LColIndex := LStartIndex to Pred(Header.Columns.Count) do
|
|
begin
|
|
if (LColIndex > LStartIndex) then
|
|
LAddString := LAddString + ',';
|
|
LAddString := LAddString + AnsiQuotedStr(Header.Columns.Items[LColIndex].Text, '"');
|
|
end;//for
|
|
AStrings.Add(LAddString);
|
|
end;//if
|
|
|
|
{ Loop thru the virtual tree for Data }
|
|
LChildNode := GetFirst;
|
|
while Assigned(LChildNode) do
|
|
begin
|
|
LAddString := EmptyStr;
|
|
|
|
{ Read for each column and then populate the text }
|
|
for LColIndex := LStartIndex to Pred(Header.Columns.Count) do
|
|
begin
|
|
LCellText := Text[LChildNode, LColIndex];
|
|
if (LCellText = EmptyStr) then
|
|
LCellText := ' ';
|
|
if (LColIndex > LStartIndex) then
|
|
LAddString := LAddString + ',';
|
|
LAddString := LAddString + AnsiQuotedStr(LCellText, '"');
|
|
end;//for - Header.Columns.Count
|
|
|
|
AStrings.Add(LAddString);
|
|
LChildNode := LChildNode.NextSibling;
|
|
end;//while Assigned(LChildNode);
|
|
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.GetStaticText(Node: PVirtualNode; Column: TColumnIndex): String;
|
|
|
|
begin
|
|
Assert(Assigned(Node), 'Node must not be nil.');
|
|
|
|
if not (vsInitialized in Node.States) then
|
|
InitNode(Node);
|
|
Result := '';
|
|
|
|
DoGetText(Node, Column, ttStatic, Result);
|
|
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 Enabled then // Es werden sonst nur die Farben verwendet von Font die an Canvas.Font übergeben wurden
|
|
Canvas.Font.Color := FColors.NodeFontColor
|
|
else
|
|
Canvas.Font.Color := FColors.DisabledColor;
|
|
|
|
if (toHotTrack in FOptions.FPaintOptions) and (Node = FCurrentHotNode) then
|
|
begin
|
|
if not (tsUseExplorerTheme in FStates) then
|
|
begin
|
|
Canvas.Font.Style := Canvas.Font.Style + [fsUnderline];
|
|
Canvas.Font.Color := FColors.HotColor;
|
|
end;
|
|
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
|
|
(tsUseExplorerTheme in FStates) then
|
|
Canvas.Font.Color := FColors.SelectionTextColor;
|
|
end
|
|
else
|
|
if vsSelected in Node.States then
|
|
begin
|
|
if (Focused or (toPopupMode in FOptions.FPaintOptions)) and not
|
|
(tsUseExplorerTheme in FStates) then
|
|
Canvas.Font.Color := FColors.SelectionTextColor;
|
|
end;
|
|
end;
|
|
end;
|
|
if Canvas.Font.Color = clDefault then
|
|
Canvas.Font.Color := GetDefaultColor(dctFont);
|
|
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;
|
|
Height: Integer;
|
|
|
|
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;
|
|
InflateRect(R, -FTextMargin, 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
|
|
DoPaintText(Node, Canvas, Column, ttNormal);
|
|
Height := ComputeNodeHeight(Canvas, Node, Column);
|
|
// 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;
|
|
|
|
// Center the text vertically if it fits entirely into the content rect.
|
|
if R.Bottom - R.Top > Height then
|
|
InflateRect(R, 0, (Height - R.Bottom - R.Top) div 2);
|
|
end
|
|
else
|
|
begin
|
|
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), {%H-}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 Font.Color = clDefault then
|
|
Canvas.Font.Color := GetDefaultColor(dctFont);
|
|
if toFullRowSelect in FOptions.FSelectionOptions then
|
|
begin
|
|
if Node = FDropTargetNode then
|
|
begin
|
|
if (FLastDropMode = dmOnNode) or (vsSelected in Node.States) then
|
|
Canvas.Font.Color := FColors.SelectionTextColor
|
|
else
|
|
Canvas.Font.Color := FColors.NodeFontColor;
|
|
end
|
|
else
|
|
if vsSelected in Node.States then
|
|
begin
|
|
if Focused or (toPopupMode in FOptions.FPaintOptions) then
|
|
Canvas.Font.Color := FColors.SelectionTextColor
|
|
else
|
|
Canvas.Font.Color := FColors.NodeFontColor;
|
|
end;
|
|
if Canvas.Font.Color = clDefault then
|
|
Canvas.Font.Color := GetDefaultColor(dctFont);
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.SaveToCSVFile(
|
|
const FileNameWithPath: TFileName; const IncludeHeading: Boolean): Boolean;
|
|
var
|
|
LResultList : TStringList;
|
|
begin
|
|
Result := False;
|
|
if (FileNameWithPath = '') then
|
|
Exit;
|
|
|
|
LResultList := TStringList.Create;
|
|
try
|
|
{ Get the data from grid. }
|
|
GetDataFromGrid(LResultList, IncludeHeading);
|
|
{ Save File to Disk }
|
|
LResultList.SaveToFile(FileNameWithPath);
|
|
Result := True;
|
|
finally
|
|
FreeAndNil(LResultList);
|
|
end;//try-finally
|
|
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, {%H-}TM);
|
|
FTextHeight := TM.tmHeight;
|
|
|
|
GetTextExtentPoint32(MemDC, '...', 3, {%H-}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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.AddChild(Parent: PVirtualNode; UserData: Pointer): PVirtualNode;
|
|
var
|
|
NewNodeText: String;
|
|
begin
|
|
Result := inherited AddChild(Parent, UserData);
|
|
// Restore the prviously restored node if the caption of this node is knwon and no other node was selected
|
|
if (toRestoreSelection in TreeOptions.SelectionOptions) and Assigned(FPreviouslySelected) and Assigned(OnGetText) then
|
|
begin
|
|
// See if this was the previously selected node and restore it in this case
|
|
Self.OnGetText(Self, Result, 0, ttNormal, {%H-}NewNodeText);
|
|
if FPreviouslySelected.IndexOf(NewNodeText) >= 0 then
|
|
begin
|
|
// Select this node and make sure that the parent node is expanded
|
|
Include(FStates, tsPreviouslySelectedLocked);
|
|
try
|
|
Self.Selected[Result] := True;
|
|
finally
|
|
Exclude(FStates, tsPreviouslySelectedLocked);
|
|
end;
|
|
// if a there is a selected node now, then make sure that it is visible
|
|
if Self.GetFirstSelected <> nil then
|
|
Self.ScrollIntoView(Self.GetFirstSelected, True);
|
|
end;
|
|
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.CalculateStaticTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
const Text: String): Integer;
|
|
|
|
begin
|
|
Result := 0;
|
|
if (Length(Text) > 0) and (Alignment <> taCenter) and not
|
|
(vsMultiline in Node.States) and (toShowStaticText in TreeOptions.FStringOptions) then
|
|
begin
|
|
DoPaintText(Node, Canvas, Column, ttStatic);
|
|
|
|
Inc(Result, DoTextMeasuring(Canvas, Node, Column, Text).cx);
|
|
Inc(Result, FTextMargin);
|
|
end;
|
|
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).cx);
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
|
|
// For hit tests it is necessary to consider cases where columns are empty and automatic column spanning is enabled.
|
|
// This method simply checks the given column's text and if this is empty then the column is considered as being empty.
|
|
|
|
begin
|
|
Result := Length(Text[Node, Column]) = 0;
|
|
// If there is no text then let the ancestor decide if the column is to be considered as being empty
|
|
// (e.g. by asking the application). If there is text then the column is never be considered as being empty.
|
|
if Result then
|
|
Result := inherited ColumnIsEmpty(Node, Column);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
{$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}
|
|
|
|
destructor TCustomVirtualStringTree.Destroy;
|
|
begin
|
|
FreeAndNil(FPreviouslySelected);
|
|
inherited;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink;
|
|
|
|
begin
|
|
Result := inherited DoCreateEditor(Node, Column);
|
|
// Enable generic label editing support if the application does not have own editors.
|
|
if Result = nil then
|
|
Result := TStringEditLink.Create;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex;
|
|
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.DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex;
|
|
Canvas: TCanvas = nil): Integer;
|
|
|
|
begin
|
|
if Canvas = nil then
|
|
Canvas := Self.Canvas;
|
|
Result := CalculateStaticTextWidth(Canvas, Node, Column, StaticText[Node, Column]);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualDrawTree.DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex;
|
|
var LineBreakStyle: TVTTooltipLineBreakStyle): String;
|
|
|
|
begin
|
|
Result := inherited DoGetNodeHint(Node, Column, LineBreakStyle);
|
|
if (FHintData.Kind = vhkText) and Assigned(FOnGetHint) then
|
|
FOnGetHint(Self, Node, Column, LineBreakStyle, Result);
|
|
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);
|
|
try
|
|
|
|
// 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;
|
|
finally
|
|
RestoreFontChangeEvent(PaintInfo.Canvas);
|
|
end;
|
|
{$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
|
|
Result := '';
|
|
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): TSize;
|
|
|
|
var
|
|
R: TRect;
|
|
DrawFormat: Integer;
|
|
|
|
begin
|
|
GetTextExtentPoint32(Canvas.Handle, PChar(Text), Length(Text), {%H-}Result);
|
|
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, Result.cx, MaxInt);
|
|
DrawText(Canvas.Handle, PChar(Text), Length(Text), R, DrawFormat);
|
|
Result.cx := R.Right - R.Left;
|
|
end;
|
|
if Assigned(FOnMeasureTextWidth) then
|
|
FOnMeasureTextWidth(Self, Canvas, Node, Column, Text, Result.cx);
|
|
if Assigned(FOnMeasureTextHeight) then
|
|
FOnMeasureTextHeight(Self, Canvas, Node, Column, Text, Result.cy);
|
|
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 = nil;
|
|
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;
|
|
if toShowHorzGridLines in TreeOptions.PaintOptions then
|
|
Inc(Result);
|
|
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??emeier.
|
|
|
|
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="{')
|
|
else
|
|
begin
|
|
Buffer.Add('.');
|
|
Buffer.Add(Name);
|
|
Buffer.Add('{');
|
|
end;
|
|
|
|
Buffer.Add(Format('font-family: ''%s''; ', [Font.Name]));
|
|
if Font.Size < 0 then
|
|
Buffer.Add(Format('font-size: %dpx; ', [Font.Height]))
|
|
else
|
|
Buffer.Add(Format('font-size: %dpt; ', [Font.Size]));
|
|
|
|
Buffer.Add(Format('font-style: %s; ', [IfThen(fsItalic in Font.Style, 'italic', 'normal')]));
|
|
Buffer.Add(Format('font-weight: %s; ', [IfThen(fsBold in Font.Style, 'bold', 'normal')]));
|
|
Buffer.Add(Format('text-decoration: %s; ', [IfThen(fsUnderline in Font.Style, 'underline', 'none')]));
|
|
|
|
Buffer.Add('color: ');
|
|
WriteColorAsHex(Font.Color);
|
|
Buffer.Add(';}');
|
|
if Length(Name) = 0 then
|
|
Buffer.Add('"');
|
|
end;
|
|
|
|
//--------------- end local functions ---------------------------------------
|
|
|
|
var
|
|
I, J : Integer;
|
|
Level, MaxLevel: Cardinal;
|
|
AddHeader: String;
|
|
Save, Run: PVirtualNode;
|
|
GetNextNode: TGetNextNodeProc;
|
|
Text: String;
|
|
|
|
RenderColumns: Boolean;
|
|
Columns: TColumnsArray;
|
|
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{');
|
|
Buffer.Add(' border-bottom:1px; border-left: 0px; border-right: 0px; border-top: 1px;');
|
|
Buffer.Add('border-style:');
|
|
Buffer.Add(LineStyleText);
|
|
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 {vertical-align: top; ');
|
|
if toShowVertGridLines in FOptions.FPaintOptions then
|
|
Buffer.Add('border-right: 1px; border-left: 1px; ')
|
|
else
|
|
Buffer.Add('border-right: none; border-left:none; ');
|
|
if toShowHorzGridLines in FOptions.FPaintOptions then
|
|
Buffer.Add('border-top: 1px; border-bottom: 1px; ')
|
|
else
|
|
Buffer.Add('border-top:none; border-bottom: none;');
|
|
Buffer.Add('border-width: thin; border-style: ');
|
|
Buffer.Add(LineStyleText);
|
|
Buffer.Add(CellPadding);
|
|
Buffer.Add('}');
|
|
Buffer.Add('</style>');
|
|
Buffer.AddNewLine;
|
|
|
|
// General table properties.
|
|
Buffer.Add('<table class="default" style="border-collapse: collapse;" bgcolor=');
|
|
WriteColorAsHex(Color);
|
|
Buffer.Add(AddHeader);
|
|
Buffer.Add(' cellspacing="0">');
|
|
Buffer.AddNewLine;
|
|
|
|
Columns := nil;
|
|
RenderColumns := FHeader.UseColumns;
|
|
if RenderColumns then
|
|
begin
|
|
Columns := FHeader.FColumns.GetVisibleColumns;
|
|
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({%H-}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" class="normalborder"> </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
|
|
case FOptions.ExportMode of
|
|
emChecked:
|
|
Result := Node.CheckState = csCheckedNormal;
|
|
emUnchecked:
|
|
Result := Node.CheckState = csUncheckedNormal;
|
|
emVisibleDueToExpansion: //Do not export nodes that are not visible because their parent is not expanded
|
|
Result := not Assigned(Node.Parent) or Self.Expanded[Node.Parent];
|
|
emSelected: // export selected nodes only
|
|
Result := Selected[Node];
|
|
else
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TCustomVirtualStringTree.AddToSelection(Node: PVirtualNode);
|
|
var
|
|
lSelectedNodeCaption: String;
|
|
begin
|
|
inherited;
|
|
if (toRestoreSelection in TreeOptions.SelectionOptions) and Assigned(Self.OnGetText) and Self.Selected[Node] and not (tsPreviouslySelectedLocked in FStates) then
|
|
begin
|
|
if not Assigned(FPreviouslySelected) then
|
|
begin
|
|
FPreviouslySelected := TStringList.Create();
|
|
FPreviouslySelected.Duplicates := dupIgnore;
|
|
FPreviouslySelected.Sorted := True; //Improves performance, required to use Find()
|
|
FPreviouslySelected.CaseSensitive := False;
|
|
end;
|
|
if Self.SelectedCount = 1 then
|
|
FPreviouslySelected.Clear();
|
|
Self.OnGetText(Self, Node, 0, ttNormal, {%H-}lSelectedNodeCaption);
|
|
FPreviouslySelected.Add(lSelectedNodeCaption);
|
|
end;//if
|
|
UpdateNextNodeToSelect(Node);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TCustomVirtualStringTree.RemoveFromSelection(Node: PVirtualNode);
|
|
var
|
|
lSelectedNodeCaption: String;
|
|
lIndex: Integer;
|
|
begin
|
|
inherited;
|
|
if (toRestoreSelection in TreeOptions.SelectionOptions) and Assigned(FPreviouslySelected) and not Self.Selected[Node] then
|
|
begin
|
|
if Self.SelectedCount = 0 then
|
|
FPreviouslySelected.Clear()
|
|
else
|
|
begin
|
|
Self.OnGetText(Self, Node, 0, ttNormal, {%H-}lSelectedNodeCaption);
|
|
if FPreviouslySelected.Find(lSelectedNodeCaption, lIndex) then
|
|
FPreviouslySelected.Delete(lIndex);
|
|
end;//else
|
|
end;//if
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.ContentToRTF(Source: TVSTTextSourceType): String;
|
|
|
|
// Renders the current tree content (depending on Source) as RTF (rich text).
|
|
// Based on ideas and code from Frank van den Bergh and Andreas H??emeier.
|
|
|
|
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({%H-}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({%H-}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;
|
|
LocaleBuffer: array [0..1] of Char;
|
|
|
|
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
|
|
taLeftJustify:
|
|
Buffer.Add('\ql');
|
|
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');
|
|
Buffer.AddNewLine;
|
|
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({%H-}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 + '}';
|
|
{$ifndef INCOMPLETE_WINAPI}
|
|
if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, @LocaleBuffer[0], Length(LocaleBuffer)) <> 0) and (LocaleBuffer[0] = '0'{metric}) then
|
|
S := S + '\paperw16840\paperh11907'// This sets A4 landscape format
|
|
else
|
|
S := S + '\paperw15840\paperh12240';//[JAM:marder] This sets US Letter landscape format
|
|
{$else}
|
|
S := S + '\paperw16840\paperh11907';// This sets A4 landscape format
|
|
{$endif}
|
|
// Make sure a small margin is used so that a lot of the table fits on a paper. This defines a margin of 0.5"
|
|
S := S + '\margl720\margr720\margt720\margb720';
|
|
Result := S + Buffer.AsString + '}';
|
|
Fonts.Free;
|
|
Colors.Free;
|
|
|
|
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, {%H-}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, {%H-}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;
|
|
|
|
//----------------- TLazVirtualStringTree ---------------------------------------------------------------------------------
|
|
|
|
function TLazVirtualStringTree.GetOptions: TStringTreeOptions;
|
|
|
|
begin
|
|
Result := FOptions as TStringTreeOptions;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TLazVirtualStringTree.SetOptions(const Value: TStringTreeOptions);
|
|
|
|
begin
|
|
FOptions.Assign(Value);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TLazVirtualStringTree.GetOptionsClass: TTreeOptionsClass;
|
|
|
|
begin
|
|
Result := TStringTreeOptions;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$if CompilerVersion >= 23}
|
|
class constructor TLazVirtualStringTree.Create();
|
|
begin
|
|
TCustomStyleEngine.RegisterStyleHook(TLazVirtualStringTree, TVclStyleScrollBarsHook);
|
|
end;
|
|
{$ifend}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
function TCustomVirtualDrawTree.GetDefaultHintKind: TVTHintKind;
|
|
|
|
begin
|
|
Result := vhkOwnerDraw;
|
|
end;
|
|
|
|
//----------------- TLazVirtualDrawTree -----------------------------------------------------------------------------------
|
|
|
|
function TLazVirtualDrawTree.GetOptions: TVirtualTreeOptions;
|
|
|
|
begin
|
|
Result := FOptions as TVirtualTreeOptions;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TLazVirtualDrawTree.SetOptions(const Value: TVirtualTreeOptions);
|
|
|
|
begin
|
|
FOptions.Assign(Value);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TLazVirtualDrawTree.GetOptionsClass: TTreeOptionsClass;
|
|
|
|
begin
|
|
Result := TVirtualTreeOptions;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
{$if CompilerVersion >= 23}
|
|
class constructor TLazVirtualDrawTree.Create();
|
|
begin
|
|
TCustomStyleEngine.RegisterStyleHook(TLazVirtualDrawTree, TVclStyleScrollBarsHook);
|
|
end;
|
|
{$ifend}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
// XE2+ VCL Style
|
|
{$if CompilerVersion >= 23 }
|
|
|
|
{ TVclStyleScrollBarsHook }
|
|
|
|
procedure TVclStyleScrollBarsHook.CalcScrollBarsRect;
|
|
var
|
|
P: TPoint;
|
|
BorderValue: TSize;
|
|
BarInfo: TScrollBarInfo;
|
|
I: Integer;
|
|
|
|
procedure CalcVerticalRects;
|
|
begin
|
|
BarInfo.cbSize := SizeOf(BarInfo);
|
|
GetScrollBarInfo(Handle, Integer(OBJID_VSCROLL), BarInfo);
|
|
FVertScrollBarWindow.Visible := not(STATE_SYSTEM_INVISIBLE and BarInfo.rgstate[0] <> 0);
|
|
FVertScrollBarWindow.Enabled := not(STATE_SYSTEM_UNAVAILABLE and BarInfo.rgstate[0] <> 0);
|
|
if FVertScrollBarWindow.Visible then
|
|
begin
|
|
// ScrollBar Rect
|
|
P := BarInfo.rcScrollBar.TopLeft;
|
|
ScreenToClient(Handle, P);
|
|
FVertScrollBarRect.TopLeft := P;
|
|
P := BarInfo.rcScrollBar.BottomRight;
|
|
ScreenToClient(Handle, P);
|
|
FVertScrollBarRect.BottomRight := P;
|
|
OffsetRect(FVertScrollBarRect, BorderValue.cx, BorderValue.cy);
|
|
|
|
I := GetSystemMetrics(SM_CYVTHUMB);
|
|
// Down Button
|
|
FVertScrollBarDownButtonRect := FVertScrollBarRect;
|
|
FVertScrollBarDownButtonRect.Top := FVertScrollBarDownButtonRect.Bottom - I;
|
|
|
|
// UP Button
|
|
FVertScrollBarUpButtonRect := FVertScrollBarRect;
|
|
FVertScrollBarUpButtonRect.Bottom := FVertScrollBarUpButtonRect.Top + I;
|
|
|
|
FVertScrollBarSliderTrackRect := FVertScrollBarRect;
|
|
Inc(FVertScrollBarSliderTrackRect.Top, I);
|
|
Dec(FVertScrollBarSliderTrackRect.Bottom, I);
|
|
end;
|
|
end;
|
|
|
|
procedure CalcHorizontalRects;
|
|
begin
|
|
BarInfo.cbSize := SizeOf(BarInfo);
|
|
GetScrollBarInfo(Handle, Integer(OBJID_HSCROLL), BarInfo);
|
|
FHorzScrollBarWindow.Visible := not(STATE_SYSTEM_INVISIBLE and BarInfo.rgstate[0] <> 0);
|
|
FHorzScrollBarWindow.Enabled := not(STATE_SYSTEM_UNAVAILABLE and BarInfo.rgstate[0] <> 0);
|
|
if FHorzScrollBarWindow.Visible then
|
|
begin
|
|
// ScrollBar Rect
|
|
P := BarInfo.rcScrollBar.TopLeft;
|
|
ScreenToClient(Handle, P);
|
|
FHorzScrollBarRect.TopLeft := P;
|
|
P := BarInfo.rcScrollBar.BottomRight;
|
|
ScreenToClient(Handle, P);
|
|
FHorzScrollBarRect.BottomRight := P;
|
|
OffsetRect(FHorzScrollBarRect, BorderValue.cx, BorderValue.cy);
|
|
|
|
I := GetSystemMetrics(SM_CXHTHUMB);
|
|
// Down Button
|
|
FHorzScrollBarDownButtonRect := FHorzScrollBarRect;
|
|
FHorzScrollBarDownButtonRect.Left := FHorzScrollBarDownButtonRect.Right - I;
|
|
|
|
// UP Button
|
|
FHorzScrollBarUpButtonRect := FHorzScrollBarRect;
|
|
FHorzScrollBarUpButtonRect.Right := FHorzScrollBarUpButtonRect.Left + I;
|
|
|
|
FHorzScrollBarSliderTrackRect := FHorzScrollBarRect;
|
|
Inc(FHorzScrollBarSliderTrackRect.Left, I);
|
|
Dec(FHorzScrollBarSliderTrackRect.Right, I);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
BorderValue.cx := 0;
|
|
BorderValue.cy := 0;
|
|
if HasBorder then
|
|
if HasClientEdge then
|
|
begin
|
|
BorderValue.cx := GetSystemMetrics(SM_CXEDGE);
|
|
BorderValue.cy := GetSystemMetrics(SM_CYEDGE);
|
|
end;
|
|
CalcVerticalRects;
|
|
CalcHorizontalRects;
|
|
|
|
end;
|
|
|
|
constructor TVclStyleScrollBarsHook.Create(AControl: TWinControl);
|
|
begin
|
|
inherited;
|
|
FVertScrollBarWindow := TVclStyleScrollBarWindow.CreateParented(GetParent(Control.Handle));
|
|
FVertScrollBarWindow.ScrollBarWindowOwner := Self;
|
|
FVertScrollBarWindow.ScrollBarVertical := True;
|
|
|
|
FHorzScrollBarWindow := TVclStyleScrollBarWindow.CreateParented(GetParent(Control.Handle));
|
|
FHorzScrollBarWindow.ScrollBarWindowOwner := Self;
|
|
|
|
FVertScrollBarSliderState := tsThumbBtnVertNormal;
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzNormal;
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
end;
|
|
|
|
destructor TVclStyleScrollBarsHook.Destroy;
|
|
begin
|
|
FVertScrollBarWindow.ScrollBarWindowOwner := nil;
|
|
FreeAndNil(FVertScrollBarWindow);
|
|
FHorzScrollBarWindow.ScrollBarWindowOwner := nil;
|
|
FreeAndNil(FHorzScrollBarWindow);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.DrawHorzScrollBar(DC: HDC);
|
|
var
|
|
B: TBitmap;
|
|
Details: TThemedElementDetails;
|
|
R: TRect;
|
|
begin
|
|
if ((Handle = 0) or (DC = 0)) then
|
|
Exit;
|
|
if FHorzScrollBarWindow.Visible and StyleServices.Available then
|
|
begin
|
|
B := TBitmap.Create;
|
|
try
|
|
B.Width := FHorzScrollBarRect.Width;
|
|
B.Height := FHorzScrollBarRect.Height;
|
|
MoveWindowOrg(B.Canvas.Handle, -FHorzScrollBarRect.Left, -FHorzScrollBarRect.Top);
|
|
R := FHorzScrollBarRect;
|
|
R.Left := FHorzScrollBarUpButtonRect.Right;
|
|
R.Right := FHorzScrollBarDownButtonRect.Left;
|
|
|
|
Details := StyleServices.GetElementDetails(tsUpperTrackHorzNormal);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, R);
|
|
|
|
if FHorzScrollBarWindow.Enabled then
|
|
Details := StyleServices.GetElementDetails(FHorzScrollBarSliderState);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, GetHorzScrollBarSliderRect);
|
|
|
|
if FHorzScrollBarWindow.Enabled then
|
|
Details := StyleServices.GetElementDetails(FHorzScrollBarUpButtonState)
|
|
else
|
|
Details := StyleServices.GetElementDetails(tsArrowBtnLeftDisabled);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, FHorzScrollBarUpButtonRect);
|
|
|
|
if FHorzScrollBarWindow.Enabled then
|
|
Details := StyleServices.GetElementDetails(FHorzScrollBarDownButtonState)
|
|
else
|
|
Details := StyleServices.GetElementDetails(tsArrowBtnRightDisabled);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, FHorzScrollBarDownButtonRect);
|
|
|
|
MoveWindowOrg(B.Canvas.Handle, FHorzScrollBarRect.Left, FHorzScrollBarRect.Top);
|
|
with FHorzScrollBarRect do
|
|
BitBlt(DC, Left, Top, B.Width, B.Height, B.Canvas.Handle, 0, 0, SRCCOPY);
|
|
finally
|
|
B.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.DrawVertScrollBar(DC: HDC);
|
|
var
|
|
B: TBitmap;
|
|
Details: TThemedElementDetails;
|
|
R: TRect;
|
|
begin
|
|
if ((Handle = 0) or (DC = 0)) then
|
|
Exit;
|
|
if FVertScrollBarWindow.Visible and StyleServices.Available then
|
|
begin
|
|
B := TBitmap.Create;
|
|
try
|
|
B.Width := FVertScrollBarRect.Width;
|
|
B.Height := FVertScrollBarWindow.Height;
|
|
MoveWindowOrg(B.Canvas.Handle, -FVertScrollBarRect.Left, -FVertScrollBarRect.Top);
|
|
R := FVertScrollBarRect;
|
|
R.Bottom := B.Height + FVertScrollBarRect.Top;
|
|
Details := StyleServices.GetElementDetails(tsUpperTrackVertNormal);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, R);
|
|
R.Top := FVertScrollBarUpButtonRect.Bottom;
|
|
R.Bottom := FVertScrollBarDownButtonRect.Top;
|
|
|
|
Details := StyleServices.GetElementDetails(tsUpperTrackVertNormal);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, R);
|
|
|
|
if FVertScrollBarWindow.Enabled then
|
|
Details := StyleServices.GetElementDetails(FVertScrollBarSliderState);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, GetVertScrollBarSliderRect);
|
|
|
|
if FVertScrollBarWindow.Enabled then
|
|
Details := StyleServices.GetElementDetails(FVertScrollBarUpButtonState)
|
|
else
|
|
Details := StyleServices.GetElementDetails(tsArrowBtnUpDisabled);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, FVertScrollBarUpButtonRect);
|
|
|
|
if FVertScrollBarWindow.Enabled then
|
|
Details := StyleServices.GetElementDetails(FVertScrollBarDownButtonState)
|
|
else
|
|
Details := StyleServices.GetElementDetails(tsArrowBtnDownDisabled);
|
|
StyleServices.DrawElement(B.Canvas.Handle, Details, FVertScrollBarDownButtonRect);
|
|
|
|
MoveWindowOrg(B.Canvas.Handle, FVertScrollBarRect.Left, FVertScrollBarRect.Top);
|
|
with FVertScrollBarRect do
|
|
BitBlt(DC, Left, Top, B.Width, B.Height, B.Canvas.Handle, 0, 0, SRCCOPY);
|
|
finally
|
|
B.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TVclStyleScrollBarsHook.GetHorzScrollBarSliderRect: TRect;
|
|
var
|
|
P: TPoint;
|
|
BarInfo: TScrollBarInfo;
|
|
begin
|
|
if FHorzScrollBarWindow.Visible and FHorzScrollBarWindow.Enabled then
|
|
begin
|
|
BarInfo.cbSize := SizeOf(BarInfo);
|
|
GetScrollBarInfo(Handle, Integer(OBJID_HSCROLL), BarInfo);
|
|
P := BarInfo.rcScrollBar.TopLeft;
|
|
ScreenToClient(Handle, P);
|
|
Result.TopLeft := P;
|
|
P := BarInfo.rcScrollBar.BottomRight;
|
|
ScreenToClient(Handle, P);
|
|
Result.BottomRight := P;
|
|
Result.Left := BarInfo.xyThumbTop;
|
|
Result.Right := BarInfo.xyThumbBottom;
|
|
if HasBorder then
|
|
if HasClientEdge then
|
|
OffsetRect(Result, 2, 2)
|
|
else
|
|
OffsetRect(Result, 1, 1);
|
|
end
|
|
else
|
|
Result := Rect(0, 0, 0, 0);
|
|
end;
|
|
|
|
function TVclStyleScrollBarsHook.GetVertScrollBarSliderRect: TRect;
|
|
var
|
|
P: TPoint;
|
|
BarInfo: TScrollBarInfo;
|
|
begin
|
|
if FVertScrollBarWindow.Visible and FVertScrollBarWindow.Enabled then
|
|
begin
|
|
BarInfo.cbSize := SizeOf(BarInfo);
|
|
GetScrollBarInfo(Handle, Integer(OBJID_VSCROLL), BarInfo);
|
|
P := BarInfo.rcScrollBar.TopLeft;
|
|
ScreenToClient(Handle, P);
|
|
Result.TopLeft := P;
|
|
P := BarInfo.rcScrollBar.BottomRight;
|
|
ScreenToClient(Handle, P);
|
|
Result.BottomRight := P;
|
|
Result.Top := BarInfo.xyThumbTop;
|
|
Result.Bottom := BarInfo.xyThumbBottom;
|
|
if HasBorder then
|
|
if HasClientEdge then
|
|
OffsetRect(Result, 2, 2)
|
|
else
|
|
OffsetRect(Result, 1, 1);
|
|
end
|
|
else
|
|
Result := Rect(0, 0, 0, 0);
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.MouseLeave;
|
|
begin
|
|
inherited;
|
|
if FVertScrollBarSliderState = tsThumbBtnVertHot then
|
|
FVertScrollBarSliderState := tsThumbBtnVertNormal;
|
|
|
|
if FHorzScrollBarSliderState = tsThumbBtnHorzHot then
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzNormal;
|
|
|
|
if FVertScrollBarUpButtonState = tsArrowBtnUpHot then
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
|
|
if FVertScrollBarDownButtonState = tsArrowBtnDownHot then
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
|
|
if FHorzScrollBarUpButtonState = tsArrowBtnLeftHot then
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
|
|
if FHorzScrollBarDownButtonState = tsArrowBtnRightHot then
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
|
|
PaintScrollBars;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.PaintScrollBars;
|
|
begin
|
|
FVertScrollBarWindow.Repaint;
|
|
FHorzScrollBarWindow.Repaint;
|
|
end;
|
|
|
|
function TVclStyleScrollBarsHook.PointInTreeHeader(const P: TPoint): Boolean;
|
|
begin
|
|
Result := TBaseVirtualTree(Control).FHeader.InHeader(P);
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.UpdateScrollBarWindow;
|
|
var
|
|
R: TRect;
|
|
Owner: TBaseVirtualTree;
|
|
HeaderHeight: Integer;
|
|
BorderWidth: Integer;
|
|
begin
|
|
Owner := TBaseVirtualTree(Control);
|
|
if (hoVisible in Owner.Header.Options) then
|
|
HeaderHeight := Owner.FHeader.Height
|
|
else
|
|
HeaderHeight := 0;
|
|
BorderWidth := 0;
|
|
// VertScrollBarWindow
|
|
if FVertScrollBarWindow.Visible then
|
|
begin
|
|
R := FVertScrollBarRect;
|
|
if Control.BidiMode = bdRightToLeft then
|
|
begin
|
|
OffsetRect(R, -R.Left, 0);
|
|
if HasBorder then
|
|
OffsetRect(R, GetSystemMetrics(SM_CXEDGE), 0);
|
|
end;
|
|
if HasBorder then
|
|
BorderWidth := GetSystemMetrics(SM_CYEDGE) * 2;
|
|
ShowWindow(FVertScrollBarWindow.Handle, SW_SHOW);
|
|
SetWindowPos(FVertScrollBarWindow.Handle, HWND_TOP, Control.Left + R.Left, Control.Top + R.Top + HeaderHeight, R.Right - R.Left,
|
|
Control.Height - HeaderHeight - BorderWidth, SWP_SHOWWINDOW);
|
|
end
|
|
else
|
|
ShowWindow(FVertScrollBarWindow.Handle, SW_HIDE);
|
|
|
|
// HorzScrollBarWindow
|
|
if FHorzScrollBarWindow.Visible then
|
|
begin
|
|
R := FHorzScrollBarRect;
|
|
if Control.BidiMode = bdRightToLeft then
|
|
OffsetRect(R, FVertScrollBarRect.Width, 0);
|
|
ShowWindow(FHorzScrollBarWindow.Handle, SW_SHOW);
|
|
SetWindowPos(FHorzScrollBarWindow.Handle, HWND_TOP, Control.Left + R.Left, Control.Top + R.Top + HeaderHeight, R.Right - R.Left,
|
|
R.Bottom - R.Top, SWP_SHOWWINDOW);
|
|
end
|
|
else
|
|
ShowWindow(FHorzScrollBarWindow.Handle, SW_HIDE);
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMCaptureChanged(var Msg: TMessage);
|
|
begin
|
|
if FVertScrollBarWindow.Visible and FVertScrollBarWindow.Enabled then
|
|
begin
|
|
if FVertScrollBarUpButtonState = tsArrowBtnUpPressed then
|
|
begin
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
PaintScrollBars;
|
|
end;
|
|
|
|
if FVertScrollBarDownButtonState = tsArrowBtnDownPressed then
|
|
begin
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
PaintScrollBars;
|
|
end;
|
|
end;
|
|
|
|
if FHorzScrollBarWindow.Visible and FHorzScrollBarWindow.Enabled then
|
|
begin
|
|
if FHorzScrollBarUpButtonState = tsArrowBtnLeftPressed then
|
|
begin
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
PaintScrollBars;
|
|
end;
|
|
|
|
if FHorzScrollBarDownButtonState = tsArrowBtnRightPressed then
|
|
begin
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
PaintScrollBars;
|
|
end;
|
|
end;
|
|
|
|
CallDefaultProc(TMessage(Msg));
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMHScroll(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.CMUpdateVclStyleScrollBars(var Message: TMessage);
|
|
begin
|
|
CalcScrollBarsRect;
|
|
PaintScrollBars;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMKeyDown(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMKeyUp(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMLButtonDown(var Msg: TWMMouse);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMLButtonUp(var Msg: TWMMouse);
|
|
var
|
|
P: TPoint;
|
|
begin
|
|
P := Point(Msg.XPos, Msg.YPos);
|
|
ScreenToClient(Handle, P);
|
|
if not PointInTreeHeader(P) then
|
|
begin
|
|
if FVertScrollBarWindow.Visible then
|
|
begin
|
|
if FVertScrollBarSliderState = tsThumbBtnVertPressed then
|
|
begin
|
|
PostMessage(Handle, WM_VSCROLL, Integer(SmallPoint(SB_ENDSCROLL, 0)), 0);
|
|
FLeftMouseButtonDown := False;
|
|
FVertScrollBarSliderState := tsThumbBtnVertNormal;
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
ReleaseCapture;
|
|
Exit;
|
|
end;
|
|
|
|
if FVertScrollBarUpButtonState = tsArrowBtnUpPressed then
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
|
|
if FVertScrollBarDownButtonState = tsArrowBtnDownPressed then
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
end;
|
|
|
|
if FHorzScrollBarWindow.Visible then
|
|
begin
|
|
if FHorzScrollBarSliderState = tsThumbBtnHorzPressed then
|
|
begin
|
|
PostMessage(Handle, WM_HSCROLL, Integer(SmallPoint(SB_ENDSCROLL, 0)), 0);
|
|
FLeftMouseButtonDown := False;
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzNormal;
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
ReleaseCapture;
|
|
Exit;
|
|
end;
|
|
|
|
if FHorzScrollBarUpButtonState = tsArrowBtnLeftPressed then
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
|
|
if FHorzScrollBarDownButtonState = tsArrowBtnRightPressed then
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
end;
|
|
PaintScrollBars;
|
|
end;
|
|
FLeftMouseButtonDown := False;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMMouseMove(var Msg: TWMMouse);
|
|
var
|
|
SF: TScrollInfo;
|
|
begin
|
|
inherited;
|
|
if FVertScrollBarSliderState = tsThumbBtnVertPressed then
|
|
begin
|
|
SF.fMask := SIF_ALL;
|
|
SF.cbSize := SizeOf(SF);
|
|
GetScrollInfo(Handle, SB_VERT, SF);
|
|
if SF.nPos <> Round(FScrollPos) then
|
|
FScrollPos := SF.nPos;
|
|
|
|
FScrollPos := FScrollPos + (SF.nMax - SF.nMin) * ((Mouse.CursorPos.Y - FPrevScrollPos) / FVertScrollBarSliderTrackRect.Height);
|
|
if FScrollPos < SF.nMin then
|
|
FScrollPos := SF.nMin;
|
|
if FScrollPos > SF.nMax then
|
|
FScrollPos := SF.nMax;
|
|
if SF.nPage <> 0 then
|
|
if Round(FScrollPos) > SF.nMax - Integer(SF.nPage) + 1 then
|
|
FScrollPos := SF.nMax - Integer(SF.nPage) + 1;
|
|
FPrevScrollPos := Mouse.CursorPos.Y;
|
|
SF.nPos := Round(FScrollPos);
|
|
|
|
SetScrollInfo(Handle, SB_VERT, SF, False);
|
|
PostMessage(Handle, WM_VSCROLL, Integer(SmallPoint(SB_THUMBPOSITION, Min(Round(FScrollPos), High(SmallInt)))), 0); // Min() prevents range check error
|
|
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if FHorzScrollBarSliderState = tsThumbBtnHorzPressed then
|
|
begin
|
|
SF.fMask := SIF_ALL;
|
|
SF.cbSize := SizeOf(SF);
|
|
GetScrollInfo(Handle, SB_HORZ, SF);
|
|
if SF.nPos <> Round(FScrollPos) then
|
|
FScrollPos := SF.nPos;
|
|
|
|
FScrollPos := FScrollPos + (SF.nMax - SF.nMin) * ((Mouse.CursorPos.X - FPrevScrollPos) / FHorzScrollBarSliderTrackRect.Width);
|
|
if FScrollPos < SF.nMin then
|
|
FScrollPos := SF.nMin;
|
|
if FScrollPos > SF.nMax then
|
|
FScrollPos := SF.nMax;
|
|
if SF.nPage <> 0 then
|
|
if Round(FScrollPos) > SF.nMax - Integer(SF.nPage) + 1 then
|
|
FScrollPos := SF.nMax - Integer(SF.nPage) + 1;
|
|
FPrevScrollPos := Mouse.CursorPos.X;
|
|
SF.nPos := Round(FScrollPos);
|
|
|
|
SetScrollInfo(Handle, SB_HORZ, SF, False);
|
|
PostMessage(Handle, WM_HSCROLL, Integer(SmallPoint(SB_THUMBPOSITION, Round(FScrollPos))), 0);
|
|
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if FHorzScrollBarSliderState = tsThumbBtnHorzHot then
|
|
begin
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzNormal;
|
|
PaintScrollBars;
|
|
end
|
|
else
|
|
if FVertScrollBarSliderState = tsThumbBtnVertHot then
|
|
begin
|
|
FVertScrollBarSliderState := tsThumbBtnVertNormal;
|
|
PaintScrollBars;
|
|
end
|
|
else
|
|
if FHorzScrollBarUpButtonState = tsArrowBtnLeftHot then
|
|
begin
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
PaintScrollBars;
|
|
end
|
|
else
|
|
if FHorzScrollBarDownButtonState = tsArrowBtnRightHot then
|
|
begin
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
PaintScrollBars;
|
|
end
|
|
else
|
|
if FVertScrollBarUpButtonState = tsArrowBtnUpHot then
|
|
begin
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
PaintScrollBars;
|
|
end
|
|
else
|
|
if FVertScrollBarDownButtonState = tsArrowBtnDownHot then
|
|
begin
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
PaintScrollBars;
|
|
end;
|
|
|
|
CallDefaultProc(TMessage(Msg));
|
|
if FLeftMouseButtonDown then
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMMouseWheel(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMNCLButtonDblClk(var Msg: TWMMouse);
|
|
begin
|
|
WMNCLButtonDown(Msg);
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMNCLButtonDown(var Msg: TWMMouse);
|
|
var
|
|
P: TPoint;
|
|
SF: TScrollInfo;
|
|
begin
|
|
P := Point(Msg.XPos, Msg.YPos);
|
|
ScreenToClient(Handle, P);
|
|
|
|
if HasBorder then
|
|
if HasClientEdge then
|
|
begin
|
|
P.X := P.X + 2;
|
|
P.Y := P.Y + 2;
|
|
end
|
|
else
|
|
begin
|
|
P.X := P.X + 1;
|
|
P.Y := P.Y + 1;
|
|
end;
|
|
|
|
if not PointInTreeHeader(P) then
|
|
begin
|
|
if FVertScrollBarWindow.Visible then
|
|
begin
|
|
if PtInRect(GetVertScrollBarSliderRect, P) then
|
|
begin
|
|
FLeftMouseButtonDown := True;
|
|
SF.fMask := SIF_ALL;
|
|
SF.cbSize := SizeOf(SF);
|
|
GetScrollInfo(Handle, SB_VERT, SF);
|
|
// FListPos := SF.nPos;
|
|
FScrollPos := SF.nPos;
|
|
FPrevScrollPos := Mouse.CursorPos.Y;
|
|
FVertScrollBarSliderState := tsThumbBtnVertPressed;
|
|
PaintScrollBars;
|
|
SetCapture(Handle);
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if FVertScrollBarWindow.Enabled then
|
|
begin
|
|
if PtInRect(FVertScrollBarDownButtonRect, P) then
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownPressed;
|
|
if PtInRect(FVertScrollBarUpButtonRect, P) then
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpPressed;
|
|
end;
|
|
end;
|
|
|
|
if FHorzScrollBarWindow.Visible then
|
|
begin
|
|
if PtInRect(GetHorzScrollBarSliderRect, P) then
|
|
begin
|
|
FLeftMouseButtonDown := True;
|
|
SF.fMask := SIF_ALL;
|
|
SF.cbSize := SizeOf(SF);
|
|
GetScrollInfo(Handle, SB_HORZ, SF);
|
|
// FListPos := SF.nPos;
|
|
FScrollPos := SF.nPos;
|
|
FPrevScrollPos := Mouse.CursorPos.X;
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzPressed;
|
|
PaintScrollBars;
|
|
SetCapture(Handle);
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if FHorzScrollBarWindow.Enabled then
|
|
begin
|
|
if PtInRect(FHorzScrollBarDownButtonRect, P) then
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightPressed;
|
|
if PtInRect(FHorzScrollBarUpButtonRect, P) then
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftPressed;
|
|
end;
|
|
end;
|
|
FLeftMouseButtonDown := True;
|
|
PaintScrollBars;
|
|
end;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMNCLButtonUp(var Msg: TWMMouse);
|
|
var
|
|
P: TPoint;
|
|
B: Boolean;
|
|
begin
|
|
P := Point(Msg.XPos, Msg.YPos);
|
|
ScreenToClient(Handle, P);
|
|
|
|
if HasBorder then
|
|
if HasClientEdge then
|
|
begin
|
|
P.X := P.X + 2;
|
|
P.Y := P.Y + 2;
|
|
end
|
|
else
|
|
begin
|
|
P.X := P.X + 1;
|
|
P.Y := P.Y + 1;
|
|
end;
|
|
|
|
B := PointInTreeHeader(P);
|
|
|
|
if not B then
|
|
begin
|
|
if FVertScrollBarWindow.Visible then
|
|
if FVertScrollBarWindow.Enabled then
|
|
begin
|
|
if FVertScrollBarSliderState = tsThumbBtnVertPressed then
|
|
begin
|
|
FLeftMouseButtonDown := False;
|
|
FVertScrollBarSliderState := tsThumbBtnVertNormal;
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if PtInRect(FVertScrollBarDownButtonRect, P) then
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownHot
|
|
else
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
|
|
if PtInRect(FVertScrollBarUpButtonRect, P) then
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpHot
|
|
else
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
end;
|
|
|
|
if FHorzScrollBarWindow.Visible then
|
|
if FHorzScrollBarWindow.Enabled then
|
|
begin
|
|
if FHorzScrollBarSliderState = tsThumbBtnHorzPressed then
|
|
begin
|
|
FLeftMouseButtonDown := False;
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzNormal;
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if PtInRect(FHorzScrollBarDownButtonRect, P) then
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightHot
|
|
else
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
|
|
if PtInRect(FHorzScrollBarUpButtonRect, P) then
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftHot
|
|
else
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
end;
|
|
CallDefaultProc(TMessage(Msg));
|
|
end;
|
|
|
|
if not B and (FHorzScrollBarWindow.Visible) or (FVertScrollBarWindow.Visible) then
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMNCMouseMove(var Msg: TWMMouse);
|
|
var
|
|
P: TPoint;
|
|
MustUpdateScroll: Boolean;
|
|
B: Boolean;
|
|
begin
|
|
inherited;
|
|
P := Point(Msg.XPos, Msg.YPos);
|
|
ScreenToClient(Handle, P);
|
|
|
|
if PointInTreeHeader(P) then
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
Exit;
|
|
end;
|
|
|
|
if HasBorder then
|
|
if HasClientEdge then
|
|
begin
|
|
P.X := P.X + 2;
|
|
P.Y := P.Y + 2;
|
|
end
|
|
else
|
|
begin
|
|
P.X := P.X + 1;
|
|
P.Y := P.Y + 1;
|
|
end;
|
|
|
|
MustUpdateScroll := False;
|
|
if FVertScrollBarWindow.Enabled then
|
|
begin
|
|
B := PtInRect(GetVertScrollBarSliderRect, P);
|
|
if B and (FVertScrollBarSliderState = tsThumbBtnVertNormal) then
|
|
begin
|
|
FVertScrollBarSliderState := tsThumbBtnVertHot;
|
|
MustUpdateScroll := True;
|
|
end
|
|
else if not B and (FVertScrollBarSliderState = tsThumbBtnVertHot) then
|
|
begin
|
|
FVertScrollBarSliderState := tsThumbBtnVertNormal;
|
|
MustUpdateScroll := True;
|
|
end;
|
|
|
|
B := PtInRect(FVertScrollBarDownButtonRect, P);
|
|
if B and (FVertScrollBarDownButtonState = tsArrowBtnDownNormal) then
|
|
begin
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownHot;
|
|
MustUpdateScroll := True;
|
|
end
|
|
else if not B and (FVertScrollBarDownButtonState = tsArrowBtnDownHot) then
|
|
begin
|
|
FVertScrollBarDownButtonState := tsArrowBtnDownNormal;
|
|
MustUpdateScroll := True;
|
|
end;
|
|
B := PtInRect(FVertScrollBarUpButtonRect, P);
|
|
if B and (FVertScrollBarUpButtonState = tsArrowBtnUpNormal) then
|
|
begin
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpHot;
|
|
MustUpdateScroll := True;
|
|
end
|
|
else if not B and (FVertScrollBarUpButtonState = tsArrowBtnUpHot) then
|
|
begin
|
|
FVertScrollBarUpButtonState := tsArrowBtnUpNormal;
|
|
MustUpdateScroll := True;
|
|
end;
|
|
end;
|
|
|
|
if FHorzScrollBarWindow.Enabled then
|
|
begin
|
|
B := PtInRect(GetHorzScrollBarSliderRect, P);
|
|
if B and (FHorzScrollBarSliderState = tsThumbBtnHorzNormal) then
|
|
begin
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzHot;
|
|
MustUpdateScroll := True;
|
|
end
|
|
else if not B and (FHorzScrollBarSliderState = tsThumbBtnHorzHot) then
|
|
begin
|
|
FHorzScrollBarSliderState := tsThumbBtnHorzNormal;
|
|
MustUpdateScroll := True;
|
|
end;
|
|
|
|
B := PtInRect(FHorzScrollBarDownButtonRect, P);
|
|
if B and (FHorzScrollBarDownButtonState = tsArrowBtnRightNormal) then
|
|
begin
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightHot;
|
|
MustUpdateScroll := True;
|
|
end
|
|
else if not B and (FHorzScrollBarDownButtonState = tsArrowBtnRightHot) then
|
|
begin
|
|
FHorzScrollBarDownButtonState := tsArrowBtnRightNormal;
|
|
MustUpdateScroll := True;
|
|
end;
|
|
|
|
B := PtInRect(FHorzScrollBarUpButtonRect, P);
|
|
if B and (FHorzScrollBarUpButtonState = tsArrowBtnLeftNormal) then
|
|
begin
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftHot;
|
|
MustUpdateScroll := True;
|
|
end
|
|
else if not B and (FHorzScrollBarUpButtonState = tsArrowBtnLeftHot) then
|
|
begin
|
|
FHorzScrollBarUpButtonState := tsArrowBtnLeftNormal;
|
|
MustUpdateScroll := True;
|
|
end;
|
|
end;
|
|
|
|
if MustUpdateScroll then
|
|
PaintScrollBars;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMNCPaint(var Msg: TMessage);
|
|
begin
|
|
CalcScrollBarsRect;
|
|
UpdateScrollBarWindow;
|
|
// PaintScrollBars;
|
|
// Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMSize(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
CalcScrollBarsRect;
|
|
UpdateScrollBarWindow;
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMMove(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
if not (tsWindowCreating in TBaseVirtualTree(Control).FStates) then
|
|
begin
|
|
CalcScrollBarsRect;
|
|
UpdateScrollBarWindow;
|
|
PaintScrollBars;
|
|
end;
|
|
Handled := True;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMPosChanged(var Msg: TMessage);
|
|
begin
|
|
WMMove(Msg);
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.WMVScroll(var Msg: TMessage);
|
|
begin
|
|
CallDefaultProc(TMessage(Msg));
|
|
PaintScrollBars;
|
|
Handled := True;
|
|
end;
|
|
|
|
{ TVclStyleScrollBarsHook.TVclStyleScrollBarWindow }
|
|
|
|
constructor TVclStyleScrollBarsHook.TVclStyleScrollBarWindow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
ControlStyle := ControlStyle + [csOverrideStylePaint];
|
|
FScrollBarWindowOwner := nil;
|
|
FScrollBarVertical := False;
|
|
FScrollBarVisible := False;
|
|
FScrollBarEnabled := False;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.TVclStyleScrollBarWindow.CreateParams(var Params: TCreateParams);
|
|
begin
|
|
inherited;
|
|
Params.Style := Params.Style or WS_CHILDWINDOW or WS_CLIPCHILDREN or WS_CLIPSIBLINGS;
|
|
Params.ExStyle := Params.ExStyle or WS_EX_NOPARENTNOTIFY;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.TVclStyleScrollBarWindow.WMEraseBkgnd(var Msg: TMessage);
|
|
begin
|
|
Msg.Result := 1;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.TVclStyleScrollBarWindow.WMNCHitTest(var Msg: TWMNCHitTest);
|
|
begin
|
|
Msg.Result := HTTRANSPARENT;
|
|
end;
|
|
|
|
procedure TVclStyleScrollBarsHook.TVclStyleScrollBarWindow.WMPaint(var Msg: TWMPaint);
|
|
var
|
|
PS: TPaintStruct;
|
|
DC: HDC;
|
|
begin
|
|
BeginPaint(Handle, PS);
|
|
try
|
|
if FScrollBarWindowOwner <> nil then
|
|
begin
|
|
DC := GetWindowDC(Handle);
|
|
try
|
|
if FScrollBarVertical then
|
|
begin
|
|
MoveWindowOrg(DC, -FScrollBarWindowOwner.FVertScrollBarRect.Left, -FScrollBarWindowOwner.FVertScrollBarRect.Top);
|
|
FScrollBarWindowOwner.DrawVertScrollBar(DC);
|
|
end
|
|
else
|
|
begin
|
|
MoveWindowOrg(DC, -FScrollBarWindowOwner.FHorzScrollBarRect.Left, -FScrollBarWindowOwner.FHorzScrollBarRect.Top);
|
|
FScrollBarWindowOwner.DrawHorzScrollBar(DC);
|
|
end;
|
|
finally
|
|
ReleaseDC(Handle, DC);
|
|
end;
|
|
end;
|
|
finally
|
|
EndPaint(Handle, PS);
|
|
end;
|
|
end;
|
|
{$ifend}
|
|
|
|
initialization
|
|
// Necessary for dynamic package loading.
|
|
Initialized := False;
|
|
NeedToUnitialize := False;
|
|
|
|
finalization
|
|
if Initialized then
|
|
FinalizeGlobalStructures;
|
|
|
|
InternalClipboardFormats.Free;
|
|
InternalClipboardFormats := nil;
|
|
|
|
end.
|