// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include "group_tree.h"
#include "interface_manager.h"
#include "interface_element.h"
#include "view_bitmap.h"
#include "view_text.h"
#include "group_container.h"
#include "action_handler.h"
#include "lua_ihm.h"
#include "nel/misc/i_xml.h"
#include "nel/misc/i18n.h"
#include "game_share/xml_auto_ptr.h"
using namespace std;
using namespace NLMISC;
// ----------------------------------------------------------------------------
// SNode
// ----------------------------------------------------------------------------
// TestYoyo
//uint SNodeCount= 0;
// ----------------------------------------------------------------------------
CGroupTree::SNode::SNode()
{
Opened = false;
Father = NULL;
FontSize = -1;
YDecal = 0;
DisplayText = true;
Template = NULL;
Show= true;
NodeAddedCallback = NULL;
Color = CRGBA::White;
ParentTree = NULL;
LastVisibleSon = NULL;
// TestYoyo
//nlinfo("SNode(): %8x, c%d", this, SNodeCount++);
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::updateLastVisibleSon()
{
LastVisibleSon = NULL;
if (!Show || !Opened) return;
for (sint sonIndex = (sint)Children.size() - 1; sonIndex >= 0; -- sonIndex)
{
if (Children[sonIndex]->Show)
{
LastVisibleSon = Children[sonIndex];
break;
}
}
for(uint k = 0; k < Children.size(); ++k)
{
Children[k]->updateLastVisibleSon();
}
}
// ----------------------------------------------------------------------------
CGroupTree::SNode::~SNode()
{
makeOrphan();
// IMPORTANT : must delete in reverse order because "makeOrphan" is called when deleting sons, thus changing vector size...
for (sint i = (sint)Children.size() - 1; i >= 0; --i)
delete Children[i];
Children.clear();
// TestYoyo
//nlinfo("~SNode(): %8x, c%d", this, --SNodeCount);
}
void CGroupTree::SNode::setParentTree(CGroupTree *parent)
{
ParentTree = parent;
for (uint k = 0; k < Children.size(); ++k)
{
Children[k]->setParentTree(parent);
}
}
void CGroupTree::SNode::setFather(SNode *father)
{
Father = father;
setParentTree(father ? father->ParentTree : NULL);
}
CGroupTree::SNode *CGroupTree::SNode::getNodeFromId(const std::string &id)
{
if (Id == id) return this;
// breadth first
for (uint k = 0; k < Children.size(); ++k)
{
if (Children[k]->Id == id)
{
return Children[k];
}
}
for (uint k = 0; k < Children.size(); ++k)
{
SNode *found = Children[k]->getNodeFromId(id);
if (found) return found;
}
return NULL;
}
void CGroupTree::SNode::makeOrphan()
{
if (ParentTree)
{
ParentTree->forceRebuild();
setParentTree(NULL);
}
//
if (Template != NULL)
{
if (Template->getParent())
{
// don't delete because may want to keep it. NB: deleted by smartptr at dtor
Template->getParent()->delGroup(Template, true);
}
}
//
if (Father)
{
Father->detachChild(this);
Father = NULL;
}
}
// ----------------------------------------------------------------------------
struct CNodeSorter
{
bool operator()(const CGroupTree::SNode *lhs, const CGroupTree::SNode *rhs) const
{
return lhs->Text < rhs->Text;
}
};
// ----------------------------------------------------------------------------
void CGroupTree::SNode::sort()
{
std::sort(Children.begin(), Children.end(), CNodeSorter());
for(uint k = 0; k < Children.size(); ++k)
{
Children[k]->sort();
}
}
// ----------------------------------------------------------------------------
struct CNodeSorterByBitmap
{
bool operator()(const CGroupTree::SNode *lhs, const CGroupTree::SNode *rhs) const
{
if (lhs->Bitmap != rhs->Bitmap) return lhs->Bitmap < rhs->Bitmap;
return lhs->Text < rhs->Text;
}
};
// ----------------------------------------------------------------------------
void CGroupTree::SNode::sortByBitmap()
{
std::sort(Children.begin(), Children.end(), CNodeSorterByBitmap());
for(uint k = 0; k < Children.size(); ++k)
{
Children[k]->sortByBitmap();
}
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::addChild (SNode *pNode)
{
if(!pNode) return;
pNode->makeOrphan();
Children.push_back(pNode);
pNode->setFather(this);
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::addChildSorted(SNode *pNode)
{
if (!pNode) return;
pNode->makeOrphan();
std::vector::iterator it = std::lower_bound(Children.begin(), Children.end(), pNode, CNodeSorter());
Children.insert(it, pNode);
pNode->setFather(this);
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::addChildSortedByBitmap(SNode *pNode)
{
if (!pNode) return;
pNode->makeOrphan();
std::vector::iterator it = std::lower_bound(Children.begin(), Children.end(), pNode, CNodeSorterByBitmap());
Children.insert(it, pNode);
pNode->setFather(this);
}
// ----------------------------------------------------------------------------
bool CGroupTree::SNode::isChild(SNode *pNode) const
{
return std::find(Children.begin(), Children.end(), pNode) != Children.end();
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::detachChild(SNode *pNode)
{
nlassert(pNode);
nlassert(isChild(pNode));
Children.erase(std::remove(Children.begin(), Children.end(), pNode), Children.end());
pNode->setFather(NULL);
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::deleteChild(SNode *pNode)
{
delete pNode;
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::addChildFront (SNode *pNode)
{
if(!pNode) return;
pNode->makeOrphan();
Children.insert(Children.begin(), pNode);
pNode->setFather(this);
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::addChildAtIndex(SNode *pNode, sint index)
{
if(!pNode) return;
if (index < 0 || index > (sint) Children.size())
{
nlwarning(" bad index %d (%d elements in the vector)", index, Children.size());
return;
}
pNode->makeOrphan();
if (pNode->Father)
{
pNode->Father->detachChild(pNode);
}
Children.insert(Children.begin() + index, pNode);
pNode->setFather(this);
}
// ----------------------------------------------------------------------------
void CGroupTree::SNode::closeAll()
{
Opened = false;
for (uint i = 0; i < Children.size(); ++i)
Children[i]->closeAll();
}
// ----------------------------------------------------------------------------
bool CGroupTree::SNode::parse (xmlNodePtr cur, CGroupTree * parentGroup)
{
if (stricmp((char*)cur->name, "node") == 0)
{
CXMLAutoPtr id((const char*) xmlGetProp (cur, (xmlChar*)"id"));
if (id)
Id = (const char*)id;
else
Id = toString(parentGroup->getIdNumber());
CXMLAutoPtr name((const char*) xmlGetProp (cur, (xmlChar*)"name"));
if (name)
{
const char *ptrName = (const char*)name;
Text = ucstring(ptrName);
if ((strlen(ptrName)>2) && (ptrName[0] == 'u') && (ptrName[1] == 'i'))
Text = CI18N::get (ptrName);
}
CXMLAutoPtr color((const char*) xmlGetProp (cur, (xmlChar*)"color"));
if (color)
{
Color = convertColor(color);
}
CXMLAutoPtr open((const char*) xmlGetProp (cur, (xmlChar*)"opened"));
if (open) Opened = convertBool(open);
CXMLAutoPtr show((const char*) xmlGetProp (cur, (xmlChar*)"show"));
if (open) Show = convertBool(show);
CXMLAutoPtr ah((const char*) xmlGetProp (cur, (xmlChar*)"handler"));
if (ah) AHName = (const char*)ah;
CXMLAutoPtr cond((const char*) xmlGetProp (cur, (xmlChar*)"cond"));
if (cond) AHCond = (const char*)cond;
CXMLAutoPtr params((const char*) xmlGetProp (cur, (xmlChar*)"params"));
if (params) AHParams = (const char*)params;
CXMLAutoPtr ahRight((const char*) xmlGetProp (cur, (xmlChar*)"handler_right"));
if (ahRight) AHNameRight = (const char*)ahRight;
CXMLAutoPtr paramsRight((const char*) xmlGetProp (cur, (xmlChar*)"params_right"));
if (paramsRight) AHParamsRight = (const char*)paramsRight;
CXMLAutoPtr bitmap((const char*) xmlGetProp (cur, (xmlChar*)"bitmap"));
if (bitmap) Bitmap = (const char*)bitmap;
FontSize = parentGroup->getFontSize();
CXMLAutoPtr fs((const char*) xmlGetProp (cur, (xmlChar*)"fontsize"));
if (fs) fromString((const char*)fs, FontSize);
YDecal = parentGroup->getYDecal();
CXMLAutoPtr yDecalPtr((const char*) xmlGetProp (cur, (xmlChar*)"y_decal"));
if (yDecalPtr) fromString((const char*)yDecalPtr, YDecal);
xmlNodePtr child = cur->children;
while (child != NULL)
{
SNode *pNode = new SNode;
pNode->parse (child, parentGroup);
addChild(pNode);
child = child->next;
}
}
return true;
}
// ----------------------------------------------------------------------------
// CGroupTree
// ----------------------------------------------------------------------------
NLMISC_REGISTER_OBJECT(CViewBase, CGroupTree, std::string, "tree");
// ----------------------------------------------------------------------------
CGroupTree::CGroupTree(const TCtorParam ¶m)
:CInterfaceGroup(param)
{
_IdGenerator = 0;
_XExtend= 0;
_BmpW = 14;
_BmpH = 14;
_FontSize = 12;
_YDecal = 0;
_MustRebuild = true;
_OverColor = CRGBA(255, 255, 255, 128);
_OverColorBack = CRGBA(64, 64, 64, 255);
_SelectedNode = NULL;
_SelectedLine = -1;
_SelectedColor = CRGBA(255, 128, 128, 128);
_RootNode = NULL;
_OverLine = -1;
_SelectAncestorOnClose= false;
_NavigateOneBranch= false;
_ArboOpenFirst= "arbo_open_first.tga";
_ArboCloseJustOne= "arbo_close_just_one.tga";
_ArboSonWithoutSon= "arbo_son_without_son.tga";
_ArboSonLast= "arbo_son_last.tga";
_ArboSon= "arbo_son.tga";
_ArboLevel= "arbo_level.tga";
_RectangleOutlineMode= false;
_RectangleX= 0;
_RectangleY= 0;
_RectangleW= 10;
_RectangleH= 10;
_RectangleDeltaRL= 0;
_AvoidSelectNodeByIdIR= false;
}
// ----------------------------------------------------------------------------
CGroupTree::~CGroupTree()
{
removeAll();
if (_RootNode != NULL) delete _RootNode;
}
// ----------------------------------------------------------------------------
bool CGroupTree::parse (xmlNodePtr cur, CInterfaceGroup * parentGroup)
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
if (!CInterfaceGroup::parse(cur, parentGroup))
return false;
CXMLAutoPtr ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"col_over");
if (ptr) _OverColor = convertColor(ptr);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"col_select");
if (ptr) _SelectedColor = convertColor(ptr);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"col_over_back");
if (ptr) _OverColorBack = convertColor(ptr);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"fontsize");
if (ptr) fromString((const char*)ptr, _FontSize);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"select_ancestor_on_close");
if (ptr) _SelectAncestorOnClose = convertBool(ptr);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"navigate_one_branch");
if (ptr) _NavigateOneBranch = convertBool(ptr);
// read optional arbo bmps
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_open_first");
if (ptr) _ArboOpenFirst= (const char*)ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_close_just_one");
if (ptr) _ArboCloseJustOne= (const char*)ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_son_without_son");
if (ptr) _ArboSonWithoutSon= (const char*)ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_son_last");
if (ptr) _ArboSonLast= (const char*)ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_son");
if (ptr) _ArboSon= (const char*)ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_level");
if (ptr) _ArboLevel= (const char*)ptr;
ptr = (char*) xmlGetProp (cur, (xmlChar*)"arbo_x_extend");
if (ptr) _ArboXExtend= (const char*)ptr;
// Rectangle selection style
ptr = (char*) xmlGetProp (cur, (xmlChar*)"rectangle_outline");
if (ptr) _RectangleOutlineMode= convertBool(ptr);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"rectangle_x");
if (ptr) fromString((const char*)ptr, _RectangleX);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"rectangle_y");
if (ptr) fromString((const char*)ptr, _RectangleY);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"rectangle_w");
if (ptr) fromString((const char*)ptr, _RectangleW);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"rectangle_h");
if (ptr) fromString((const char*)ptr, _RectangleH);
ptr = (char*) xmlGetProp (cur, (xmlChar*)"rectangle_drl");
if (ptr) fromString((const char*)ptr, _RectangleDeltaRL);
_RootNode = new SNode;
// bool ok = true;
cur = cur->children;
while (cur)
{
// Check that this is a camera node
if ( stricmp((char*)cur->name, "node") == 0 )
{
SNode *pNode = new SNode;
if (!pNode->parse(cur, this))
{
delete pNode;
nlwarning("cannot parse node");
}
else
{
_RootNode->addChild(pNode);
}
}
cur = cur->next;
}
_RootNode->Opened = true;
_ResizeFromChildW = _ResizeFromChildH = true;
CViewRenderer &rVR = pIM->getViewRenderer();
sint32 id = rVR.getTextureIdFromName(_ArboOpenFirst);
if (id != -1)
rVR.getTextureSizeFromId(id, _BmpW, _BmpH);
sint32 dummy;
id = rVR.getTextureIdFromName(_ArboXExtend);
if (id != -1)
rVR.getTextureSizeFromId(id, _XExtend, dummy);
else
// if not found, reset, to avoid errors
_ArboXExtend= "";
return true;
}
// ----------------------------------------------------------------------------
sint32 CGroupTree::getHrcIconXStart(sint32 depth)
{
return depth*(_BmpW+_XExtend);
}
// ----------------------------------------------------------------------------
sint32 CGroupTree::getHrcIconXEnd(sint32 depth)
{
return depth*(_BmpW+_XExtend) + _BmpW;
}
// ----------------------------------------------------------------------------
void CGroupTree::checkCoords()
{
CInterfaceGroup::checkCoords();
}
// ----------------------------------------------------------------------------
void CGroupTree::updateCoords()
{
if (_MustRebuild)
rebuild();
CInterfaceGroup::updateCoords();
}
// ----------------------------------------------------------------------------
void CGroupTree::drawSelection(sint x, sint y, sint w, CRGBA col)
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
CViewRenderer &rVR = pIM->getViewRenderer();
if(!_RectangleOutlineMode)
{
rVR.drawRotFlipBitmap (_RenderLayer, _XReal+_OffsetX+x, _YReal+_OffsetY+y,
w, _BmpH, 0, false, rVR.getBlankTextureId(),
col );
}
else
{
// draw the outline
x+= _XReal+_OffsetX+_RectangleX;
y+= _YReal+_OffsetY+_RectangleY;
w= _RectangleW;
sint32 h= _RectangleH;
sint32 rl= _RenderLayer + _RectangleDeltaRL;
rVR.drawRotFlipBitmap (rl, x, y, 1, h, 0, false, rVR.getBlankTextureId(), col );
rVR.drawRotFlipBitmap (rl, x+w-1, y, 1, h, 0, false, rVR.getBlankTextureId(), col );
rVR.drawRotFlipBitmap (rl, x, y, w, 1, 0, false, rVR.getBlankTextureId(), col );
rVR.drawRotFlipBitmap (rl, x, y+h-1, w, 1, 0, false, rVR.getBlankTextureId(), col );
}
}
// ----------------------------------------------------------------------------
CGroupTree::SNode *CGroupTree::getNodeUnderMouse() const
{
if (_OverLine == -1) return NULL;
return _Lines[_OverLine].Node;
}
// ----------------------------------------------------------------------------
void CGroupTree::draw()
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
// CViewRenderer &rVR = pIM->getViewRenderer();
// get the clip area
sint32 clipx, clipy, clipw, cliph;
getClip(clipx, clipy, clipw, cliph);
// change the over
bool bDisplayOver = true;
if (!pIM->isMouseHandlingEnabled())
{
bDisplayOver = false;
}
else
if (pIM->getModalWindow() == NULL)
{
sint32 x = pIM->getPointer()->getX();
sint32 y = pIM->getPointer()->getY();
CInterfaceGroup *pIG = pIM->getWindowUnder(x, y);
CInterfaceGroup *pParent = this;
bool bFound = false;
while (pParent != NULL)
{
if (pParent == pIG)
{
bFound = true;
break;
}
pParent = pParent->getParent();
}
// if the mouse is not in the clipped area
if ((x < clipx) ||
(x > (clipx + clipw)) ||
(y < clipy) ||
(y > (clipy + cliph)) || !bFound)
{
_OverLine = -1;
}
else
{
x = x - _OffsetX;
y = y - _OffsetY;
for (sint32 i = 0; i < (sint32)_Lines.size(); ++i)
{
if ((y >= (_YReal+(sint32)(_Lines.size()-i-1)*_BmpH)) &&
(y < (_YReal+(sint32)(_Lines.size()-i)*_BmpH)))
{
_OverLine = i;
}
}
if (_OverLine != -1)
{
if (x < _XReal + getHrcIconXEnd(_Lines[_OverLine].Depth))
bDisplayOver = false;
}
}
}
// some over to display?
if ((_OverLine != -1) && bDisplayOver)
{
// Find the first container
CInterfaceGroup *pIG = _Parent;
CGroupContainer *pGC = dynamic_cast(pIG);
while (pIG != NULL)
{
pIG = pIG->getParent();
if (pIG == NULL) break;
if (dynamic_cast(pIG) != NULL)
pGC = dynamic_cast(pIG);
}
// avoid if window grayed
if (pGC != NULL)
{
if (pGC->isGrayed())
bDisplayOver = false;
}
// Has to display the over?
if (bDisplayOver)
{
// !NULL if the text over must displayed across all windows
CViewText *viewTextExtend= NULL;
// If the line is a simple Text line (not template)
if(_Lines[_OverLine].Node && _Lines[_OverLine].Node->DisplayText)
{
// Get the view text
viewTextExtend= safe_cast(_Lines[_OverLine].TextOrTemplate);
// If this viewText is not too big, no need
if(viewTextExtend->getXReal() + viewTextExtend->getWReal() <= (clipx+clipw) )
viewTextExtend= NULL;
}
// draw simple over
if(!viewTextExtend)
{
CRGBA col = _OverColor;
if(getModulateGlobalColor())
{
col.modulateFromColor (_OverColor, pIM->getGlobalColorForContent());
}
else
{
col= _OverColor;
col.A = (uint8)(((sint32)col.A*((sint32)pIM->getGlobalColorForContent().A+1))>>8);
}
drawSelection( getHrcIconXEnd(_Lines[_OverLine].Depth + _Lines[_OverLine].getNumAdditionnalBitmap()), ((sint)_Lines.size()-_OverLine-1)*_BmpH,
_WReal-getHrcIconXEnd(_Lines[_OverLine].Depth + _Lines[_OverLine].getNumAdditionnalBitmap()), col);
}
// Draw extended over
else
{
CRGBA col = _OverColorBack;
// must add the selection color
if(_SelectedLine == _OverLine)
{
// simulate alpha blend of the selection bitmap
CRGBA sel= _SelectedColor;
sel.A= (uint8)((sel.A*((sint32)pIM->getGlobalColorForContent().A+1))>>8);
col.blendFromuiRGBOnly(col, sel, sel.A);
}
// will be drawn over all the interface
pIM->setOverExtendViewText(viewTextExtend, col);
}
}
}
// some selection to display
if (_SelectedLine != -1)
{
CRGBA col = _SelectedColor;
if(getModulateGlobalColor())
{
col.modulateFromColor (_SelectedColor, pIM->getGlobalColorForContent());
}
else
{
col= _SelectedColor;
col.A = (uint8)(((sint32)col.A*((sint32)pIM->getGlobalColorForContent().A+1))>>8);
}
drawSelection( getHrcIconXEnd(_Lines[_SelectedLine].Depth + _Lines[_SelectedLine].getNumAdditionnalBitmap()), ((sint)_Lines.size()-_SelectedLine-1)*_BmpH,
_WReal-getHrcIconXEnd(_Lines[_SelectedLine].Depth + _Lines[_SelectedLine].getNumAdditionnalBitmap()), col );
}
CInterfaceGroup::draw();
}
// ----------------------------------------------------------------------------
void CGroupTree::selectLine(uint line, bool runAH /*= true*/)
{
if(line>=_Lines.size())
return;
CInterfaceManager *pIM = CInterfaceManager::getInstance();
if (!_Lines[line].Node)
{
// just deleted : must wait next draw to know the new line under mouse
return;
}
if (!_Lines[line].Node->AHName.empty() && runAH)
{
_CancelNextSelectLine = false;
/*
pIM->runActionHandler ( _Lines[line].Node->AHName,
this,
_Lines[line].Node->AHParams );
*/
if (!_CancelNextSelectLine)
{
_SelectedLine = line;
_SelectedNode = _Lines[line].Node;
}
pIM->runActionHandler ( _Lines[line].Node->AHName,
this,
_Lines[line].Node->AHParams );
_CancelNextSelectLine = false;
}
}
// ----------------------------------------------------------------------------
bool CGroupTree::rightButton(uint line)
{
if(line>=_Lines.size())
return false;
if (!_Lines[_OverLine].Node || _Lines[_OverLine].Node->AHNameRight.empty())
return false;
if (line != (uint) _SelectedLine) selectLine(line, false);
CInterfaceManager *pIM = CInterfaceManager::getInstance();
pIM->runActionHandler ( _Lines[line].Node->AHNameRight,
this,
_Lines[line].Node->AHParamsRight );
return true;
}
// ----------------------------------------------------------------------------
const std::string &CGroupTree::getSelectedNodeId() const
{
if(_SelectedLine>=0 && _SelectedLine<(sint)_Lines.size() && _Lines[_SelectedLine].Node)
{
return _Lines[_SelectedLine].Node->Id;
}
else
{
static string empty;
return empty;
}
}
// ----------------------------------------------------------------------------
bool CGroupTree::handleEvent (const CEventDescriptor& event)
{
if (!_Active) return false;
if (CInterfaceGroup::handleEvent(event)) return true;
// The line must be over (pre-selected)
if (event.getType() == CEventDescriptor::mouse && _OverLine>=0)
{
const CEventDescriptorMouse &eventDesc = (const CEventDescriptorMouse &)event;
if (!isIn(eventDesc.getX(), eventDesc.getY()))
return false;
sint32 x = eventDesc.getX() - _OffsetX;
// sint32 y = eventDesc.getY() - _OffsetY;
bool bText = false;
if (x >= (_XReal+getHrcIconXEnd(_Lines[_OverLine].Depth + _Lines[_OverLine].getNumAdditionnalBitmap())))
bText = true;
bool bIcon = false;
if ((x > (_XReal+getHrcIconXStart(_Lines[_OverLine].Depth)-_XExtend)) &&
(x < (_XReal+getHrcIconXEnd(_Lines[_OverLine].Depth + _Lines[_OverLine].getNumAdditionnalBitmap()))))
bIcon = true;
if (eventDesc.getEventTypeExtended() == CEventDescriptorMouse::mouserightdown)
{
if (bText)
{
return rightButton(_OverLine != -1 ? _OverLine : _SelectedLine);
}
}
if (eventDesc.getEventTypeExtended() == CEventDescriptorMouse::mouseleftdown)
{
// line selection
if (bText)
{
selectLine(_OverLine);
}
// close of node
if (bIcon)
{
SNode *changedNode= _Lines[_OverLine].Node;
if (changedNode)
{
// if "SelectAncestorOnClose" feature wanted, if it was opened before, and if some node selected
if(_SelectAncestorOnClose && changedNode->Opened && _SelectedNode)
{
// check that the selected node is a son of the closing node
SNode *parent= _SelectedNode->Father;
while(parent)
{
if(parent==changedNode)
{
// Then change selection to this parent first
selectLine(_OverLine);
break;
}
parent= parent->Father;
}
}
// standard hrc
if(!_NavigateOneBranch)
{
// open/close the node
changedNode->Opened = !changedNode->Opened;
}
// else must close all necessary nodes.
else
{
if(changedNode->Opened)
changedNode->closeAll();
else
{
// must closeAll all his brothers first.
if(changedNode->Father)
{
changedNode->Father->closeAll();
changedNode->Father->Opened= true;
}
changedNode->Opened= true;
}
}
CInterfaceManager *pIM = CInterfaceManager::getInstance();
pIM->runActionHandler(changedNode->AHNameClose, this, changedNode->AHParamsClose);
}
forceRebuild();
}
return true;
}
}
return false;
}
// ----------------------------------------------------------------------------
void CGroupTree::unselect()
{
_SelectedLine = -1;
_SelectedNode = NULL;
}
// ----------------------------------------------------------------------------
void CGroupTree::reset()
{
unselect();
if (_RootNode)
{
_RootNode->closeAll();
_RootNode->Opened = true;
}
forceRebuild();
}
// ----------------------------------------------------------------------------
void CGroupTree::forceRebuild()
{
_MustRebuild = true;
invalidateCoords();
}
// ----------------------------------------------------------------------------
void CGroupTree::rebuild()
{
// Remove all
removeAll();
if (_RootNode)
_RootNode->Opened = true;
// Rebuild all depending on the logic
if(_RootNode)
addTextLine (0, _RootNode);
// Reformating
sint32 sizeH = (sint32)_Lines.size()*_BmpH;
for (uint i = 0; i < _Lines.size(); ++i)
_Lines[i].TextOrTemplate->setY (_Lines[i].Node->YDecal + sizeH - ((1+_Lines[i].TextOrTemplate->getY())*_BmpH));
// Add the hierarchy bitmaps
addHierarchyBitmaps();
// Find if we can display selection
if (_SelectedNode != NULL)
{
_SelectedLine = -1;
for (uint i = 0; i < _Lines.size(); ++i)
if (_Lines[i].Node == _SelectedNode)
{
_SelectedLine = i;
break;
}
}
// Ok no more need to rebuild all this
_MustRebuild = false;
}
// ----------------------------------------------------------------------------
void CGroupTree::setRootNode (SNode *pNewRoot)
{
// reset selection
_SelectedLine= -1;
_SelectedNode= NULL;
CRefPtr refPtrNewRoot = pNewRoot;
// clear old
delete _RootNode;
// If the node was deleted
if (pNewRoot && !refPtrNewRoot)
{
// NB nico : if anyone need that a day, please feel free to modify ...
nlwarning("Trying to set a node that is part of the tree as root node (not supported yet ...)");
}
// set new (may be NULL)
_RootNode = pNewRoot;
if(pNewRoot)
pNewRoot->setParentTree(this);
rebuild();
}
// ----------------------------------------------------------------------------
void CGroupTree::removeAll()
{
// Remove (but not delete) the groups template if any
for(uint i=0;i<_Lines.size();i++)
{
if (_Lines[i].Node)
{
if(_Lines[i].Node->Template)
{
delGroup(_Lines[i].Node->Template, true);
}
}
}
// Clear all lines, but don't delete templates
_Lines.clear();
// Delete all BmpViews and/or all text views
clearViews();
}
// ----------------------------------------------------------------------------
void CGroupTree::addTextLine (uint8 nDepth, CGroupTree::SNode *pNode)
{
pNode->setParentTree(this);
if (nDepth > 0)
{
SLine line;
line.Depth = nDepth-1;
line.Node = pNode;
if (pNode->DisplayText)
{
CViewText *pVT = new CViewText(TCtorParam());
line.TextOrTemplate = pVT;
pVT->setId("t"+toString(_Lines.size()));
pVT->setText(pNode->Text);
pVT->setColor(pNode->Color);
if(pNode->FontSize==-1)
pVT->setFontSize(_FontSize);
else
pVT->setFontSize(pNode->FontSize);
}
else
{
line.TextOrTemplate = pNode->Template;
}
line.TextOrTemplate->setPosRef (Hotspot_BL);
line.TextOrTemplate->setParentPosRef (Hotspot_BL);
line.TextOrTemplate->setParent (this);
line.TextOrTemplate->setParentPos (NULL);
line.TextOrTemplate->setX (getHrcIconXEnd(nDepth-1 + line.getNumAdditionnalBitmap()));
line.TextOrTemplate->setY ((sint32)_Lines.size());
line.TextOrTemplate->setModulateGlobalColor(this->getModulateGlobalColor());
if (pNode->DisplayText)
addView (line.TextOrTemplate);
else
addGroup ((CInterfaceGroup*)line.TextOrTemplate);
if (pNode->NodeAddedCallback)
{
pNode->NodeAddedCallback->nodeAdded(pNode, line.TextOrTemplate);
}
_Lines.push_back(line);
}
// recurs
if (pNode->Opened)
{
// **** standard hierarchy display, or if root
if(!_NavigateOneBranch || nDepth==0)
{
for (uint i = 0; i < pNode->Children.size(); ++i)
{
// add the branch only if want to show it
if(pNode->Children[i]->Show)
addTextLine (nDepth+1, pNode->Children[i]);
}
}
// **** display only the branch navigated
else
{
// find the first child opened
sint childOpen= -1;
for (uint i = 0; i < pNode->Children.size(); ++i)
{
// don't take hid ones
if(pNode->Children[i]->Show && pNode->Children[i]->Opened)
{
childOpen= i;
break;
}
}
// If some chidl opened, add just this line
if(childOpen>=0)
{
addTextLine (nDepth+1, pNode->Children[childOpen]);
}
// else add all closed, but showable lines
else
{
for (uint i = 0; i < pNode->Children.size(); ++i)
{
if(pNode->Children[i]->Show)
addTextLine (nDepth+1, pNode->Children[i]);
}
}
}
}
}
// ----------------------------------------------------------------------------
CViewBitmap *CGroupTree::createViewBitmap(uint line, const std::string &idPrefix, const std::string &texture)
{
CViewBitmap *pVB = new CViewBitmap(TCtorParam());
pVB->setId(idPrefix+toString(_Lines.size())+"_"+toString(_Lines[line].Bmps.size()));
pVB->setParent (this);
pVB->setParentPos (NULL);
pVB->setModulateGlobalColor(this->getModulateGlobalColor());
pVB->setTexture(texture);
return pVB;
}
// ----------------------------------------------------------------------------
void CGroupTree::addHierarchyBitmaps ()
{
if (_RootNode) _RootNode->updateLastVisibleSon();
for (uint nLayer = 0; nLayer < 256; nLayer++)
{
sint32 nCurRootLine = -1;
bool bCurRootLineLast = false;
bool bCurRootLineLastChild = false;
for (uint nLine = 0; nLine < _Lines.size(); nLine++)
if (nLayer <= _Lines[nLine].Depth)
{
// A Bitmap must be created
CViewBitmap *pVB = createViewBitmap(nLine, "t", "blank.tga");
pVB->setX (getHrcIconXStart(nLayer));
pVB->setY ((sint32)_Lines.size()*_BmpH - ((1+nLine)*_BmpH));
bool bAddBitmap = true;
bool bAddXExtendBitmap = false;
// Choose a bitmap
// Are we on the last depth in the line ?
if (_Lines[nLine].Depth == nLayer)
{
nCurRootLine = nLine;
if (_Lines[nLine].Node == _Lines[nCurRootLine].Node->Father->LastVisibleSon)
bCurRootLineLast = true;
else
bCurRootLineLast = false;
bCurRootLineLastChild = false;
// do i have some child shown?
bool haveSomeVisibleChild= false;
for(uint k=0;k<_Lines[nLine].Node->Children.size();k++)
{
if(_Lines[nLine].Node->Children[k]->Show)
{
haveSomeVisibleChild= true;
break;
}
}
// if so
if (haveSomeVisibleChild)
{
// Yes am I opened ?
if (_Lines[nLine].Node->Opened)
{
pVB->setTexture(_ArboOpenFirst);
}
else
{
// No I am closed
pVB->setTexture(_ArboCloseJustOne);
}
}
else
{
/*
// No child
// If there's a bitmap on this line , left an empty bitmap
if (!_Lines[nLine].Node->Bitmap.empty())
{
pVB->setTexture("blank.tga"); // create a transparent bitmap to have correct "child_resize_w"
pVB->setColor(CRGBA(0, 0, 0, 0));
}
else
{
pVB->setTexture(_ArboSonWithoutSon);
}
*/
pVB->setTexture(_ArboSonWithoutSon);
}
// if not the root line, must add Extend Bitmap
if(nLayer)
bAddXExtendBitmap= true;
}
else
{
// No we are before the last depth, Do we have any current root ?
if (nCurRootLine != -1)
{
// Yes, do the current line is child of current root line ?
bool bFound = false;
for (uint i = 0; i < _Lines[nCurRootLine].Node->Children.size(); ++i)
if (_Lines[nLine].Node == _Lines[nCurRootLine].Node->Children[i])
{
bFound = true;
break;
}
if (bFound)
{
// is it the last child ?
bool lastSonDisplay= _Lines[nLine].Node == _Lines[nCurRootLine].Node->LastVisibleSon;
// Special for _NavigateOneBranch mode
if(_NavigateOneBranch)
{
// if node opened, then his brother are hid! => he behaves like a "last son"
if(_Lines[nLine].Node->Opened)
lastSonDisplay= true;
}
// if must display like last child
if (lastSonDisplay)
{
// Yes this is the last child
pVB->setTexture(_ArboSonLast);
bCurRootLineLastChild = true;
}
else
{
// No so we have brothers
pVB->setTexture(_ArboSon);
}
}
else
{
// Not found, display a line
pVB->setTexture(_ArboLevel);
// We have to not display a line if we have passed the last child of this root
// We never have to display a line also if we are in _NavigateOneBranch mode
if (bCurRootLineLastChild || _NavigateOneBranch)
bAddBitmap = false;
}
}
}
// Add the bitmap
if (bAddBitmap)
{
addView (pVB);
_Lines[nLine].Bmps.push_back(pVB);
// if must add the special extend bitmap, and if exist
if(bAddXExtendBitmap && !_ArboXExtend.empty())
{
CViewBitmap *pVB = createViewBitmap(nLine, "ext_t", _ArboXExtend);
pVB->setX (getHrcIconXStart(nLayer) - _XExtend);
pVB->setY ((sint32)_Lines.size()*_BmpH - ((1+nLine)*_BmpH));
addView (pVB);
_Lines[nLine].Bmps.push_back(pVB);
}
}
else
{
delete pVB;
}
}
}
// add additionnal bitmap for each line
for (uint nLine = 0; nLine < _Lines.size(); nLine++)
{
if (!_Lines[nLine].Node->Bitmap.empty())
{
CViewBitmap *pVB = createViewBitmap(nLine, "custom_bm", _Lines[nLine].Node->Bitmap);
pVB->setX (getHrcIconXStart(_Lines[nLine].Depth + 1));
pVB->setY ((sint32)_Lines.size()*_BmpH - ((1+nLine)*_BmpH));
_Lines[nLine].Bmps.push_back(pVB);
addView (pVB);
}
}
}
// ***************************************************************************
CGroupTree::SNode *CGroupTree::selectNodeByIdRecurse(SNode *pNode, const std::string &nodeId)
{
// select this node?
if(pNode!=_RootNode)
{
if(pNode->Id == nodeId)
return pNode;
}
// try with sons
for(uint i=0;iChildren.size();i++)
{
SNode *ret= selectNodeByIdRecurse(pNode->Children[i], nodeId);
if(ret)
return ret;
}
// not found => NULL
return NULL;
}
// ***************************************************************************
bool CGroupTree::selectNodeById(const std::string &nodeId, bool triggerAH)
{
CInterfaceManager *pIM= CInterfaceManager::getInstance();
SNode *selNode= NULL;
// Avoid infinite recurs
if(_AvoidSelectNodeByIdIR)
return true;
// first find in the hierarchy
selNode= selectNodeByIdRecurse(_RootNode, nodeId);
// if found
if(selNode)
{
// Opens the hierarchy
SNode *pFather = selNode->Father;
while(pFather != NULL)
{
pFather->Opened = true;
pFather = pFather->Father;
}
if (triggerAH)
{
// runAH may infinite recurs (HTML browse...)
_AvoidSelectNodeByIdIR= true;
// launch the action handler
pIM->runActionHandler ( selNode->AHName,
this,
selNode->AHParams );
}
// runAH may infinite recurs (HTML browse...)
_AvoidSelectNodeByIdIR= false;
// mark as selected
_SelectedNode = selNode;
forceRebuild();
return true;
}
else
{
return false;
}
}
// ***************************************************************************
class CHandlerTreeReset : public IActionHandler
{
public:
void execute (CCtrlBase * /* pCaller */, const std::string &sParams)
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
CGroupTree *pTree = dynamic_cast(pIM->getElementFromId(sParams));
if (pTree != NULL)
pTree->reset();
}
protected:
};
REGISTER_ACTION_HANDLER( CHandlerTreeReset, "tree_reset");
// ***************************************************************************
void CGroupTree::changeNavigateOneBranch(bool newState)
{
if(newState!=_NavigateOneBranch)
{
_NavigateOneBranch= newState;
// if new is true, then must reset both open state and selection
if(_NavigateOneBranch)
{
reset();
// reselect the first line
selectLine(0);
}
// else just rebuild
else
{
forceRebuild();
}
}
}
// ***************************************************************************
void CGroupTree::cancelNextSelectLine()
{
_CancelNextSelectLine = true;
}
// ***************************************************************************
int CGroupTree::luaGetRootNode(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "getRootNode", 0);
CLuaIHM::pushReflectableOnStack(ls, getRootNode());
return 1;
}
// ***************************************************************************
int CGroupTree::luaSetRootNode(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "setRootNode", 1);
if (ls.isNil())
{
setRootNode(NULL);
return 0;
}
setRootNode(SNode::luaGetNodeOnStack(ls, "CGroupTree::setRootNode"));
return 0;
}
// ***************************************************************************
int CGroupTree::luaForceRebuild(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "forceRebuild", 0);
forceRebuild();
return 0;
}
// ***************************************************************************
CGroupTree::SNode *CGroupTree::SNode::luaGetNodeOnStack(CLuaState &ls, const char * /* funcName */)
{
SNode *node = dynamic_cast(CLuaIHM::getReflectableOnStack(ls, 1));
CLuaIHM::check(ls, node != NULL, "SNode expected");
return node;
}
// ***************************************************************************
int CGroupTree::SNode::luaDetachChild(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaDetachChild";
CLuaIHM::checkArgCount(ls, funcName, 1);
detachChild(luaGetNodeOnStack(ls, funcName));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaSort(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaSort";
CLuaIHM::checkArgCount(ls, funcName, 0);
sort();
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaSortByBitmap(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaSort";
CLuaIHM::checkArgCount(ls, funcName, 0);
sortByBitmap();
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaDeleteChild(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaDeleteChild";
CLuaIHM::checkArgCount(ls, funcName, 1);
deleteChild(luaGetNodeOnStack(ls, funcName));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaAddChild(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaAddChild";
CLuaIHM::checkArgCount(ls, funcName, 1);
addChild(luaGetNodeOnStack(ls, funcName));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaAddChildSorted(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaAddChildSorted";
CLuaIHM::checkArgCount(ls, funcName, 1);
addChildSorted(luaGetNodeOnStack(ls, funcName));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaAddChildSortedByBitmap(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaAddChildSortedByBitmap";
CLuaIHM::checkArgCount(ls, funcName, 1);
addChildSorted(luaGetNodeOnStack(ls, funcName));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaGetNodeFromId(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaGetNodeFromId";
CLuaIHM::checkArgCount(ls, funcName, 1);
CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
SNode *result = getNodeFromId(ls.toString(1));
if (result)
{
CLuaIHM::pushReflectableOnStack(ls, result);
}
else
{
ls.pushNil();
}
return 1;
}
// ***************************************************************************
int CGroupTree::SNode::luaGetParentTree(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "getParentTree", 0);
if (ParentTree)
{
CLuaIHM::pushUIOnStack(ls, ParentTree);
}
else
{
ls.pushNil();
}
return 1;
}
// ***************************************************************************
int CGroupTree::luaGetNodeUnderMouse(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "getNodeUnderMouse", 0);
SNode *node = getNodeUnderMouse();
if (node)
{
CLuaIHM::pushReflectableOnStack(ls, node);
}
else
{
ls.pushNil();
}
return 1;
}
// ***************************************************************************
int CGroupTree::luaCancelNextSelectLine(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "cancelNextSelectLine", 0);
cancelNextSelectLine();
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaAddChildFront(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaAddChildFront";
CLuaIHM::checkArgCount(ls, funcName, 1);
addChild(luaGetNodeOnStack(ls, funcName));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaIsChild(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaIsChild";
CLuaIHM::checkArgCount(ls, funcName, 1);
ls.push(isChild(luaGetNodeOnStack(ls, funcName)));
return 1;
}
// ***************************************************************************
int CGroupTree::SNode::luaAddChildAtIndex(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaAddChildAtIndex";
CLuaIHM::checkArgCount(ls, funcName, 2);
CLuaIHM::checkArgType(ls, funcName, 2, LUA_TNUMBER);
addChildAtIndex(luaGetNodeOnStack(ls, funcName), (sint) ls.toNumber(2));
return 0;
}
// ***************************************************************************
int CGroupTree::SNode::luaGetFather(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaGetFather";
CLuaIHM::checkArgCount(ls, funcName, 0);
if(Father)
CLuaIHM::pushReflectableOnStack(ls, Father);
else
ls.pushNil();
return 1;
}
// ***************************************************************************
int CGroupTree::SNode::luaGetNumChildren(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaGetNumChildren";
CLuaIHM::checkArgCount(ls, funcName, 0);
ls.push((double) Children.size());
return 1;
}
// ***************************************************************************
int CGroupTree::SNode::luaGetChild(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaGetChild";
CLuaIHM::checkArgCount(ls, funcName, 1);
CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER);
//
sint index = (sint) ls.toNumber(1);
if (index < 0 || index >= (sint) Children.size())
{
std::string range = Children.empty() ? "" : toString("[0, %d]", Children.size() - 1);
CLuaIHM::fails(ls, "Bad index of tree node child : %d, range is %s", (int) index, range.c_str());
}
CLuaIHM::pushReflectableOnStack(ls, Children[index]);
return 1;
}
// ***************************************************************************
int CGroupTree::SNode::luaCloseAll(CLuaState &ls)
{
const char *funcName = "CGroupTree::SNode::luaGetFather";
CLuaIHM::checkArgCount(ls, funcName, 0);
closeAll();
return 0;
}
// ***************************************************************************
int CGroupTree::luaSelectNodeById(CLuaState &ls)
{
const char *funcName = "selectNodeById";
CLuaIHM::checkArgCount(ls, funcName, 2);
CLuaIHM::checkArgType(ls, funcName, 1, LUA_TSTRING);
CLuaIHM::checkArgType(ls, funcName, 2, LUA_TBOOLEAN);
selectNodeById(ls.toString(1), ls.toBoolean(2));
return 0;
}
// ***************************************************************************
int CGroupTree::luaGetSelectedNodeId(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "getSelectedNodeId", 0);
ls.push(getSelectedNodeId());
return 1;
}
// ***************************************************************************
int CGroupTree::luaSelectLine(CLuaState &ls)
{
CLuaIHM::checkArgType(ls, "CGroupTree::selectLine", 1, LUA_TNUMBER);
CLuaIHM::checkArgType(ls, "CGroupTree::selectLine", 2, LUA_TBOOLEAN);
selectLine((uint) ls.toNumber(1), ls.toBoolean(2));
return 0;
}
// ***************************************************************************
int CGroupTree::luaUnselect(CLuaState &ls)
{
CLuaIHM::checkArgCount(ls, "unselect", 0);
unselect();
return 0;
}
// ***************************************************************************
uint CGroupTree::SLine::getNumAdditionnalBitmap() const
{
if (!Node) return 0;
return Node->getNumBitmap();
}