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