// 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 "nel/gui/group_tab.h" #include "nel/misc/xml_auto_ptr.h" #include "nel/gui/lua_ihm.h" #include "nel/gui/widget_manager.h" #include "nel/gui/interface_group.h" #include "nel/gui/view_text.h" using namespace std; using namespace NLMISC; NLMISC_REGISTER_OBJECT(CViewBase, CGroupTab, std::string, "tab"); namespace NLGUI { // *************************************************************************** CGroupTab::CGroupTab(const TCtorParam ¶m) : CInterfaceGroup(param) { _Selection= -1; _NextSelection = -1; _BaseRenderLayer= 0; _Setuped= false; _HideOutTabs = false; _FirstTabIndex = -1; _LastTabIndex = -1; } std::string CGroupTab::getProperty( const std::string &name ) const { if( name == "hide_out_tabs" ) { return toString( _HideOutTabs ); } else if( name == "onchange" ) { return _AHOnChange; } else if( name == "onchange_params" ) { return _ParamsOnChange; } else return CInterfaceGroup::getProperty( name ); } void CGroupTab::setProperty( const std::string &name, const std::string &value ) { if( name == "hide_out_tabs" ) { bool b; if( fromString( value, b ) ) _HideOutTabs = b; return; } else if( name == "onchange" ) { _AHOnChange = value; return; } else if( name == "onchange_params" ) { _ParamsOnChange = value; return; } else CInterfaceGroup::setProperty( name, value ); } // *************************************************************************** bool CGroupTab::parse (xmlNodePtr cur, CInterfaceGroup *parentGroup) { if( !CInterfaceGroup::parse(cur, parentGroup) ) return false; CXMLAutoPtr prop((const char*)xmlGetProp(cur, (xmlChar*)"hide_out_tabs")); if (prop) { _HideOutTabs = convertBool(prop); } prop = (char*) xmlGetProp( cur, (xmlChar*)"onchange" ); if (prop) _AHOnChange = (const char *) prop; prop = (char*) xmlGetProp( cur, (xmlChar*)"onchange_params" ); if (prop) _ParamsOnChange = (const char *) prop; return true; } // *************************************************************************** void CGroupTab::setup() { if(_Setuped) return; _Setuped= true; _Buttons.clear(); _Groups.clear(); /* Buttons must be named tab0,tab1,tab2... and tab_array0_0, tab_array0_1 .... (for vector of tab) Only 10 tab array are allowed */ for(sint tabArrayIndex= -1;tabArrayIndex<10;tabArrayIndex++) { // prefix according to array or not string prefix; if(tabArrayIndex==-1) prefix= "tab"; else prefix= toString("tab_array%d_", tabArrayIndex); // for all tab of this type (standard tab or array of tab), find the Buttons and groups. uint tabIndex=0; for(;;) { // find the ctrl named "tab0" CCtrlTabButton *but= dynamic_cast(getCtrl(toString("%s%d", prefix.c_str(), tabIndex))); if(!but) break; // find the associated group CInterfaceGroup *pGroup = NULL; CInterfaceGroup *pFather = this; while ((pGroup == NULL) && (pFather != NULL)) { pGroup = pFather->getGroup(but->_AssociatedGroup); pFather = pFather->getParent(); } // add to the button and group list _Buttons.push_back(but); _Groups.push_back(pGroup); // try next tabIndex++; } } // at the first setup, select by default the 1st if(_Selection<0) select(0); } // *************************************************************************** void CGroupTab::addTab(CCtrlTabButton * tabB) { addCtrl(tabB); _Setuped = false; updateCoords(); selectFromCtrl(tabB); if(_HideOutTabs && !_AHOnChange.empty()) CAHManager::getInstance()->runActionHandler(_AHOnChange, this, _ParamsOnChange); } // *************************************************************************** void CGroupTab::addTab(CCtrlTabButton * tabB, sint index) { if(index<(sint)_Buttons.size() && index>=0) { vector buttons = _Buttons; for(sint i=0;i<(sint)_Buttons.size();i++) delCtrl(_Buttons[i], true); _Setuped = false; updateCoords(); uint count=0; CCtrlTabButton* lastTab=NULL; for(sint i=0;i<(sint)buttons.size();i++) { if(i==index) { tabB->setId("tab" + NLMISC::toString(count)); tabB->setParentPos(lastTab); if(i==0) tabB->setParentPosRef(Hotspot_TL); else tabB->setParentPosRef(Hotspot_TR); tabB->setPosRef(Hotspot_TL); addCtrl(tabB); lastTab = tabB; count++; } buttons[i]->setId("tab" + NLMISC::toString(count)); buttons[i]->setParentPos(lastTab); if(i==0 && index!=0) buttons[i]->setParentPosRef(Hotspot_TL); else buttons[i]->setParentPosRef(Hotspot_TR); buttons[i]->setPosRef(Hotspot_TL); addCtrl(buttons[i]); lastTab = buttons[i]; count++; } _Setuped = false; updateCoords(); // we have added a new button in first position // then it must recover the reference if(index==0) { CCtrlTabButton * tab0 = _Buttons[0]; for(uint i=0; i<_Buttons.size(); ++i) _Buttons[i]->initRBRefFromRadioButton(tab0); select(_Selection); } else { CCtrlTabButton * tab0 = _Buttons[0]; _Buttons[index]->initRBRefFromRadioButton(tab0); } } else { tabB->setId(string("tab") + NLMISC::toString(_Buttons.size())); if(_Buttons.size()==0) { tabB->setParentPos(NULL); tabB->setParentPosRef(Hotspot_TL); } else { tabB->setParentPos(_Buttons[_Buttons.size()-1]); tabB->setParentPosRef(Hotspot_TR); } tabB->setPosRef(Hotspot_TL); addCtrl(tabB); } _Setuped = false; updateCoords(); if(_HideOutTabs && !_AHOnChange.empty()) CAHManager::getInstance()->runActionHandler(_AHOnChange, this, _ParamsOnChange); } // *************************************************************************** int CGroupTab::luaAddTab(CLuaState &ls) { CLuaIHM::checkArgCount(ls, "CGroupTab::addTab", 1); CCtrlTabButton *tabB = dynamic_cast(CLuaIHM::getUIOnStack(ls, 1)); if (tabB) { // don't use addTab to avoid selection of new tab addCtrl(tabB); _Setuped = false; updateCoords(); if(_HideOutTabs && !_AHOnChange.empty()) CAHManager::getInstance()->runActionHandler(_AHOnChange, this, _ParamsOnChange); } return 0; } // *************************************************************************** int CGroupTab::luaAddTabWithOrder(CLuaState &ls) { const char *funcName = "addTabWithOrder"; CLuaIHM::checkArgCount(ls, funcName, 2); CLuaIHM::checkArgType(ls, funcName, 2, LUA_TNUMBER); CCtrlTabButton *tabB = dynamic_cast(CLuaIHM::getUIOnStack(ls, 1)); if (tabB) { // don't use addTab to avoid selection of new tab addTab(tabB, (sint) ls.toNumber(2)); } return 0; } // *************************************************************************** void CGroupTab::removeTab(sint index) { if(!(index>=0 && index<(sint)_Buttons.size())) return; vector buttons = _Buttons; for(sint i=0;i<(sint)_Buttons.size();i++) { bool deleteElt = (i!=index); CViewText* tabVT = _Buttons[i]->getViewText(); if(tabVT && !deleteElt) delView(tabVT, deleteElt); delCtrl(_Buttons[i], deleteElt); if(!deleteElt) (_Groups[i]->getParent())->delGroup(_Groups[i], deleteElt); } _Setuped = false; updateCoords(); uint count=0; CCtrlTabButton* lastTab = NULL; for(sint i=0;i<(sint)buttons.size();i++) { if(i!=index) { buttons[i]->setId("tab"+NLMISC::toString(count)); buttons[i]->setParentPos(lastTab); if((i==0) || (index==0 && i==1)) buttons[i]->setParentPosRef(Hotspot_TL); else _Buttons[i]->setParentPosRef(Hotspot_TR); buttons[i]->setPosRef(Hotspot_TL); lastTab = buttons[i]; addCtrl(buttons[i]); count++; } } _Setuped = false; updateCoords(); // we have removed the first button which is the only one to own the reference // then the new first button recovers the reference if(index==0) { CCtrlTabButton * tab0 = _Buttons[0]; for(uint i=0; i<_Buttons.size(); ++i) _Buttons[i]->initRBRefFromRadioButton(tab0); select(_Selection); } if(_HideOutTabs) { select(_FirstTabIndex); if(!_AHOnChange.empty()) CAHManager::getInstance()->runActionHandler(_AHOnChange, this, _ParamsOnChange); } } // *************************************************************************** int CGroupTab::luaRemoveTab(CLuaState &ls) { const char *funcName = "removeTab"; CLuaIHM::checkArgCount(ls, funcName, 1); CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER); removeTab((uint) ls.toNumber(1)); return 0; } // *************************************************************************** void CGroupTab::removeAll() { for(sint i=0;i<(sint)_Buttons.size();i++) { CViewText* tabVT = _Buttons[i]->getViewText(); if(tabVT) delView(tabVT, false); delCtrl(_Buttons[i], false); (_Groups[i]->getParent())->delGroup(_Groups[i], false); } _Setuped = false; updateCoords(); } // *************************************************************************** int CGroupTab::luaRemoveAll(CLuaState &ls) { CLuaIHM::checkArgCount(ls, "CGroupTab::removeAll", 0); removeAll(); return 0; } // *************************************************************************** CCtrlTabButton* CGroupTab::getTabButton(sint index) { if(index>=0 && index<(sint)_Buttons.size()) { return _Buttons[index]; } return NULL; } // *************************************************************************** int CGroupTab::luaGetTabButton(CLuaState &ls) { const char *funcName = "getTabButton"; CLuaIHM::checkArgCount(ls, funcName, 1); CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER); CCtrlTabButton* tab = getTabButton((uint) ls.toNumber(1)); if(tab != NULL) { CLuaIHM::pushUIOnStack(ls, tab); return 1; } return 0; } // *************************************************************************** void CGroupTab::updateFirstTabButton() { if(!_HideOutTabs || (_Selection<0) || (_Buttons.size()==0) || (_Parent->getWReal()<0) || _FirstTabIndex>=(sint)_Buttons.size()) return; sint oldFirstTabIndex = _FirstTabIndex; sint oldLastTabIndex = _LastTabIndex; if(_FirstTabIndex<0) { for(uint i=0; i<_Buttons.size(); i++) { CCtrlTabButton * tab = _Buttons[i]; if(tab->getActive()) { _FirstTabIndex = i; break; } } } sint selection = _Selection; if(selection>=(sint)_Buttons.size()) selection = _FirstTabIndex; if(selection < _FirstTabIndex) _FirstTabIndex = selection; sint32 maxWidth = _Parent->getWReal(); sint32 buttonsWidth = 0; _LastTabIndex = 0; // desactive first tabs for(uint i=0; i<(uint)_FirstTabIndex; i++) { CCtrlTabButton * tab = _Buttons[i]; if(tab->getActive()) tab->setActive(false); } // active tabs from _FirstTabIndex and search for last showed tab for(uint i=_FirstTabIndex; i<_Buttons.size(); i++) { CCtrlTabButton * tab = _Buttons[i]; sint32 tabWidth = tab->getWMax(); if(buttonsWidth+tabWidth <= maxWidth) { buttonsWidth += tabWidth; if(!tab->getActive()) tab->setActive(true); _LastTabIndex = i; } else break; } // check if selected tab is in showed tabs if(_LastTabIndex < selection) { for(uint i=_LastTabIndex+1; i<=(uint)selection; i++) { CCtrlTabButton * tab = _Buttons[i]; buttonsWidth += tab->getWMax(); if(!tab->getActive()) tab->setActive(true); } while(buttonsWidth>maxWidth) { CCtrlTabButton * tab = _Buttons[_FirstTabIndex]; buttonsWidth -= tab->getWMax(); _FirstTabIndex++; if(tab->getActive()) tab->setActive(false); } } // add tabs before the "_FirstTabIndex" one if it remains place while(buttonsWidth0) { CCtrlTabButton * tab = _Buttons[_FirstTabIndex-1]; buttonsWidth += tab->getWMax(); if(buttonsWidth<=maxWidth) { _FirstTabIndex--; if(!tab->getActive()) tab->setActive(true); } } // desactive last tabs for(uint i=_LastTabIndex+1; i<_Buttons.size(); i++) { CCtrlTabButton * tab = _Buttons[i]; if(tab->getActive()) tab->setActive(false); } if(!_AHOnChange.empty() && ((oldFirstTabIndex!=_FirstTabIndex) || (oldLastTabIndex!=_LastTabIndex))) CAHManager::getInstance()->runActionHandler(_AHOnChange, this, _ParamsOnChange); } // *************************************************************************** int CGroupTab::luaShowTabButton(CLuaState &ls) { const char *funcName = "showTabButton"; CLuaIHM::checkArgCount(ls, funcName, 1); CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER); sint showTab = (sint)ls.toNumber(1); if(showTab>=0 && showTab<(sint)_Buttons.size()) { sint32 maxWidth = _Parent->getWReal(); sint32 buttonsWidth = 0; if(showTab<_FirstTabIndex) { _FirstTabIndex = showTab; sint lastTabIndex = _FirstTabIndex; for(uint i=_FirstTabIndex; i<_Buttons.size(); i++) { CCtrlTabButton * tab = _Buttons[i]; sint32 tabWidth = tab->getWMax(); if(buttonsWidth+tabWidth <= maxWidth) { buttonsWidth += tabWidth; if(!tab->getActive()) tab->setActive(true); lastTabIndex = i; } else break; } if(lastTabIndex <_Selection) select(lastTabIndex); else updateFirstTabButton(); } else if(showTab>_LastTabIndex) { for(uint i=_FirstTabIndex; i<=(uint)showTab; i++) buttonsWidth += _Buttons[i]->getWMax(); while(buttonsWidth>maxWidth) { buttonsWidth -= _Buttons[_FirstTabIndex]->getWMax(); _FirstTabIndex++; } if(_Selection<_FirstTabIndex) select(_FirstTabIndex); else updateFirstTabButton(); } } return 0; } // *************************************************************************** void CGroupTab::updateCoords () { if(!_Setuped) setup(); // special for groupTab. Because the ctrl may overlap each other from left to right, they are inserted in reverse // order. BUT, for correct TR/TL coord handling, must updtae in the reverse sens too! // **** just basis CInterfaceGroup::doUpdateCoords(); // **** update in reverse order _XReal += _OffsetX; _YReal += _OffsetY; vector::reverse_iterator ite; for (ite = _EltOrder.rbegin() ; ite != _EltOrder.rend(); ite++) { CViewBase *pIE = *ite; pIE->updateCoords(); } _XReal -= _OffsetX; _YReal -= _OffsetY; // **** complete with child resize CInterfaceGroup::updateCoords(); updateFirstTabButton(); } // *************************************************************************** void CGroupTab::select(sint index) { if(index<0) index= -1; if(index<(sint)_Buttons.size()) { sint i; // validate this radio button. if(index>=0) _Buttons[index]->setPushed(true); else for(i=0;i<(sint)_Buttons.size();i++) _Buttons[i]->setPushed(false); _NextSelection = index; // set all render layer to their correct state for(i=0;i<(sint)_Buttons.size();i++) { // set the selected one +1, so it will be over _Buttons[i]->setRenderLayer(_BaseRenderLayer + (i==index?1:0) ); if (i==index) { _Buttons[i]->setBlink(false); if (_Buttons[i]->_AHOnLeftClick2 != NULL) // call like if press on it _Buttons[i]->_AHOnLeftClick2->execute(_Buttons[i], _Buttons[i]->getParamsOnLeftClick()); } } // show/hide all the associated groups for(i=0;i<(sint)_Groups.size();i++) { if(_Groups[i]) _Groups[i]->setActive(i==index); } // ok! _Selection= index; updateFirstTabButton(); } } // *************************************************************************** void CGroupTab::selectFromCtrl(CCtrlTabButton *button) { // search in all buttons for(uint i=0;i<_Buttons.size();i++) { // found? if(_Buttons[i]==button) { select(i); return; } } } // *************************************************************************** void CGroupTab::selectDefault(CCtrlTabButton *ifSelectionIs) { if(!_HideOutTabs && _Selection>=0 && _Selection<(sint)_Buttons.size() && _Buttons[_Selection]==ifSelectionIs) { // parse all active button for(uint i=0;i<_Buttons.size();i++) { if(_Buttons[i]->getActive()) { select(i); return; } } // default: unselect select(-1); } } // *************************************************************************** void CGroupTab::selectDefaultIfCurrentHid() { if(_Selection>=0 && _Selection<(sint)_Buttons.size() && _Buttons[_Selection]!=NULL && _Buttons[_Selection]->getActive()==false) { selectDefault(_Buttons[_Selection]); } } // *************************************************************************** sint CGroupTab::getSelection() const { return _Selection; } NLMISC_REGISTER_OBJECT(CViewBase, CCtrlTabButton, std::string, "tab_button"); // *************************************************************************** std::string CGroupTab::getAssociatedGroupSelection() const { if(_Selection>=0 && _Selection<(sint)_Buttons.size()) { return _Buttons[_Selection]->_AssociatedGroup; } return ""; } // *************************************************************************** CInterfaceGroup* CGroupTab::getGroup(sint index) { if(index>=0 && index<(sint)_Groups.size()) { return _Groups[index]; } return NULL; } // *************************************************************************** int CGroupTab::luaGetGroup(CLuaState &ls) { const char *funcName = "getGroup"; CLuaIHM::checkArgCount(ls, funcName, 1); CLuaIHM::checkArgType(ls, funcName, 1, LUA_TNUMBER); CInterfaceGroup* group = getGroup((uint) ls.toNumber(1)); if(group != NULL) { CLuaIHM::pushUIOnStack(ls, group); return 1; } return 0; } // *************************************************************************** CCtrlTabButton::CCtrlTabButton(const TCtorParam ¶m) : CCtrlTextButton(param) { _DefaultX= 0; _AHOnLeftClick2 = NULL; _BlinkDate = 0; _Blinking = false; _BlinkState = false; } std::string CCtrlTabButton::getProperty( const std::string &name ) const { if( name == "group" ) return _AssociatedGroup; else return CCtrlTextButton::getProperty( name ); } void CCtrlTabButton::setProperty( const std::string &name, const std::string &value ) { if( name == "group" ) { _AssociatedGroup = value; } else CCtrlTextButton::setProperty( name, value ); } // *************************************************************************** bool CCtrlTabButton::parse (xmlNodePtr cur, CInterfaceGroup *parentGroup) { if(!CCtrlTextButton::parse(cur, parentGroup)) return false; // if left click not setuped, set default _AHOnLeftClick2 = _AHOnLeftClick; string dummy; _AHOnLeftClick= CAHManager::getInstance()->getAH("tab_select", dummy); // read the associated group to show/hide CXMLAutoPtr prop; prop = (char*) xmlGetProp( cur, (xmlChar*)"group" ); if(prop) _AssociatedGroup= (const char*)prop; // backup the x _DefaultX= _X; return true; } // *************************************************************************** void CCtrlTabButton::setActive(bool state) { if(state!=getActive()) { CCtrlTextButton::setActive(state); // special for correct display of textbuttons. reset to 0 when the button is hid if(state) setX(_DefaultX); else setX(0); // if hide, and I was the selected tab, select a default active one if(state==false) { CGroupTab *parent= dynamic_cast(getParent()); if(parent) parent->selectDefault(this); } } } // *************************************************************************** bool CCtrlTabButton::handleEvent (const NLGUI::CEventDescriptor &event) { if (event.getType() == NLGUI::CEventDescriptor::system) { const NLGUI::CEventDescriptorSystem &systemEvent = (const NLGUI::CEventDescriptorSystem &) event; if (systemEvent.getEventTypeExtended() == NLGUI::CEventDescriptorSystem::clocktick) if (_Blinking) { uint dbclickDelay = CWidgetManager::getInstance()->getUserDblClickDelay(); const CWidgetManager::SInterfaceTimes × = CWidgetManager::getInstance()->getInterfaceTimes(); if (( times.thisFrameMs - _BlinkDate) > dbclickDelay) { if (_BlinkState) { setTextColorNormal(CRGBA::White); setTextModulateGlobalColorNormal(false); } else { setTextColorNormal(_TextColorNormalBlink); setTextModulateGlobalColorNormal(_TextModulateGlobalColorNormalBlink); } _BlinkState = !_BlinkState; _BlinkDate = times.thisFrameMs; } } } return CCtrlTextButton::handleEvent(event); } // *************************************************************************** void CCtrlTabButton::setBlink (bool b) { if (b) { if (!_Blinking) { _TextColorNormalBlink = getTextColorNormal(); _TextModulateGlobalColorNormalBlink = getTextModulateGlobalColorNormal(); CWidgetManager::getInstance()->registerClockMsgTarget(this); } _Blinking = true; } else { if (_Blinking) { CWidgetManager::getInstance()->unregisterClockMsgTarget(this); setTextColorNormal(_TextColorNormalBlink); setTextModulateGlobalColorNormal(_TextModulateGlobalColorNormalBlink); } _Blinking = false; } } // *************************************************************************** // Action handler for Tab selection class CHandlerTabSelect : public IActionHandler { public: virtual void execute (CCtrlBase *pCaller, const string &/* Params */) { CCtrlTabButton *but= dynamic_cast(pCaller); // get the parent TabGroup CGroupTab *parent= dynamic_cast(but->getParent()); if(parent) parent->selectFromCtrl(but); } }; REGISTER_ACTION_HANDLER(CHandlerTabSelect, "tab_select" ); }