1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
6 // Copyright (C) 2013-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "nel/gui/interface_expr.h"
24 #include "nel/gui/interface_expr_node.h"
25 #include "nel/gui/reflect.h"
26 #include "nel/gui/db_manager.h"
27 #include "nel/misc/cdb_branch.h"
28 #include "nel/gui/action_handler.h"
29 #include "nel/gui/interface_link.h"
30 #include "nel/gui/interface_element.h"
31 #include "nel/gui/interface_group.h"
32 #include "nel/gui/widget_manager.h"
33 #include "nel/misc/algo.h"
38 using namespace NLMISC
;
51 CInterfaceLink::TLinkList
CInterfaceLink::_LinkList
;
52 CInterfaceLink::TLinkVect
CInterfaceLink::_LinksWithNoTarget
;
54 CInterfaceLink
*CInterfaceLink::_FirstTriggeredLink
[2] = { NULL
, NULL
};
55 CInterfaceLink
*CInterfaceLink::_LastTriggeredLink
[2] = { NULL
, NULL
};
56 CInterfaceLink
*CInterfaceLink::_CurrUpdatedLink
= NULL
;
57 CInterfaceLink
*CInterfaceLink::_NextUpdatedLink
= NULL
;
58 uint
CInterfaceLink::_CurrentTriggeredLinkList
= 0;
60 bool CInterfaceLink::_UpdateAllLinks
= false;
67 /** Tool fct : affect a value to a reflected property of an interface element.
69 static bool affect(const CInterfaceExprValue
&value
, CInterfaceElement
&destElem
, const CReflectedProperty
&property
)
72 CInterfaceExprValue valueToAffect
= value
;
75 case CReflectedProperty::Boolean
:
76 if (valueToAffect
.toBool())
78 (destElem
.*(property
.SetMethod
.SetBool
))(valueToAffect
.getBool());
86 case CReflectedProperty::SInt32
:
87 if (valueToAffect
.toInteger())
89 (destElem
.*(property
.SetMethod
.SetSInt32
))((sint32
) valueToAffect
.getInteger());
97 case CReflectedProperty::Float
:
98 if (valueToAffect
.toDouble())
100 (destElem
.*(property
.SetMethod
.SetFloat
))((float) valueToAffect
.getDouble());
108 case CReflectedProperty::String
:
109 case CReflectedProperty::StringRef
:
110 if (valueToAffect
.toString())
112 (destElem
.*(property
.SetMethod
.SetString
))(valueToAffect
.getString());
120 #ifdef RYZOM_LUA_UCSTRING
121 case CReflectedProperty::UCString
:
122 case CReflectedProperty::UCStringRef
:
123 if (valueToAffect
.toString())
125 (destElem
.*(property
.SetMethod
.SetUCString
))(ucstring::makeFromUtf8(valueToAffect
.getString()));
133 case CReflectedProperty::RGBA
:
134 if (valueToAffect
.toRGBA())
136 (destElem
.*(property
.SetMethod
.SetRGBA
))(valueToAffect
.getRGBA());
151 CInterfaceLink::CInterfaceLinkUpdater::CInterfaceLinkUpdater()
153 NLGUI::CDBManager::getInstance()->addFlushObserver( this );
156 CInterfaceLink::CInterfaceLinkUpdater::~CInterfaceLinkUpdater()
160 void CInterfaceLink::CInterfaceLinkUpdater::onObserverCallFlush()
162 CInterfaceLink::updateTrigeredLinks();
169 //===========================================================
170 CInterfaceLink::CInterfaceLink() : _On(true)
172 // add an entry in the links list
173 _LinkList
.push_front(this);
174 _ListEntry
= _LinkList
.begin();
175 _PrevTriggeredLink
[0] = _PrevTriggeredLink
[1] = NULL
;
176 _NextTriggeredLink
[0] = _NextTriggeredLink
[1] = NULL
;
177 _Triggered
[0] = _Triggered
[1] = false;
179 _AHCondParsed
= NULL
;
182 //===========================================================
183 CInterfaceLink::~CInterfaceLink()
185 if (this == _CurrUpdatedLink
)
187 _CurrUpdatedLink
= NULL
;
189 if (this == _NextUpdatedLink
)
191 _NextUpdatedLink
= _NextUpdatedLink
->_NextTriggeredLink
[1 - _CurrentTriggeredLinkList
];
193 // unlink observer from both update lists
194 unlinkFromTriggerList(0);
195 unlinkFromTriggerList(1);
197 removeObservers(_ObservedNodes
);
198 // removes from the link list
199 _LinkList
.erase(_ListEntry
);
203 delete _AHCondParsed
;
204 _AHCondParsed
= NULL
;
207 //===========================================================
208 bool CInterfaceLink::init(const std::vector
<CTargetInfo
> &targets
, const std::vector
<CCDBTargetInfo
> &cdbTargets
, const std::string
&expr
, const std::string
&actionHandler
, const std::string
&ahParams
, const std::string
&ahCond
, CInterfaceGroup
*parentGroup
)
210 CInterfaceExprValue result
;
211 // Build the parse tree
212 nlassert(!_ParseTree
);
213 _ParseTree
= CInterfaceExpr::buildExprTree(expr
);
216 // wrong expression : release the link
217 nlwarning("CInterfaceLink::init : can't parse expression %s", expr
.c_str());
218 _LinksWithNoTarget
.push_back(TLinkSmartPtr(this));
221 // Examine the expression, to see which database nodes it depends on
222 _ParseTree
->getDepends(_ObservedNodes
);
223 // must keep observerd node sorted (for std::set_xx ops)
224 std::sort(_ObservedNodes
.begin(), _ObservedNodes
.end());
225 if (!targets
.empty())
227 // retrieve properties from the element
228 _Targets
.resize(targets
.size());
229 for (uint k
= 0; k
< targets
.size(); ++k
)
231 CInterfaceElement
*elem
= targets
[k
].Elem
;
234 nlwarning("<CInterfaceLink::init> : Element %d is NULL", k
);
235 _Targets
[k
]._InterfaceElement
= NULL
;
238 _Targets
[k
]._Property
= elem
->getReflectedProperty(targets
[k
].PropertyName
);
239 if (!_Targets
[k
]._Property
)
241 nlwarning("<CInterfaceLink::init> : Can't retrieve property %s for element %d.", targets
[k
].PropertyName
.c_str(), k
);
242 _Targets
[k
]._InterfaceElement
= NULL
;
245 _Targets
[k
]._InterfaceElement
= elem
;
251 // There are no target for this link, so, put in a dedicated list to ensure that the link will be destroyed at exit
252 _LinksWithNoTarget
.push_back(TLinkSmartPtr(this));
254 _CDBTargets
= cdbTargets
;
257 createObservers(_ObservedNodes
);
260 _ActionHandler
= actionHandler
;
261 _AHParams
= ahParams
;
262 nlassert(!_AHCondParsed
);
266 _AHCondParsed
= CInterfaceExpr::buildExprTree(ahCond
);
268 _AHParent
= parentGroup
;
272 //===========================================================
273 void CInterfaceLink::uninit()
275 for (uint32 i
= 0; i
< _LinksWithNoTarget
.size(); i
++)
277 CInterfaceLink
*pLink
= _LinksWithNoTarget
[i
];
280 _LinksWithNoTarget
.erase(_LinksWithNoTarget
.begin()+i
);
286 //===========================================================
287 void CInterfaceLink::update(ICDBNode
* /* node */)
289 // mark link as triggered
290 linkInTriggerList(_CurrentTriggeredLinkList
);
293 //===========================================================
294 void CInterfaceLink::createObservers(const TNodeVect
&nodes
)
296 for(std::vector
<ICDBNode
*>::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
)
300 ICDBNode::CTextId textId
;
301 (*it
)->addObserver(this, textId
);
305 CCDBNodeBranch
*br
= static_cast<CCDBNodeBranch
*>(*it
);
306 NLGUI::CDBManager::getInstance()->addBranchObserver( br
, this );
311 //===========================================================
312 void CInterfaceLink::removeObservers(const TNodeVect
&nodes
)
314 for(std::vector
<ICDBNode
*>::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
)
318 ICDBNode::CTextId textId
;
319 (*it
)->removeObserver(this, textId
);
323 CCDBNodeBranch
*br
= static_cast<CCDBNodeBranch
*>(*it
);
324 NLGUI::CDBManager::getInstance()->removeBranchObserver( br
, this );
329 //===========================================================
330 void CInterfaceLink::updateAllLinks()
332 nlassert(!_UpdateAllLinks
);
333 _UpdateAllLinks
= true;
334 for(TLinkList::iterator it
= _LinkList
.begin(); it
!= _LinkList
.end(); ++it
)
336 CInterfaceLink
*pLink
= *it
;
339 _UpdateAllLinks
= false;
342 //===========================================================
343 void CInterfaceLink::update()
345 //_On = false; // prvent recursive calls
346 if (!_ParseTree
) return;
347 static TNodeVect observedNodes
; // should not be too big, so ok to keep it static.
348 static TNodeVect tmpNodes
;
349 observedNodes
.clear();
350 CInterfaceExprValue result
;
351 _ParseTree
->evalWithDepends(result
, observedNodes
);
352 // std::set_xxx require ordered container
353 std::sort(observedNodes
.begin(), observedNodes
.end());
354 // we assume that _ObservedNode are also sorted
355 // compute new observers
357 std::set_difference(observedNodes
.begin(), observedNodes
.end(), _ObservedNodes
.begin(), _ObservedNodes
.end(), std::back_inserter(tmpNodes
));
358 // add these observers
359 createObservers(tmpNodes
);
360 // compute lost observers
362 std::set_difference(_ObservedNodes
.begin(), _ObservedNodes
.end(), observedNodes
.begin(), observedNodes
.end(), std::back_inserter(tmpNodes
));
363 // remove these observers
364 removeObservers(tmpNodes
);
365 // subsitute new (sorted) list to current
366 _ObservedNodes
.swap(observedNodes
);
368 for (uint k
= 0; k
< _Targets
.size(); ++k
)
370 CInterfaceElement
*elem
= _Targets
[k
]._InterfaceElement
;
371 const CReflectedProperty
*prop
= _Targets
[k
]._Property
;
374 if (!affect(result
, *elem
, *prop
))
376 nlwarning("CInterfaceLink::update: Result conversion failed");
380 if (_CDBTargets
.size())
382 CInterfaceExprValue resultCopy
= result
;
383 if (resultCopy
.toInteger())
385 sint64 resultValue
= resultCopy
.getInteger();
386 for (uint k
= 0; k
< _CDBTargets
.size(); ++k
)
388 NLMISC::CCDBNodeLeaf
*node
= _CDBTargets
[k
].Leaf
;
391 node
= _CDBTargets
[k
].Leaf
= NLGUI::CDBManager::getInstance()->getDbProp(_CDBTargets
[k
].LeafName
, false);
395 // assuming setvalue64 always works
396 node
->setValue64(resultValue
);
400 nlwarning("CInterfaceLink::update: Node does not exist: '%s'", _CDBTargets
[k
].LeafName
.c_str());
406 nlwarning("CInterfaceLink::update: Result conversion to db target failed");
409 // if there's an action handler, execute it
410 if (!_ActionHandler
.empty())
412 // If there is a condition, test it.
413 bool launch
= _AHCond
.empty();
414 if (_AHCondParsed
) // todo: maybe makes more sense to make condition also cover target
416 CInterfaceExprValue result
;
417 _AHCondParsed
->eval(result
);
418 launch
= result
.getBool();
422 CAHManager::getInstance()->runActionHandler(_ActionHandler
, _AHParent
, _AHParams
);
423 // do not add any code after this line because this can be deleted !!!!
427 //_On = true; // prevent recursive calls
430 // predicate to remove an element from a list
431 struct CRemoveTargetPred
433 CInterfaceElement
*Target
;
434 bool operator()(const CInterfaceLink::CTarget
&target
) const { return target
._InterfaceElement
== Target
; }
437 //===========================================================
438 void CInterfaceLink::removeTarget(CInterfaceElement
*elem
)
440 CRemoveTargetPred pred
;
442 _Targets
.erase(std::remove_if(_Targets
.begin(), _Targets
.end(), pred
), _Targets
.end());
445 //===========================================================
446 bool CInterfaceLink::CTargetInfo::affect(const CInterfaceExprValue
&value
)
451 nlwarning("<CInterfaceLink::CTargetInfo::affect> : Target element is NULL");
455 const CReflectedProperty
*property
= Elem
->getReflectedProperty(PropertyName
);
458 nlwarning("<CInterfaceLink::CTargetInfo::affect> : Can't retrieve property %s for element %s.", PropertyName
.c_str(), Elem
->getId().c_str());
462 // try to affect the property
463 if (!NLGUI::affect(value
, *Elem
, *property
))
465 nlwarning("<CInterfaceLink::CTargetInfo::affect> Couldn't convert the value to affect to %s", Elem
->getId().c_str());
473 //===========================================================
474 void CInterfaceLink::removeAllLinks()
476 _LinksWithNoTarget
.clear();
477 TLinkList::iterator curr
= _LinkList
.begin();
478 TLinkList::iterator nextIt
;
480 while(curr
!= _LinkList
.end())
484 CInterfaceLink
&link
= **curr
;
486 NLMISC::CSmartPtr
<CInterfaceLink
> linkPtr
= *curr
; // must keep a smart ptr because of the test in the loop
487 for (k
= 0; k
< link
._Targets
.size(); ++k
)
489 if (link
._Targets
[k
]._InterfaceElement
)
491 nlinfo("InterfaceElement %s was not remove.", link
._Targets
[k
]._InterfaceElement
->getId().c_str());
492 link
._Targets
[k
]._InterfaceElement
->removeLink(&link
);
495 linkPtr
= NULL
; // effectively destroy link
498 nlassert(_LinkList
.empty());
501 // ***************************************************************************
502 bool CInterfaceLink::splitLinkTarget(const std::string
&target
, CInterfaceGroup
*parentGroup
, std::string
&propertyName
, CInterfaceElement
*&targetElm
)
504 // the last token of the target gives the name of the property
505 std::string::size_type lastPos
= target
.find_last_of(':');
506 if (lastPos
== (target
.length() - 1))
508 // todo hulud interface syntax error
509 nlwarning("The target should at least contains a path and a property as follow 'path:property'");
514 CInterfaceElement
*elm
= NULL
;
517 if (lastPos
== std::string::npos
)
525 elmProp
= target
.substr(lastPos
+ 1);
526 elmPath
= parentGroup
->getId() + ":" + target
.substr(0, lastPos
);
527 elm
= parentGroup
->getElement(elmPath
);
532 // try the absolute adress of the element
533 elmPath
= target
.substr(0, lastPos
);
534 elm
= CWidgetManager::getInstance()->getElementFromId(elmPath
);
535 elmProp
= target
.substr(lastPos
+ 1);
540 // todo hulud interface syntax error
541 nlwarning("<splitLinkTarget> can't find target link %s", elmPath
.c_str());
545 propertyName
= elmProp
;
550 // ***************************************************************************
551 bool CInterfaceLink::splitLinkTargets(const std::string
&targets
, CInterfaceGroup
*parentGroup
,std::vector
<CInterfaceLink::CTargetInfo
> &targetsVect
)
553 std::vector
<std::string
> targetNames
;
554 NLMISC::splitString(targets
, ",", targetNames
);
556 targetsVect
.reserve(targetNames
.size());
557 bool everythingOk
= true;
558 for (uint k
= 0; k
< targetNames
.size(); ++k
)
560 CInterfaceLink::CTargetInfo ti
;
561 std::string::size_type startPos
= targetNames
[k
].find_first_not_of(" ");
562 if(startPos
== std::string::npos
)
564 // todo hulud interface syntax error
565 nlwarning("<splitLinkTargets> empty target encountered");
568 std::string::size_type lastPos
= targetNames
[k
].find_last_not_of(" ");
569 if (startPos
>= lastPos
)
571 nlwarning("<splitLinkTargets> empty target encountered");
575 if (!splitLinkTarget(targetNames
[k
].substr(startPos
, lastPos
- startPos
+1), parentGroup
, ti
.PropertyName
, ti
.Elem
))
577 // todo hulud interface syntax error
578 nlwarning("<splitLinkTargets> Can't get link target");
579 everythingOk
= false;
582 targetsVect
.push_back(ti
);
588 // ***************************************************************************
589 bool CInterfaceLink::splitLinkTargetsExt(const std::string
&targets
, CInterfaceGroup
*parentGroup
,std::vector
<CInterfaceLink::CTargetInfo
> &targetsVect
, std::vector
<CInterfaceLink::CCDBTargetInfo
> &cdbTargetsVect
)
591 std::vector
<std::string
> targetNames
;
592 NLMISC::splitString(targets
, ",", targetNames
);
594 targetsVect
.reserve(targetNames
.size());
595 cdbTargetsVect
.clear(); // no reserve because less used
596 bool everythingOk
= true;
597 for (uint k
= 0; k
< targetNames
.size(); ++k
)
599 std::string::size_type startPos
= targetNames
[k
].find_first_not_of(" ");
600 if(startPos
== std::string::npos
)
602 // todo hulud interface syntax error
603 nlwarning("<splitLinkTargets> empty target encountered");
606 std::string::size_type lastPos
= targetNames
[k
].find_last_not_of(" ");
607 if (startPos
>= (lastPos
+1))
609 nlwarning("<splitLinkTargets> empty target encountered");
613 if (targetNames
[k
][startPos
] == '@')
615 CInterfaceLink::CCDBTargetInfo ti
;
616 ti
.LeafName
= targetNames
[k
].substr((startPos
+1), (lastPos
+1) - (startPos
+1));
617 // Do not allow Write on SERVER: or LOCAL:
618 static const std::string dbServer
= "SERVER:";
619 static const std::string dbLocal
= "LOCAL:";
620 static const std::string dbLocalR2
= "LOCAL:R2";
621 if( (0==ti
.LeafName
.compare(0, dbServer
.size(), dbServer
)) ||
622 (0==ti
.LeafName
.compare(0, dbLocal
.size(), dbLocal
))
625 if (0!=ti
.LeafName
.compare(0, dbLocalR2
.size(), dbLocalR2
))
627 //nlwarning("You are not allowed to write on 'SERVER:...' or 'LOCAL:...' database");
632 ti
.Leaf
= NLGUI::CDBManager::getInstance()->getDbProp(ti
.LeafName
, false);
633 cdbTargetsVect
.push_back(ti
);
637 CInterfaceLink::CTargetInfo ti
;
638 if (!splitLinkTarget(targetNames
[k
].substr(startPos
, lastPos
- startPos
+1), parentGroup
, ti
.PropertyName
, ti
.Elem
))
640 // todo hulud interface syntax error
641 nlwarning("<splitLinkTargets> Can't get link target");
642 everythingOk
= false;
645 targetsVect
.push_back(ti
);
652 //===========================================================
653 void CInterfaceLink::checkNbRefs()
655 uint numValidPtr
= 0;
656 for(uint k
= 0; k
< _Targets
.size(); ++k
)
658 if (_Targets
[k
]._InterfaceElement
!= NULL
) ++numValidPtr
;
660 nlassert(numValidPtr
== (uint
) crefs
);
664 //===========================================================
665 void CInterfaceLink::setTargetProperty (const std::string
&Target
, const CInterfaceExprValue
&val
)
668 string elt
= Target
.substr(0,Target
.rfind(':'));
669 CInterfaceElement
*pIE
= CWidgetManager::getInstance()->getElementFromId(elt
);
670 CInterfaceGroup
*pIG
= dynamic_cast<CInterfaceGroup
*>(pIE
);
673 pIG
= pIE
->getParent();
677 std::vector
<CTargetInfo
> vTargets
;
678 splitLinkTargets(Target
, pIG
, vTargets
);
679 if (!vTargets
.empty() && (vTargets
[0].Elem
))
681 vTargets
[0].affect(val
);
686 //-----------------------------------------------
687 void CInterfaceLink::linkInTriggerList(uint list
)
690 if (_Triggered
[list
]) return; // already inserted in list
691 _Triggered
[list
] = true;
692 nlassert(!_PrevTriggeredLink
[list
]);
693 nlassert(!_NextTriggeredLink
[list
]);
694 if (!_FirstTriggeredLink
[list
])
696 _FirstTriggeredLink
[list
] = _LastTriggeredLink
[list
] = this;
700 nlassert(!_LastTriggeredLink
[list
]->_NextTriggeredLink
[list
]);
701 _LastTriggeredLink
[list
]->_NextTriggeredLink
[list
] = this;
702 _PrevTriggeredLink
[list
] = _LastTriggeredLink
[list
];
703 _LastTriggeredLink
[list
] = this;
707 //-----------------------------------------------
708 void CInterfaceLink::unlinkFromTriggerList(uint list
)
711 if (!_Triggered
[list
])
713 // not linked in this list
714 nlassert(_PrevTriggeredLink
[list
] == NULL
);
715 nlassert(_NextTriggeredLink
[list
] == NULL
);
718 if (_PrevTriggeredLink
[list
])
720 _PrevTriggeredLink
[list
]->_NextTriggeredLink
[list
] = _NextTriggeredLink
[list
];
724 // this was the first node
725 _FirstTriggeredLink
[list
] = _NextTriggeredLink
[list
];
727 if (_NextTriggeredLink
[list
])
729 _NextTriggeredLink
[list
]->_PrevTriggeredLink
[list
] = _PrevTriggeredLink
[list
];
733 // this was the last node
734 _LastTriggeredLink
[list
] = _PrevTriggeredLink
[list
];
736 _PrevTriggeredLink
[list
] = NULL
;
737 _NextTriggeredLink
[list
] = NULL
;
738 _Triggered
[list
] = false;
745 //-----------------------------------------------
746 void CInterfaceLink::updateTrigeredLinks()
748 static bool called
= false;
751 _CurrUpdatedLink
= _FirstTriggeredLink
[_CurrentTriggeredLinkList
];
752 while (_CurrUpdatedLink
)
754 // modified node should now store them in other list when they're modified
755 _CurrentTriggeredLinkList
= 1 - _CurrentTriggeredLinkList
;
756 // switch list so that modified node are stored in the other list
757 while (_CurrUpdatedLink
)
759 _NextUpdatedLink
= _CurrUpdatedLink
->_NextTriggeredLink
[1 - _CurrentTriggeredLinkList
];
760 _CurrUpdatedLink
->update();
761 if (_CurrUpdatedLink
) // this may be modified by the call (if current observer is removed)
763 _CurrUpdatedLink
->unlinkFromTriggerList(1 - _CurrentTriggeredLinkList
);
765 _CurrUpdatedLink
= _NextUpdatedLink
;
767 nlassert(_FirstTriggeredLink
[1 - _CurrentTriggeredLinkList
] == NULL
);
768 nlassert(_LastTriggeredLink
[1 - _CurrentTriggeredLinkList
] == NULL
);
769 // examine other list to see if nodes have been registered
770 _CurrUpdatedLink
= _FirstTriggeredLink
[_CurrentTriggeredLinkList
];