Salome HOME
Merge branch 'Dev_2.1.0' of salome:modules/shaper into Dev_2.1.0
[modules/shaper.git] / src / XGUI / XGUI_OperationMgr.cpp
1 // Copyright (C) 2014-20xx CEA/DEN, EDF R&D -->
2
3 // File:        XGUI_OperationMgr.cpp
4 // Created:     20 Apr 2014
5 // Author:      Natalia ERMOLAEVA
6
7 #include "XGUI_OperationMgr.h"
8 #include "XGUI_ModuleConnector.h"
9 #include "XGUI_Workshop.h"
10 #include "XGUI_ErrorMgr.h"
11
12 #include <ModuleBase_IPropertyPanel.h>
13 #include <ModuleBase_ModelWidget.h>
14 #include "ModuleBase_Operation.h"
15 #include "ModuleBase_IWorkshop.h"
16 #include "ModuleBase_IModule.h"
17 #include <ModuleBase_IViewer.h>
18 #include "ModuleBase_OperationDescription.h"
19 #include "ModuleBase_OperationFeature.h"
20 #include "ModuleBase_Tools.h"
21
22 #include "ModelAPI_CompositeFeature.h"
23 #include "ModelAPI_Session.h"
24
25 #include <XGUI_PropertyPanel.h>
26 #include <QToolButton>
27
28 #include <QMessageBox>
29 #include <QApplication>
30 #include <QKeyEvent>
31
32 //#define DEBUG_CURRENT_FEATURE
33
34 XGUI_OperationMgr::XGUI_OperationMgr(QObject* theParent,
35                                      ModuleBase_IWorkshop* theWorkshop)
36 : QObject(theParent), myWorkshop(theWorkshop)
37 {
38 }
39
40 XGUI_OperationMgr::~XGUI_OperationMgr()
41 {
42 }
43
44 ModuleBase_Operation* XGUI_OperationMgr::currentOperation() const
45 {
46   return myOperations.count() > 0 ? myOperations.last() : 0;
47 }
48
49 bool XGUI_OperationMgr::isCurrentOperation(ModuleBase_Operation* theOperation)
50 {
51   if(!hasOperation())
52     return false;
53   return currentOperation() == theOperation;
54 }
55
56 bool XGUI_OperationMgr::hasOperation() const
57 {
58   return !myOperations.isEmpty() && (myOperations.last() != NULL);
59 }
60
61 bool XGUI_OperationMgr::hasOperation(const QString& theId) const
62 {
63   foreach(ModuleBase_Operation* aOp, myOperations) {
64     if (aOp->id() == theId)
65       return true;
66   }
67   return false;
68 }
69
70 ModuleBase_Operation* XGUI_OperationMgr::findOperation(const QString& theId) const
71 {
72   foreach(ModuleBase_Operation* aOp, myOperations) {
73     if (aOp->id() == theId)
74       return aOp;
75   }
76   return 0;
77 }
78
79
80 int XGUI_OperationMgr::operationsCount() const
81 {
82   return myOperations.count();
83 }
84
85 QStringList XGUI_OperationMgr::operationList() const
86 {
87   QStringList result;
88   foreach(ModuleBase_Operation* eachOperation, myOperations) {
89     ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>(eachOperation);
90     if (aFOperation) {
91       FeaturePtr aFeature = aFOperation->feature();
92       if(aFeature) {
93         result << QString::fromStdString(aFeature->getKind());
94       }
95     }
96   }
97   return result;
98 }
99
100 ModuleBase_Operation* XGUI_OperationMgr::previousOperation(ModuleBase_Operation* theOperation) const
101 {
102   int idx = myOperations.lastIndexOf(theOperation);
103   if(idx == -1 || idx == 0) {
104     return NULL;
105   }
106   return myOperations.at(idx - 1);
107 }
108
109 bool XGUI_OperationMgr::eventFilter(QObject *theObject, QEvent *theEvent)
110 {
111   if (theEvent->type() == QEvent::KeyRelease) {
112     QKeyEvent* aKeyEvent = dynamic_cast<QKeyEvent*>(theEvent);
113     if(aKeyEvent) {
114       return onKeyReleased(aKeyEvent);
115     }
116   }
117   return QObject::eventFilter(theObject, theEvent);
118 }
119
120 bool XGUI_OperationMgr::startOperation(ModuleBase_Operation* theOperation)
121 {
122   if (hasOperation())
123     currentOperation()->postpone();
124   myOperations.append(theOperation);
125
126   connect(theOperation, SIGNAL(beforeStarted()), SLOT(onBeforeOperationStarted()));
127   connect(theOperation, SIGNAL(beforeAborted()), SLOT(onBeforeOperationAborted()));
128   connect(theOperation, SIGNAL(beforeCommitted()), SLOT(onBeforeOperationCommitted()));
129
130   connect(theOperation, SIGNAL(started()), SLOT(onOperationStarted()));
131   connect(theOperation, SIGNAL(aborted()), SLOT(onOperationAborted()));
132   connect(theOperation, SIGNAL(committed()), SLOT(onOperationCommitted()));
133
134   connect(theOperation, SIGNAL(stopped()), SLOT(onOperationStopped()));
135   connect(theOperation, SIGNAL(resumed()), SLOT(onOperationResumed()));
136   ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>
137                                                                         (theOperation);
138   if (aFOperation)
139     connect(aFOperation, SIGNAL(activatedByPreselection()),
140             SIGNAL(operationActivatedByPreselection()));
141
142   bool isStarted = theOperation->start();
143   if (isStarted)
144     onValidateOperation();
145   return isStarted;
146 }
147
148 bool XGUI_OperationMgr::abortAllOperations()
149 {
150   bool aResult = true;
151   if(!hasOperation())
152     return aResult;
153
154   if (operationsCount() == 1) {
155     ModuleBase_Operation* aCurrentOperation = currentOperation();
156     if (canStopOperation(aCurrentOperation)) {
157       abortOperation(aCurrentOperation);
158     }
159     else
160       aResult = false;
161   }
162   else {
163     aResult = QMessageBox::question(qApp->activeWindow(),
164                                     tr("Abort operation"),
165                                     tr("All active operations will be aborted."),
166                                     QMessageBox::Ok | QMessageBox::Cancel,
167                                     QMessageBox::Cancel) == QMessageBox::Ok;
168     while(aResult && hasOperation()) {
169       abortOperation(currentOperation());
170     }
171   }
172   return aResult;
173 }
174
175 bool XGUI_OperationMgr::commitAllOperations()
176 {
177   bool isCompositeCommitted = false;
178   while (hasOperation()) {
179     ModuleBase_Operation* anOperation = currentOperation();
180     if (workshop()->errorMgr()->isApplyEnabled()) {
181       onCommitOperation();
182     } else {
183       abortOperation(anOperation);
184     }
185     ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>
186                                                                             (anOperation);
187     if (aFOperation) {
188       FeaturePtr aFeature = aFOperation->feature();
189       CompositeFeaturePtr aComposite = 
190           std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aFeature);
191       isCompositeCommitted = aComposite.get();
192       if (isCompositeCommitted)
193         break;
194     }
195   }
196   return true;
197 }
198
199 void XGUI_OperationMgr::onValidateOperation()
200 {
201   if (!hasOperation())
202     return;
203   ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>
204                                                                           (currentOperation());
205   if(aFOperation && aFOperation->feature().get())
206     workshop()->errorMgr()->updateActions(aFOperation->feature());
207 }
208
209 void XGUI_OperationMgr::updateApplyOfOperations(ModuleBase_Operation* theOperation)
210 {
211   XGUI_ErrorMgr* anErrorMgr = workshop()->errorMgr();
212   if (theOperation) {
213     ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>(theOperation);
214     if (aFOperation)
215       anErrorMgr->updateAcceptAllAction(aFOperation->feature());
216   }
217   else {
218     foreach(ModuleBase_Operation* anOperation, myOperations) {
219       if (anOperation)
220         updateApplyOfOperations(anOperation);
221     }
222   }
223 }
224
225 bool XGUI_OperationMgr::canStopOperation(ModuleBase_Operation* theOperation)
226 {
227   //in case of nested (sketch) operation no confirmation needed
228   if (isGrantedOperation(theOperation->id()))
229     return true;
230   if (theOperation && theOperation->isModified()) {
231     QString aMessage = tr("%1 operation will be aborted.").arg(theOperation->id());
232     int anAnswer = QMessageBox::question(qApp->activeWindow(),
233                                          tr("Abort operation"),
234                                          aMessage,
235                                          QMessageBox::Ok | QMessageBox::Cancel,
236                                          QMessageBox::Cancel);
237     return anAnswer == QMessageBox::Ok;
238   }
239   return true;
240 }
241
242 bool XGUI_OperationMgr::commitOperation()
243 {
244   if (hasOperation() && currentOperation()->isValid()) {
245     onCommitOperation();
246     return true;
247   }
248   return false;
249 }
250
251 void XGUI_OperationMgr::resumeOperation(ModuleBase_Operation* theOperation)
252 {
253   theOperation->resume();
254 }
255
256 bool XGUI_OperationMgr::isGrantedOperation(const QString& theId)
257 {
258   bool isGranted = false;
259
260   QListIterator<ModuleBase_Operation*> anIt(myOperations);
261   anIt.toBack();
262   ModuleBase_Operation* aPreviousOperation = 0;
263   while (anIt.hasPrevious() && !isGranted) {
264     ModuleBase_Operation* anOp = anIt.previous();
265     if (anOp)
266       isGranted = anOp->isGranted(theId);
267   }
268   return isGranted;
269 }
270
271 void XGUI_OperationMgr::setCurrentFeature(const FeaturePtr& theFeature)
272 {
273   SessionPtr aMgr = ModelAPI_Session::get();
274   DocumentPtr aDoc = aMgr->activeDocument();
275   bool aIsOp = aMgr->isOperation();
276   if (!aIsOp)
277     aMgr->startOperation();
278   aDoc->setCurrentFeature(theFeature, false);
279   if (!aIsOp)
280     aMgr->finishOperation();
281 }
282
283 bool XGUI_OperationMgr::canStartOperation(const QString& theId)
284 {
285   bool aCanStart = true;
286   ModuleBase_Operation* aCurrentOp = currentOperation();
287   if (aCurrentOp) {
288     bool aGranted = aCurrentOp->isGranted(theId);
289     // the started operation is granted for the current one,
290     // e.g. current - Sketch, started - Line
291     if (aGranted) {
292       aCanStart = true;
293     }
294     else {
295       if (!isGrantedOperation(theId)) {
296         // the operation is not granted in the current list of operations
297         // e.g. Edit Parameter when Sketch, Line in Sketch is active.
298         aCanStart = abortAllOperations();
299       }
300       else if (canStopOperation(aCurrentOp)) {
301         // the started operation is granted in the parrent operation,
302         // e.g. current - Line in Sketch, started Circle 
303         if (workshop()->errorMgr()->isApplyEnabled() && aCurrentOp->isModified())
304           aCurrentOp->commit();
305         else
306           abortOperation(aCurrentOp);
307       } else {
308         aCanStart = false;
309       }
310     }
311   }
312   return aCanStart;
313 }
314
315 void XGUI_OperationMgr::abortOperation(ModuleBase_Operation* theOperation)
316 {
317   ModuleBase_Operation* aCurrentOperation = currentOperation();
318   if (theOperation == aCurrentOperation)
319     theOperation->abort();
320   else {
321     // it is possible to trigger upper operation(e.g. sketch, current is sketch line)
322     // all operation from the current to triggered should also be aborted
323     // operations over the parameter one are not aborted(e.g. extrusion cut, sketch abort)
324     while(hasOperation()) {
325       ModuleBase_Operation* aCurrentOperation = currentOperation();
326       aCurrentOperation->abort();
327       if(theOperation == aCurrentOperation)
328         break;
329     }
330   }
331 }
332
333 void XGUI_OperationMgr::onCommitOperation()
334 {
335   ModuleBase_Operation* anOperation = currentOperation();
336   if (anOperation)
337     anOperation->commit();
338 }
339
340 void XGUI_OperationMgr::onAbortOperation()
341 {
342   ModuleBase_Operation* aCurrentOperation = currentOperation();
343   if (aCurrentOperation && canStopOperation(aCurrentOperation)) {
344     abortOperation(aCurrentOperation);
345   }
346 }
347
348 void XGUI_OperationMgr::onBeforeOperationStarted()
349 {
350   ModuleBase_Operation* aCurrentOperation = dynamic_cast<ModuleBase_Operation*>(sender());
351   if (!aCurrentOperation)
352     return;
353
354   /// Set current feature and remeber old current feature
355   ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>(aCurrentOperation);
356   if (aFOperation) {
357     SessionPtr aMgr = ModelAPI_Session::get();
358     DocumentPtr aDoc = aMgr->activeDocument();
359     // the parameter of current feature should be false, we should use all feature, not only visible
360     // in order to correctly save the previous feature of the nested operation, where the
361     // features can be not visible in the tree. The problem case is Edit sketch entitity(line)
362     // in the Sketch, created in ExtrusionCut operation. The entity disappears by commit.
363     // When sketch entity operation started, the sketch should be cashed here as the current.
364     // Otherwise(the flag is true), the ExtrusionCut is cashed, when commit happens, the sketch
365     // is disabled, sketch entity is disabled as extrusion cut is created earliest then sketch.
366     // As a result the sketch disappears from the viewer. However after commit it is displayed back.
367     aFOperation->setPreviousCurrentFeature(aDoc->currentFeature(false));
368
369 #ifdef DEBUG_CURRENT_FEATURE
370     FeaturePtr aFeature = aFOperation->feature();
371     QString aKind = aFeature ? aFeature->getKind().c_str() : "";
372     qDebug(QString("onBeforeOperationStarted(), edit operation = %1, feature = %2")
373             .arg(aFOperation->isEditOperation())
374             .arg(ModuleBase_Tools::objectInfo(aFeature)).toStdString().c_str());
375
376     qDebug(QString("\tdocument->currentFeature(false) = %1").arg(
377             ModuleBase_Tools::objectInfo(ModelAPI_Session::get()->activeDocument()->currentFeature(false))).toStdString().c_str());
378 #endif
379
380     if (aFOperation->isEditOperation()) // it should be performed by the feature edit only
381       // in create operation, the current feature is changed by addFeature()
382       aDoc->setCurrentFeature(aFOperation->feature(), false);
383
384 #ifdef DEBUG_CURRENT_FEATURE
385     qDebug("\tdocument->setCurrentFeature");
386     qDebug(QString("\tdocument->currentFeature(false) = %1").arg(
387             ModuleBase_Tools::objectInfo(ModelAPI_Session::get()->activeDocument()->currentFeature(false))).toStdString().c_str());
388 #endif
389   ModuleBase_IModule* aModule = myWorkshop->module();
390   if (aModule)
391     aModule->beforeOperationStarted(aFOperation);
392   }
393 }
394
395 void XGUI_OperationMgr::onOperationStarted()
396 {
397   ModuleBase_Operation* aSenderOperation = dynamic_cast<ModuleBase_Operation*>(sender());
398   updateApplyOfOperations(aSenderOperation);
399   emit operationStarted(aSenderOperation);
400 }
401
402 void XGUI_OperationMgr::onBeforeOperationAborted()
403 {
404   onBeforeOperationCommitted();
405 }
406
407 void XGUI_OperationMgr::onOperationAborted()
408 {
409   ModuleBase_Operation* aSenderOperation = dynamic_cast<ModuleBase_Operation*>(sender());
410   emit operationAborted(aSenderOperation);
411 }
412
413 void XGUI_OperationMgr::onBeforeOperationCommitted()
414 {
415   ModuleBase_Operation* aCurrentOperation = dynamic_cast<ModuleBase_Operation*>(sender());
416   if (!aCurrentOperation)
417     return;
418
419   /// Restore the previous current feature
420   ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>(aCurrentOperation);
421   if (aFOperation) {
422 #ifdef DEBUG_CURRENT_FEATURE
423     QString aKind = aFOperation->feature()->getKind().c_str();
424     qDebug(QString("onBeforeOperationCommitted(), edit operation = %1, feature = %2")
425             .arg(aFOperation->isEditOperation())
426             .arg(ModuleBase_Tools::objectInfo(aFOperation->feature())).toStdString().c_str());
427
428     qDebug(QString("\tdocument->currentFeature(false) = %1").arg(
429             ModuleBase_Tools::objectInfo(ModelAPI_Session::get()->activeDocument()->currentFeature(false))).toStdString().c_str());
430 #endif
431
432     if (aFOperation->isEditOperation()) {
433       /// Restore the previous current feature
434       setCurrentFeature(aFOperation->previousCurrentFeature());
435     }
436     else { // create operation
437       // the Top created feature should stays the current. In nested operations, like Line in the Sketch or
438       // Sketch in ExtrusionCut, a previous feature should be restored on commit. It is performed here
439       // in order to perform it in the current transaction without opening a new one.
440       if (myOperations.front() != aFOperation)
441         setCurrentFeature(aFOperation->previousCurrentFeature());
442     }
443 #ifdef DEBUG_CURRENT_FEATURE
444     qDebug("\tdocument->setCurrentFeature");
445     qDebug(QString("\tdocument->currentFeature(false) = %1").arg(
446             ModuleBase_Tools::objectInfo(ModelAPI_Session::get()->activeDocument()->currentFeature(false))).toStdString().c_str());
447 #endif
448     ModuleBase_IModule* aModule = myWorkshop->module();
449     if (aModule)
450       aModule->beforeOperationStopped(aFOperation);
451   }
452 }
453
454 void XGUI_OperationMgr::onOperationCommitted()
455 {
456   // apply state for all features from the stack of operations should be updated
457   updateApplyOfOperations();
458
459   ModuleBase_Operation* aSenderOperation = dynamic_cast<ModuleBase_Operation*>(sender());
460   emit operationCommitted(aSenderOperation);
461 }
462
463 void XGUI_OperationMgr::onOperationResumed()
464 {
465   ModuleBase_Operation* aSenderOperation = dynamic_cast<ModuleBase_Operation*>(sender());
466   emit operationResumed(aSenderOperation);
467 }
468
469 void XGUI_OperationMgr::onOperationStopped()
470 {
471   ModuleBase_Operation* aSenderOperation = dynamic_cast<ModuleBase_Operation*>(sender());
472   ModuleBase_Operation* aCurrentOperation = currentOperation();
473   if (!aSenderOperation || !aCurrentOperation || aSenderOperation != aCurrentOperation)
474     return;
475
476   myOperations.removeAll(aCurrentOperation);
477   aCurrentOperation->deleteLater();
478
479   emit operationStopped(aCurrentOperation);
480
481   // get last operation which can be resumed
482   ModuleBase_Operation* aResultOp = 0;
483   QListIterator<ModuleBase_Operation*> anIt(myOperations);
484   anIt.toBack();
485   while (anIt.hasPrevious()) {
486     ModuleBase_Operation* anOp = anIt.previous();
487     if (anOp) {
488       aResultOp = anOp;
489       break;
490     }
491   }
492   if (aResultOp) {
493     bool isModified = aCurrentOperation->isModified();
494     aResultOp->setIsModified(aResultOp->isModified() || isModified);
495     resumeOperation(aResultOp);
496     onValidateOperation();
497   }
498 }
499
500 bool XGUI_OperationMgr::onKeyReleased(QKeyEvent* theEvent)
501 {
502   QObject* aSender = sender();
503
504   // Let the manager decide what to do with the given key combination.
505   ModuleBase_Operation* anOperation = currentOperation();
506   bool isAccepted = true;
507   switch (theEvent->key()) {
508     case Qt::Key_Return:
509     case Qt::Key_Enter: {
510       isAccepted = onProcessEnter();
511     }
512     break;
513     case Qt::Key_N:
514     case Qt::Key_P: {
515       bool noModifiers = (theEvent->modifiers() == Qt::NoModifier);
516       if (noModifiers) {
517         ModuleBase_IViewer* aViewer = myWorkshop->viewer();
518         Handle(AIS_InteractiveContext) aContext = aViewer->AISContext();
519         if (!aContext.IsNull()) {
520           Handle(V3d_View) aView = aViewer->activeView();
521           if ((theEvent->key() == Qt::Key_N))
522             aContext->HilightNextDetected(aView);
523           else if ((theEvent->key() == Qt::Key_P))
524             aContext->HilightPreviousDetected(aView);
525         }
526       }
527     }
528
529     break;
530     default:
531       isAccepted = false;
532       break;
533   }
534   //if(anOperation) {
535   //  anOperation->keyReleased(theEvent->key());
536   //}
537   return isAccepted;
538 }
539
540 bool XGUI_OperationMgr::onProcessEnter()
541 {
542   bool isAccepted = true;
543   ModuleBase_Operation* aOperation = currentOperation();
544   ModuleBase_IPropertyPanel* aPanel = aOperation->propertyPanel();
545   ModuleBase_ModelWidget* aActiveWgt = aPanel->activeWidget();
546   bool isAborted = false;
547   if (!aActiveWgt) {
548     QWidget* aFocusWidget = aPanel->focusWidget();
549     QToolButton* aCancelBtn = aPanel->findChild<QToolButton*>(PROP_PANEL_CANCEL);
550     if (aFocusWidget && aCancelBtn && aFocusWidget == aCancelBtn) {
551       abortOperation(aOperation);
552       isAborted = true;
553     }
554   }
555   if (!isAborted) {
556     if (!aActiveWgt || !aActiveWgt->processEnter()) {
557       if (!myWorkshop->module()->processEnter(aActiveWgt ? aActiveWgt->attributeID() : "")) {
558         ModuleBase_OperationFeature* aFOperation = dynamic_cast<ModuleBase_OperationFeature*>(currentOperation());
559         if (!aFOperation || myWorkshop->module()->getFeatureError(aFOperation->feature()).isEmpty()) {
560           emit keyEnterReleased();
561           commitOperation();
562         }
563         else
564           isAccepted = false;
565       }
566     }
567   }
568   return isAccepted;
569 }
570
571 XGUI_Workshop* XGUI_OperationMgr::workshop() const
572 {
573   XGUI_ModuleConnector* aConnector = dynamic_cast<XGUI_ModuleConnector*>(myWorkshop);
574   return aConnector->workshop();
575 }
576