//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #define PROTECTED_THINGS_DISABLE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tier1/utlstring.h" // memdbgon must be the last include file in a .cpp file!!! #include #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 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;iSetFont(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;iCountVisibleNodes(); } } 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;iGetVisibleMaxWidth(); 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;iSetVisible(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;im_ItemIndex; } } return -1; } int TreeNode::GetNextChildItemIndex( TreeNode *pCurrentChild ) { int i; for (i=0;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;iAddSelectedItem( 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;iSelectNextChild(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;iFindNodesInRange_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 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 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;iGetChildrenCount();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;iGetChildrenCount();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;iGetChildrenCount();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;iMarkForDeletion(); } 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(); }