Salome HOME
1. Correction for perfomance problem by Apply button state update: do not listen...
[modules/shaper.git] / src / XGUI / XGUI_PropertyPanel.cpp
1 // Copyright (C) 2014-20xx CEA/DEN, EDF R&D -->
2
3 /*
4  * XGUI_PropertyPanel.cpp
5  *
6  *  Created on: Apr 29, 2014
7  *      Author: sbh
8  */
9
10 #include <XGUI_PropertyPanel.h>
11 #include <XGUI_ActionsMgr.h>
12 #include <XGUI_OperationMgr.h>
13 //#include <AppElements_Constants.h>
14 #include <ModuleBase_WidgetMultiSelector.h>
15 #include <ModuleBase_Tools.h>
16 #include <ModuleBase_PageBase.h>
17 #include <ModuleBase_PageWidget.h>
18 #include <ModuleBase_WidgetFactory.h>
19 #include <ModuleBase_OperationDescription.h>
20
21 #include <ModelAPI_Session.h>
22 #include <ModelAPI_Validator.h>
23
24 #include <QEvent>
25 #include <QFrame>
26 #include <QIcon>
27 #include <QKeyEvent>
28 #include <QLayoutItem>
29 #include <QToolButton>
30 #include <QVBoxLayout>
31 #include <QGridLayout>
32 #include <QWidget>
33 #include <QToolButton>
34 #include <QAction>
35
36 #ifdef _DEBUG
37 #include <iostream>
38 #endif
39
40 //#define DEBUG_TAB_WIDGETS
41
42 XGUI_PropertyPanel::XGUI_PropertyPanel(QWidget* theParent, XGUI_OperationMgr* theMgr)
43     : ModuleBase_IPropertyPanel(theParent), 
44     myActiveWidget(NULL),
45     myPreselectionWidget(NULL),
46     myPanelPage(NULL),
47     myOperationMgr(theMgr)
48 {
49   setWindowTitle(tr("Property Panel"));
50   QAction* aViewAct = toggleViewAction();
51   setObjectName(PROP_PANEL);
52   setStyleSheet("::title { position: relative; padding-left: 5px; text-align: left center }");
53
54   QWidget* aContent = new QWidget(this);
55   QGridLayout* aMainLayout = new QGridLayout(aContent);
56   const int kPanelColumn = 0;
57   int aPanelRow = 0;
58   aMainLayout->setContentsMargins(3, 3, 3, 3);
59   setWidget(aContent);
60
61   QFrame* aFrm = new QFrame(aContent);
62   aFrm->setFrameStyle(QFrame::Raised);
63   aFrm->setFrameShape(QFrame::Panel);
64   QHBoxLayout* aBtnLay = new QHBoxLayout(aFrm);
65   ModuleBase_Tools::zeroMargins(aBtnLay);
66   aMainLayout->addWidget(aFrm, aPanelRow++, kPanelColumn);
67
68   myHeaderWidget = aFrm;
69
70   QStringList aBtnNames;
71   aBtnNames << QString(PROP_PANEL_HELP)
72             << QString(PROP_PANEL_OK)
73             << QString(PROP_PANEL_CANCEL);
74   foreach(QString eachBtnName, aBtnNames) {
75     QToolButton* aBtn = new QToolButton(aFrm);
76     aBtn->setObjectName(eachBtnName);
77     aBtn->setAutoRaise(true);
78     aBtnLay->addWidget(aBtn);
79   }
80   aBtnLay->insertStretch(1, 1);
81
82   myPanelPage = new ModuleBase_PageWidget(aContent);
83   myPanelPage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
84   aMainLayout->addWidget(myPanelPage, aPanelRow, kPanelColumn);
85
86   // spit to make the preview button on the bottom of the panel
87   aMainLayout->setRowStretch(aPanelRow++, 1);
88
89   // preview button on the bottom of panel
90   aFrm = new QFrame(aContent);
91   aBtnLay = new QHBoxLayout(aFrm);
92   aBtnLay->addStretch(1);
93   ModuleBase_Tools::zeroMargins(aBtnLay);
94   aMainLayout->addWidget(aFrm, aPanelRow++, kPanelColumn);
95
96   QToolButton* aBtn = new QToolButton(aFrm);
97   aBtn->setObjectName(PROP_PANEL_PREVIEW);
98   aBtnLay->addWidget(aBtn);
99 }
100
101 XGUI_PropertyPanel::~XGUI_PropertyPanel()
102 {
103 }
104
105 void XGUI_PropertyPanel::cleanContent()
106 {
107   if (myActiveWidget)
108     myActiveWidget->deactivate();
109
110   /// as the widgets are deleted later, it is important that the signals
111   /// of these widgets are not processed. An example of the error is issue 986.
112   /// In the given case, the property panel is firstly filled by new widgets
113   /// of restarted operation and after that the mouse release signal come from
114   /// the widget of the previous operation (Point2d widget about mouse is released
115   /// and focus is out of this widget)
116   QList<ModuleBase_ModelWidget*>::const_iterator anIt = myWidgets.begin(),
117                                                  aLast = myWidgets.end();
118   for (; anIt != aLast; anIt++) {
119     QWidget* aWidget = *anIt;
120     if (aWidget) {
121       aWidget->blockSignals(true);
122     }
123   }
124
125   myWidgets.clear();
126   myPanelPage->clearPage();
127   myActiveWidget = NULL;
128
129   findButton(PROP_PANEL_PREVIEW)->setVisible(false); /// by default it is hidden
130   setWindowTitle(tr("Property Panel"));
131 }
132
133 void XGUI_PropertyPanel::setModelWidgets(const QList<ModuleBase_ModelWidget*>& theWidgets)
134 {
135   myWidgets = theWidgets;
136   if (theWidgets.empty()) return;
137   foreach (ModuleBase_ModelWidget* aWidget, theWidgets) {
138     connect(aWidget, SIGNAL(focusInWidget(ModuleBase_ModelWidget*)),
139             this,    SLOT(onFocusInWidget(ModuleBase_ModelWidget*)));
140     connect(aWidget, SIGNAL(focusOutWidget(ModuleBase_ModelWidget*)),
141             this,    SLOT(onActivateNextWidget(ModuleBase_ModelWidget*)));
142     connect(aWidget, SIGNAL(keyReleased(QObject*, QKeyEvent*)),
143             this,    SIGNAL(keyReleased(QObject*, QKeyEvent*)));
144     connect(aWidget, SIGNAL(enterClicked(QObject*)),
145             this,    SIGNAL(enterClicked(QObject*)));
146
147   }
148 }
149
150 const QList<ModuleBase_ModelWidget*>& XGUI_PropertyPanel::modelWidgets() const
151 {
152   return myWidgets;
153 }
154
155 ModuleBase_PageBase* XGUI_PropertyPanel::contentWidget()
156 {
157   return static_cast<ModuleBase_PageBase*>(myPanelPage);
158 }
159
160 void XGUI_PropertyPanel::updateContentWidget(FeaturePtr theFeature)
161 {
162   // Invalid feature case on abort of the operation
163   if (theFeature.get() == NULL)
164     return;
165   if (theFeature->isAction() || !theFeature->data())
166     return;
167   foreach(ModuleBase_ModelWidget* eachWidget, myWidgets) {
168     if (!eachWidget->feature().get())
169       eachWidget->setFeature(theFeature);
170     eachWidget->restoreValue();
171   }
172   // the repaint is used here to immediately react in GUI to the values change.
173   repaint();
174 }
175
176 void XGUI_PropertyPanel::createContentPanel(FeaturePtr theFeature)
177 {
178   // Invalid feature case on abort of the operation
179   if (theFeature.get() == NULL)
180     return;
181   if (theFeature->isAction() || !theFeature->data())
182     return;
183
184   if (myWidgets.empty()) {
185     ModuleBase_Operation* anOperation = myOperationMgr->currentOperation();
186     QString aXmlRepr = anOperation->getDescription()->xmlRepresentation();
187
188     ModuleBase_WidgetFactory aFactory(aXmlRepr.toStdString(), myOperationMgr->workshop());
189     aFactory.createPanel(contentWidget(), theFeature);
190     /// Apply button should be update if the feature was modified by the panel
191     myOperationMgr->onValidateOperation();
192   }
193 }
194
195 void XGUI_PropertyPanel::activateNextWidget(ModuleBase_ModelWidget* theWidget)
196 {
197   // it is possible that the property panel widgets have not been visualized
198   // (e.g. on start operation), so it is strictly important to do not check visualized state
199   activateNextWidget(theWidget, false);
200 }
201
202 void XGUI_PropertyPanel::onFocusInWidget(ModuleBase_ModelWidget* theWidget)
203 {
204   if (theWidget->canAcceptFocus())
205     activateWidget(theWidget);
206 }
207
208 void XGUI_PropertyPanel::onActivateNextWidget(ModuleBase_ModelWidget* theWidget)
209 {
210   // this slot happens when some widget lost focus, the next widget which accepts the focus
211   // should be shown, so the second parameter is true
212   // it is important for features where in cases the same attributes are used, isCase for this
213   // attribute returns true, however it can be placed in hidden stack widget(extrusion: elements,
214   // sketch multi rotation -> single/full point)
215   // it is important to check the widget visibility to do not check of the next widget visible
216   // state if the current is not shown. (example: sketch circle re-entrant operation after mouse
217   // release in the viewer, next, radius, widget should be activated but the first is not visualized)
218   bool isVisible = theWidget->isVisible();
219   activateNextWidget(theWidget, isVisible);
220 }
221
222
223 void XGUI_PropertyPanel::activateNextWidget(ModuleBase_ModelWidget* theWidget,
224                                             const bool isCheckVisibility)
225 {
226   // TO CHECK: Editing operation does not have automatical activation of widgets
227   if (isEditingMode()) {
228     activateWidget(NULL);
229     return;
230   }
231   ModelAPI_ValidatorsFactory* aValidators = ModelAPI_Session::get()->validators();
232
233   QList<ModuleBase_ModelWidget*>::const_iterator anIt = myWidgets.begin(), aLast = myWidgets.end();
234   bool isFoundWidget = false;
235   ModuleBase_Tools::activateWindow(this, "XGUI_PropertyPanel::activateNextWidget()");
236   for (; anIt != aLast; anIt++) {
237     ModuleBase_ModelWidget* aCurrentWidget = *anIt;
238     if (isFoundWidget || !theWidget) {
239
240       if (!aValidators->isCase(aCurrentWidget->feature(), aCurrentWidget->attributeID()))
241         continue; // this attribute is not participated in the current case
242       if (isCheckVisibility && !aCurrentWidget->isInternal()) {
243         if (!aCurrentWidget->isVisible())
244           continue;
245       }
246       if (!aCurrentWidget->isObligatory())
247         continue; // not obligatory widgets are not activated automatically
248
249       if (aCurrentWidget->focusTo()) {
250         return;
251       }
252     }
253     isFoundWidget = isFoundWidget || (*anIt) == theWidget;
254   }
255   // set focus to Ok/Cancel button in Property panel if there are no more active widgets
256   // it should be performed before activateWidget(NULL) because it emits some signals which
257   // can be processed by moudule for example as to activate another widget with setting focus
258   QWidget* aNewFocusWidget = 0;
259   QToolButton* anOkBtn = findButton(PROP_PANEL_OK);
260   if (anOkBtn->isEnabled())
261     aNewFocusWidget = anOkBtn;
262   else {
263     QToolButton* aCancelBtn = findButton(PROP_PANEL_CANCEL);
264     if (aCancelBtn->isEnabled())
265       aNewFocusWidget = aCancelBtn;
266   }
267   if (aNewFocusWidget)
268     aNewFocusWidget->setFocus(Qt::TabFocusReason);
269
270   activateWidget(NULL);
271 }
272
273 void findDirectChildren(QWidget* theParent, QList<QWidget*>& theWidgets, const bool theDebug)
274 {
275   QList<QWidget*> aWidgets;
276
277   if (theParent) {
278     QLayout* aLayout = theParent->layout();
279     if (aLayout) {
280       for (int i = 0, aCount = aLayout->count(); i < aCount; i++) {
281         QLayoutItem* anItem = aLayout->itemAt(i);
282         QWidget* aWidget = anItem ? anItem->widget() : 0;
283         if (aWidget) {
284           if (aWidget->isVisible()) {
285             if (aWidget->focusPolicy() != Qt::NoFocus)
286               theWidgets.append(aWidget);
287             findDirectChildren(aWidget, theWidgets, false);
288           }
289         }
290         else if (anItem->layout()) {
291           QLayout* aLayout = anItem->layout();
292           for (int i = 0, aCount = aLayout->count(); i < aCount; i++) {
293             QLayoutItem* anItem = aLayout->itemAt(i);
294             QWidget* aWidget = anItem ? anItem->widget() : 0;
295             if (aWidget) {
296               if (aWidget->isVisible()) {
297                 if (aWidget->focusPolicy() != Qt::NoFocus)
298                   theWidgets.append(aWidget);
299                 findDirectChildren(aWidget, theWidgets, false);
300               }
301             }
302             else {
303               // TODO: improve recursive search for the case when here QLayout is used
304               // currently, the switch widget uses only 1 level of qlayout in qlayout using for
305               // example for construction plane feature. If there are more levels,
306               // it should be implemented here
307             }
308           }
309         }
310       }
311     }
312   }
313 #ifdef DEBUG_TAB_WIDGETS
314   if (theDebug) {
315     QStringList aWidgetTypes;
316     QList<QWidget*>::const_iterator anIt =  theWidgets.begin(), aLast = theWidgets.end();
317     for (; anIt != aLast; anIt++) {
318       QWidget* aWidget = *anIt;
319       if (aWidget)
320         aWidgetTypes.append(aWidget->objectName());
321     }
322     QString anInfo = QString("theWidgets[%1]: %2").arg(theWidgets.count()).arg(aWidgetTypes.join(","));
323     qDebug(anInfo.toStdString().c_str());
324   }
325 #endif
326 }
327
328 bool XGUI_PropertyPanel::focusNextPrevChild(bool theIsNext)
329 {
330   // it wraps the Tabs clicking to follow in the chain:
331   // controls, last control, Apply, Cancel, first control, controls
332   bool isChangedFocus = false;
333
334   QWidget* aFocusWidget = focusWidget();
335 #ifdef DEBUG_TAB_WIDGETS
336   if (aFocusWidget) {
337     QString anInfo = QString("focus Widget: %1").arg(aFocusWidget->objectName());
338     qDebug(anInfo.toStdString().c_str());
339   }
340 #endif
341
342   QWidget* aNewFocusWidget = 0;
343   if (aFocusWidget) {
344     QList<QWidget*> aChildren;
345     findDirectChildren(this, aChildren, true);
346     int aChildrenCount = aChildren.count();
347     int aFocusWidgetIndex = aChildren.indexOf(aFocusWidget);
348     if (aFocusWidgetIndex >= 0) {
349       if (theIsNext) {
350         if (aFocusWidgetIndex == aChildrenCount-1) {
351           // after the last widget focus should be set to "Apply"
352           QToolButton* anOkBtn = findButton(PROP_PANEL_OK);
353           if (anOkBtn->isEnabled())
354             aNewFocusWidget = anOkBtn;
355           else {
356             QToolButton* aCancelBtn = findButton(PROP_PANEL_CANCEL);
357             if (aCancelBtn->isEnabled())
358               aNewFocusWidget = aCancelBtn;
359           }
360         }
361         else {
362           aNewFocusWidget = aChildren[aFocusWidgetIndex+1];
363         }
364       }
365       else {
366         if (aFocusWidgetIndex == 0) {
367           // before the first widget, the last should accept focus
368           aNewFocusWidget = aChildren[aChildrenCount - 1];
369         }
370         else {
371           // before the "Apply" button, the last should accept focus for consistency with "Next"
372           QToolButton* anOkBtn = findButton(PROP_PANEL_OK);
373           if (aFocusWidget == anOkBtn) {
374             aNewFocusWidget = aChildren[aChildrenCount - 1];
375           }
376           else {
377             aNewFocusWidget = aChildren[aFocusWidgetIndex-1];
378           }
379         }
380       }
381     }
382   }
383   if (aNewFocusWidget) {
384     if (myActiveWidget) {
385       myActiveWidget->getControls();
386       bool isFirstControl = !theIsNext;
387       QWidget* aLastFocusControl = myActiveWidget->getControlAcceptingFocus(isFirstControl);
388       if (aFocusWidget == aLastFocusControl) {
389         setActiveWidget(NULL);
390       }
391     }
392
393     //ModuleBase_Tools::setFocus(aNewFocusWidget, "XGUI_PropertyPanel::focusNextPrevChild()");
394     aNewFocusWidget->setFocus(theIsNext ? Qt::TabFocusReason : Qt::BacktabFocusReason);
395     isChangedFocus = true;
396   }
397   return isChangedFocus;
398 }
399
400 void XGUI_PropertyPanel::activateNextWidget()
401 {
402   activateNextWidget(myActiveWidget);
403 }
404
405 void XGUI_PropertyPanel::activateWidget(ModuleBase_ModelWidget* theWidget, const bool theEmitSignal)
406 {
407   std::string aPreviosAttributeID;
408   if(myActiveWidget)
409     aPreviosAttributeID = myActiveWidget->attributeID();
410
411   // Avoid activation of already actve widget. It could happen on focusIn event many times
412   if (setActiveWidget(theWidget) && theEmitSignal) {
413     emit widgetActivated(myActiveWidget);
414     if (!myActiveWidget && !isEditingMode()) {
415       emit noMoreWidgets(aPreviosAttributeID);
416     }
417   }
418 }
419
420 bool XGUI_PropertyPanel::setActiveWidget(ModuleBase_ModelWidget* theWidget)
421 {
422   // Avoid activation of already actve widget. It could happen on focusIn event many times
423   if (theWidget == myActiveWidget) {
424     return false;
425   }
426   std::string aPreviosAttributeID;
427   if(myActiveWidget) {
428     aPreviosAttributeID = myActiveWidget->attributeID();
429     myActiveWidget->deactivate();
430     myActiveWidget->setHighlighted(false);
431   }
432   if(theWidget) {
433     emit beforeWidgetActivated(theWidget);
434     theWidget->setHighlighted(true);
435     theWidget->activate();
436   }
437   myActiveWidget = theWidget;
438   return true;
439 }
440
441 void XGUI_PropertyPanel::setFocusOnOkButton()
442 {
443   QToolButton* anOkBtn = findButton(PROP_PANEL_OK);
444   ModuleBase_Tools::setFocus(anOkBtn, "XGUI_PropertyPanel::setFocusOnOkButton()");
445 }
446
447 void XGUI_PropertyPanel::setCancelEnabled(bool theEnabled)
448 {
449   QToolButton* anCancelBtn = findButton(PROP_PANEL_CANCEL);
450   anCancelBtn->setEnabled(theEnabled);
451 }
452
453 bool XGUI_PropertyPanel::isCancelEnabled() const
454 {
455   QToolButton* anCancelBtn = findButton(PROP_PANEL_CANCEL);
456   return anCancelBtn->isEnabled();
457 }
458
459 void XGUI_PropertyPanel::setEditingMode(bool isEditing)
460 {
461   ModuleBase_IPropertyPanel::setEditingMode(isEditing);
462   foreach(ModuleBase_ModelWidget* aWgt, myWidgets) {
463     aWgt->setEditingMode(isEditing);
464   }
465 }
466
467 void XGUI_PropertyPanel::setupActions(XGUI_ActionsMgr* theMgr)
468 {
469   QStringList aButtonNames;
470   aButtonNames << PROP_PANEL_OK << PROP_PANEL_CANCEL << PROP_PANEL_HELP << PROP_PANEL_PREVIEW;
471   QList<XGUI_ActionsMgr::OperationStateActionId> aActionIds;
472   aActionIds << XGUI_ActionsMgr::Accept << XGUI_ActionsMgr::Abort << XGUI_ActionsMgr::Help
473              << XGUI_ActionsMgr::Preview;
474   for (int i = 0; i < aButtonNames.size(); ++i) {
475     QToolButton* aBtn = findButton(aButtonNames.at(i).toStdString().c_str());
476     QAction* anAct = theMgr->operationStateAction(aActionIds.at(i));
477     aBtn->setDefaultAction(anAct);
478   }
479 }
480
481 ModuleBase_ModelWidget* XGUI_PropertyPanel::preselectionWidget() const
482 {
483   return myPreselectionWidget;
484 }
485
486 void XGUI_PropertyPanel::setPreselectionWidget(ModuleBase_ModelWidget* theWidget)
487 {
488   myPreselectionWidget = theWidget;
489 }
490
491
492 void XGUI_PropertyPanel::closeEvent(QCloseEvent* theEvent)
493 {
494   ModuleBase_Operation* aOp = myOperationMgr->currentOperation();
495   if (aOp) {
496     if (myOperationMgr->canStopOperation(aOp)) {
497       myOperationMgr->abortOperation(aOp);
498       if (myOperationMgr->hasOperation())
499         theEvent->ignore();
500       else
501         theEvent->accept();
502     } else 
503       theEvent->ignore();
504   } else
505     ModuleBase_IPropertyPanel::closeEvent(theEvent);
506 }
507
508 QToolButton* XGUI_PropertyPanel::findButton(const char* theInternalName) const
509 {
510   return findChild<QToolButton*>(theInternalName);
511 }