1106 lines
27 KiB
C++
1106 lines
27 KiB
C++
// (c) 2014 Nic Anderson
|
|
|
|
#ifndef GUITREETABLE_CPP
|
|
#define GUITREETABLE_CPP
|
|
|
|
#include "GUITreeTable.h"
|
|
#include <IGUIEnvironment.h>
|
|
|
|
using core::clamp;
|
|
|
|
namespace irr {
|
|
namespace gui {
|
|
|
|
GUITreeTableNode::GUITreeTableNode( IGUIWindow* pWindow, irrTreeNode* pNode )
|
|
: window( pWindow )
|
|
, node( pNode )
|
|
{
|
|
// Creates a bug for some reason
|
|
//if ( window )
|
|
// window->grab();
|
|
}
|
|
|
|
GUITreeTableNode::GUITreeTableNode( const GUITreeTableNode& pOther )
|
|
: window( pOther.window )
|
|
, node( pOther.node )
|
|
{
|
|
// Is this needed or did it not matter?
|
|
//if ( window )
|
|
// window->grab();
|
|
}
|
|
|
|
GUITreeTableNode::~GUITreeTableNode()
|
|
{
|
|
// Creates a bug for some reason
|
|
//if ( window )
|
|
// window->drop();
|
|
}
|
|
|
|
IGUIWindow* GUITreeTableNode::getWindow()
|
|
{
|
|
return window;
|
|
}
|
|
|
|
irrTreeNode* GUITreeTableNode::getTreeNode()
|
|
{
|
|
return node;
|
|
}
|
|
|
|
irrTreeElement* GUITreeTableNode::getTreeNodeElement()
|
|
{
|
|
if ( node )
|
|
return node->element;
|
|
return 0;
|
|
}
|
|
|
|
void GUITreeTableNode::setWindow( IGUIWindow* pWindow )
|
|
{
|
|
// Creates a bug for some reason
|
|
//if ( pWindow )
|
|
// pWindow->grab();
|
|
|
|
//if ( window )
|
|
// window->drop();
|
|
|
|
window = pWindow;
|
|
}
|
|
|
|
void GUITreeTableNode::setTreeNode( irrTreeNode* pNode )
|
|
{
|
|
node = pNode;
|
|
}
|
|
|
|
void GUITreeTableNode::setTreeNodeElem( irrTreeElement* pElement )
|
|
{
|
|
if ( node )
|
|
node->element = pElement;
|
|
}
|
|
|
|
bool GUITreeTableNode::isAncestorOf( GUITreeTableNode* pNode )
|
|
{
|
|
irrTreeNode* childNode = pNode->getTreeNode();
|
|
while ( childNode->getParent() )
|
|
{
|
|
childNode = childNode->getParent();
|
|
if ( childNode == node )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GUITreeTableNode::operator== ( const GUITreeTableNode& other )
|
|
{
|
|
return ( node == other.node ) && ( window == other.window );
|
|
}
|
|
|
|
|
|
/* &&&&&&&&&&&&&&& Tree Table &&&&&&&&&&&&&& */
|
|
|
|
GUITreeTable::GUITreeTable( IGUIEnvironment* pEnvironment,
|
|
IGUIElement* pParent, recti pRect, s32 id )
|
|
: IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect )
|
|
, treeRoot(0)
|
|
, elementFactory(0)
|
|
, selectedNode(0)
|
|
, nodeMenu(0)
|
|
, activity( EGTTABLEA_None )
|
|
, lastEvent( EGTTE_None )
|
|
, focusOnHover( false )
|
|
, allowLinkingToParent( false )
|
|
, canSelectedNodeLoseFocus( false )
|
|
, lineThickness(2)
|
|
, nodeWindowSize(0,0,150,100)
|
|
, linkColor1(0xffcdcdcd)
|
|
, linkColor2(0xffdddddd)
|
|
, selectedHighlightColor(0x9900ffde)
|
|
{
|
|
#ifdef _COMPILE_GUI_TREE_TABLE_NICE_
|
|
backgroundColor2 = Environment->getSkin()->getColor(EGDC_WINDOW);
|
|
// Darken
|
|
backgroundColor2.setRed( clamp(backgroundColor2.getRed() - 20, (u32)0, (u32)255) );
|
|
backgroundColor2.setGreen( clamp(backgroundColor2.getGreen() - 20, (u32)0, (u32)255) );
|
|
backgroundColor2.setBlue( clamp(backgroundColor2.getBlue() - 20, (u32)0, (u32)255) );
|
|
#endif
|
|
}
|
|
|
|
GUITreeTable::~GUITreeTable()
|
|
{
|
|
// Kill the node menu grabbed on its construction
|
|
nodeMenu->drop();
|
|
}
|
|
|
|
IGUIWindow* GUITreeTable::addNode( irrTreeElement* pElement )
|
|
{
|
|
vector2di winHalfSize = nodeWindowSize.LowerRightCorner/2;
|
|
IGUIWindow* window = Environment->addWindow(
|
|
//recti(lastMousePos-winHalfSize,
|
|
// lastMousePos+winHalfSize),
|
|
recti(firstMousePos-winHalfSize,
|
|
firstMousePos+winHalfSize),
|
|
false, // modal?
|
|
0, // text?
|
|
this//, // parent
|
|
//++windowIds
|
|
);
|
|
|
|
window->grab(); /* Bugfix? Requires more looking into.
|
|
The program crashes when the window is not grabbed here. */
|
|
window->setDraggable(true);
|
|
window->getMaximizeButton()->remove();
|
|
window->getMinimizeButton()->remove();
|
|
window->getCloseButton()->remove();
|
|
|
|
/* Forces the parent to respond first to window selection,
|
|
but also forces the table to send GUI events to the windows
|
|
by returning false for every unhandled event. */
|
|
window->setSubElement(true);
|
|
|
|
if ( elementFactory )
|
|
{
|
|
if ( !pElement )
|
|
pElement = elementFactory->buildElementOfWindow( window );
|
|
else
|
|
elementFactory->buildElementOfWindow( window );
|
|
}
|
|
irrTreeNode* node = new irrTreeNode( /*windowIds*/ 0, 0 );
|
|
if ( pElement )
|
|
node->setElem( pElement );
|
|
|
|
nodeList.push_back( GUITreeTableNode( window, node ) );
|
|
|
|
Environment->setFocus(window);
|
|
|
|
return window;
|
|
}
|
|
|
|
void GUITreeTable::removeSelectedNode()
|
|
{
|
|
if ( ! selectedNode ) return;
|
|
|
|
activity = EGTTABLEA_None;
|
|
canSelectedNodeLoseFocus = true;
|
|
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
irrTreeNode* removableNode;
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( *selectedNode == *i )
|
|
{
|
|
removableNode = i->getTreeNode();
|
|
i->setTreeNode(0);
|
|
delete removableNode;
|
|
removeChild( i->getWindow() );
|
|
i = nodeList.erase(i);
|
|
selectedNode = 0;
|
|
|
|
Environment->setFocus(this);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GUITreeTable::removeNodeWithTreeElem( irrTreeElement* pElement )
|
|
{
|
|
activity = EGTTABLEA_None;
|
|
canSelectedNodeLoseFocus = true;
|
|
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
irrTreeNode* removableNode;
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( (*i).getTreeNodeElement() == pElement )
|
|
{
|
|
removableNode = i->getTreeNode();
|
|
i->setTreeNode(0);
|
|
delete removableNode;
|
|
removeChild( i->getWindow() );
|
|
i = nodeList.erase(i);
|
|
selectedNode = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
IGUIWindow* GUITreeTable::duplicateSelectedNode()
|
|
{
|
|
GUITreeTableNode* node = getSelectedNode();
|
|
IGUIWindow* returnWindow = 0;
|
|
if ( node )
|
|
{
|
|
if ( elementFactory )
|
|
{
|
|
returnWindow = addNode( elementFactory->buildElementDuplicateOf( node->getTreeNode()->getElem() ) );
|
|
elementFactory->amendDuplicateWindow( returnWindow );
|
|
}
|
|
}
|
|
return returnWindow;
|
|
}
|
|
|
|
void GUITreeTable::clearAll(bool pClearWindows)
|
|
{
|
|
activity = EGTTABLEA_None;
|
|
canSelectedNodeLoseFocus = true;
|
|
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
if ( pClearWindows )
|
|
{
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
i->getWindow()->remove();
|
|
}
|
|
}
|
|
nodeList.clear();
|
|
treeRoot = 0;
|
|
selectedNode = 0;
|
|
}
|
|
|
|
void GUITreeTable::clearAllTrees()
|
|
{
|
|
activity = EGTTABLEA_None;
|
|
canSelectedNodeLoseFocus = true;
|
|
|
|
irr::core::array<irrTreeNode*>* kids;
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
kids = &( (*i).getTreeNode()->children );
|
|
while ( kids->size() )
|
|
kids->erase( kids->size() - 1 );
|
|
}
|
|
}
|
|
|
|
void GUITreeTable::setElementFactory( GUITreeTableElementFactory* pFactory )
|
|
{
|
|
elementFactory = pFactory;
|
|
}
|
|
|
|
void GUITreeTable::setFocusOnHover( bool pFocus )
|
|
{
|
|
focusOnHover = pFocus;
|
|
}
|
|
|
|
void GUITreeTable::setLinkLineThickness( s32 pThickness )
|
|
{
|
|
lineThickness = pThickness;
|
|
}
|
|
|
|
void GUITreeTable::setNodeWindowRect( recti pRect )
|
|
{
|
|
nodeWindowSize = pRect;
|
|
nodeWindowSize.repair();
|
|
}
|
|
|
|
recti GUITreeTable::getNodeWindowRect()
|
|
{
|
|
return nodeWindowSize;
|
|
}
|
|
|
|
void GUITreeTable::setLinkColor1( irr::video::SColor pColor )
|
|
{
|
|
linkColor1 = pColor;
|
|
}
|
|
|
|
void GUITreeTable::setLinkColor2( irr::video::SColor pColor )
|
|
{
|
|
linkColor2 = pColor;
|
|
}
|
|
|
|
void GUITreeTable::setSelectedHighlightColor( irr::video::SColor pColor )
|
|
{
|
|
selectedHighlightColor = pColor;
|
|
}
|
|
|
|
EGUITreeTableEvent GUITreeTable::getLastEventState()
|
|
{
|
|
return lastEvent;
|
|
}
|
|
|
|
IGUIWindow* GUITreeTable::getSelectedNodeWindow()
|
|
{
|
|
if ( selectedNode )
|
|
return selectedNode->getWindow();
|
|
return 0;
|
|
}
|
|
|
|
GUITreeTableNode* GUITreeTable::getSelectedNode()
|
|
{
|
|
return selectedNode;
|
|
}
|
|
|
|
GUITreeTableNode* GUITreeTable::getTreeRootNode()
|
|
{
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( i->getTreeNode() == treeRoot )
|
|
return &(*i);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GUITreeTable::setTreeRootNode( GUITreeTableNode& pNewRoot )
|
|
{
|
|
/* Verify that the node is in the node list (before we blindly set it) */
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( *i == pNewRoot )
|
|
{
|
|
treeRoot = pNewRoot.getTreeNode();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GUITreeTable::setTreeRoot( irrTreeNode* pNewRoot )
|
|
{
|
|
/* Verify that the node is in the node list (before we blindly set it) */
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( i->getTreeNode() == pNewRoot )
|
|
{
|
|
treeRoot = pNewRoot;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
list<GUITreeTableNode>& GUITreeTable::getNodeList()
|
|
{
|
|
return nodeList;
|
|
}
|
|
|
|
IGUIContextMenu* GUITreeTable::getNodeMenu( bool pUpdatePosition )
|
|
{
|
|
if ( !nodeMenu )
|
|
{
|
|
nodeMenu = Environment->addContextMenu(
|
|
//recti(0,0,175,175),
|
|
recti(0,0,1,1),
|
|
this,
|
|
-1 );
|
|
|
|
// Set the settings for the menu
|
|
nodeMenu->setCloseHandling( ECMC_REMOVE );
|
|
|
|
// Add items to the menu
|
|
nodeMenu->addItem( L"Add node", (s32)EGTTABLE_NMCI_Add, false );
|
|
nodeMenu->addItem( L"Delete", (s32)EGTTABLE_NMCI_Delete, false );
|
|
nodeMenu->addItem( L"Duplicate", (s32)EGTTABLE_NMCI_Duplicate, false );
|
|
nodeMenu->addItem( L"Link to Child", (s32)EGTTABLE_NMCI_LinkToChild, false );
|
|
nodeMenu->addItem( L"Link to Parent", (s32)EGTTABLE_NMCI_LinkToParent, false );
|
|
nodeMenu->addItem( L"Break from Child", (s32)EGTTABLE_NMCI_BreakFromChild, false );
|
|
nodeMenu->addItem( L"Break from Parent", (s32)EGTTABLE_NMCI_BreakFromParent, false );
|
|
nodeMenu->addItem( L"Make root", (s32)EGTTABLE_NMCI_MakeRoot, false );
|
|
|
|
// Debug test
|
|
//assert( nodeMenu->findItemWithCommandId( EGTTABLE_NMCI_Add )
|
|
// == EGTTABLE_NMCI_Add );
|
|
|
|
// Cancel out the removal process later (when the node calls IGUIElement::remove() )
|
|
nodeMenu->grab();
|
|
}
|
|
if ( pUpdatePosition )
|
|
nodeMenu->setRelativePosition(
|
|
lastMousePos
|
|
- vector2di(0, nodeMenu->getAbsoluteClippingRect().getHeight()/2)
|
|
);
|
|
|
|
return nodeMenu;
|
|
}
|
|
|
|
/* NOTE: You need to be careful with how you are using this thing.
|
|
The context menu itself will already close when it loses focus.
|
|
You don't have to set the focus for it. */
|
|
void GUITreeTable::hideNodeMenu()
|
|
{
|
|
nodeMenu->remove();
|
|
//canSelectedNodeLoseFocus = true;
|
|
}
|
|
|
|
bool GUITreeTable::OnEvent( const SEvent& event )
|
|
{
|
|
if ( isEnabled() && isVisible() )
|
|
switch ( event.EventType )
|
|
{
|
|
case irr::EET_MOUSE_INPUT_EVENT:
|
|
if ( handleMouseEvent( event ) )
|
|
return true;
|
|
break;
|
|
case EET_KEY_INPUT_EVENT:
|
|
if ( handleKeyEvent( event ) )
|
|
return true;
|
|
break;
|
|
case EET_GUI_EVENT:
|
|
if ( handleGuiEvent( event ) )
|
|
return true;
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
// Apparently no recent event, so clear event flag
|
|
lastEvent = EGTTE_None;
|
|
|
|
return IGUIElement::OnEvent(event);
|
|
}
|
|
|
|
void GUITreeTable::sendEvent()
|
|
{
|
|
if ( !Parent ) return;
|
|
|
|
SEvent newEvent;
|
|
newEvent.EventType = EET_GUI_EVENT;
|
|
newEvent.GUIEvent.Caller = this;
|
|
newEvent.GUIEvent.EventType = (EGUI_EVENT_TYPE)(EGET_COUNT+1);
|
|
Parent->OnEvent(newEvent);
|
|
}
|
|
|
|
bool GUITreeTable::handleMouseEvent( const SEvent& event )
|
|
{
|
|
lastMousePos.set( event.MouseInput.X, event.MouseInput.Y );
|
|
|
|
/* Note: The check on the AbsoluteClippingRect has been removed since
|
|
this is done by the engine(??). It can be added again if need be. */
|
|
|
|
switch ( event.MouseInput.Event )
|
|
{
|
|
/* Node selection is done regardless of the window focus. */
|
|
case EMIE_LMOUSE_PRESSED_DOWN:
|
|
if ( activity == EGTTABLEA_Scroll ) // Was accidentally an assignment
|
|
{
|
|
activity = EGTTABLEA_None;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case EMIE_MMOUSE_PRESSED_DOWN:
|
|
if ( isPointInside( lastMousePos ) )
|
|
{
|
|
// Steal focus
|
|
if ( Environment->getFocus() != this
|
|
&& ! isMyChild(Environment->getFocus()) )
|
|
{
|
|
Environment->setFocus(this);
|
|
lastEvent = EGTTE_NoSelectNode;
|
|
sendEvent();
|
|
}
|
|
// Node menu automatically closes
|
|
activity = EGTTABLEA_Scroll;
|
|
firstMousePos = lastMousePos;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case EMIE_MMOUSE_LEFT_UP:
|
|
if ( activity == EGTTABLEA_Scroll )
|
|
{
|
|
activity = EGTTABLEA_None;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case EMIE_MOUSE_MOVED:
|
|
if ( activity == EGTTABLEA_Scroll )
|
|
{
|
|
moveChildrenBy(lastMousePos - firstMousePos);
|
|
firstMousePos = lastMousePos;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case EMIE_RMOUSE_PRESSED_DOWN:
|
|
activity = EGTTABLEA_None;
|
|
break;
|
|
|
|
case EMIE_RMOUSE_LEFT_UP:
|
|
if ( activity == EGTTABLEA_None
|
|
&& ( Environment->getFocus() == this || isMyChild( Environment->getFocus() ) )
|
|
)
|
|
{
|
|
activity = EGTTABLEA_UsingContextMenu; // VERY important
|
|
|
|
if ( getNodeMenu()->getParent() != this )
|
|
addChild( getNodeMenu() );
|
|
|
|
//canSelectedNodeLoseFocus = false; // MUST come before focus setting
|
|
Environment->setFocus(getNodeMenu(true));
|
|
|
|
if ( selectedNode )
|
|
{
|
|
// Set the node menu properties for this specific node
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_Add, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_Delete, true );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_Duplicate, true );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToChild, true );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToParent, true );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromChild,
|
|
(selectedNode->getTreeNode()->children.size() != 0 ) );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromParent,
|
|
(selectedNode->getTreeNode()->parent != 0 )
|
|
);
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_MakeRoot, true );
|
|
|
|
} else {
|
|
// Set the node menu properties for this specific node
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_Add, true );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_Delete, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_Duplicate, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToChild, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToParent, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromChild, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromParent, false );
|
|
nodeMenu->setItemEnabled( EGTTABLE_NMCI_MakeRoot, false );
|
|
}
|
|
lastEvent = EGTTE_OpenNodeMenu;
|
|
sendEvent();
|
|
return true;
|
|
}
|
|
else {
|
|
// Kill all activity
|
|
activity = EGTTABLEA_None;
|
|
if ( isPointInside( lastMousePos ) )
|
|
{
|
|
selectedNode = getNodeAt( lastMousePos );
|
|
// Don't care about the window
|
|
if ( selectedNode )
|
|
Environment->setFocus( selectedNode->getWindow() );
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Make position correction for allowing adding nodes here
|
|
firstMousePos = lastMousePos;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GUITreeTable::handleKeyEvent( const SEvent& event )
|
|
{
|
|
// Don't handle events when the key is not pressed
|
|
if ( ! event.KeyInput.PressedDown )
|
|
return false;
|
|
|
|
/* Selected-node-specific events */
|
|
if ( selectedNode && Environment->getFocus() == selectedNode->getWindow() )
|
|
{
|
|
switch ( event.KeyInput.Key )
|
|
{
|
|
case KEY_DELETE:
|
|
lastEvent = EGTTE_RemoveNode;
|
|
sendEvent();
|
|
removeSelectedNode();
|
|
return true;
|
|
|
|
case KEY_KEY_D:
|
|
lastEvent = EGTTE_DuplicateNode;
|
|
sendEvent();
|
|
duplicateSelectedNode();
|
|
return true;
|
|
|
|
case KEY_UP:
|
|
selectedNode->getWindow()->move( vector2di(0,-30) );
|
|
return true;
|
|
|
|
case KEY_DOWN:
|
|
selectedNode->getWindow()->move( vector2di(0,30) );
|
|
return true;
|
|
|
|
case KEY_RIGHT:
|
|
selectedNode->getWindow()->move( vector2di(30,0) );
|
|
return true;
|
|
|
|
case KEY_LEFT:
|
|
selectedNode->getWindow()->move( vector2di(-30,0) );
|
|
return true;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
if ( Environment->getFocus() == this )
|
|
switch( event.KeyInput.Key )
|
|
{
|
|
case KEY_ADD: // by the Numpad
|
|
case KEY_PLUS: // near the backspace
|
|
lastEvent = EGTTE_AddNode;
|
|
sendEvent();
|
|
addNode();
|
|
return true;
|
|
|
|
case KEY_UP:
|
|
moveChildrenBy( vector2di(0,100) );
|
|
return true;
|
|
|
|
case KEY_DOWN:
|
|
moveChildrenBy( vector2di(0,-100) );
|
|
return true;
|
|
|
|
case KEY_RIGHT:
|
|
moveChildrenBy( vector2di(-100,0) );
|
|
return true;
|
|
|
|
case KEY_LEFT:
|
|
moveChildrenBy( vector2di(100,0) );
|
|
return true;
|
|
|
|
default: break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GUITreeTable::handleGuiEvent( const SEvent& event )
|
|
{
|
|
GUITreeTableNode* tableNode;
|
|
|
|
switch ( event.GUIEvent.EventType )
|
|
{
|
|
case EGET_ELEMENT_FOCUS_LOST:
|
|
/* Lose the selection only if the mouse is outside of the element. */
|
|
/* Note that the menu passes back the focus-lost event BEFORE it removes
|
|
itself from the parent. */
|
|
if ( selectedNode && selectedNode->getWindow() == event.GUIEvent.Caller )
|
|
{
|
|
switch ( activity )
|
|
{
|
|
case EGTTABLEA_UsingContextMenu: // requires selection be kept
|
|
case EGTTABLEA_LinkingNodeToChild:
|
|
case EGTTABLEA_LinkingNodeToParent:
|
|
case EGTTABLEA_BreakFromChild:
|
|
break;
|
|
default:
|
|
// Don't deselect if the mouse isn't even inside the table area
|
|
if ( isPointInside( lastMousePos ) )
|
|
{
|
|
if ( ! selectedNode->getWindow()->isPointInside(lastMousePos) )
|
|
{
|
|
lastEvent = EGTTE_NoSelectNode;
|
|
sendEvent();
|
|
activity = EGTTABLEA_None;
|
|
selectedNode = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if ( event.GUIEvent.Caller == this )
|
|
{
|
|
activity = EGTTABLEA_None;
|
|
canSelectedNodeLoseFocus = true;
|
|
}
|
|
// Caller is not this OR a child OR the menu (must be outside click)
|
|
return false; // returning "true" prevents environment from changing focus
|
|
|
|
case EGET_ELEMENT_FOCUSED:
|
|
/* Focus events are called before mouse events. But rather than
|
|
relying on the focus also being the selection, it is merely
|
|
a means to make a selection. */
|
|
{
|
|
if ( event.GUIEvent.Caller == this && activity == EGTTABLEA_None )
|
|
{
|
|
if ( Parent )
|
|
Parent->bringToFront(this);
|
|
|
|
lastEvent = EGTTE_NoSelectNode;
|
|
sendEvent();
|
|
if ( selectedNode )
|
|
{
|
|
// Drop activity
|
|
activity = EGTTABLEA_None;
|
|
canSelectedNodeLoseFocus = true;
|
|
selectedNode = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Intercept selection
|
|
if ( isMyChild( event.GUIEvent.Caller )
|
|
&& event.GUIEvent.Caller->getType() == EGUIET_WINDOW )
|
|
{
|
|
switch( activity )
|
|
{
|
|
case EGTTABLEA_Scroll:
|
|
activity = EGTTABLEA_None;
|
|
break;
|
|
|
|
case EGTTABLEA_LinkingNodeToChild:
|
|
tableNode = getNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller );
|
|
if ( tableNode && (tableNode != selectedNode)
|
|
&& (allowLinkingToParent || !tableNode->isAncestorOf(selectedNode)) )
|
|
{
|
|
selectedNode->getTreeNode()->stealNode( *(tableNode->getTreeNode()) );
|
|
}
|
|
activity = EGTTABLEA_None;
|
|
lastEvent = EGTTE_LinkToChild;
|
|
canSelectedNodeLoseFocus = true;
|
|
sendEvent();
|
|
return true; // Prevent changing focus to clicked node
|
|
|
|
case EGTTABLEA_LinkingNodeToParent:
|
|
tableNode = getNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller );
|
|
if ( tableNode && (tableNode != selectedNode)
|
|
&& (allowLinkingToParent || !selectedNode->isAncestorOf(tableNode)) )
|
|
{
|
|
tableNode->getTreeNode()->stealNode( *(selectedNode->getTreeNode()) );
|
|
}
|
|
activity = EGTTABLEA_None;
|
|
lastEvent = EGTTE_LinkToParent;
|
|
canSelectedNodeLoseFocus = true;
|
|
sendEvent();
|
|
return true; // Prevent changing focus to clicked node
|
|
|
|
case EGTTABLEA_BreakFromChild:
|
|
tableNode = getNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller );
|
|
if ( tableNode && (tableNode != selectedNode) )
|
|
{
|
|
if ( tableNode->getTreeNode()->parent == selectedNode->getTreeNode() )
|
|
{
|
|
tableNode->getTreeNode()->parent = 0;
|
|
selectedNode->getTreeNode()->removeChild( tableNode->getTreeNode() );
|
|
}
|
|
}
|
|
activity = EGTTABLEA_None;
|
|
lastEvent = EGTTE_BreakFromChild;
|
|
canSelectedNodeLoseFocus = true;
|
|
sendEvent();
|
|
return true; // Prevent changing focus to clicked node
|
|
|
|
case EGTTABLEA_UsingContextMenu:
|
|
default:
|
|
// Select node with this window
|
|
selectNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller );
|
|
if ( selectedNode )
|
|
{
|
|
lastEvent = EGTTE_SelectNode;
|
|
} else {
|
|
lastEvent = EGTTE_NoSelectNode;
|
|
}
|
|
sendEvent();
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EGET_ELEMENT_HOVERED:
|
|
if ( focusOnHover )
|
|
if ( event.GUIEvent.Caller == this )
|
|
{
|
|
if ( Environment->getFocus() != this && !isMyChild(Environment->getFocus()) )
|
|
Environment->setFocus(this);
|
|
return true; /* Prevent hovering bugs from a parent setting this to focus
|
|
by handling the event. */
|
|
}
|
|
break;
|
|
|
|
//case EGET_ELEMENT_CLOSED:
|
|
// /* Called by the menu when it loses focus! */
|
|
// break;
|
|
|
|
case EGET_MENU_ITEM_SELECTED:
|
|
if ( event.GUIEvent.Caller == nodeMenu )
|
|
{
|
|
// Find out what the menu selected
|
|
return sendMenuEvent( (EGUITreeTableNodeMenuCommandId) nodeMenu->getItemCommandId( nodeMenu->getSelectedItem() ) );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GUITreeTable::sendMenuEvent( EGUITreeTableNodeMenuCommandId pCommand )
|
|
{
|
|
switch( pCommand )
|
|
{
|
|
case EGTTABLE_NMCI_Add:
|
|
addNode();
|
|
lastEvent = EGTTE_AddNode;
|
|
sendEvent();
|
|
activity = EGTTABLEA_None;
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_Delete:
|
|
lastEvent = EGTTE_RemoveNode;
|
|
sendEvent();
|
|
removeSelectedNode();
|
|
// OPTIMIZATION: refreshLinkCache();
|
|
//activity = EGTTABLEA_None; // unnecessary
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_Duplicate:
|
|
lastEvent = EGTTE_DuplicateNode;
|
|
sendEvent();
|
|
duplicateSelectedNode();
|
|
activity = EGTTABLEA_None;
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_LinkToChild:
|
|
activity = EGTTABLEA_LinkingNodeToChild;
|
|
// OPTIMIZATION: refreshLinkCache();
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_LinkToParent:
|
|
activity = EGTTABLEA_LinkingNodeToParent;
|
|
// OPTIMIZATION: refreshLinkCache();
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_BreakFromChild:
|
|
activity = EGTTABLEA_BreakFromChild;
|
|
// OPTIMIZATION: refreshLinkCache();
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_BreakFromParent:
|
|
if ( selectedNode )
|
|
{
|
|
if ( selectedNode->getTreeNode()->parent )
|
|
{
|
|
selectedNode->getTreeNode()->parent->removeChild(
|
|
selectedNode->getTreeNode()
|
|
);
|
|
// OPTIMIZATION: refreshLinkCache();
|
|
}
|
|
selectedNode->getTreeNode()->parent = 0;
|
|
}
|
|
activity = EGTTABLEA_None;
|
|
return true;
|
|
|
|
case EGTTABLE_NMCI_MakeRoot:
|
|
if ( selectedNode )
|
|
{
|
|
lastEvent = EGTTE_PreMakeRoot;
|
|
sendEvent();
|
|
treeRoot = selectedNode->getTreeNode();
|
|
lastEvent = EGTTE_PostMakeRoot;
|
|
sendEvent();
|
|
}
|
|
activity = EGTTABLEA_None;
|
|
return true;
|
|
|
|
default:
|
|
activity = EGTTABLEA_None;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GUITreeTable::draw()
|
|
{
|
|
if ( !isVisible() )
|
|
return;
|
|
|
|
/* Children of this element are not drawn because that would require
|
|
checking to see if such children were windows in the list of nodes.
|
|
This slows the drawing process. Furthermore, there shouldn't be
|
|
other types of GUI elements as direct children of this element anyways. */
|
|
|
|
// Draw a background
|
|
#ifdef _COMPILE_GUI_TREE_TABLE_NICE_
|
|
Environment->getVideoDriver()->draw2DRectangle( AbsoluteRect,
|
|
Environment->getSkin()->getColor(EGDC_WINDOW),
|
|
Environment->getSkin()->getColor(EGDC_WINDOW),
|
|
backgroundColor2,
|
|
backgroundColor2,
|
|
&AbsoluteClippingRect
|
|
);
|
|
#else
|
|
Environment->getSkin()->draw2DRectangle(
|
|
this,
|
|
Environment->getSkin()->getColor(EGDC_WINDOW),
|
|
AbsoluteRect,
|
|
&AbsoluteClippingRect);
|
|
#endif
|
|
|
|
/* Go through the list and draw all of the links between nodes
|
|
for the tree.
|
|
OPTIMIZATION:
|
|
The links should be cached and drawn. Then the following process
|
|
would only be done when the links change.
|
|
The cached links can be moved (rather than rebuilt) when the middle
|
|
mouse button is pressed and the mouse moves, thereby saving time
|
|
in reconstruction.
|
|
With all of that, the draw process is reduced to a single for-loop,
|
|
which may not need to go through all of the nodes (since some may
|
|
not even be connected). */
|
|
u32 c;
|
|
recti r;
|
|
core::list<GUITreeTableNode>::Iterator nodeIter = nodeList.begin();
|
|
GUITreeTableNode* ttnode;
|
|
core::array<irrTreeNode*>* children;
|
|
//for ( u32 n=0; n < nodeList.size(); n++ )
|
|
for ( ; nodeIter != nodeList.end(); ++nodeIter )
|
|
{
|
|
if ( selectedNode && (nodeIter->getWindow() == selectedNode->getWindow()) )
|
|
{
|
|
// Draw a box around the selected node
|
|
r = nodeIter->getWindow()->getAbsolutePosition();
|
|
r.UpperLeftCorner -= vector2di(2,2);
|
|
r.LowerRightCorner += vector2di(2,2);
|
|
Environment->getSkin()->draw2DRectangle(
|
|
this,
|
|
selectedHighlightColor,
|
|
r,
|
|
&AbsoluteClippingRect
|
|
);
|
|
}
|
|
|
|
children = &(nodeIter->getTreeNode()->children);
|
|
for ( c=0; c < children->size(); c++ )
|
|
{
|
|
ttnode = findListNodeWithTreeNode( (*children)[c] );
|
|
if ( ttnode )
|
|
{
|
|
drawLink( nodeIter->getWindow(), ttnode->getWindow() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the windows
|
|
IGUIElement::draw();
|
|
}
|
|
|
|
GUITreeTableNode* GUITreeTable::findListNodeWithTreeNode( irrTreeNode* pNode )
|
|
{
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( i->getTreeNode() == pNode )
|
|
return &(*i);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GUITreeTable::drawLink( IGUIElement* pFromElement, IGUIElement* pToElement )
|
|
{
|
|
vector2di pt1 = pFromElement->getAbsolutePosition().getCenter();
|
|
vector2di pt3 = pToElement->getAbsolutePosition().getCenter();
|
|
|
|
s32 hf = (pt1.X < pt3.X) ? 1 : -1;
|
|
s32 vf = (pt1.Y < pt3.Y) ? 1 : -1;
|
|
recti line1( pt1.X, pt1.Y, pt3.X, pt1.Y+lineThickness*hf);
|
|
recti line2( pt3.X-lineThickness*vf, pt1.Y+lineThickness*hf, pt3.X, pt3.Y );
|
|
line1.repair();
|
|
line2.repair();
|
|
|
|
if ( hf > 0 )
|
|
{
|
|
Environment->getVideoDriver()->draw2DRectangle(
|
|
line1,
|
|
linkColor1, // left up
|
|
linkColor2, // right up
|
|
linkColor1, // left down
|
|
linkColor2, // right down
|
|
&AbsoluteClippingRect
|
|
);
|
|
} else {
|
|
Environment->getVideoDriver()->draw2DRectangle(
|
|
line1,
|
|
linkColor2, // left up
|
|
linkColor1, // right up
|
|
linkColor2, // left down
|
|
linkColor1, // right down
|
|
&AbsoluteClippingRect
|
|
);
|
|
}
|
|
|
|
if ( vf > 0 )
|
|
{
|
|
Environment->getVideoDriver()->draw2DRectangle(
|
|
line2,
|
|
linkColor1, // left up
|
|
linkColor1, // right up
|
|
linkColor2, // left down
|
|
linkColor2, // right down
|
|
&AbsoluteClippingRect
|
|
);
|
|
} else {
|
|
Environment->getVideoDriver()->draw2DRectangle(
|
|
line2,
|
|
linkColor2, // left up
|
|
linkColor2, // right up
|
|
linkColor1, // left down
|
|
linkColor1, // right down
|
|
&AbsoluteClippingRect
|
|
);
|
|
}
|
|
}
|
|
|
|
GUITreeTableNode* GUITreeTable::getNodeAt( vector2di pPos )
|
|
{
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( i->getWindow()->getAbsolutePosition().isPointInside(pPos) )
|
|
return &(*i);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GUITreeTable::selectNodeAt( vector2di pPos )
|
|
{
|
|
selectedNode = getNodeAt( pPos );
|
|
}
|
|
|
|
GUITreeTableNode* GUITreeTable::getNodeWithWindow( IGUIWindow* pWindow )
|
|
{
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
if ( i->getWindow() == pWindow )
|
|
{
|
|
return &(*i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GUITreeTable::selectNodeWithWindow( IGUIWindow* pWindow )
|
|
{
|
|
selectedNode = getNodeWithWindow( pWindow );
|
|
}
|
|
|
|
void GUITreeTable::moveChildrenBy( vector2di pDistance )
|
|
{
|
|
core::list<IGUIElement*>::Iterator c = Children.begin();
|
|
for ( ; c != Children.end(); ++c )
|
|
{
|
|
(*c)->move( pDistance );
|
|
}
|
|
|
|
// OPTIMIZATION: refreshLinkCache();
|
|
}
|
|
|
|
recti GUITreeTable::getEnclosingRect()
|
|
{
|
|
recti border, winRect;
|
|
bool set = false;
|
|
list<GUITreeTableNode>::Iterator i = nodeList.begin();
|
|
for ( ; i != nodeList.end(); ++i )
|
|
{
|
|
winRect = i->getWindow()->getRelativePosition();
|
|
if ( set )
|
|
{
|
|
border.addInternalPoint( winRect.UpperLeftCorner );
|
|
border.addInternalPoint( winRect.LowerRightCorner );
|
|
} else {
|
|
border = winRect;
|
|
set = true;
|
|
}
|
|
}
|
|
return border;
|
|
}
|
|
|
|
}}
|
|
|
|
#endif
|