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