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>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "action_historic.h"
22 #include "property_accessor.h"
23 #include "../object_factory_client.h"
24 #include "../editor.h"
25 #include "nel/gui/lua_ihm.h"
39 //====================================================================================
40 CActionHistoric::CActionHistoric() : _Scenario(NULL
, st_edit
)
42 _NewActionIsPending
= false;
43 _CurrActionIndex
= -1;
48 //====================================================================================
49 CActionHistoric::~CActionHistoric()
53 //====================================================================================
54 void CActionHistoric::newSingleAction(const ucstring
&name
)
56 //H_AUTO(R2_CActionHistoric_newSingleAction)
58 if (_SubActionCount
== 0)
61 _NewActionIsPending
= false;
63 if (!_NewActionIsPending
)
65 _NewActionName
= name
;
69 //====================================================================================
70 void CActionHistoric::cancelAction()
72 //H_AUTO(R2_CActionHistoric_cancelAction)
73 if (_NewAction
&& _NewActionIsPending
)
75 _NewAction
->rollback(_DMC
, _Scenario
);
78 _NewActionIsPending
= false;
80 _NewActionName
.clear();
81 getEditor().callEnvMethod("onCancelActionInHistoric", 0, 0);
84 //====================================================================================
85 void CActionHistoric::newMultiAction(const ucstring
&name
, uint actionCount
)
87 //H_AUTO(R2_CActionHistoric_newMultiAction)
88 _SubActionCount
= 1; // force previous multi action to finish
90 _NewActionIsPending
= false;
91 _SubActionCount
= actionCount
;
92 _NewActionName
= name
;
95 //====================================================================================
96 void CActionHistoric::flushPendingAction()
98 //H_AUTO(R2_CActionHistoric_flushPendingAction)
99 if (_NewAction
&& _NewActionIsPending
)
101 _NewAction
->flush(_DMC
, _Scenario
);
105 //====================================================================================
106 void CActionHistoric::newPendingMultiAction(const ucstring
&name
, uint actionCount
)
108 //H_AUTO(R2_CActionHistoric_newPendingMultiAction)
109 _SubActionCount
= 1; // force previous multi action to finish
111 _NewActionIsPending
= true;
112 _SubActionCount
= actionCount
;
113 _NewActionName
= name
;
114 getEditor().callEnvMethod("onPendingActionBegin", 0, 0);
117 //====================================================================================
118 void CActionHistoric::newPendingAction(const ucstring
&name
)
120 //H_AUTO(R2_CActionHistoric_newPendingAction)
122 _NewActionIsPending
= true;
123 _NewActionName
= name
;
124 getEditor().callEnvMethod("onPendingActionBegin", 0, 0);
127 //====================================================================================
128 void CActionHistoric::endAction()
130 //H_AUTO(R2_CActionHistoric_endAction)
131 // if current action is void, no-op
137 if (_SubActionCount
== 0)
142 if (_SubActionCount
> 0)
144 return; // several actions expected before the merge
147 // Push the new action -> discard all actions beyond _CurrActionIndex
148 uint newSize
= (uint
) std::max((sint
) _Actions
.getSize() + _CurrActionIndex
, 0);
149 while (_Actions
.getSize() > newSize
)
153 _CurrActionIndex
= -1;
154 _Actions
.push(_NewAction
);
155 if (!_NewActionIsPending
)
157 _NewAction
->flush(_DMC
, _Scenario
); // redo may trigger other observers that complete the action
159 _NewAction
->setCompleted();
160 _CurrActionIndex
= -1;
162 _NewActionIsPending
= false;
163 // warn lua that a new action has been added
164 if (isUndoSupported())
166 getEditor().callEnvMethod("onNewActionAddedInHistoric", 0, 0);
170 //====================================================================================
171 void CActionHistoric::forceEndMultiAction()
173 //H_AUTO(R2_CActionHistoric_forceEndMultiAction)
174 if (_NewActionIsPending
&& _NewAction
)
176 _NewAction
->flush(_DMC
, _Scenario
);
178 if (_SubActionCount
> 1) _SubActionCount
= 1;
182 //====================================================================================
183 void CActionHistoric::setMaxNumActions(uint count
)
185 //H_AUTO(R2_CActionHistoric_setMaxNumActions)
186 nlassert(count
>= 1);
187 _Actions
.setMaxSize(count
);
190 //====================================================================================
191 void CActionHistoric::clear(CObject
*newScenario
)
193 //H_AUTO(R2_CActionHistoric_clear)
194 if (_NewActionIsPending
)
196 nlwarning("Historic was cleared while a pending action!!");
199 _CurrActionIndex
= -1;
201 _NewActionIsPending
= false;
202 _Scenario
.setHighLevel(newScenario
? CRequestBase::cloneObject(newScenario
) : NULL
);
203 // warn lua that all actions have been cleared
204 getEditor().callEnvMethod("onClearActionHistoric", 0, 0);
208 //====================================================================================
209 sint
CActionHistoric::getPreviousActionIndex() const
211 //H_AUTO(R2_CActionHistoric_getPreviousActionIndex)
212 sint index
= (sint
) _Actions
.getSize() + _CurrActionIndex
;
213 return index
>= 0 ? index
: -1;
216 //====================================================================================
217 sint
CActionHistoric::getNextActionIndex() const
219 //H_AUTO(R2_CActionHistoric_getNextActionIndex)
220 if (_Actions
.empty()) return -1;
221 sint index
= (sint
) _Actions
.getSize() + (_CurrActionIndex
+ 1);
222 return index
>= 0 && index
< (sint
) _Actions
.getSize() ? index
: -1;
225 //====================================================================================
226 bool CActionHistoric::canUndo() const
228 //H_AUTO(R2_CActionHistoric_canUndo)
229 if (_NewActionIsPending
) return false;
230 return getPreviousActionIndex() != -1;
233 //====================================================================================
234 bool CActionHistoric::canRedo() const
236 //H_AUTO(R2_CActionHistoric_canRedo)
237 if (_NewActionIsPending
) return false;
238 return getNextActionIndex() != -1;
241 //====================================================================================
242 bool CActionHistoric::undo()
244 //H_AUTO(R2_CActionHistoric_undo)
245 nlassert(!_NewActionIsPending
);
246 sint index
= getPreviousActionIndex();
247 if (index
== -1) return false;
248 _Actions
[index
]->undo(_DMC
, _Scenario
);
253 //====================================================================================
254 bool CActionHistoric::redo()
256 //H_AUTO(R2_CActionHistoric_redo)
257 nlassert(!_NewActionIsPending
);
258 sint index
= getNextActionIndex();
259 if (index
== -1) return false;
260 _Actions
[index
]->redo(_DMC
, _Scenario
);
265 //====================================================================================
266 const ucstring
*CActionHistoric::getPreviousActionName() const
268 //H_AUTO(R2_CActionHistoric_getPreviousActionName)
269 sint index
= getPreviousActionIndex();
270 return index
!= -1 ? &_Actions
[index
]->getName() : NULL
;
273 //====================================================================================
274 const ucstring
*CActionHistoric::getNextActionName() const
276 //H_AUTO(R2_CActionHistoric_getNextActionName)
277 sint index
= getNextActionIndex();
278 return index
!= -1 ? &_Actions
[index
]->getName() : NULL
;
281 //====================================================================================
282 void CActionHistoric::requestInsertNode(const std::string
& instanceId
, const std::string
& name
,sint32 position
, const std::string
& key
, CObject
* value
)
284 //H_AUTO(R2_CActionHistoric_requestInsertNode)
285 if (_Scenario
.getHighLevel())
287 CObject
*target
= _DMC
->find(instanceId
, name
);
288 if (value
->getGhost() || (target
&& target
->getGhost()))
290 // direct effect, assumed to be local display only
291 getEditor().getDMC().nodeInserted(instanceId
, name
, position
, key
, value
->clone());
295 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
296 CRequestBase::TSmartPtr req
= new CRequestInsertNode(instanceId
, name
, position
, key
, value
);
297 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
298 _NewAction
->pushRequest(req
);
301 //====================================================================================
302 void CActionHistoric::requestSetNode(const std::string
& instanceId
,const std::string
& attrName
, CObject
* value
)
304 //H_AUTO(R2_CActionHistoric_requestSetNode)
305 if (_Scenario
.getHighLevel())
307 CObject
*clObj
= _DMC
->find(instanceId
, attrName
);
308 if (clObj
&& clObj
->getGhost())
310 value
->setGhost(true);
312 if (value
->getGhost())
314 // direct effect, assumed to be local display only
315 getEditor().getDMC().nodeSet(instanceId
, attrName
, value
->clone());
319 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
320 CRequestBase::TSmartPtr req
= new CRequestSetNode(instanceId
, attrName
, value
);
321 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
322 _NewAction
->pushRequest(req
);
325 //====================================================================================
326 void CActionHistoric::requestEraseNode(const std::string
& instanceId
, const std::string
& attrName
, sint32 position
)
328 //H_AUTO(R2_CActionHistoric_requestEraseNode)
329 if (_Scenario
.getHighLevel())
331 CObject
*obj
= _DMC
->find(instanceId
, attrName
, position
);
332 if (obj
&& obj
->getGhost())
334 // direct effect, assumed to be local display only
335 getEditor().getDMC().nodeErased(instanceId
, attrName
, position
);
339 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
340 CRequestBase::TSmartPtr req
= new CRequestEraseNode(instanceId
, attrName
, position
);
341 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
342 _NewAction
->pushRequest(req
);
345 //====================================================================================
346 void CActionHistoric::requestMoveNode(const std::string
& instanceId
, const std::string
& attrName
, sint32 position
, const std::string
& destInstanceId
, const std::string
& destAttrName
, sint32 destPosition
)
348 //H_AUTO(R2_CActionHistoric_requestMoveNode)
349 if (_Scenario
.getHighLevel())
351 CObject
*src
= _DMC
->find(instanceId
, attrName
, position
);
352 CObject
*dest
= _DMC
->find(destInstanceId
, destAttrName
);
355 nlassert(src
->getGhost() == dest
->getGhost());
358 // direct effect, assumed to be local display only
359 getEditor().getDMC().nodeMoved(instanceId
, attrName
, position
, destInstanceId
, destAttrName
, destPosition
);
364 if (!_NewAction
) _NewAction
= new CAction(_NewActionName
);
365 CRequestBase::TSmartPtr req
= new CRequestMoveNode(instanceId
, attrName
, position
, destInstanceId
, destAttrName
, destPosition
);
366 //if (_NewActionIsPending) req->redo(_DMC, _Scenario);
367 _NewAction
->pushRequest(req
);
375 //====================================================================================
376 CActionHistoric::CAction::CAction(const ucstring
&name
) : _Name(name
), _FlushedCount(0), _Completed(false), _Flushing(false)
380 //====================================================================================
381 void CActionHistoric::CAction::pushRequest(CRequestBase
*req
)
383 //H_AUTO(R2_CAction_pushRequest)
385 nlassert(!_Completed
);
386 _Requests
.push_back(req
);
390 //====================================================================================
391 CActionHistoric::CAction::~CAction()
395 //====================================================================================
396 void CActionHistoric::CAction::flush(IDynamicMapClient
*dmc
, CScenario
&scenario
)
398 //H_AUTO(R2_CAction_flush)
399 nlwarning("Flushing action at 0x%p", this);
400 if (_Flushing
) return;
402 nlassert(_FlushedCount
<= _Requests
.size());
403 nlassert(!_Completed
);
404 while (_FlushedCount
!= _Requests
.size())
406 _Requests
[_FlushedCount
]->redo(dmc
, scenario
);
408 nlassert(_FlushedCount
<= _Requests
.size());
410 nlassert(_FlushedCount
== _Requests
.size());
414 //====================================================================================
415 void CActionHistoric::CAction::rollback(IDynamicMapClient
*dmc
, CScenario
&scenario
)
417 //H_AUTO(R2_CAction_rollback)
418 nlassert(_FlushedCount
<= _Requests
.size());
419 for (sint k
= _FlushedCount
- 1; k
>= 0; --k
)
421 _Requests
[k
]->undo(dmc
, scenario
);
426 //====================================================================================
427 void CActionHistoric::CAction::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
429 //H_AUTO(R2_CAction_redo)
430 nlassert(_FlushedCount
<= _Requests
.size());
431 nlassert(_Completed
);
433 for (uint k
= 0; k
< _Requests
.size(); ++k
)
435 _Requests
[k
]->redo(dmc
, scenario
);
439 //====================================================================================
440 void CActionHistoric::CAction::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
442 //H_AUTO(R2_CAction_undo)
443 nlassert(_FlushedCount
<= _Requests
.size());
444 nlassert(_Completed
);
446 for (sint k
= (sint
)_Requests
.size() - 1; k
>= 0; --k
)
448 _Requests
[k
]->undo(dmc
, scenario
);
456 //====================================================================================
457 CObject
*CActionHistoric::CRequestBase::cloneObject(const CObject
*src
)
459 //H_AUTO(R2_CRequestBase_cloneObject)
460 CObject
*result
= src
->clone();
461 struct CDisableRefIDs
: public IObjectVisitor
463 virtual void visit(CObjectRefId
&obj
)
465 CObjectRefIdClient
*refId
= NLMISC::safe_cast
<CObjectRefIdClient
*>(&obj
);
466 refId
->enable(false); // disable events
469 CDisableRefIDs disableRefIDs
;
470 result
->visit(disableRefIDs
);
474 //====================================================================================
475 CActionHistoric::CRequestSetNode::CRequestSetNode(const std::string
&instanceId
,
476 const std::string
& attrName
,
480 _InstanceId
= instanceId
;
481 _AttrName
= attrName
;
482 _NewValue
= cloneObject(value
);
485 void CActionHistoric::CRequestSetNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
487 //H_AUTO(R2_CRequestSetNode_redo)
489 if (scenario
.getHighLevel()) // undo allowed ?
491 CObject
*old
= scenario
.find(_InstanceId
, _AttrName
);
492 if (old
) // may be not found if defined in the base and not redefined yet
494 // maybe a shadowed property ?
495 /*CObject *shadow = dmc->getPropertyAccessor().getShadowingValue(old);
496 if (shadow) old = shadow; // use the shadow instead*/
497 _OldValue
= cloneObject(old
);
498 nlwarning("**** backupped value for undo request set node");
501 // modify local version
502 scenario
.setNode(_InstanceId
, _AttrName
, _NewValue
);
505 dmc
->doRequestSetNode(_InstanceId
, _AttrName
, _NewValue
);
508 void CActionHistoric::CRequestSetNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
510 //H_AUTO(R2_CRequestSetNode_undo)
511 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
514 // was a value from the base ? just erase ...
516 // modify local version
517 scenario
.eraseNode(_InstanceId
, _AttrName
, -1);
519 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, -1);
523 // modify local version
524 scenario
.setNode(_InstanceId
, _AttrName
, _OldValue
);
526 // TODO nico : handle case where same value than the base is restored here
527 dmc
->doRequestSetNode(_InstanceId
, _AttrName
, _OldValue
);
532 //====================================================================================
533 CActionHistoric::CRequestEraseNode::CRequestEraseNode(const std::string
& instanceId
, const std::string
& attrName
, sint32 position
)
535 //H_AUTO(R2_CRequestEraseNode_CRequestEraseNode)
536 _InstanceId
= instanceId
;
537 _AttrName
= attrName
;
538 _Position
= position
;
541 void CActionHistoric::CRequestEraseNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
543 //H_AUTO(R2_CRequestEraseNode_redo)
546 if (scenario
.getHighLevel()) // undo allowed ?
548 CObject
*old
= scenario
.find(_InstanceId
, _AttrName
, _Position
);
551 nlwarning("Can't redo erase request!!");
552 nlassert(0); // TMP TMP
555 if (!old
->getNameInParent(_ParentInstanceId
, _AttrNameInParent
, _PositionInParent
))
557 nlwarning("Can't retrieve name in parent, requestEraseNode undo will fail!");
559 _OldValue
= cloneObject(old
);
561 // modify local version
562 scenario
.eraseNode(_InstanceId
, _AttrName
, _Position
);
565 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, _Position
);
568 void CActionHistoric::CRequestEraseNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
570 //H_AUTO(R2_CRequestEraseNode_undo)
571 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
575 nlwarning("Can't undo erase node, previous node not saved");
578 if (_ParentInstanceId
.empty())
580 nlwarning("Can't undo erase node, parent name not known");
583 // modify local version
584 scenario
.insertNode(_ParentInstanceId
, _AttrNameInParent
, _PositionInParent
, "", cloneObject(_OldValue
));
586 dmc
->doRequestInsertNode(_ParentInstanceId
, _AttrNameInParent
, _PositionInParent
, "", _OldValue
);
589 #ifdef RYZOM_LUA_UCSTRING
590 CLuaIHM::push(getEditor().getLua(), ucstring::makeFromUtf8(_InstanceId
));
592 getEditor().getLua().push(_InstanceId
);
594 getEditor().callEnvMethod("setUndoRedoInstances", 1, 0);
596 //====================================================================================
597 CActionHistoric::CRequestInsertNode::CRequestInsertNode(const std::string
& instanceId
,
598 const std::string
&attrName
,
600 const std::string
& key
,
603 _InstanceId
= instanceId
;
604 _AttrName
= attrName
;
605 _Position
= position
;
607 _Value
= cloneObject(value
);
610 void CActionHistoric::CRequestInsertNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
612 //H_AUTO(R2_CRequestInsertNode_redo)
614 if (scenario
.getHighLevel())
616 // modify local version
617 scenario
.insertNode(_InstanceId
, _AttrName
, _Position
, _Key
, cloneObject(_Value
));
620 dmc
->doRequestInsertNode(_InstanceId
, _AttrName
, _Position
, _Key
, _Value
);
622 CObject
* nodeId
= _Value
->findAttr("InstanceId");
623 #ifdef RYZOM_LUA_UCSTRING
624 CLuaIHM::push(getEditor().getLua(), ucstring::makeFromUtf8(nodeId
->toString()));
626 getEditor().getLua().push(nodeId
->toString());
628 getEditor().callEnvMethod("setUndoRedoInstances", 1, 0);
631 void CActionHistoric::CRequestInsertNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
633 //H_AUTO(R2_CRequestInsertNode_undo)
634 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
636 // if there's a key here, not good for us :(
637 // so try to find another shorter name for this object...
640 std::string instanceId
;
641 std::string attrName
;
643 CObject
*currObj
= scenario
.find(_InstanceId
, _AttrName
, _Position
, _Key
);
644 if (!currObj
) return;
645 if (currObj
->getShortestName(instanceId
, attrName
, position
))
647 // modify local version
648 scenario
.eraseNode(instanceId
, attrName
, position
);
650 dmc
->doRequestEraseNode(instanceId
, attrName
, position
);
652 #ifdef RYZOM_LUA_UCSTRING
653 CLuaIHM::push(getEditor().getLua(), ucstring::makeFromUtf8(instanceId
));
655 getEditor().getLua().push(instanceId
);
657 getEditor().callEnvMethod("setUndoRedoInstances", 1, 0);
661 nlassert(0); // TMP : can this really happen in practice ?
662 nlwarning("Can't build request insert node reciprocal");
667 // special here : if position is -1, requestEraseNode will erase the
668 // table, not the last element!
669 if (!_AttrName
.empty() && _Position
== -1)
671 CObject
*parentTable
= scenario
.find(_InstanceId
, _AttrName
);
672 if (parentTable
&& parentTable
->isTable())
674 uint index
= parentTable
->getSize() - 1;
675 // modify local version
676 scenario
.eraseNode(_InstanceId
, _AttrName
, index
);
678 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, index
);
683 // modify local version
684 scenario
.eraseNode(_InstanceId
, _AttrName
, _Position
);
686 dmc
->doRequestEraseNode(_InstanceId
, _AttrName
, _Position
);
690 //====================================================================================
691 CActionHistoric::CRequestMoveNode::CRequestMoveNode(const std::string
&srcInstanceId
,
692 const std::string
&srcAttrName
,
694 const std::string
&destInstanceId
,
695 const std::string
&destAttrName
,
698 _SrcInstanceId
= srcInstanceId
;
699 _SrcAttrName
= srcAttrName
;
700 _SrcPosition
= srcPosition
;
701 _DestInstanceId
= destInstanceId
;
702 _DestAttrName
= destAttrName
;
703 _DestPosition
= destPosition
;
706 void CActionHistoric::CRequestMoveNode::redo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
708 //H_AUTO(R2_CRequestMoveNode_redo)
710 if (scenario
.getHighLevel())
712 CObject
*src
= scenario
.find(_SrcInstanceId
, _SrcAttrName
, _SrcPosition
);
715 nlwarning("Can't find source when moving node");
718 if (!src
->getNameInParent(_SrcInstanceIdInParent
, _SrcAttrNameInParent
, _SrcPositionInParent
))
720 nlwarning("Can't find name of source object in its parent");
723 // modify local version
724 scenario
.moveNode(_SrcInstanceId
,
730 if (!src
->getShortestName(_DestInstanceIdAfterMove
, _DestAttrNameAfterMove
, _DestPositionAfterMove
))
732 nlwarning("Can't retrieve name of instance after move, undo will fail");
736 dmc
->doRequestMoveNode(_SrcInstanceId
,
744 void CActionHistoric::CRequestMoveNode::undo(IDynamicMapClient
*dmc
, CScenario
&scenario
)
746 //H_AUTO(R2_CRequestMoveNode_undo)
747 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
749 // modify local version
750 scenario
.moveNode(_DestInstanceIdAfterMove
,
751 _DestAttrNameAfterMove
,
752 _DestPositionAfterMove
,
753 _SrcInstanceIdInParent
,
754 _SrcAttrNameInParent
,
755 _SrcPositionInParent
);
757 dmc
->doRequestMoveNode(_DestInstanceIdAfterMove
,
758 _DestAttrNameAfterMove
,
759 _DestPositionAfterMove
,
760 _SrcInstanceIdInParent
,
761 _SrcAttrNameInParent
,
762 _SrcPositionInParent
);