You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2854 lines
66 KiB
2854 lines
66 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include <assert.h> |
|
|
|
#define PROTECTED_THINGS_DISABLE |
|
|
|
#include <vgui/Cursor.h> |
|
#include <vgui/IScheme.h> |
|
#include <vgui/IInput.h> |
|
#include <vgui/IPanel.h> |
|
#include <vgui/ISurface.h> |
|
#include <vgui/ISystem.h> |
|
#include <vgui/IVGui.h> |
|
#include <vgui/KeyCode.h> |
|
#include <KeyValues.h> |
|
#include <vgui/MouseCode.h> |
|
|
|
#include <vgui_controls/TreeView.h> |
|
#include <vgui_controls/ScrollBar.h> |
|
#include <vgui_controls/TextEntry.h> |
|
#include <vgui_controls/Label.h> |
|
#include <vgui_controls/Button.h> |
|
#include <vgui_controls/TextImage.h> |
|
#include <vgui_controls/ImageList.h> |
|
#include <vgui_controls/ImagePanel.h> |
|
|
|
#include "tier1/utlstring.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
#ifndef max |
|
#define max(a,b) (((a) > (b)) ? (a) : (b)) |
|
#endif |
|
|
|
using namespace vgui; |
|
enum |
|
{ |
|
WINDOW_BORDER_WIDTH=2 // the width of the window's border |
|
}; |
|
|
|
#define TREE_INDENT_AMOUNT 20 |
|
|
|
namespace vgui |
|
{ |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Displays an editable text field for the text control |
|
//----------------------------------------------------------------------------- |
|
class TreeNodeText : public TextEntry |
|
{ |
|
DECLARE_CLASS_SIMPLE( TreeNodeText, TextEntry ); |
|
|
|
public: |
|
TreeNodeText(Panel *parent, const char *panelName, TreeView *tree) : BaseClass(parent, panelName), m_pTree( tree ) |
|
{ |
|
m_bEditingInPlace = false; |
|
m_bLabelEditingAllowed = false; |
|
SetDragEnabled( false ); |
|
SetDropEnabled( false ); |
|
AddActionSignalTarget( this ); |
|
m_bArmForEditing = false; |
|
m_bWaitingForRelease = false; |
|
m_lArmingTime = 0L; |
|
SetAllowKeyBindingChainToParent( true ); |
|
} |
|
|
|
MESSAGE_FUNC( OnTextChanged, "TextChanged" ) |
|
{ |
|
GetParent()->InvalidateLayout(); |
|
} |
|
|
|
bool IsKeyRebound( KeyCode code, int modifiers ) |
|
{ |
|
// If in editing mode, don't try and chain keypresses |
|
if ( m_bEditingInPlace ) |
|
{ |
|
return false; |
|
} |
|
|
|
return BaseClass::IsKeyRebound( code, modifiers ); |
|
} |
|
|
|
virtual void PaintBackground() |
|
{ |
|
BaseClass::PaintBackground(); |
|
|
|
if ( !m_bLabelEditingAllowed ) |
|
return; |
|
|
|
if ( !m_bEditingInPlace ) |
|
return; |
|
|
|
int w, h; |
|
GetSize( w, h ); |
|
surface()->DrawSetColor( GetFgColor() ); |
|
surface()->DrawOutlinedRect( 0, 0, w, h ); |
|
} |
|
|
|
virtual void ApplySchemeSettings(IScheme *pScheme) |
|
{ |
|
TextEntry::ApplySchemeSettings(pScheme); |
|
SetBorder(NULL); |
|
SetCursor(dc_arrow); |
|
} |
|
|
|
virtual void OnKeyCodeTyped(KeyCode code) |
|
{ |
|
if ( m_bEditingInPlace ) |
|
{ |
|
if ( code == KEY_ENTER ) |
|
{ |
|
FinishEditingInPlace(); |
|
} |
|
else if ( code == KEY_ESCAPE ) |
|
{ |
|
FinishEditingInPlace( true ); |
|
} |
|
else |
|
{ |
|
BaseClass::OnKeyCodeTyped( code ); |
|
} |
|
return; |
|
} |
|
else if ( code == KEY_ENTER && IsLabelEditingAllowed() ) |
|
{ |
|
EnterEditingInPlace(); |
|
} |
|
else |
|
{ |
|
// let parent deal with it (don't chain back to TextEntry) |
|
CallParentFunction(new KeyValues("KeyCodeTyped", "code", code)); |
|
} |
|
} |
|
|
|
#define CLICK_TO_EDIT_DELAY_MSEC 500 |
|
|
|
virtual void OnTick() |
|
{ |
|
BaseClass::OnTick(); |
|
if ( m_bArmForEditing ) |
|
{ |
|
long msecSinceArming = system()->GetTimeMillis() - m_lArmingTime; |
|
|
|
if ( msecSinceArming > CLICK_TO_EDIT_DELAY_MSEC ) |
|
{ |
|
m_bArmForEditing = false; |
|
m_bWaitingForRelease = false; |
|
ivgui()->RemoveTickSignal( GetVPanel() ); |
|
EnterEditingInPlace(); |
|
} |
|
} |
|
} |
|
|
|
virtual void OnMouseReleased( MouseCode code ) |
|
{ |
|
if ( m_bEditingInPlace ) |
|
{ |
|
BaseClass::OnMouseReleased( code ); |
|
return; |
|
} |
|
else |
|
{ |
|
if ( m_bWaitingForRelease && !IsBeingDragged() ) |
|
{ |
|
m_bArmForEditing = true; |
|
m_bWaitingForRelease = false; |
|
m_lArmingTime = system()->GetTimeMillis(); |
|
ivgui()->AddTickSignal( GetVPanel() ); |
|
} |
|
else |
|
{ |
|
m_bWaitingForRelease = false; |
|
} |
|
} |
|
|
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MouseReleased", "code", code)); |
|
} |
|
|
|
virtual void OnCursorMoved( int x, int y ) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); |
|
} |
|
|
|
virtual void OnMousePressed(MouseCode code) |
|
{ |
|
if ( m_bEditingInPlace ) |
|
{ |
|
BaseClass::OnMousePressed( code ); |
|
return; |
|
} |
|
else |
|
{ |
|
bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); |
|
bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); |
|
|
|
// make sure there is only one item selected |
|
// before "WaitingForRelease" which leads to label editing. |
|
CUtlVector< int > list; |
|
m_pTree->GetSelectedItems( list ); |
|
bool bIsOnlyOneItemSelected = ( list.Count() == 1 ); |
|
|
|
if ( !shift && |
|
!ctrl && |
|
!m_bArmForEditing && |
|
IsLabelEditingAllowed() && |
|
bIsOnlyOneItemSelected && |
|
IsTextFullySelected() && |
|
!IsBeingDragged() ) |
|
{ |
|
m_bWaitingForRelease = true; |
|
} |
|
} |
|
|
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MousePressed", "code", code)); |
|
} |
|
|
|
void SetLabelEditingAllowed( bool state ) |
|
{ |
|
m_bLabelEditingAllowed = state; |
|
} |
|
|
|
bool IsLabelEditingAllowed() |
|
{ |
|
return m_bLabelEditingAllowed; |
|
} |
|
|
|
virtual void OnMouseDoublePressed(MouseCode code) |
|
{ |
|
// Once we are editing, double pressing shouldn't chain up |
|
if ( m_bEditingInPlace ) |
|
{ |
|
BaseClass::OnMouseDoublePressed( code ); |
|
return; |
|
} |
|
|
|
if ( m_bArmForEditing ) |
|
{ |
|
m_bArmForEditing = false; |
|
m_bWaitingForRelease = false; |
|
ivgui()->RemoveTickSignal( GetVPanel() ); |
|
} |
|
|
|
CallParentFunction(new KeyValues("MouseDoublePressed", "code", code)); |
|
} |
|
|
|
void EnterEditingInPlace() |
|
{ |
|
if ( m_bEditingInPlace ) |
|
return; |
|
|
|
m_bEditingInPlace = true; |
|
char buf[ 1024 ]; |
|
GetText( buf, sizeof( buf ) ); |
|
m_OriginalText = buf; |
|
SetCursor(dc_ibeam); |
|
SetEditable( true ); |
|
SelectNone(); |
|
GotoTextEnd(); |
|
RequestFocus(); |
|
SelectAllText(false); |
|
m_pTree->SetLabelBeingEdited( true ); |
|
} |
|
|
|
void FinishEditingInPlace( bool revert = false ) |
|
{ |
|
if ( !m_bEditingInPlace ) |
|
return; |
|
|
|
m_pTree->SetLabelBeingEdited( false ); |
|
SetEditable( false ); |
|
SetCursor(dc_arrow); |
|
m_bEditingInPlace = false; |
|
char buf[ 1024 ]; |
|
GetText( buf, sizeof( buf ) ); |
|
|
|
// Not actually changed... |
|
if ( !Q_strcmp( buf, m_OriginalText.Get() ) ) |
|
return; |
|
|
|
if ( revert ) |
|
{ |
|
SetText( m_OriginalText.Get() ); |
|
GetParent()->InvalidateLayout(); |
|
} |
|
else |
|
{ |
|
KeyValues *kv = new KeyValues( "LabelChanged", "original", m_OriginalText.Get(), "changed", buf ); |
|
PostActionSignal( kv ); |
|
} |
|
} |
|
|
|
virtual void OnKillFocus() |
|
{ |
|
BaseClass::OnKillFocus(); |
|
|
|
FinishEditingInPlace(); |
|
} |
|
|
|
virtual void OnMouseWheeled(int delta) |
|
{ |
|
if ( m_bEditingInPlace ) |
|
{ |
|
BaseClass::OnMouseWheeled( delta ); |
|
return; |
|
} |
|
|
|
CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); |
|
} |
|
// editable - cursor normal, and ability to edit text |
|
|
|
bool IsBeingEdited() const |
|
{ |
|
return m_bEditingInPlace; |
|
} |
|
|
|
private: |
|
|
|
bool m_bEditingInPlace; |
|
CUtlString m_OriginalText; |
|
bool m_bLabelEditingAllowed; |
|
|
|
bool m_bArmForEditing; |
|
bool m_bWaitingForRelease; |
|
long m_lArmingTime; |
|
TreeView *m_pTree; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: icon for the tree node (folder icon, file icon, etc.) |
|
//----------------------------------------------------------------------------- |
|
class TreeNodeImage : public ImagePanel |
|
{ |
|
public: |
|
TreeNodeImage(Panel *parent, const char *name) : ImagePanel(parent, name) |
|
{ |
|
SetBlockDragChaining( true ); |
|
} |
|
|
|
//!! this could possibly be changed to just disallow mouse input on the image panel |
|
virtual void OnMousePressed(MouseCode code) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MousePressed", "code", code)); |
|
} |
|
|
|
virtual void OnMouseDoublePressed(MouseCode code) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MouseDoublePressed", "code", code)); |
|
} |
|
|
|
virtual void OnMouseWheeled(int delta) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); |
|
} |
|
|
|
virtual void OnCursorMoved( int x, int y ) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); |
|
} |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Scrollable area of the tree control, holds the tree itself only |
|
//----------------------------------------------------------------------------- |
|
class TreeViewSubPanel : public Panel |
|
{ |
|
public: |
|
TreeViewSubPanel(Panel *parent) : Panel(parent) {} |
|
|
|
virtual void ApplySchemeSettings(IScheme *pScheme) |
|
{ |
|
Panel::ApplySchemeSettings(pScheme); |
|
|
|
SetBorder(NULL); |
|
} |
|
|
|
virtual void OnMouseWheeled(int delta) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); |
|
} |
|
virtual void OnMousePressed(MouseCode code) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MousePressed", "code", code)); |
|
} |
|
virtual void OnMouseDoublePressed(MouseCode code) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("MouseDoublePressed", "code", code)); |
|
} |
|
|
|
virtual void OnCursorMoved( int x, int y ) |
|
{ |
|
// let parent deal with it |
|
CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); |
|
} |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A single entry in the tree |
|
//----------------------------------------------------------------------------- |
|
class TreeNode : public Panel |
|
{ |
|
DECLARE_CLASS_SIMPLE( TreeNode, Panel ); |
|
|
|
public: |
|
TreeNode(Panel *parent, TreeView *pTreeView); |
|
~TreeNode(); |
|
void SetText(const char *pszText); |
|
void SetFont(HFont font); |
|
void SetKeyValues(KeyValues *data); |
|
bool IsSelected(); |
|
// currently unused, could be re-used if necessary |
|
// bool IsInFocus(); |
|
virtual void PaintBackground(); |
|
virtual void PerformLayout(); |
|
TreeNode *GetParentNode(); |
|
int GetChildrenCount(); |
|
void ClearChildren(); |
|
int ComputeInsertionPosition( TreeNode *pChild ); |
|
int FindChild( TreeNode *pChild ); |
|
void AddChild(TreeNode *pChild); |
|
void SetNodeExpanded(bool bExpanded); |
|
bool IsExpanded(); |
|
int CountVisibleNodes(); |
|
void CalculateVisibleMaxWidth(); |
|
void OnChildWidthChange(); |
|
int GetMaxChildrenWidth(); |
|
int GetVisibleMaxWidth(); |
|
int GetDepth(); |
|
bool HasParent(TreeNode *pTreeNode); |
|
bool IsBeingDisplayed(); |
|
virtual void SetVisible(bool state); |
|
virtual void Paint(); |
|
virtual void ApplySchemeSettings(IScheme *pScheme); |
|
virtual void SetBgColor( Color color ); |
|
virtual void SetFgColor( Color color ); |
|
virtual void OnSetFocus(); |
|
void SelectPrevChild(TreeNode *pCurrentChild); |
|
void SelectNextChild(TreeNode *pCurrentChild); |
|
|
|
int GetPrevChildItemIndex( TreeNode *pCurrentChild ); |
|
int GetNextChildItemIndex( TreeNode *pCurrentChild ); |
|
|
|
virtual void ClosePreviousParents( TreeNode *pPreviousParent ); |
|
virtual void StepInto( bool bClosePrevious=true ); |
|
virtual void StepOut( bool bClosePrevious=true ); |
|
virtual void StepOver( bool bClosePrevious=true ); |
|
virtual void OnKeyCodeTyped(KeyCode code); |
|
virtual void OnMouseWheeled(int delta); |
|
virtual void OnMousePressed( MouseCode code); |
|
virtual void OnMouseReleased( MouseCode code); |
|
virtual void OnCursorMoved( int x, int y ); |
|
virtual bool IsDragEnabled() const; |
|
void PositionAndSetVisibleNodes(int &nStart, int &nCount, int x, int &y); |
|
|
|
// counts items above this item including itself |
|
int CountVisibleIndex(); |
|
|
|
virtual void OnCreateDragData( KeyValues *msg ); |
|
// For handling multiple selections... |
|
virtual void OnGetAdditionalDragPanels( CUtlVector< Panel * >& dragabbles ); |
|
virtual void OnMouseDoublePressed( MouseCode code ); |
|
TreeNode *FindItemUnderMouse( int &nStart, int& nCount, int x, int &y, int mx, int my ); |
|
MESSAGE_FUNC_PARAMS( OnLabelChanged, "LabelChanged", data ); |
|
void EditLabel(); |
|
void SetLabelEditingAllowed( bool state ); |
|
bool IsLabelEditingAllowed() const; |
|
|
|
virtual bool IsDroppable( CUtlVector< KeyValues * >& msglist ); |
|
virtual void OnPanelDropped( CUtlVector< KeyValues * >& msglist ); |
|
virtual HCursor GetDropCursor( CUtlVector< KeyValues * >& msglist ); |
|
virtual bool GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist ); |
|
|
|
void FindNodesInRange( CUtlVector< TreeNode * >& list, int startIndex, int endIndex ); |
|
|
|
void RemoveChildren(); |
|
|
|
void SetSelectionTextColor( const Color& clr ); |
|
void SetSelectionBgColor( const Color& clr ); |
|
void SetSelectionUnfocusedBgColor( const Color& clr ); |
|
public: |
|
int m_ItemIndex; |
|
int m_ParentIndex; |
|
KeyValues *m_pData; |
|
CUtlVector<TreeNode *> m_Children; |
|
bool m_bExpand; |
|
|
|
private: |
|
|
|
void FindNodesInRange_R( CUtlVector< TreeNode * >& list, bool& finished, bool& foundStart, int startIndex, int endIndex ); |
|
|
|
int m_iNodeWidth; |
|
int m_iMaxVisibleWidth; |
|
|
|
TreeNodeText *m_pText; |
|
TextImage *m_pExpandImage; |
|
TreeNodeImage *m_pImagePanel; |
|
|
|
bool m_bExpandableWithoutChildren; |
|
|
|
TreeView *m_pTreeView; |
|
int m_nClickedItem; |
|
bool m_bClickedSelected; |
|
}; |
|
|
|
|
|
TreeNode::TreeNode(Panel *parent, TreeView *pTreeView) : |
|
BaseClass(parent, "TreeNode" ), |
|
m_nClickedItem( 0 ), |
|
m_bClickedSelected( false ) |
|
{ |
|
m_pData = NULL; |
|
m_pTreeView = pTreeView; |
|
m_ItemIndex = -1; |
|
m_iNodeWidth = 0; |
|
m_iMaxVisibleWidth = 0; |
|
|
|
m_pExpandImage = new TextImage("+"); |
|
m_pExpandImage->SetPos(3, 1); |
|
|
|
m_pImagePanel = new TreeNodeImage(this, "TreeImage"); |
|
m_pImagePanel->SetPos(TREE_INDENT_AMOUNT, 3); |
|
|
|
m_pText = new TreeNodeText(this, "TreeNodeText",pTreeView); |
|
m_pText->SetMultiline(false); |
|
m_pText->SetEditable(false); |
|
m_pText->SetPos(TREE_INDENT_AMOUNT*2, 0); |
|
m_pText->AddActionSignalTarget( this ); |
|
|
|
m_bExpand = false; |
|
m_bExpandableWithoutChildren = false; |
|
} |
|
|
|
TreeNode::~TreeNode() |
|
{ |
|
delete m_pExpandImage; |
|
if ( m_pData ) |
|
{ |
|
m_pData->deleteThis(); |
|
} |
|
} |
|
|
|
void TreeNode::SetText(const char *pszText) |
|
{ |
|
m_pText->SetText(pszText); |
|
InvalidateLayout(); |
|
} |
|
|
|
void TreeNode::SetLabelEditingAllowed( bool state ) |
|
{ |
|
Assert( m_pTreeView->IsLabelEditingAllowed() ); |
|
m_pText->SetLabelEditingAllowed( state ); |
|
} |
|
|
|
bool TreeNode::IsLabelEditingAllowed() const |
|
{ |
|
return m_pText->IsLabelEditingAllowed(); |
|
} |
|
|
|
bool TreeNode::GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
return m_pTreeView->GetItemDropContextMenu( m_ItemIndex, menu, msglist ); |
|
} |
|
|
|
bool TreeNode::IsDroppable( CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
return m_pTreeView->IsItemDroppable( m_ItemIndex, msglist ); |
|
} |
|
|
|
void TreeNode::OnPanelDropped( CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
m_pTreeView->OnItemDropped( m_ItemIndex, msglist ); |
|
} |
|
|
|
HCursor TreeNode::GetDropCursor( CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
return m_pTreeView->GetItemDropCursor( m_ItemIndex, msglist ); |
|
} |
|
|
|
|
|
void TreeNode::OnCreateDragData( KeyValues *msg ) |
|
{ |
|
// make sure the dragged item appears selected, |
|
// on the off chance it appears deselected by a cntl mousedown |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, false ); |
|
|
|
m_pTreeView->GenerateDragDataForItem( m_ItemIndex, msg ); |
|
} |
|
|
|
// For handling multiple selections... |
|
void TreeNode::OnGetAdditionalDragPanels( CUtlVector< Panel * >& dragabbles ) |
|
{ |
|
CUtlVector< int > list; |
|
m_pTreeView->GetSelectedItems( list ); |
|
int c = list.Count(); |
|
// walk this in reverse order so that panels are in order of selection |
|
// even though GetSelectedItems returns items in reverse selection order |
|
for ( int i = c - 1; i >= 0; --i ) |
|
{ |
|
int itemIndex = list[ i ]; |
|
// Skip self |
|
if ( itemIndex == m_ItemIndex ) |
|
continue; |
|
|
|
dragabbles.AddToTail( ( Panel * )m_pTreeView->GetItem( itemIndex ) ); |
|
} |
|
} |
|
|
|
void TreeNode::OnLabelChanged( KeyValues *data ) |
|
{ |
|
char const *oldString = data->GetString( "original" ); |
|
char const *newString = data->GetString( "changed" ); |
|
if ( m_pTreeView->IsLabelEditingAllowed() ) |
|
{ |
|
m_pTreeView->OnLabelChanged( m_ItemIndex, oldString, newString ); |
|
} |
|
} |
|
|
|
void TreeNode::EditLabel() |
|
{ |
|
if ( m_pText->IsLabelEditingAllowed() && |
|
!m_pText->IsBeingEdited() ) |
|
{ |
|
m_pText->EnterEditingInPlace(); |
|
} |
|
} |
|
|
|
void TreeNode::SetFont(HFont font) |
|
{ |
|
Assert( font ); |
|
if ( !font ) |
|
return; |
|
|
|
m_pText->SetFont(font); |
|
m_pExpandImage->SetFont(font); |
|
InvalidateLayout(); |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
m_Children[i]->SetFont(font); |
|
} |
|
} |
|
|
|
void TreeNode::SetKeyValues(KeyValues *data) |
|
{ |
|
if ( m_pData != data ) |
|
{ |
|
if (m_pData) |
|
{ |
|
m_pData->deleteThis(); |
|
} |
|
|
|
m_pData = data->MakeCopy(); |
|
} |
|
|
|
// set text |
|
m_pText->SetText(data->GetString("Text", "")); |
|
m_bExpandableWithoutChildren = data->GetInt("Expand"); |
|
InvalidateLayout(); |
|
} |
|
|
|
bool TreeNode::IsSelected() |
|
{ |
|
return m_pTreeView->IsItemSelected( m_ItemIndex ); |
|
} |
|
|
|
void TreeNode::PaintBackground() |
|
{ |
|
if ( !m_pText->IsBeingEdited() ) |
|
{ |
|
// setup panel drawing |
|
if ( IsSelected() ) |
|
{ |
|
m_pText->SelectAllText(false); |
|
} |
|
else |
|
{ |
|
m_pText->SelectNoText(); |
|
} |
|
} |
|
|
|
BaseClass::PaintBackground(); |
|
} |
|
|
|
|
|
// currently unused, could be re-used if necessary |
|
/* |
|
bool TreeNode::IsInFocus() |
|
{ |
|
// check if our parent or one of it's children has focus |
|
VPANEL focus = input()->GetFocus(); |
|
return (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))); |
|
} |
|
*/ |
|
|
|
void TreeNode::PerformLayout() |
|
{ |
|
BaseClass::PerformLayout(); |
|
|
|
int width = 0; |
|
if (m_pData->GetInt("SelectedImage", 0) == 0 && |
|
m_pData->GetInt("Image", 0) == 0) |
|
{ |
|
width = TREE_INDENT_AMOUNT; |
|
} |
|
else |
|
{ |
|
width = TREE_INDENT_AMOUNT * 2; |
|
} |
|
|
|
m_pText->SetPos(width, 0); |
|
|
|
int contentWide, contentTall; |
|
m_pText->SetToFullWidth(); |
|
m_pText->GetSize(contentWide, contentTall); |
|
contentWide += 10; |
|
m_pText->SetSize( contentWide, m_pTreeView->GetRowHeight() ); |
|
width += contentWide; |
|
SetSize(width, m_pTreeView->GetRowHeight()); |
|
|
|
m_iNodeWidth = width; |
|
CalculateVisibleMaxWidth(); |
|
} |
|
|
|
TreeNode *TreeNode::GetParentNode() |
|
{ |
|
if (m_pTreeView->m_NodeList.IsValidIndex(m_ParentIndex)) |
|
{ |
|
return m_pTreeView->m_NodeList[m_ParentIndex]; |
|
} |
|
return NULL; |
|
} |
|
|
|
int TreeNode::GetChildrenCount() |
|
{ |
|
return m_Children.Count(); |
|
} |
|
|
|
int TreeNode::ComputeInsertionPosition( TreeNode *pChild ) |
|
{ |
|
if ( !m_pTreeView->m_pSortFunc ) |
|
{ |
|
return GetChildrenCount() - 1; |
|
} |
|
|
|
int start = 0, end = GetChildrenCount() - 1; |
|
while (start <= end) |
|
{ |
|
int mid = (start + end) >> 1; |
|
if ( m_pTreeView->m_pSortFunc( m_Children[mid]->m_pData, pChild->m_pData ) ) |
|
{ |
|
start = mid + 1; |
|
} |
|
else if ( m_pTreeView->m_pSortFunc( pChild->m_pData, m_Children[mid]->m_pData ) ) |
|
{ |
|
end = mid - 1; |
|
} |
|
else |
|
{ |
|
return mid; |
|
} |
|
} |
|
return end; |
|
} |
|
|
|
int TreeNode::FindChild( TreeNode *pChild ) |
|
{ |
|
if ( !m_pTreeView->m_pSortFunc ) |
|
{ |
|
AssertMsg( 0, "This code has never been tested. Is it correct?" ); |
|
for ( int i = 0; i < GetChildrenCount(); ++i ) |
|
{ |
|
if ( m_Children[i] == pChild ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
// Find the first entry <= to the child |
|
int start = 0, end = GetChildrenCount() - 1; |
|
while (start <= end) |
|
{ |
|
int mid = (start + end) >> 1; |
|
|
|
if ( m_Children[mid] == pChild ) |
|
return mid; |
|
|
|
if ( m_pTreeView->m_pSortFunc( m_Children[mid]->m_pData, pChild->m_pData ) ) |
|
{ |
|
start = mid + 1; |
|
} |
|
else |
|
{ |
|
end = mid - 1; |
|
} |
|
} |
|
|
|
int nMax = GetChildrenCount(); |
|
while( end < nMax ) |
|
{ |
|
// Stop when we reach a child that has a different value |
|
if ( m_pTreeView->m_pSortFunc( pChild->m_pData, m_Children[end]->m_pData ) ) |
|
return -1; |
|
|
|
if ( m_Children[end] == pChild ) |
|
return end; |
|
|
|
++end; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
void TreeNode::AddChild(TreeNode *pChild) |
|
{ |
|
int i = ComputeInsertionPosition( pChild ); |
|
m_Children.InsertAfter( i, pChild ); |
|
} |
|
|
|
void TreeNode::SetNodeExpanded(bool bExpanded) |
|
{ |
|
m_bExpand = bExpanded; |
|
|
|
if (m_bExpand) |
|
{ |
|
// see if we have any child nodes |
|
if (GetChildrenCount() < 1) |
|
{ |
|
// we need to get our children from the control |
|
m_pTreeView->GenerateChildrenOfNode(m_ItemIndex); |
|
|
|
// if we still don't have any children, then hide the expand button |
|
if (GetChildrenCount() < 1) |
|
{ |
|
m_bExpand = false; |
|
m_bExpandableWithoutChildren = false; |
|
m_pTreeView->InvalidateLayout(); |
|
return; |
|
} |
|
} |
|
|
|
m_pExpandImage->SetText("-"); |
|
} |
|
else |
|
{ |
|
m_pExpandImage->SetText("+"); |
|
|
|
if ( m_bExpandableWithoutChildren && GetChildrenCount() > 0 ) |
|
{ |
|
m_pTreeView->RemoveChildrenOfNode( m_ItemIndex ); |
|
} |
|
|
|
// check if we've closed down on one of our children, if so, we get the focus |
|
int selectedItem = m_pTreeView->GetFirstSelectedItem(); |
|
if (selectedItem != -1 && m_pTreeView->m_NodeList[selectedItem]->HasParent(this)) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, true ); |
|
} |
|
} |
|
CalculateVisibleMaxWidth(); |
|
m_pTreeView->InvalidateLayout(); |
|
} |
|
|
|
bool TreeNode::IsExpanded() |
|
{ |
|
return m_bExpand; |
|
} |
|
|
|
int TreeNode::CountVisibleNodes() |
|
{ |
|
int count = 1; // count myself |
|
if (m_bExpand) |
|
{ |
|
int i; |
|
for (i=0;i<m_Children.Count();i++) |
|
{ |
|
count += m_Children[i]->CountVisibleNodes(); |
|
} |
|
} |
|
return count; |
|
} |
|
|
|
void TreeNode::CalculateVisibleMaxWidth() |
|
{ |
|
int width; |
|
if (m_bExpand) |
|
{ |
|
int childMaxWidth = GetMaxChildrenWidth(); |
|
childMaxWidth += TREE_INDENT_AMOUNT; |
|
|
|
width = max(childMaxWidth, m_iNodeWidth); |
|
} |
|
else |
|
{ |
|
width = m_iNodeWidth; |
|
} |
|
if (width != m_iMaxVisibleWidth) |
|
{ |
|
m_iMaxVisibleWidth = width; |
|
if (GetParentNode()) |
|
{ |
|
GetParentNode()->OnChildWidthChange(); |
|
} |
|
else |
|
{ |
|
m_pTreeView->InvalidateLayout(); |
|
} |
|
} |
|
} |
|
|
|
void TreeNode::OnChildWidthChange() |
|
{ |
|
CalculateVisibleMaxWidth(); |
|
} |
|
|
|
int TreeNode::GetMaxChildrenWidth() |
|
{ |
|
int maxWidth = 0; |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
int childWidth = m_Children[i]->GetVisibleMaxWidth(); |
|
if (childWidth > maxWidth) |
|
{ |
|
maxWidth = childWidth; |
|
} |
|
} |
|
return maxWidth; |
|
} |
|
|
|
int TreeNode::GetVisibleMaxWidth() |
|
{ |
|
return m_iMaxVisibleWidth; |
|
} |
|
|
|
int TreeNode::GetDepth() |
|
{ |
|
int depth = 0; |
|
TreeNode *pParent = GetParentNode(); |
|
while (pParent) |
|
{ |
|
depth++; |
|
pParent = pParent->GetParentNode(); |
|
} |
|
return depth; |
|
} |
|
|
|
bool TreeNode::HasParent(TreeNode *pTreeNode) |
|
{ |
|
TreeNode *pParent = GetParentNode(); |
|
while (pParent) |
|
{ |
|
if (pParent == pTreeNode) |
|
return true; |
|
pParent = pParent->GetParentNode(); |
|
} |
|
return false; |
|
} |
|
|
|
bool TreeNode::IsBeingDisplayed() |
|
{ |
|
TreeNode *pParent = GetParentNode(); |
|
while (pParent) |
|
{ |
|
// our parents aren't showing us |
|
if (!pParent->m_bExpand) |
|
return false; |
|
|
|
pParent = pParent->GetParentNode(); |
|
} |
|
return true; |
|
} |
|
|
|
void TreeNode::SetVisible(bool state) |
|
{ |
|
BaseClass::SetVisible(state); |
|
|
|
bool bChildrenVisible = state && m_bExpand; |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
m_Children[i]->SetVisible(bChildrenVisible); |
|
} |
|
} |
|
|
|
void TreeNode::Paint() |
|
{ |
|
if (GetChildrenCount() > 0 || m_bExpandableWithoutChildren) |
|
{ |
|
m_pExpandImage->Paint(); |
|
} |
|
|
|
// set image |
|
int imageIndex = 0; |
|
if (IsSelected()) |
|
{ |
|
imageIndex = m_pData->GetInt("SelectedImage", 0); |
|
} |
|
else |
|
{ |
|
imageIndex = m_pData->GetInt("Image", 0); |
|
} |
|
|
|
if (imageIndex) |
|
{ |
|
IImage *pImage = m_pTreeView->GetImage(imageIndex); |
|
if (pImage) |
|
{ |
|
m_pImagePanel->SetImage(pImage); |
|
} |
|
m_pImagePanel->Paint(); |
|
} |
|
|
|
m_pText->Paint(); |
|
} |
|
|
|
void TreeNode::ApplySchemeSettings(IScheme *pScheme) |
|
{ |
|
BaseClass::ApplySchemeSettings(pScheme); |
|
|
|
SetBorder( NULL ); |
|
SetFgColor( m_pTreeView->GetFgColor() ); |
|
SetBgColor( m_pTreeView->GetBgColor() ); |
|
SetFont( m_pTreeView->GetFont() ); |
|
} |
|
|
|
void TreeNode::SetSelectionTextColor( const Color& clr ) |
|
{ |
|
if ( m_pText ) |
|
{ |
|
m_pText->SetSelectionTextColor( clr ); |
|
} |
|
} |
|
|
|
void TreeNode::SetSelectionBgColor( const Color& clr ) |
|
{ |
|
if ( m_pText ) |
|
{ |
|
m_pText->SetSelectionBgColor( clr ); |
|
} |
|
} |
|
|
|
void TreeNode::SetSelectionUnfocusedBgColor( const Color& clr ) |
|
{ |
|
if ( m_pText ) |
|
{ |
|
m_pText->SetSelectionUnfocusedBgColor( clr ); |
|
} |
|
} |
|
|
|
void TreeNode::SetBgColor( Color color ) |
|
{ |
|
BaseClass::SetBgColor( color ); |
|
if ( m_pText ) |
|
{ |
|
m_pText->SetBgColor( color ); |
|
} |
|
|
|
} |
|
|
|
void TreeNode::SetFgColor( Color color ) |
|
{ |
|
BaseClass::SetFgColor( color ); |
|
if ( m_pText ) |
|
{ |
|
m_pText->SetFgColor( color ); |
|
} |
|
} |
|
|
|
void TreeNode::OnSetFocus() |
|
{ |
|
m_pText->RequestFocus(); |
|
} |
|
|
|
int TreeNode::GetPrevChildItemIndex( TreeNode *pCurrentChild ) |
|
{ |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
if ( m_Children[i] == pCurrentChild ) |
|
{ |
|
if ( i <= 0 ) |
|
return -1; |
|
|
|
TreeNode *pChild = m_Children[i-1]; |
|
return pChild->m_ItemIndex; |
|
} |
|
} |
|
return -1; |
|
} |
|
|
|
int TreeNode::GetNextChildItemIndex( TreeNode *pCurrentChild ) |
|
{ |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
if ( m_Children[i] == pCurrentChild ) |
|
{ |
|
if ( i >= GetChildrenCount() - 1 ) |
|
return -1; |
|
|
|
TreeNode *pChild = m_Children[i+1]; |
|
return pChild->m_ItemIndex; |
|
} |
|
} |
|
return -1; |
|
} |
|
|
|
void TreeNode::SelectPrevChild(TreeNode *pCurrentChild) |
|
{ |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
if (m_Children[i] == pCurrentChild) |
|
break; |
|
} |
|
|
|
// this shouldn't happen |
|
if (i == GetChildrenCount()) |
|
{ |
|
Assert(0); |
|
return; |
|
} |
|
|
|
// were we on the first child? |
|
if (i == 0) |
|
{ |
|
// if so, then we take over! |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, true ); |
|
} |
|
else |
|
{ |
|
// see if we need to find a grandchild of the previous sibling |
|
TreeNode *pChild = m_Children[i-1]; |
|
|
|
// if this child is expanded with children, then we have to find the last child |
|
while (pChild->m_bExpand && pChild->GetChildrenCount()>0) |
|
{ |
|
// find the last child |
|
pChild = pChild->m_Children[pChild->GetChildrenCount()-1]; |
|
} |
|
m_pTreeView->AddSelectedItem( pChild->m_ItemIndex, true ); |
|
} |
|
} |
|
|
|
void TreeNode::SelectNextChild(TreeNode *pCurrentChild) |
|
{ |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
if (m_Children[i] == pCurrentChild) |
|
break; |
|
} |
|
|
|
// this shouldn't happen |
|
if (i == GetChildrenCount()) |
|
{ |
|
Assert(0); |
|
return; |
|
} |
|
|
|
// were we on the last child? |
|
if (i == GetChildrenCount() - 1) |
|
{ |
|
// tell our parent to get the next child |
|
if (GetParentNode()) |
|
{ |
|
GetParentNode()->SelectNextChild(this); |
|
} |
|
} |
|
else |
|
{ |
|
m_pTreeView->AddSelectedItem( m_Children[i+1]->m_ItemIndex, true ); |
|
} |
|
} |
|
|
|
void TreeNode::ClosePreviousParents( TreeNode *pPreviousParent ) |
|
{ |
|
// close up all the open nodes we've just stepped out of. |
|
CUtlVector< int > selected; |
|
m_pTreeView->GetSelectedItems( selected ); |
|
if ( selected.Count() == 0 ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
// Most recently clicked item |
|
TreeNode *selectedItem = m_pTreeView->GetItem( selected[ 0 ] ); |
|
TreeNode *pNewParent = selectedItem->GetParentNode(); |
|
if ( pPreviousParent && pNewParent ) |
|
{ |
|
while ( pPreviousParent->m_ItemIndex > pNewParent->m_ItemIndex ) |
|
{ |
|
pPreviousParent->SetNodeExpanded(false); |
|
pPreviousParent = pPreviousParent->GetParentNode(); |
|
} |
|
} |
|
} |
|
|
|
void TreeNode::StepInto( bool bClosePrevious ) |
|
{ |
|
if ( !m_bExpand ) |
|
{ |
|
SetNodeExpanded(true); |
|
} |
|
|
|
if ( ( GetChildrenCount() > 0 ) && m_bExpand ) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_Children[0]->m_ItemIndex, true ); |
|
} |
|
else if ( GetParentNode() ) |
|
{ |
|
TreeNode *pParent = GetParentNode(); |
|
pParent->SelectNextChild(this); |
|
|
|
if ( bClosePrevious ) |
|
{ |
|
ClosePreviousParents( pParent ); |
|
} |
|
} |
|
} |
|
|
|
void TreeNode::StepOut( bool bClosePrevious ) |
|
{ |
|
TreeNode *pParent = GetParentNode(); |
|
if ( pParent ) |
|
{ |
|
m_pTreeView->AddSelectedItem( pParent->m_ItemIndex, true ); |
|
if ( pParent->GetParentNode() ) |
|
{ |
|
pParent->GetParentNode()->SelectNextChild(pParent); |
|
} |
|
if ( bClosePrevious ) |
|
{ |
|
ClosePreviousParents( pParent ); |
|
} |
|
else |
|
{ |
|
pParent->SetNodeExpanded(true); |
|
} |
|
} |
|
} |
|
|
|
void TreeNode::StepOver( bool bClosePrevious ) |
|
{ |
|
TreeNode *pParent = GetParentNode(); |
|
if ( pParent ) |
|
{ |
|
GetParentNode()->SelectNextChild(this); |
|
if ( bClosePrevious ) |
|
{ |
|
ClosePreviousParents( pParent ); |
|
} |
|
} |
|
} |
|
|
|
void TreeNode::OnKeyCodeTyped(KeyCode code) |
|
{ |
|
switch (code) |
|
{ |
|
case KEY_LEFT: |
|
{ |
|
if (m_bExpand && GetChildrenCount() > 0) |
|
{ |
|
SetNodeExpanded(false); |
|
} |
|
else |
|
{ |
|
if (GetParentNode()) |
|
{ |
|
m_pTreeView->AddSelectedItem( GetParentNode()->m_ItemIndex, true ); |
|
} |
|
} |
|
break; |
|
} |
|
case KEY_RIGHT: |
|
{ |
|
if (!m_bExpand) |
|
{ |
|
SetNodeExpanded(true); |
|
} |
|
else if (GetChildrenCount() > 0) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_Children[0]->m_ItemIndex, true ); |
|
} |
|
break; |
|
} |
|
case KEY_UP: |
|
{ |
|
if (GetParentNode()) |
|
{ |
|
GetParentNode()->SelectPrevChild(this); |
|
} |
|
break; |
|
} |
|
case KEY_DOWN: |
|
{ |
|
if (GetChildrenCount() > 0 && m_bExpand) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_Children[0]->m_ItemIndex, true ); |
|
} |
|
else if (GetParentNode()) |
|
{ |
|
GetParentNode()->SelectNextChild(this); |
|
} |
|
break; |
|
} |
|
case KEY_SPACE: |
|
{ |
|
bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); |
|
bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); |
|
bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); |
|
if ( shift ) |
|
{ |
|
StepOut( !ctrl ); |
|
} |
|
else if ( alt ) |
|
{ |
|
StepOver( !ctrl ); |
|
} |
|
else |
|
{ |
|
StepInto( !ctrl ); |
|
} |
|
break; |
|
} |
|
case KEY_I: |
|
{ |
|
StepInto(); |
|
break; |
|
} |
|
case KEY_U: |
|
{ |
|
StepOut(); |
|
break; |
|
} |
|
case KEY_O: |
|
{ |
|
StepOver(); |
|
break; |
|
} |
|
case KEY_ESCAPE: |
|
{ |
|
if ( m_pTreeView->GetSelectedItemCount() > 0 ) |
|
{ |
|
m_pTreeView->ClearSelection(); |
|
} |
|
else |
|
{ |
|
BaseClass::OnKeyCodeTyped(code); |
|
} |
|
} |
|
break; |
|
case KEY_A: |
|
{ |
|
bool ctrldown = input()->IsKeyDown( KEY_LCONTROL ) || input()->IsKeyDown( KEY_RCONTROL ); |
|
if ( ctrldown ) |
|
{ |
|
m_pTreeView->SelectAll(); |
|
} |
|
else |
|
{ |
|
BaseClass::OnKeyCodeTyped(code); |
|
} |
|
} |
|
break; |
|
default: |
|
BaseClass::OnKeyCodeTyped(code); |
|
return; |
|
} |
|
} |
|
|
|
void TreeNode::OnMouseWheeled(int delta) |
|
{ |
|
CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); |
|
} |
|
|
|
void TreeNode::OnMouseDoublePressed( MouseCode code ) |
|
{ |
|
int x, y; |
|
input()->GetCursorPos(x, y); |
|
|
|
if (code == MOUSE_LEFT) |
|
{ |
|
ScreenToLocal(x, y); |
|
if (x > TREE_INDENT_AMOUNT) |
|
{ |
|
SetNodeExpanded(!m_bExpand); |
|
} |
|
} |
|
} |
|
|
|
bool TreeNode::IsDragEnabled() const |
|
{ |
|
int x, y; |
|
input()->GetCursorPos(x, y); |
|
((TreeNode *)this)->ScreenToLocal(x, y); |
|
if ( x < TREE_INDENT_AMOUNT ) |
|
return false; |
|
|
|
return BaseClass::IsDragEnabled(); |
|
} |
|
|
|
void TreeNode::OnMouseReleased(MouseCode code) |
|
{ |
|
BaseClass::OnMouseReleased( code ); |
|
|
|
if ( input()->GetMouseCapture() == GetVPanel() ) |
|
{ |
|
input()->SetMouseCapture( NULL ); |
|
return; |
|
} |
|
int x, y; |
|
input()->GetCursorPos(x, y); |
|
ScreenToLocal(x, y); |
|
|
|
if ( x < TREE_INDENT_AMOUNT ) |
|
return; |
|
|
|
bool ctrldown = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); |
|
bool shiftdown = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); |
|
|
|
if ( !ctrldown && !shiftdown && ( code == MOUSE_LEFT ) ) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, true ); |
|
} |
|
} |
|
|
|
void TreeNode::OnCursorMoved( int x, int y ) |
|
{ |
|
if ( input()->GetMouseCapture() != GetVPanel() ) |
|
return; |
|
|
|
LocalToScreen( x, y ); |
|
m_pTreeView->ScreenToLocal( x, y ); |
|
int newItem = m_pTreeView->FindItemUnderMouse( x, y ); |
|
if ( newItem == -1 ) |
|
{ |
|
// Fixme: Figure out best item |
|
return; |
|
} |
|
|
|
int startItem = m_nClickedItem; |
|
int endItem = newItem; |
|
if ( startItem > endItem ) |
|
{ |
|
int temp = startItem; |
|
startItem = endItem; |
|
endItem = temp; |
|
} |
|
|
|
CUtlVector< TreeNode * > list; |
|
m_pTreeView->m_pRootNode->FindNodesInRange( list, startItem, endItem ); |
|
|
|
int c = list.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
TreeNode *item = list[ i ]; |
|
if ( m_bClickedSelected ) |
|
{ |
|
m_pTreeView->AddSelectedItem( item->m_ItemIndex, false ); |
|
} |
|
else |
|
{ |
|
m_pTreeView->RemoveSelectedItem( item->m_ItemIndex ); |
|
} |
|
} |
|
} |
|
|
|
void TreeNode::OnMousePressed( MouseCode code) |
|
{ |
|
BaseClass::OnMousePressed( code ); |
|
|
|
bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); |
|
bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); |
|
int x, y; |
|
input()->GetCursorPos(x, y); |
|
|
|
bool bExpandTree = m_pTreeView->m_bLeftClickExpandsTree; |
|
|
|
if ( code == MOUSE_LEFT ) |
|
{ |
|
ScreenToLocal(x, y); |
|
if ( x < TREE_INDENT_AMOUNT ) |
|
{ |
|
if ( bExpandTree ) |
|
{ |
|
SetNodeExpanded(!m_bExpand); |
|
} |
|
// m_pTreeView->SetSelectedItem(m_ItemIndex); // explorer doesn't actually select item when it expands an item |
|
// purposely commented out in case we want to change the behavior |
|
} |
|
else |
|
{ |
|
m_nClickedItem = m_ItemIndex; |
|
if ( m_pTreeView->IsMultipleItemDragEnabled() ) |
|
{ |
|
input()->SetMouseCapture( GetVPanel() ); |
|
} |
|
|
|
if ( shift ) |
|
{ |
|
m_pTreeView->RangeSelectItems( m_ItemIndex ); |
|
} |
|
else |
|
{ |
|
if ( !IsSelected() || ctrl ) |
|
{ |
|
if ( IsSelected() && ctrl ) |
|
{ |
|
m_pTreeView->RemoveSelectedItem( m_ItemIndex ); |
|
} |
|
else |
|
{ |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, !ctrl ); |
|
} |
|
} |
|
else if ( IsSelected() && m_pTreeView->IsMultipleItemDragEnabled() ) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, !shift ); |
|
} |
|
} |
|
|
|
m_bClickedSelected = m_pTreeView->IsItemSelected( m_ItemIndex ); |
|
} |
|
} |
|
else if (code == MOUSE_RIGHT) |
|
{ |
|
// context menu selection |
|
// If the item was selected, leave selected items alone, otherwise make it the only selected item |
|
if ( !m_pTreeView->IsItemSelected( m_ItemIndex ) ) |
|
{ |
|
m_pTreeView->AddSelectedItem( m_ItemIndex, true ); |
|
} |
|
|
|
// ask parent to context menu |
|
m_pTreeView->GenerateContextMenu(m_ItemIndex, x, y); |
|
} |
|
} |
|
|
|
void TreeNode::RemoveChildren() |
|
{ |
|
int c = m_Children.Count(); |
|
for ( int i = c - 1 ; i >= 0 ; --i ) |
|
{ |
|
m_pTreeView->RemoveItem( m_Children[ i ]->m_ItemIndex, false, true ); |
|
} |
|
m_Children.RemoveAll(); |
|
} |
|
|
|
void TreeNode::FindNodesInRange( CUtlVector< TreeNode * >& list, int startIndex, int endIndex ) |
|
{ |
|
list.RemoveAll(); |
|
bool finished = false; |
|
bool foundstart = false; |
|
FindNodesInRange_R( list, finished, foundstart, startIndex, endIndex ); |
|
} |
|
|
|
void TreeNode::FindNodesInRange_R( CUtlVector< TreeNode * >& list, bool& finished, bool& foundStart, int startIndex, int endIndex ) |
|
{ |
|
if ( finished ) |
|
return; |
|
if ( foundStart == true ) |
|
{ |
|
list.AddToTail( this ); |
|
|
|
if ( m_ItemIndex == startIndex || m_ItemIndex == endIndex ) |
|
{ |
|
finished = true; |
|
return; |
|
} |
|
} |
|
else if ( m_ItemIndex == startIndex || m_ItemIndex == endIndex ) |
|
{ |
|
foundStart = true; |
|
list.AddToTail( this ); |
|
if ( startIndex == endIndex ) |
|
{ |
|
finished = true; |
|
return; |
|
} |
|
} |
|
|
|
if ( !m_bExpand ) |
|
return; |
|
|
|
|
|
int i; |
|
int c = GetChildrenCount(); |
|
for (i=0;i<c;i++) |
|
{ |
|
m_Children[i]->FindNodesInRange_R( list, finished, foundStart, startIndex, endIndex ); |
|
} |
|
} |
|
|
|
void TreeNode::PositionAndSetVisibleNodes(int &nStart, int &nCount, int x, int &y) |
|
{ |
|
// position ourselves |
|
if (nStart == 0) |
|
{ |
|
BaseClass::SetVisible(true); |
|
SetPos(x, y); |
|
y += m_pTreeView->GetRowHeight(); // m_nRowHeight |
|
nCount--; |
|
} |
|
else // still looking for first element |
|
{ |
|
nStart--; |
|
BaseClass::SetVisible(false); |
|
} |
|
|
|
x += TREE_INDENT_AMOUNT; |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
if (nCount > 0 && m_bExpand) |
|
{ |
|
m_Children[i]->PositionAndSetVisibleNodes(nStart, nCount, x, y); |
|
} |
|
else |
|
{ |
|
m_Children[i]->SetVisible(false); // this will make all grand children hidden as well |
|
} |
|
} |
|
} |
|
|
|
TreeNode *TreeNode::FindItemUnderMouse( int &nStart, int& nCount, int x, int &y, int mx, int my ) |
|
{ |
|
// position ourselves |
|
if (nStart == 0) |
|
{ |
|
int posx, posy; |
|
GetPos(posx, posy); |
|
if ( my >= posy && my < posy + m_pTreeView->GetRowHeight() ) |
|
{ |
|
return this; |
|
} |
|
y += m_pTreeView->GetRowHeight(); |
|
nCount--; |
|
} |
|
else // still looking for first element |
|
{ |
|
nStart--; |
|
} |
|
|
|
x += TREE_INDENT_AMOUNT; |
|
int i; |
|
for (i=0;i<GetChildrenCount();i++) |
|
{ |
|
if (nCount > 0 && m_bExpand) |
|
{ |
|
TreeNode *child = m_Children[i]->FindItemUnderMouse(nStart, nCount, x, y, mx, my); |
|
if ( child != NULL ) |
|
{ |
|
return child; |
|
} |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
// counts items above this item including itself |
|
int TreeNode::CountVisibleIndex() |
|
{ |
|
int nCount = 1; // myself |
|
if (GetParentNode()) |
|
{ |
|
int i; |
|
for (i=0;i<GetParentNode()->GetChildrenCount();i++) |
|
{ |
|
if (GetParentNode()->m_Children[i] == this) |
|
break; |
|
|
|
nCount += GetParentNode()->m_Children[i]->CountVisibleNodes(); |
|
} |
|
return nCount + GetParentNode()->CountVisibleIndex(); |
|
} |
|
else |
|
return nCount; |
|
} |
|
|
|
|
|
}; // namespace vgui |
|
|
|
DECLARE_BUILD_FACTORY( TreeView ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
TreeView::TreeView(Panel *parent, const char *panelName) : Panel(parent, panelName) |
|
{ |
|
m_bScrollbarExternal[ 0 ] = m_bScrollbarExternal[ 1 ] = false; |
|
m_nRowHeight = 20; |
|
m_pRootNode = NULL; |
|
m_pImageList = NULL; |
|
m_pSortFunc = NULL; |
|
m_Font = 0; |
|
|
|
m_pSubPanel = new TreeViewSubPanel(this); |
|
m_pSubPanel->SetVisible(true); |
|
m_pSubPanel->SetPos(0,0); |
|
|
|
m_pHorzScrollBar = new ScrollBar(this, "HorizScrollBar", false); |
|
m_pHorzScrollBar->AddActionSignalTarget(this); |
|
m_pHorzScrollBar->SetVisible(false); |
|
|
|
m_pVertScrollBar = new ScrollBar(this, "VertScrollBar", true); |
|
m_pVertScrollBar->SetVisible(false); |
|
m_pVertScrollBar->AddActionSignalTarget(this); |
|
|
|
m_bAllowLabelEditing = false; |
|
m_bDragEnabledItems = false; |
|
m_bDeleteImageListWhenDone = false; |
|
m_bLabelBeingEdited = false; |
|
m_bMultipleItemDragging = false; |
|
m_bLeftClickExpandsTree = true; |
|
m_bAllowMultipleSelections = false; |
|
m_nMostRecentlySelectedItem = -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
TreeView::~TreeView() |
|
{ |
|
CleanUpImageList(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Clean up the image list |
|
//----------------------------------------------------------------------------- |
|
void TreeView::CleanUpImageList( ) |
|
{ |
|
if ( m_pImageList ) |
|
{ |
|
if ( m_bDeleteImageListWhenDone ) |
|
{ |
|
delete m_pImageList; |
|
} |
|
m_pImageList = NULL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetSortFunc(TreeViewSortFunc_t pSortFunc) |
|
{ |
|
m_pSortFunc = pSortFunc; |
|
} |
|
|
|
HFont TreeView::GetFont() |
|
{ |
|
return m_Font; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetFont(HFont font) |
|
{ |
|
Assert( font ); |
|
if ( !font ) |
|
return; |
|
|
|
m_Font = font; |
|
m_nRowHeight = surface()->GetFontTall(font) + 2; |
|
|
|
if (m_pRootNode) |
|
{ |
|
m_pRootNode->SetFont(font); |
|
} |
|
InvalidateLayout(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetRowHeight() |
|
{ |
|
return m_nRowHeight; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetVisibleMaxWidth() |
|
{ |
|
if (m_pRootNode) |
|
{ |
|
return m_pRootNode->GetVisibleMaxWidth(); |
|
} |
|
else |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int TreeView::AddItem(KeyValues *data, int parentItemIndex) |
|
{ |
|
Assert(parentItemIndex == -1 || m_NodeList.IsValidIndex(parentItemIndex)); |
|
|
|
TreeNode *pTreeNode = new TreeNode(m_pSubPanel, this); |
|
pTreeNode->SetDragEnabled( m_bDragEnabledItems ); |
|
pTreeNode->m_ItemIndex = m_NodeList.AddToTail(pTreeNode); |
|
pTreeNode->SetKeyValues(data); |
|
|
|
if ( m_Font != 0 ) |
|
{ |
|
pTreeNode->SetFont( m_Font ); |
|
} |
|
pTreeNode->SetBgColor( GetBgColor() ); |
|
|
|
if ( data->GetInt( "droppable", 0 ) != 0 ) |
|
{ |
|
float flContextDelay = data->GetFloat( "drophoverdelay" ); |
|
if ( flContextDelay ) |
|
{ |
|
pTreeNode->SetDropEnabled( true, flContextDelay ); |
|
} |
|
else |
|
{ |
|
pTreeNode->SetDropEnabled( true ); |
|
} |
|
} |
|
|
|
// there can be only one root |
|
if (parentItemIndex == -1) |
|
{ |
|
Assert(m_pRootNode == NULL); |
|
m_pRootNode = pTreeNode; |
|
pTreeNode->m_ParentIndex = -1; |
|
} |
|
else |
|
{ |
|
pTreeNode->m_ParentIndex = parentItemIndex; |
|
|
|
// add to parent list |
|
pTreeNode->GetParentNode()->AddChild(pTreeNode); |
|
} |
|
|
|
SETUP_PANEL( pTreeNode ); |
|
|
|
return pTreeNode->m_ItemIndex; |
|
} |
|
|
|
|
|
int TreeView::GetRootItemIndex() |
|
{ |
|
if ( m_pRootNode ) |
|
return m_pRootNode->m_ItemIndex; |
|
else |
|
return -1; |
|
} |
|
|
|
|
|
int TreeView::GetNumChildren( int itemIndex ) |
|
{ |
|
if ( itemIndex == -1 ) |
|
return 0; |
|
|
|
return m_NodeList[itemIndex]->m_Children.Count(); |
|
} |
|
|
|
|
|
int TreeView::GetChild( int iParentItemIndex, int iChild ) |
|
{ |
|
return m_NodeList[iParentItemIndex]->m_Children[iChild]->m_ItemIndex; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : itemIndex - |
|
// Output : TreeNode |
|
//----------------------------------------------------------------------------- |
|
TreeNode *TreeView::GetItem( int itemIndex ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
{ |
|
Assert( 0 ); |
|
return NULL; |
|
} |
|
|
|
return m_NodeList[ itemIndex ]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetItemCount(void) |
|
{ |
|
return m_NodeList.Count(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
KeyValues* TreeView::GetItemData(int itemIndex) |
|
{ |
|
if (!m_NodeList.IsValidIndex(itemIndex)) |
|
return NULL; |
|
else |
|
return m_NodeList[itemIndex]->m_pData; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::RemoveItem(int itemIndex, bool bPromoteChildren, bool bFullDelete ) |
|
{ |
|
// HACK: there's a bug with RemoveItem where panels are lingering. This gets around it temporarily. |
|
|
|
// FIXME: Negative item indices is a bogus interface method! |
|
// because what if you want to recursively remove everything under node 0? |
|
// Use the bFullDelete parameter instead. |
|
if ( itemIndex < 0 ) |
|
{ |
|
itemIndex = -itemIndex; |
|
bFullDelete = true; |
|
} |
|
|
|
if (!m_NodeList.IsValidIndex(itemIndex)) |
|
return; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
TreeNode *pParent = pNode->GetParentNode(); |
|
|
|
// are we promoting the children |
|
if (bPromoteChildren && pParent) |
|
{ |
|
int i; |
|
for (i=0;i<pNode->GetChildrenCount();i++) |
|
{ |
|
TreeNode *pChild = pNode->m_Children[i]; |
|
pChild->m_ParentIndex = pParent->m_ItemIndex; |
|
} |
|
} |
|
else |
|
{ |
|
// delete our children |
|
if ( bFullDelete ) |
|
{ |
|
while ( pNode->GetChildrenCount() ) |
|
RemoveItem( -pNode->m_Children[0]->m_ItemIndex, false ); |
|
} |
|
else |
|
{ |
|
int i; |
|
for (i=0;i<pNode->GetChildrenCount();i++) |
|
{ |
|
TreeNode *pDeleteChild = pNode->m_Children[i]; |
|
RemoveItem(pDeleteChild->m_ItemIndex, false); |
|
} |
|
} |
|
} |
|
|
|
// remove from our parent's children list |
|
if (pParent) |
|
{ |
|
pParent->m_Children.FindAndRemove(pNode); |
|
} |
|
|
|
// finally get rid of ourselves from the main list |
|
m_NodeList.Remove(itemIndex); |
|
|
|
if ( bFullDelete ) |
|
delete pNode; |
|
else |
|
pNode->MarkForDeletion(); |
|
|
|
// Make sure we don't leave ourselves with an invalid selected item. |
|
m_SelectedItems.FindAndRemove( pNode ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::RemoveAll() |
|
{ |
|
int i; |
|
for (i=0;i<m_NodeList.MaxElementIndex();i++) |
|
{ |
|
if (!m_NodeList.IsValidIndex(i)) |
|
continue; |
|
|
|
m_NodeList[i]->MarkForDeletion(); |
|
} |
|
m_NodeList.RemoveAll(); |
|
m_pRootNode = NULL; |
|
ClearSelection(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool TreeView::ModifyItem(int itemIndex, KeyValues *data) |
|
{ |
|
if (!m_NodeList.IsValidIndex(itemIndex)) |
|
return false; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
TreeNode *pParent = pNode->GetParentNode(); |
|
bool bReSort = ( m_pSortFunc && pParent ); |
|
int nChildIndex = -1; |
|
if ( bReSort ) |
|
{ |
|
nChildIndex = pParent->FindChild( pNode ); |
|
} |
|
|
|
pNode->SetKeyValues(data); |
|
|
|
// Changing the data can cause it to re-sort |
|
if ( bReSort ) |
|
{ |
|
int nChildren = pParent->GetChildrenCount(); |
|
bool bLeftBad = (nChildIndex > 0) && m_pSortFunc( pNode->m_pData, pParent->m_Children[nChildIndex-1]->m_pData ); |
|
bool bRightBad = (nChildIndex < nChildren - 1) && m_pSortFunc( pParent->m_Children[nChildIndex+1]->m_pData, pNode->m_pData ); |
|
if ( bLeftBad || bRightBad ) |
|
{ |
|
pParent->m_Children.Remove( nChildIndex ); |
|
pParent->AddChild( pNode ); |
|
} |
|
} |
|
|
|
InvalidateLayout(); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: set the selection colors of an element in the tree view |
|
//----------------------------------------------------------------------------- |
|
|
|
void TreeView::SetItemSelectionTextColor( int itemIndex, const Color& clr ) |
|
{ |
|
Assert( m_NodeList.IsValidIndex(itemIndex) ); |
|
if ( !m_NodeList.IsValidIndex(itemIndex) ) |
|
return; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
pNode->SetSelectionTextColor( clr ); |
|
} |
|
|
|
void TreeView::SetItemSelectionBgColor( int itemIndex, const Color& clr ) |
|
{ |
|
Assert( m_NodeList.IsValidIndex(itemIndex) ); |
|
if ( !m_NodeList.IsValidIndex(itemIndex) ) |
|
return; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
pNode->SetSelectionBgColor( clr ); |
|
} |
|
|
|
void TreeView::SetItemSelectionUnfocusedBgColor( int itemIndex, const Color& clr ) |
|
{ |
|
Assert( m_NodeList.IsValidIndex(itemIndex) ); |
|
if ( !m_NodeList.IsValidIndex(itemIndex) ) |
|
return; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
pNode->SetSelectionUnfocusedBgColor( clr ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: set the fg color of an element in the tree view |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetItemFgColor(int itemIndex, const Color& color) |
|
{ |
|
Assert( m_NodeList.IsValidIndex(itemIndex) ); |
|
if ( !m_NodeList.IsValidIndex(itemIndex) ) |
|
return; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
pNode->SetFgColor( color ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: set the bg color of an element in the tree view |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetItemBgColor(int itemIndex, const Color& color) |
|
{ |
|
Assert( m_NodeList.IsValidIndex(itemIndex) ); |
|
if ( !m_NodeList.IsValidIndex(itemIndex) ) |
|
return; |
|
|
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
pNode->SetBgColor( color ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetItemParent(int itemIndex) |
|
{ |
|
return m_NodeList[itemIndex]->m_ParentIndex; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetImageList(ImageList *imageList, bool deleteImageListWhenDone) |
|
{ |
|
CleanUpImageList(); |
|
m_pImageList = imageList; |
|
m_bDeleteImageListWhenDone = deleteImageListWhenDone; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
IImage *TreeView::GetImage(int index) |
|
{ |
|
return m_pImageList->GetImage(index); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::GetSelectedItems( CUtlVector< int >& list ) |
|
{ |
|
list.RemoveAll(); |
|
|
|
int c = m_SelectedItems.Count(); |
|
for ( int i = 0 ; i < c; ++i ) |
|
{ |
|
list.AddToTail( m_SelectedItems[ i ]->m_ItemIndex ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::GetSelectedItemData( CUtlVector< KeyValues * >& list ) |
|
{ |
|
list.RemoveAll(); |
|
|
|
int c = m_SelectedItems.Count(); |
|
for ( int i = 0 ; i < c; ++i ) |
|
{ |
|
list.AddToTail( m_SelectedItems[ i ]->m_pData ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool TreeView::IsItemIDValid(int itemIndex) |
|
{ |
|
return m_NodeList.IsValidIndex(itemIndex); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetHighestItemID() |
|
{ |
|
return m_NodeList.MaxElementIndex(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::ExpandItem(int itemIndex, bool bExpand) |
|
{ |
|
if (!m_NodeList.IsValidIndex(itemIndex)) |
|
return; |
|
|
|
m_NodeList[itemIndex]->SetNodeExpanded(bExpand); |
|
InvalidateLayout(); |
|
} |
|
|
|
bool TreeView::IsItemExpanded( int itemIndex ) |
|
{ |
|
if (!m_NodeList.IsValidIndex(itemIndex)) |
|
return false; |
|
|
|
return m_NodeList[itemIndex]->IsExpanded(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Scrolls the list according to the mouse wheel movement |
|
//----------------------------------------------------------------------------- |
|
void TreeView::OnMouseWheeled(int delta) |
|
{ |
|
if ( !m_pVertScrollBar->IsVisible() ) |
|
{ |
|
return; |
|
} |
|
int val = m_pVertScrollBar->GetValue(); |
|
val -= (delta * 3); |
|
m_pVertScrollBar->SetValue(val); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::OnSizeChanged(int wide, int tall) |
|
{ |
|
BaseClass::OnSizeChanged(wide, tall); |
|
InvalidateLayout(); |
|
Repaint(); |
|
} |
|
|
|
void TreeView::GetScrollBarSize( bool vertical, int& w, int& h ) |
|
{ |
|
int idx = vertical ? 0 : 1; |
|
|
|
if ( m_bScrollbarExternal[ idx ] ) |
|
{ |
|
w = h = 0; |
|
return; |
|
} |
|
|
|
if ( vertical ) |
|
{ |
|
m_pVertScrollBar->GetSize( w, h ); |
|
} |
|
else |
|
{ |
|
m_pHorzScrollBar->GetSize( w, h ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::PerformLayout() |
|
{ |
|
int wide, tall; |
|
GetSize( wide, tall ); |
|
|
|
if ( !m_pRootNode ) |
|
{ |
|
m_pSubPanel->SetSize( wide, tall ); |
|
return; |
|
} |
|
|
|
int sbhw, sbhh; |
|
GetScrollBarSize( false, sbhw, sbhh ); |
|
int sbvw, sbvh; |
|
GetScrollBarSize( true, sbvw, sbvh ); |
|
|
|
bool vbarNeeded = false; |
|
bool hbarNeeded = false; |
|
|
|
// okay we have to check if we need either scroll bars, since if we need one |
|
// it might make it necessary to have the other one |
|
int nodesVisible = tall / m_nRowHeight; |
|
|
|
// count the number of visible items |
|
int visibleItemCount = m_pRootNode->CountVisibleNodes(); |
|
int maxWidth = m_pRootNode->GetVisibleMaxWidth() + 10; // 10 pixel buffer |
|
|
|
vbarNeeded = visibleItemCount > nodesVisible; |
|
|
|
if (!vbarNeeded) |
|
{ |
|
if (maxWidth > wide) |
|
{ |
|
hbarNeeded = true; |
|
|
|
// recalculate if vbar is needed now |
|
// double check that we really don't need it |
|
nodesVisible = (tall - sbhh) / m_nRowHeight; |
|
vbarNeeded = visibleItemCount > nodesVisible; |
|
} |
|
} |
|
else |
|
{ |
|
// we've got the vertical bar here, so shrink the width |
|
hbarNeeded = maxWidth > (wide - (sbvw+2)); |
|
|
|
if (hbarNeeded) |
|
{ |
|
nodesVisible = (tall - sbhh) / m_nRowHeight; |
|
} |
|
} |
|
|
|
int subPanelWidth = wide; |
|
int subPanelHeight = tall; |
|
|
|
int vbarPos = 0; |
|
if (vbarNeeded) |
|
{ |
|
subPanelWidth -= (sbvw + 2); |
|
int barSize = tall; |
|
if (hbarNeeded) |
|
{ |
|
barSize -= sbhh; |
|
} |
|
|
|
//!! need to make it recalculate scroll positions |
|
m_pVertScrollBar->SetVisible(true); |
|
m_pVertScrollBar->SetEnabled(false); |
|
m_pVertScrollBar->SetRangeWindow( nodesVisible ); |
|
m_pVertScrollBar->SetRange( 0, visibleItemCount); |
|
m_pVertScrollBar->SetButtonPressedScrollValue( 1 ); |
|
|
|
if ( !m_bScrollbarExternal[ 0 ] ) |
|
{ |
|
m_pVertScrollBar->SetPos(wide - (sbvw + WINDOW_BORDER_WIDTH), 0); |
|
m_pVertScrollBar->SetSize(sbvw, barSize - 2); |
|
} |
|
|
|
// need to figure out |
|
vbarPos = m_pVertScrollBar->GetValue(); |
|
} |
|
else |
|
{ |
|
m_pVertScrollBar->SetVisible(false); |
|
m_pVertScrollBar->SetValue( 0 ); |
|
} |
|
|
|
int hbarPos = 0; |
|
if (hbarNeeded) |
|
{ |
|
subPanelHeight -= (sbhh + 2); |
|
int barSize = wide; |
|
if (vbarNeeded) |
|
{ |
|
barSize -= sbvw; |
|
} |
|
m_pHorzScrollBar->SetVisible(true); |
|
m_pHorzScrollBar->SetEnabled(false); |
|
m_pHorzScrollBar->SetRangeWindow( barSize ); |
|
m_pHorzScrollBar->SetRange( 0, maxWidth); |
|
m_pHorzScrollBar->SetButtonPressedScrollValue( 10 ); |
|
|
|
if ( !m_bScrollbarExternal[ 1 ] ) |
|
{ |
|
m_pHorzScrollBar->SetPos(0, tall - (sbhh + WINDOW_BORDER_WIDTH)); |
|
m_pHorzScrollBar->SetSize(barSize - 2, sbhh); |
|
} |
|
|
|
hbarPos = m_pHorzScrollBar->GetValue(); |
|
} |
|
else |
|
{ |
|
m_pHorzScrollBar->SetVisible(false); |
|
m_pHorzScrollBar->SetValue( 0 ); |
|
} |
|
|
|
m_pSubPanel->SetSize(subPanelWidth, subPanelHeight); |
|
|
|
int y = 0; |
|
m_pRootNode->PositionAndSetVisibleNodes(vbarPos, visibleItemCount, -hbarPos, y); |
|
|
|
Repaint(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::MakeItemVisible(int itemIndex) |
|
{ |
|
// first make sure that all parents are expanded |
|
TreeNode *pNode = m_NodeList[itemIndex]; |
|
TreeNode *pParent = pNode->GetParentNode(); |
|
while (pParent) |
|
{ |
|
if (!pParent->m_bExpand) |
|
{ |
|
pParent->SetNodeExpanded(true); |
|
} |
|
pParent = pParent->GetParentNode(); |
|
} |
|
|
|
// recalculate scroll bar due to possible exapnsion |
|
PerformLayout(); |
|
|
|
if (!m_pVertScrollBar->IsVisible()) |
|
return; |
|
|
|
int visibleIndex = pNode->CountVisibleIndex()-1; |
|
int range = m_pVertScrollBar->GetRangeWindow(); |
|
int vbarPos = m_pVertScrollBar->GetValue(); |
|
|
|
// do we need to scroll up or down? |
|
if (visibleIndex < vbarPos) |
|
{ |
|
m_pVertScrollBar->SetValue(visibleIndex); |
|
} |
|
else if (visibleIndex+1 > vbarPos+range) |
|
{ |
|
m_pVertScrollBar->SetValue(visibleIndex+1-range); |
|
} |
|
InvalidateLayout(); |
|
} |
|
|
|
void TreeView::GetVBarInfo( int &top, int &nItemsVisible, bool& hbarVisible ) |
|
{ |
|
int wide, tall; |
|
GetSize( wide, tall ); |
|
nItemsVisible = tall / m_nRowHeight; |
|
|
|
if ( m_pVertScrollBar->IsVisible() ) |
|
{ |
|
top = m_pVertScrollBar->GetValue(); |
|
} |
|
else |
|
{ |
|
top = 0; |
|
} |
|
hbarVisible = m_pHorzScrollBar->IsVisible(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::ApplySchemeSettings(IScheme *pScheme) |
|
{ |
|
BaseClass::ApplySchemeSettings(pScheme); |
|
|
|
SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); |
|
SetBgColor(GetSchemeColor("TreeView.BgColor", GetSchemeColor("WindowDisabledBgColor", pScheme), pScheme)); |
|
SetFont( pScheme->GetFont( "Default", IsProportional() ) ); |
|
m_pSubPanel->SetBgColor( GetBgColor() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetBgColor( Color color ) |
|
{ |
|
BaseClass::SetBgColor( color ); |
|
m_pSubPanel->SetBgColor( color ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::OnSliderMoved( int position ) |
|
{ |
|
InvalidateLayout(); |
|
Repaint(); |
|
} |
|
|
|
void TreeView::GenerateDragDataForItem( int itemIndex, KeyValues *msg ) |
|
{ |
|
// Implemented by subclassed TreeView |
|
} |
|
|
|
void TreeView::SetDragEnabledItems( bool state ) |
|
{ |
|
m_bDragEnabledItems = state; |
|
} |
|
|
|
void TreeView::OnLabelChanged( int itemIndex, char const *oldString, char const *newString ) |
|
{ |
|
} |
|
|
|
bool TreeView::IsLabelEditingAllowed() const |
|
{ |
|
return m_bAllowLabelEditing; |
|
} |
|
|
|
void TreeView::SetLabelBeingEdited( bool state ) |
|
{ |
|
m_bLabelBeingEdited = state; |
|
} |
|
|
|
bool TreeView::IsLabelBeingEdited() const |
|
{ |
|
return m_bLabelBeingEdited; |
|
} |
|
|
|
void TreeView::SetAllowLabelEditing( bool state ) |
|
{ |
|
m_bAllowLabelEditing = state; |
|
} |
|
|
|
void TreeView::EnableExpandTreeOnLeftClick( bool bEnable ) |
|
{ |
|
m_bLeftClickExpandsTree = bEnable; |
|
} |
|
|
|
int TreeView::FindItemUnderMouse( int mx, int my ) |
|
{ |
|
mx = clamp( mx, 0, GetWide() - 1 ); |
|
my = clamp( my, 0, GetTall() - 1 ); |
|
if ( mx >= TREE_INDENT_AMOUNT ) |
|
{ |
|
// Find what's under this position |
|
// need to figure out |
|
int vbarPos = m_pVertScrollBar->IsVisible() ? m_pVertScrollBar->GetValue() : 0; |
|
int hbarPos = m_pHorzScrollBar->IsVisible() ? m_pHorzScrollBar->GetValue() : 0; |
|
int count = m_pRootNode->CountVisibleNodes(); |
|
|
|
int y = 0; |
|
TreeNode *item = m_pRootNode->FindItemUnderMouse( vbarPos, count, -hbarPos, y, mx, my ); |
|
if ( item ) |
|
{ |
|
return item->m_ItemIndex; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
void TreeView::OnMousePressed( MouseCode code ) |
|
{ |
|
bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); |
|
bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); |
|
|
|
// Try to map mouse position to a row |
|
if ( code == MOUSE_LEFT && m_pRootNode ) |
|
{ |
|
int mx, my; |
|
input()->GetCursorPos( mx, my ); |
|
ScreenToLocal( mx, my ); |
|
if ( mx >= TREE_INDENT_AMOUNT ) |
|
{ |
|
// Find what's under this position |
|
// need to figure out |
|
int vbarPos = m_pVertScrollBar->IsVisible() ? m_pVertScrollBar->GetValue() : 0; |
|
int hbarPos = m_pHorzScrollBar->IsVisible() ? m_pHorzScrollBar->GetValue() : 0; |
|
int count = m_pRootNode->CountVisibleNodes(); |
|
|
|
int y = 0; |
|
TreeNode *item = m_pRootNode->FindItemUnderMouse( vbarPos, count, -hbarPos, y, mx, my ); |
|
if ( item ) |
|
{ |
|
if ( !item->IsSelected() ) |
|
{ |
|
AddSelectedItem( item->m_ItemIndex, !ctrl && !shift ); |
|
} |
|
return; |
|
} |
|
else |
|
{ |
|
ClearSelection(); |
|
} |
|
} |
|
} |
|
|
|
BaseClass::OnMousePressed( code ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : state - |
|
//----------------------------------------------------------------------------- |
|
void TreeView::SetAllowMultipleSelections( bool state ) |
|
{ |
|
m_bAllowMultipleSelections = state; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool TreeView::IsMultipleSelectionAllowed() const |
|
{ |
|
return m_bAllowMultipleSelections; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetSelectedItemCount() const |
|
{ |
|
return m_SelectedItems.Count(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : - |
|
//----------------------------------------------------------------------------- |
|
void TreeView::ClearSelection() |
|
{ |
|
m_SelectedItems.RemoveAll(); |
|
m_nMostRecentlySelectedItem = -1; |
|
PostActionSignal( new KeyValues( "TreeViewItemSelectionCleared" ) ); |
|
} |
|
|
|
void TreeView::RangeSelectItems( int endItem ) |
|
{ |
|
int startItem = m_nMostRecentlySelectedItem; |
|
ClearSelection(); |
|
m_nMostRecentlySelectedItem = startItem; |
|
|
|
if ( !m_NodeList.IsValidIndex( startItem ) ) |
|
{ |
|
AddSelectedItem( endItem, false ); |
|
return; |
|
} |
|
|
|
Assert( m_NodeList.IsValidIndex( endItem ) ); |
|
|
|
if ( !m_pRootNode ) |
|
{ |
|
return; |
|
} |
|
|
|
CUtlVector< TreeNode * > list; |
|
m_pRootNode->FindNodesInRange( list, startItem, endItem ); |
|
|
|
int c = list.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
TreeNode *item = list[ i ]; |
|
AddSelectedItem( item->m_ItemIndex, false ); |
|
} |
|
} |
|
|
|
void TreeView::FindNodesInRange( int startItem, int endItem, CUtlVector< int >& itemIndices ) |
|
{ |
|
CUtlVector< TreeNode * > nodes; |
|
m_pRootNode->FindNodesInRange( nodes, startItem, endItem ); |
|
|
|
int c = nodes.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
TreeNode *item = nodes[ i ]; |
|
itemIndices.AddToTail( item->m_ItemIndex ); |
|
} |
|
} |
|
|
|
void TreeView::RemoveSelectedItem( int itemIndex ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return; |
|
|
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
Assert( sel ); |
|
int slot = m_SelectedItems.Find( sel ); |
|
if ( slot != m_SelectedItems.InvalidIndex() ) |
|
{ |
|
m_SelectedItems.Remove( slot ); |
|
PostActionSignal( new KeyValues( "TreeViewItemDeselected", "itemIndex", itemIndex ) ); |
|
|
|
m_nMostRecentlySelectedItem = itemIndex; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void TreeView::AddSelectedItem( int itemIndex, bool clearCurrentSelection, bool requestFocus /* = true */, bool bMakeItemVisible /*= true*/ ) |
|
{ |
|
if ( clearCurrentSelection ) |
|
{ |
|
ClearSelection(); |
|
} |
|
|
|
// Assume it's bogus |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return; |
|
|
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
Assert( sel ); |
|
if ( requestFocus ) |
|
{ |
|
sel->RequestFocus(); |
|
} |
|
|
|
// Item 0 is most recently selected!!! |
|
int slot = m_SelectedItems.Find( sel ); |
|
if ( slot == m_SelectedItems.InvalidIndex() ) |
|
{ |
|
m_SelectedItems.AddToHead( sel ); |
|
} |
|
else if ( slot != 0 ) |
|
{ |
|
m_SelectedItems.Remove( slot ); |
|
m_SelectedItems.AddToHead( sel ); |
|
} |
|
|
|
if ( bMakeItemVisible ) |
|
{ |
|
MakeItemVisible( itemIndex ); |
|
} |
|
|
|
PostActionSignal( new KeyValues( "TreeViewItemSelected", "itemIndex", itemIndex ) ); |
|
InvalidateLayout(); |
|
|
|
if ( clearCurrentSelection ) |
|
{ |
|
m_nMostRecentlySelectedItem = itemIndex; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int TreeView::GetFirstSelectedItem() const |
|
{ |
|
if ( m_SelectedItems.Count() <= 0 ) |
|
return -1; |
|
return m_SelectedItems[ 0 ]->m_ItemIndex; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : itemIndex - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool TreeView::IsItemSelected( int itemIndex ) |
|
{ |
|
// Assume it's bogus |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return false; |
|
|
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
return m_SelectedItems.Find( sel ) != m_SelectedItems.InvalidIndex(); |
|
} |
|
|
|
void TreeView::SetLabelEditingAllowed( int itemIndex, bool state ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return; |
|
|
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
sel->SetLabelEditingAllowed( state ); |
|
} |
|
|
|
void TreeView::StartEditingLabel( int itemIndex ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return; |
|
|
|
Assert( IsLabelEditingAllowed() ); |
|
|
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
Assert( sel->IsLabelEditingAllowed() ); |
|
if ( !sel->IsLabelEditingAllowed() ) |
|
return; |
|
|
|
sel->EditLabel(); |
|
} |
|
|
|
int TreeView::GetPrevChildItemIndex( int itemIndex ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return -1; |
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
TreeNode *parent = sel->GetParentNode(); |
|
if ( !parent ) |
|
return -1; |
|
|
|
return parent->GetPrevChildItemIndex( sel ); |
|
} |
|
|
|
int TreeView::GetNextChildItemIndex( int itemIndex ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return -1; |
|
TreeNode *sel = m_NodeList[ itemIndex ]; |
|
TreeNode *parent = sel->GetParentNode(); |
|
if ( !parent ) |
|
return -1; |
|
|
|
return parent->GetNextChildItemIndex( sel ); |
|
} |
|
|
|
bool TreeView::IsItemDroppable( int itemIndex, CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
// Derived classes should implement |
|
return false; |
|
} |
|
|
|
void TreeView::OnItemDropped( int itemIndex, CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
} |
|
|
|
bool TreeView::GetItemDropContextMenu( int itemIndex, Menu *menu, CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
return false; |
|
} |
|
|
|
HCursor TreeView::GetItemDropCursor( int itemIndex, CUtlVector< KeyValues * >& msglist ) |
|
{ |
|
return dc_arrow; |
|
} |
|
|
|
void TreeView::RemoveChildrenOfNode( int itemIndex ) |
|
{ |
|
if ( !m_NodeList.IsValidIndex( itemIndex ) ) |
|
return; |
|
|
|
TreeNode *node = m_NodeList[ itemIndex ]; |
|
node->RemoveChildren(); |
|
} |
|
|
|
ScrollBar *TreeView::SetScrollBarExternal( bool vertical, Panel *newParent ) |
|
{ |
|
if ( vertical ) |
|
{ |
|
m_bScrollbarExternal[ 0 ] = true; |
|
m_pVertScrollBar->SetParent( newParent ); |
|
return m_pVertScrollBar; |
|
} |
|
m_bScrollbarExternal[ 1 ] = true; |
|
m_pHorzScrollBar->SetParent( newParent ); |
|
return m_pHorzScrollBar; |
|
} |
|
|
|
// if this is set, then clicking on one row and dragging will select a run or items, etc. |
|
void TreeView::SetMultipleItemDragEnabled( bool state ) |
|
{ |
|
m_bMultipleItemDragging = state; |
|
} |
|
|
|
bool TreeView::IsMultipleItemDragEnabled() const |
|
{ |
|
return m_bMultipleItemDragging; |
|
} |
|
|
|
void TreeView::SelectAll() |
|
{ |
|
m_SelectedItems.RemoveAll(); |
|
FOR_EACH_LL( m_NodeList, i ) |
|
{ |
|
m_SelectedItems.AddToTail( m_NodeList[ i ] ); |
|
} |
|
|
|
PostActionSignal( new KeyValues( "TreeViewItemSelected", "itemIndex", GetRootItemIndex() ) ); |
|
InvalidateLayout(); |
|
}
|
|
|