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) 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/>.
22 #include "action_historic.h"
23 #include "property_accessor.h"
24 #include "../object_factory_client.h"
25 #include "../editor.h"
26 #include "nel/gui/lua_ihm.h"
40 //====================================================================================
41 CActionHistoric::CActionHistoric() : _Scenario(NULL
, st_edit
)
43 _NewActionIsPending
= false;
44 _CurrActionIndex
= -1;
49 //====================================================================================
50 CActionHistoric::~CActionHistoric()
54 //====================================================================================
55 void CActionHistoric::newSingleAction(const ucstring
&name
)
57 //H_AUTO(R2_CActionHistoric_newSingleAction)
59 if (_SubActionCount
== 0)
62 _NewActionIsPending
= false;
64 if (!_NewActionIsPending
)
66 _NewActionName
= name
;
70 //====================================================================================
71 void CActionHistoric::cancelAction()
73 //H_AUTO(R2_CActionHistoric_cancelAction)
74 if (_NewAction
&& _NewActionIsPending
)
76 _NewAction
->rollback(_DMC
, _Scenario
);
79 _NewActionIsPending
= false;
81 _NewActionName
.clear();
82 getEditor().callEnvMethod("onCancelActionInHistoric", 0, 0);
85 //====================================================================================
86 void CActionHistoric::newMultiAction(const ucstring
&name
, uint actionCount
)
88 //H_AUTO(R2_CActionHistoric_newMultiAction)
89 _SubActionCount
= 1; // force previous multi action to finish
91 _NewActionIsPending
= false;
92 _SubActionCount
= actionCount
;
93 _NewActionName
= name
;
96 //====================================================================================
97 void CActionHistoric::flushPendingAction()
99 //H_AUTO(R2_CActionHistoric_flushPendingAction)
100 if (_NewAction
&& _NewActionIsPending
)
102 _NewAction
->flush(_DMC
, _Scenario
);
106 //====================================================================================
107 void CActionHistoric::newPendingMultiAction(const ucstring
&name
, uint actionCount
)
109 //H_AUTO(R2_CActionHistoric_newPendingMultiAction)
110 _SubActionCount
= 1; // force previous multi action to finish
112 _NewActionIsPending
= true;
113 _SubActionCount
= actionCount
;
114 _NewActionName
= name
;
115 getEditor().callEnvMethod("onPendingActionBegin", 0, 0);
118 //====================================================================================
119 void CActionHistoric::newPendingAction(const ucstring
&name
)
121 //H_AUTO(R2_CActionHistoric_newPendingAction)
123 _NewActionIsPending
= true;
124 _NewActionName
= name
;
125 getEditor().callEnvMethod("onPendingActionBegin", 0, 0);
128 //====================================================================================
129 void CActionHistoric::endAction()
131 //H_AUTO(R2_CActionHistoric_endAction)
132 // if current action is void, no-op
138 if (_SubActionCount
== 0)
143 if (_SubActionCount
> 0)
145 return; // several actions expected before the merge
148 // Push the new action -> discard all actions beyond _CurrActionIndex
149 uint newSize
= (uint
) std::max((sint
) _Actions
.getSize() + _CurrActionIndex
, 0);
150 while (_Actions
.getSize() > newSize
)
154 _CurrActionIndex
= -1;
155 _Actions
.push(_NewAction
);
156 if (!_NewActionIsPending
)
158 _NewAction
->flush(_DMC
, _Scenario
); // redo may trigger other observers that complete the action
160 _NewAction
->setCompleted();
161 _CurrActionIndex
= -1;
163 _NewActionIsPending
= false;
164 // warn lua that a new action has been added
165 if (isUndoSupported())
167 getEditor().callEnvMethod("onNewActionAddedInHistoric", 0, 0);
171 //====================================================================================
172 void CActionHistoric::forceEndMultiAction()
174 //H_AUTO(R2_CActionHistoric_forceEndMultiAction)
175 if (_NewActionIsPending
&& _NewAction
)
177 _NewAction
->flush(_DMC
, _Scenario
);
179 if (_SubActionCount
> 1) _SubActionCount
= 1;
183 //====================================================================================
184 void CActionHistoric::setMaxNumActions(uint count
)
186 //H_AUTO(R2_CActionHistoric_setMaxNumActions)
187 nlassert(count
>= 1);
188 _Actions
.setMaxSize(count
);
191 //====================================================================================
192 void CActionHistoric::clear(CObject
*newScenario
)
194 //H_AUTO(R2_CActionHistoric_clear)
195 if (_NewActionIsPending
)
197 nlwarning("Historic was cleared while a pending action!!");
200 _CurrActionIndex
= -1;
202 _NewActionIsPending
= false;
203 _Scenario
.setHighLevel(newScenario
? CRequestBase::cloneObject(newScenario
) : NULL
);
204 // warn lua that all actions have been cleared
205 getEditor().callEnvMethod("onClearActionHistoric", 0, 0);
209 //====================================================================================
210 sint
CActionHistoric::getPreviousActionIndex() const
212 //H_AUTO(R2_CActionHistoric_getPreviousActionIndex)
213 sint index
= (sint
) _Actions
.getSize() + _CurrActionIndex
;
214 return index
>= 0 ? index
: -1;
217 //====================================================================================
218 sint
CActionHistoric::getNextActionIndex() const
220 //H_AUTO(R2_CActionHistoric_getNextActionIndex)
221 if (_Actions
.empty()) return -1;
222 sint index
= (sint
) _Actions
.getSize() + (_CurrActionIndex
+ 1);
223 return index
>= 0 && index
< (sint
) _Actions
.getSize() ? index
: -1;
226 //====================================================================================
227 bool CActionHistoric::canUndo() const
229 //H_AUTO(R2_CActionHistoric_canUndo)
230 if (_NewActionIsPending
) return false;
231 return getPreviousActionIndex() != -1;
234 //====================================================================================
235 bool CActionHistoric::canRedo() const
237 //H_AUTO(R2_CActionHistoric_canRedo)
238 if (_NewActionIsPending
) return false;
239 return getNextActionIndex() != -1;
242 //====================================================================================
243 bool CActionHistoric::undo()
245 //H_AUTO(R2_CActionHistoric_undo)
246 nlassert(!_NewActionIsPending
);
247 sint index
= getPreviousActionIndex();
248 if (index
== -1) return false;
249 _Actions
[index
]->undo(_DMC
, _Scenario
);
254 //====================================================================================
255 bool CActionHistoric::redo()
257 //H_AUTO(R2_CActionHistoric_redo)
258 nlassert(!_NewActionIsPending
);
259 sint index
= getNextActionIndex();
260 if (index
== -1) return false;
261 _Actions
[index
]->redo(_DMC
, _Scenario
);
266 //====================================================================================
267 const ucstring
*CActionHistoric::getPreviousActionName() const
269 //H_AUTO(R2_CActionHistoric_getPreviousActionName)
270 sint index
= getPreviousActionIndex();
271 return index
!= -1 ? &_Actions
[index
]->getName() : NULL
;
274 //====================================================================================
275 const ucstring
*CActionHistoric::getNextActionName() const
277 //H_AUTO(R2_CActionHistoric_getNextActionName)
278 sint index
= getNextActionIndex();
279 return index
!= -1 ? &_Actions
[index
]->getName() : NULL
;
282 //====================================================================================
283 void CActionHistoric::requestInsertNode(const std::string
& instanceId
, const std::string
& name
,sint32 position
, const std::string
& key
, CObject
* value
)
285 //H_AUTO(R2_CActionHistoric_requestInsertNode)
286 if (_Scenario
.getHighLevel())
288 CObject
*target
= _DMC
->find(instanceId
, name
);
289 if (value
->getGhost() || (target
&& target
->getGhost()))
291 // direct effect, assumed to be local display only
292 getEditor().getDMC().nodeInserted(instanceId
, name
, position
, key
, value
->clone());
296 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
297 CRequestBase::TSmartPtr req
= new CRequestInsertNode(instanceId
, name
, position
, key
, value
);
298 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
299 _NewAction
->pushRequest(req
);
302 //====================================================================================
303 void CActionHistoric::requestSetNode(const std::string
& instanceId
,const std::string
& attrName
, CObject
* value
)
305 //H_AUTO(R2_CActionHistoric_requestSetNode)
306 if (_Scenario
.getHighLevel())
308 CObject
*clObj
= _DMC
->find(instanceId
, attrName
);
309 if (clObj
&& clObj
->getGhost())
311 value
->setGhost(true);
313 if (value
->getGhost())
315 // direct effect, assumed to be local display only
316 getEditor().getDMC().nodeSet(instanceId
, attrName
, value
->clone());
320 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
321 CRequestBase::TSmartPtr req
= new CRequestSetNode(instanceId
, attrName
, value
);
322 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
323 _NewAction
->pushRequest(req
);
326 //====================================================================================
327 void CActionHistoric::requestEraseNode(const std::string
& instanceId
, const std::string
& attrName
, sint32 position
)
329 //H_AUTO(R2_CActionHistoric_requestEraseNode)
330 if (_Scenario
.getHighLevel())
332 CObject
*obj
= _DMC
->find(instanceId
, attrName
, position
);
333 if (obj
&& obj
->getGhost())
335 // direct effect, assumed to be local display only
336 getEditor().getDMC().nodeErased(instanceId
, attrName
, position
);
340 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
341 CRequestBase::TSmartPtr req
= new CRequestEraseNode(instanceId
, attrName
, position
);
342 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
343 _NewAction
->pushRequest(req
);
346 //====================================================================================
347 void CActionHistoric::requestMoveNode(const std::string
& instanceId
, const std::string
& attrName
, sint32 position
, const std::string
& destInstanceId
, const std::string
& destAttrName
, sint32 destPosition
)
349 //H_AUTO(R2_CActionHistoric_requestMoveNode)
350 if (_Scenario
.getHighLevel())
352 CObject
*src
= _DMC
->find(instanceId
, attrName
, position
);
353 CObject
*dest
= _DMC
->find(destInstanceId
, destAttrName
);
356 nlassert(src
->getGhost() == dest
->getGhost());
359 // direct effect, assumed to be local display only
360 getEditor().getDMC().nodeMoved(instanceId
, attrName
, position
, destInstanceId
, destAttrName
, destPosition
);
365 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
366 CRequestBase::TSmartPtr req
= new CRequestMoveNode(instanceId
, attrName
, position
, destInstanceId
, destAttrName
, destPosition
);
367 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
368 _NewAction
->pushRequest(req
);
376 //====================================================================================
377 CActionHistoric::CAction::CAction(const ucstring
&name
) : _Name(name
), _FlushedCount(0), _Completed(false), _Flushing(false)
381 //====================================================================================
382 void CActionHistoric::CAction::pushRequest(CRequestBase
*req
)
384 //H_AUTO(R2_CAction_pushRequest)
386 nlassert(!_Completed
);
387 _Requests
.push_back(req
);
391 //====================================================================================
392 CActionHistoric::CAction::~CAction()
396 //====================================================================================
397 void CActionHistoric::CAction::flush(IDynamicMapClient
*dmc
, CScenario
&scenario
)
399 //H_AUTO(R2_CAction_flush)
400 nlwarning("Flushing action at 0x%p", this);
401 if (_Flushing
) return;
403 nlassert(_FlushedCount
<= _Requests
.size());
404 nlassert(!_Completed
);
405 while (_FlushedCount
!= _Requests
.size())
407 _Requests
[_FlushedCount
]->redo(dmc
, scenario
);
409 nlassert(_FlushedCount
<= _Requests
.size());
411 nlassert(_FlushedCount
== _Requests
.size());
415 //====================================================================================
416 void CActionHistoric::CAction::rollback(IDynamicMapClient
*dmc
, CScenario
&scenario
)
418 //H_AUTO(R2_CAction_rollback)
419 nlassert(_FlushedCount
<= _Requests
.size());
420 for (sint k
= _FlushedCount
- 1; k
>= 0; --k
)
422 _Requests
[k
]->undo(dmc
, scenario
);
427 //====================================================================================
428 void CActionHistoric::CAction::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
430 //H_AUTO(R2_CAction_redo)
431 nlassert(_FlushedCount
<= _Requests
.size());
432 nlassert(_Completed
);
434 for (uint k
= 0; k
< _Requests
.size(); ++k
)
436 _Requests
[k
]->redo(dmc
, scenario
);
440 //====================================================================================
441 void CActionHistoric::CAction::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
443 //H_AUTO(R2_CAction_undo)
444 nlassert(_FlushedCount
<= _Requests
.size());
445 nlassert(_Completed
);
447 for (sint k
= (sint
)_Requests
.size() - 1; k
>= 0; --k
)
449 _Requests
[k
]->undo(dmc
, scenario
);
457 //====================================================================================
458 CObject
*CActionHistoric::CRequestBase::cloneObject(const CObject
*src
)
460 //H_AUTO(R2_CRequestBase_cloneObject)
461 CObject
*result
= src
->clone();
462 struct CDisableRefIDs
: public IObjectVisitor
464 virtual void visit(CObjectRefId
&obj
)
466 CObjectRefIdClient
*refId
= NLMISC::safe_cast
<CObjectRefIdClient
*>(&obj
);
467 refId
->enable(false); // disable events
470 CDisableRefIDs disableRefIDs
;
471 result
->visit(disableRefIDs
);
475 //====================================================================================
476 CActionHistoric::CRequestSetNode::CRequestSetNode(const std::string
&instanceId
,
477 const std::string
& attrName
,
481 _InstanceId
= instanceId
;
482 _AttrName
= attrName
;
483 _NewValue
= cloneObject(value
);
486 void CActionHistoric::CRequestSetNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
488 //H_AUTO(R2_CRequestSetNode_redo)
490 if (scenario
.getHighLevel()) // undo allowed ?
492 CObject
*old
= scenario
.find(_InstanceId
, _AttrName
);
493 if (old
) // may be not found if defined in the base and not redefined yet
495 // maybe a shadowed property ?
496 /*CObject *shadow = dmc->getPropertyAccessor().getShadowingValue(old);
497 if (shadow) old = shadow; // use the shadow instead*/
498 _OldValue
= cloneObject(old
);
499 nlwarning("**** backupped value for undo request set node");
502 // modify local version
503 scenario
.setNode(_InstanceId
, _AttrName
, _NewValue
);
506 dmc
->doRequestSetNode(_InstanceId
, _AttrName
, _NewValue
);
509 void CActionHistoric::CRequestSetNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
511 //H_AUTO(R2_CRequestSetNode_undo)
512 nlassert(scenario
.getHighLevel()); // if this assert fires, then 'clear' was called with a NULL pointer ! The action historic need a start scenario to have undo capability
515 // was a value from the base ? just erase ...
517 // modify local version
518 scenario
.eraseNode(_InstanceId
, _AttrName
, -1);
520 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, -1);
524 // modify local version
525 scenario
.setNode(_InstanceId
, _AttrName
, _OldValue
);
527 // TODO nico : handle case where same value than the base is restored here
528 dmc
->doRequestSetNode(_InstanceId
, _AttrName
, _OldValue
);
533 //====================================================================================
534 CActionHistoric::CRequestEraseNode::CRequestEraseNode(const std::string
& instanceId
, const std::string
& attrName
, sint32 position
)
536 //H_AUTO(R2_CRequestEraseNode_CRequestEraseNode)
537 _InstanceId
= instanceId
;
538 _AttrName
= attrName
;
539 _Position
= position
;
542 void CActionHistoric::CRequestEraseNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
544 //H_AUTO(R2_CRequestEraseNode_redo)
547 if (scenario
.getHighLevel()) // undo allowed ?
549 CObject
*old
= scenario
.find(_InstanceId
, _AttrName
, _Position
);
552 nlwarning("Can't redo erase request!!");
553 nlassert(0); // TMP TMP
556 if (!old
->getNameInParent(_ParentInstanceId
, _AttrNameInParent
, _PositionInParent
))
558 nlwarning("Can't retrieve name in parent, requestEraseNode undo will fail!");
560 _OldValue
= cloneObject(old
);
562 // modify local version
563 scenario
.eraseNode(_InstanceId
, _AttrName
, _Position
);
566 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, _Position
);
569 void CActionHistoric::CRequestEraseNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
571 //H_AUTO(R2_CRequestEraseNode_undo)
572 nlassert(scenario
.getHighLevel()); // if this assert fires, then 'clear' was called with a NULL pointer ! The action historic need a start scenario to have undo capability
576 nlwarning("Can't undo erase node, previous node not saved");
579 if (_ParentInstanceId
.empty())
581 nlwarning("Can't undo erase node, parent name not known");
584 // modify local version
585 scenario
.insertNode(_ParentInstanceId
, _AttrNameInParent
, _PositionInParent
, "", cloneObject(_OldValue
));
587 dmc
->doRequestInsertNode(_ParentInstanceId
, _AttrNameInParent
, _PositionInParent
, "", _OldValue
);
590 #ifdef RYZOM_LUA_UCSTRING
591 CLuaIHM::push(getEditor().getLua(), ucstring::makeFromUtf8(_InstanceId
));
593 getEditor().getLua().push(_InstanceId
);
595 getEditor().callEnvMethod("setUndoRedoInstances", 1, 0);
597 //====================================================================================
598 CActionHistoric::CRequestInsertNode::CRequestInsertNode(const std::string
& instanceId
,
599 const std::string
&attrName
,
601 const std::string
& key
,
604 _InstanceId
= instanceId
;
605 _AttrName
= attrName
;
606 _Position
= position
;
608 _Value
= cloneObject(value
);
611 void CActionHistoric::CRequestInsertNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
613 //H_AUTO(R2_CRequestInsertNode_redo)
615 if (scenario
.getHighLevel())
617 // modify local version
618 scenario
.insertNode(_InstanceId
, _AttrName
, _Position
, _Key
, cloneObject(_Value
));
621 dmc
->doRequestInsertNode(_InstanceId
, _AttrName
, _Position
, _Key
, _Value
);
623 CObject
* nodeId
= _Value
->findAttr("InstanceId");
624 #ifdef RYZOM_LUA_UCSTRING
625 CLuaIHM::push(getEditor().getLua(), ucstring::makeFromUtf8(nodeId
->toString()));
627 getEditor().getLua().push(nodeId
->toString());
629 getEditor().callEnvMethod("setUndoRedoInstances", 1, 0);
632 void CActionHistoric::CRequestInsertNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
634 //H_AUTO(R2_CRequestInsertNode_undo)
635 nlassert(scenario
.getHighLevel()); // if this assert fires, then 'clear' was called with a NULL pointer ! The action historic need a start scenario to have undo capability
637 // if there's a key here, not good for us :(
638 // so try to find another shorter name for this object...
641 std::string instanceId
;
642 std::string attrName
;
644 CObject
*currObj
= scenario
.find(_InstanceId
, _AttrName
, _Position
, _Key
);
645 if (!currObj
) return;
646 if (currObj
->getShortestName(instanceId
, attrName
, position
))
648 // modify local version
649 scenario
.eraseNode(instanceId
, attrName
, position
);
651 dmc
->doRequestEraseNode(instanceId
, attrName
, position
);
653 #ifdef RYZOM_LUA_UCSTRING
654 CLuaIHM::push(getEditor().getLua(), ucstring::makeFromUtf8(instanceId
));
656 getEditor().getLua().push(instanceId
);
658 getEditor().callEnvMethod("setUndoRedoInstances", 1, 0);
662 nlassert(0); // TMP : can this really happen in practice ?
663 nlwarning("Can't build request insert node reciprocal");
668 // special here : if position is -1, requestEraseNode will erase the
669 // table, not the last element!
670 if (!_AttrName
.empty() && _Position
== -1)
672 CObject
*parentTable
= scenario
.find(_InstanceId
, _AttrName
);
673 if (parentTable
&& parentTable
->isTable())
675 uint index
= parentTable
->getSize() - 1;
676 // modify local version
677 scenario
.eraseNode(_InstanceId
, _AttrName
, index
);
679 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, index
);
684 // modify local version
685 scenario
.eraseNode(_InstanceId
, _AttrName
, _Position
);
687 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, _Position
);
691 //====================================================================================
692 CActionHistoric::CRequestMoveNode::CRequestMoveNode(const std::string
&srcInstanceId
,
693 const std::string
&srcAttrName
,
695 const std::string
&destInstanceId
,
696 const std::string
&destAttrName
,
699 _SrcInstanceId
= srcInstanceId
;
700 _SrcAttrName
= srcAttrName
;
701 _SrcPosition
= srcPosition
;
702 _DestInstanceId
= destInstanceId
;
703 _DestAttrName
= destAttrName
;
704 _DestPosition
= destPosition
;
707 void CActionHistoric::CRequestMoveNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
709 //H_AUTO(R2_CRequestMoveNode_redo)
711 if (scenario
.getHighLevel())
713 CObject
*src
= scenario
.find(_SrcInstanceId
, _SrcAttrName
, _SrcPosition
);
716 nlwarning("Can't find source when moving node");
719 if (!src
->getNameInParent(_SrcInstanceIdInParent
, _SrcAttrNameInParent
, _SrcPositionInParent
))
721 nlwarning("Can't find name of source object in its parent");
724 // modify local version
725 scenario
.moveNode(_SrcInstanceId
,
731 if (!src
->getShortestName(_DestInstanceIdAfterMove
, _DestAttrNameAfterMove
, _DestPositionAfterMove
))
733 nlwarning("Can't retrieve name of instance after move, undo will fail");
737 dmc
->doRequestMoveNode(_SrcInstanceId
,
745 void CActionHistoric::CRequestMoveNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
747 //H_AUTO(R2_CRequestMoveNode_undo)
748 nlassert(scenario
.getHighLevel()); // if this assert fires, then 'clear' was called with a NULL pointer ! The action historic need a start scenario to have undo capability
750 // modify local version
751 scenario
.moveNode(_DestInstanceIdAfterMove
,
752 _DestAttrNameAfterMove
,
753 _DestPositionAfterMove
,
754 _SrcInstanceIdInParent
,
755 _SrcAttrNameInParent
,
756 _SrcPositionInParent
);
758 dmc
->doRequestMoveNode(_DestInstanceIdAfterMove
,
759 _DestAttrNameAfterMove
,
760 _DestPositionAfterMove
,
761 _SrcInstanceIdInParent
,
762 _SrcAttrNameInParent
,
763 _SrcPositionInParent
);