Salome HOME
Merge branch 'master' into cgt/devCEA
[modules/shaper.git] / src / XGUI / XGUI_ObjectsBrowser.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
18 // email : webmaster.salome@opencascade.com<mailto:webmaster.salome@opencascade.com>
19 //
20
21 #include "XGUI_ObjectsBrowser.h"
22 #include "XGUI_Tools.h"
23 #include "XGUI_DataModel.h"
24
25 #include <ModelAPI_Data.h>
26 #include <ModelAPI_Session.h>
27 #include <ModelAPI_Document.h>
28 #include <ModelAPI_Tools.h>
29
30 #include <ModuleBase_Tools.h>
31
32 #include <QLayout>
33 #include <QLineEdit>
34 #include <QPixmap>
35 #include <QEvent>
36 #include <QMouseEvent>
37 #include <QAction>
38 #include <QStyledItemDelegate>
39 #include <QMessageBox>
40
41
42 /// Width of second column (minimum acceptable = 27)
43 #define SECOND_COL_WIDTH 30
44
45
46 /**
47 * \ingroup GUI
48 * Tree item delegate for definition of data in column items editor
49 */
50 class XGUI_TreeViewItemDelegate: public QStyledItemDelegate
51 {
52 public:
53   /// Constructor
54   /// \param theParent a parent of the delegate
55   XGUI_TreeViewItemDelegate(XGUI_DataTree* theParent):QStyledItemDelegate(theParent),
56     myTreedView(theParent) {}
57
58   /// Set data for item editor (name of the item)
59   /// \param editor a widget of editor
60   /// \param index the tree item index
61   virtual void  setEditorData ( QWidget* editor, const QModelIndex& index ) const
62   {
63     QLineEdit* aEditor = dynamic_cast<QLineEdit*>(editor);
64     if (aEditor) {
65       XGUI_DataModel* aModel = myTreedView->dataModel();
66       ObjectPtr aObj = aModel->object(index);
67       if (aObj.get() != NULL) {
68         aEditor->setText(aObj->data()->name().c_str());
69         return;
70       }
71     }
72     QStyledItemDelegate::setEditorData(editor, index);
73   }
74
75 private:
76   XGUI_DataTree* myTreedView;
77 };
78
79
80 XGUI_DataTree::XGUI_DataTree(QWidget* theParent)
81     : QTreeView(theParent)
82 {
83   setHeaderHidden(true);
84   setEditTriggers(QAbstractItemView::NoEditTriggers);
85   setSelectionBehavior(QAbstractItemView::SelectRows);
86   setSelectionMode(QAbstractItemView::ExtendedSelection);
87
88   setItemDelegateForColumn(0, new XGUI_TreeViewItemDelegate(this));
89
90   connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
91     SLOT(onDoubleClick(const QModelIndex&)));
92 }
93
94 XGUI_DataTree::~XGUI_DataTree()
95 {
96 }
97
98 XGUI_DataModel* XGUI_DataTree::dataModel() const
99 {
100   return static_cast<XGUI_DataModel*>(model());
101 }
102
103 void XGUI_DataTree::contextMenuEvent(QContextMenuEvent* theEvent)
104 {
105   emit contextMenuRequested(theEvent);
106 }
107
108 void XGUI_DataTree::commitData(QWidget* theEditor)
109 {
110   static int aEntrance = 0;
111   if (aEntrance == 0) {
112     // We have to check number of enter and exit of this function because it can be called
113     // recursively by Qt in order to avoid double modifying of a data
114     aEntrance = 1;
115     QLineEdit* aEditor = dynamic_cast<QLineEdit*>(theEditor);
116     if (aEditor) {
117       QString aName = aEditor->text();
118       QModelIndexList aIndexList = selectionModel()->selectedIndexes();
119       XGUI_DataModel* aModel = dataModel();
120       ObjectPtr aObj = aModel->object(aIndexList.first());
121
122       if (XGUI_Tools::canRename(aObj, aName)) {
123         SessionPtr aMgr = ModelAPI_Session::get();
124         aMgr->startOperation("Rename");
125         aObj->data()->setName(qPrintable(aName));
126         aMgr->finishOperation();
127       }
128     }
129   }
130   aEntrance = 0;
131 }
132
133 void XGUI_DataTree::clear()
134 {
135   dataModel()->clear();
136   reset();
137 }
138
139 void XGUI_DataTree::resizeEvent(QResizeEvent* theEvent)
140 {
141   QTreeView::resizeEvent(theEvent);
142   QSize aSize = theEvent->size();
143   if (aSize.isValid()) {
144     setColumnWidth(0, aSize.width() - SECOND_COL_WIDTH - 7);
145     setColumnWidth(1, SECOND_COL_WIDTH);
146   }
147 }
148
149 void XGUI_DataTree::onDoubleClick(const QModelIndex& theIndex)
150 {
151   if (theIndex.column() != 1)
152     return;
153   SessionPtr aMgr = ModelAPI_Session::get();
154   // When operation is opened then we can not change history
155   if (aMgr->isOperation())
156     return;
157   XGUI_DataModel* aModel = dataModel();
158   if (aModel->flags(theIndex) == 0)
159     return;
160   ObjectPtr aObj = aModel->object(theIndex);
161
162   DocumentPtr aDoc = aMgr->activeDocument();
163
164   std::string aOpName = tr("History change").toStdString();
165   if (aObj.get()) {
166     if (aObj->document() != aDoc)
167       return;
168     aMgr->startOperation(aOpName);
169     aDoc->setCurrentFeature(std::dynamic_pointer_cast<ModelAPI_Feature>(aObj), true);
170     aMgr->finishOperation();
171   } else {
172     // Ignore clicks on folders outside current document
173     if ((theIndex.internalId() == 0) && (aDoc != aMgr->moduleDocument()))
174       // Clicked folder under root but active document is another
175       return;
176     if ((theIndex.internalId() != 0) && (aDoc.get() != theIndex.internalPointer()))
177       // Cliced not on active document folder
178       return;
179
180     aMgr->startOperation(aOpName);
181     aDoc->setCurrentFeature(FeaturePtr(), true);
182     aMgr->finishOperation();
183   }
184   QModelIndex aNewIndex = aModel->lastHistoryIndex();
185   QModelIndex aParent = theIndex.parent();
186   int aSize = aModel->rowCount(aParent);
187   for (int i = 0; i < aSize; i++) {
188     update(aModel->index(i, 0, aParent));
189     update(aModel->index(i, 1, aParent));
190   }
191 }
192
193
194 //********************************************************************
195 //********************************************************************
196 //********************************************************************
197 XGUI_ActiveDocLbl::XGUI_ActiveDocLbl(const QString& theText, QWidget* theParent )
198   : QLabel(theText, theParent),
199   myPreSelectionStyle(""),
200   myNeutralStyle(""),
201   mySelectionStyle(""),
202   myIsSelected(false)
203 {
204 }
205
206 void XGUI_ActiveDocLbl::setTreeView(QTreeView* theView)
207 {
208   myTreeView = theView;
209   setFont(myTreeView->font());
210
211   QPalette aPalet = myTreeView->palette();
212   QColor aHighlight = aPalet.highlight().color();
213   QColor aHighlightText = aPalet.highlightedText().color();
214
215   myPreSelectionStyle = "QLabel {background-color: ";
216   myPreSelectionStyle += aHighlight.lighter(170).name() + "}";
217   //myPreSelectionStyle += "qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, stop:1 " +
218   //  aHighlight.lighter(170).name() + ");";
219   //myPreSelectionStyle += "border: 1px solid lightblue; border-radius: 2px }";
220
221   QString aName = aPalet.color(QPalette::Base).name();
222   myNeutralStyle = "QLabel { border: 1px solid " + aName + " }";
223
224
225 #if (!defined HAVE_SALOME) && (defined WIN32)
226   mySelectionStyle = "QLabel {background-color: ";
227   mySelectionStyle += "rgb(205, 232, 255); ";
228   //mySelectionStyle += "qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(236, 245, 255)";
229   //mySelectionStyle += ", stop:1 rgb(208, 229, 255));";
230   //mySelectionStyle += "border: 1px solid rgb(132, 172, 221); border-radius: 2px }";
231   mySelectionStyle += "border: 1px solid rgb(153, 209, 255) }";
232 #else
233   mySelectionStyle = "QLabel {background-color: " + aHighlight.name();
234   mySelectionStyle += "; color : " + aHighlightText.name() + "}";
235 #endif
236
237   myTreeView->viewport()->installEventFilter(this);
238 }
239
240
241 #if (!defined HAVE_SALOME) && (defined WIN32)
242 bool XGUI_ActiveDocLbl::event(QEvent* theEvent)
243 {
244   switch (theEvent->type()) {
245     case QEvent::Enter:
246       if (!myIsSelected)
247         setStyleSheet(myPreSelectionStyle);
248       break;
249     case QEvent::Leave:
250       if (!myIsSelected)
251         setStyleSheet(myNeutralStyle);
252       break;
253   }
254   return QLabel::event(theEvent);
255 }
256 #endif
257
258 bool XGUI_ActiveDocLbl::eventFilter(QObject* theObj, QEvent* theEvent)
259 {
260   if (theObj == myTreeView->viewport()) {
261     if (theEvent->type() == QEvent::MouseButtonRelease)
262       unselect();
263   }
264   return QLabel::eventFilter(theObj, theEvent);
265 }
266
267 static bool MYClearing = false;
268 void XGUI_ActiveDocLbl::mouseReleaseEvent( QMouseEvent* e)
269 {
270   MYClearing = true;
271   myIsSelected = true;
272   setStyleSheet(mySelectionStyle);
273   // We can not block signals because on
274   // clear selection the View state will not be updated
275   myTreeView->clearSelection();
276   QLabel::mouseReleaseEvent(e);
277   MYClearing = false;
278 }
279
280 void XGUI_ActiveDocLbl::unselect()
281 {
282   if (!MYClearing) {
283     myIsSelected = false;
284     setStyleSheet(myNeutralStyle);
285   }
286 }
287
288
289 //********************************************************************
290 //********************************************************************
291 //********************************************************************
292 XGUI_ObjectsBrowser::XGUI_ObjectsBrowser(QWidget* theParent)
293     : QWidget(theParent), myDocModel(0)
294 {
295   QVBoxLayout* aLayout = new QVBoxLayout(this);
296   ModuleBase_Tools::zeroMargins(aLayout);
297   aLayout->setSpacing(0);
298
299   QWidget* aLabelWgt = new QWidget(this);
300   aLabelWgt->setAutoFillBackground(true);
301
302   aLayout->addWidget(aLabelWgt);
303   QHBoxLayout* aLabelLay = new QHBoxLayout(aLabelWgt);
304   ModuleBase_Tools::zeroMargins(aLabelLay);
305   aLabelLay->setSpacing(0);
306
307   QLabel* aLbl = new QLabel(aLabelWgt);
308   aLbl->setPixmap(QPixmap(":pictures/assembly.png"));
309   aLbl->setMargin(2);
310   // Do not paint background of the label (in order to show icon)
311   aLbl->setAutoFillBackground(false);
312
313   aLabelLay->addWidget(aLbl);
314
315   SessionPtr aMgr = ModelAPI_Session::get();
316   DocumentPtr aDoc = aMgr->moduleDocument();
317
318   myActiveDocLbl = new XGUI_ActiveDocLbl(tr("Part set"), aLabelWgt);
319 //  myActiveDocLbl->setReadOnly(true);
320 //  myActiveDocLbl->setFrame(false);
321   myActiveDocLbl->setContextMenuPolicy(Qt::CustomContextMenu);
322
323   aLabelLay->addWidget(myActiveDocLbl);
324   aLabelLay->setStretch(1, 1);
325
326   myTreeView = new XGUI_DataTree(this);
327   myTreeView->setFrameShape(QFrame::NoFrame);
328   aLayout->addWidget(myTreeView);
329
330   QPalette aTreePalet = myTreeView->palette();
331   QColor aTreeBack = aTreePalet.color(QPalette::Base);
332
333   QPalette aPalet;
334   aPalet.setColor(QPalette::Base, aTreeBack);
335   aPalet.setColor(QPalette::Window, aTreeBack);
336   aLabelWgt->setPalette(aPalet);
337
338   myDocModel = new XGUI_DataModel(this);
339   connect(myDocModel, SIGNAL(modelAboutToBeReset()), SLOT(onBeforeReset()));
340   connect(myDocModel, SIGNAL(treeRebuilt()), SLOT(onAfterModelReset()));
341
342   connect(myTreeView, SIGNAL(contextMenuRequested(QContextMenuEvent*)), this,
343           SLOT(onContextMenuRequested(QContextMenuEvent*)));
344 }
345
346 //***************************************************
347 XGUI_ObjectsBrowser::~XGUI_ObjectsBrowser()
348 {
349 }
350
351 void XGUI_ObjectsBrowser::setXMLReader(Config_DataModelReader* theReader)
352 {
353   myDocModel->setXMLReader(theReader);
354   myTreeView->setModel(myDocModel);
355
356   // It has to be done after setting of model
357   myActiveDocLbl->setTreeView(myTreeView);
358
359   QItemSelectionModel* aSelMod = myTreeView->selectionModel();
360   connect(aSelMod, SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
361           this, SLOT(onSelectionChanged(const QItemSelection&, const QItemSelection&)));
362 }
363
364 //***************************************************
365 void XGUI_ObjectsBrowser::onContextMenuRequested(QContextMenuEvent* theEvent)
366 {
367   QModelIndexList aIndexes;
368   QObjectPtrList aSelectedData = selectedObjects(&aIndexes);
369   bool toEnable = false;
370
371   if (aSelectedData.size() == 1) {
372     QModelIndex aSelected = myTreeView->indexAt(theEvent->pos());
373     if (!aIndexes.contains(aSelected))
374       return; // menu is called on non selected item
375
376     Qt::ItemFlags aFlags = dataModel()->flags(aIndexes.first());
377     toEnable = ((aFlags & Qt::ItemIsEditable) != 0);
378   }
379   foreach(QAction* aCmd, actions()) {
380     aCmd->setEnabled(toEnable);
381   }
382   emit contextMenuRequested(theEvent);
383 }
384
385 //***************************************************
386 void XGUI_ObjectsBrowser::onLabelContextMenuRequested(const QPoint& thePnt)
387 {
388   myTreeView->selectionModel()->clearSelection();
389   //Empty feature pointer means that selected root document
390   foreach(QAction* aCmd, actions()) {
391     aCmd->setEnabled(true);
392   }
393   QContextMenuEvent aEvent(QContextMenuEvent::Mouse, thePnt, myActiveDocLbl->mapToGlobal(thePnt));
394   emit contextMenuRequested(&aEvent);
395 }
396
397 //***************************************************
398 void XGUI_ObjectsBrowser::onEditItem()
399 {
400   QObjectPtrList aSelectedData = selectedObjects();
401   if (aSelectedData.size() > 0) {
402     ObjectPtr anObject = aSelectedData.first();
403     if (anObject.get()) {  // Selection happens in TreeView
404       // check whether the object can be renamed. There should not be parts which are not loaded
405       std::set<FeaturePtr> aFeatures;
406       aFeatures.insert(ModelAPI_Feature::feature(anObject));
407       if (!XGUI_Tools::canRemoveOrRename((QWidget*)parent(), aFeatures))
408         return;
409
410       // Find index which corresponds the feature
411       QModelIndex aIndex;
412       foreach(QModelIndex aIdx, selectedIndexes()) {
413         ObjectPtr aFea = dataModel()->object(aIdx);
414         if (dataModel()->object(aIdx)->isSame(anObject)) {
415           aIndex = aIdx;
416           break;
417         }
418       }
419       if (aIndex.isValid()) {
420         myTreeView->setCurrentIndex(aIndex);
421         myTreeView->edit(aIndex);
422       }
423       return;
424     }
425   }
426 }
427
428 //***************************************************
429 QModelIndexList XGUI_ObjectsBrowser::expandedItems(const QModelIndex& theParent) const
430 {
431   QModelIndexList aIndexes;
432   QModelIndex aIndex;
433   for (int i = 0; i < myDocModel->rowCount(theParent); i++) {
434     aIndex = myDocModel->index(i, 0, theParent);
435     if (myDocModel->hasChildren(aIndex)) {
436       if (myTreeView->isExpanded(aIndex)) {
437         aIndexes.append(aIndex);
438         QModelIndexList aSubIndexes = expandedItems(aIndex);
439         if (!aSubIndexes.isEmpty())
440           aIndexes.append(aSubIndexes);
441       }
442     }
443   }
444   return aIndexes;
445 }
446
447 //***************************************************
448 void XGUI_ObjectsBrowser::rebuildDataTree()
449 {
450   myDocModel->rebuildDataTree();
451   update();
452 }
453
454 //***************************************************
455 void XGUI_ObjectsBrowser::setObjectsSelected(const QObjectPtrList& theObjects)
456 {
457   QList<QModelIndex> theIndexes;
458   QItemSelectionModel* aSelectModel = myTreeView->selectionModel();
459   aSelectModel->clear();
460
461   foreach(ObjectPtr aFeature, theObjects)
462   {
463     QModelIndex aIndex = myDocModel->objectIndex(aFeature);
464     if (aIndex.isValid()) {
465       aSelectModel->select(aIndex, QItemSelectionModel::Select);
466     }
467   }
468 }
469
470 //***************************************************
471 void XGUI_ObjectsBrowser::clearContent()
472 {
473   myTreeView->clear();
474 }
475
476 void XGUI_ObjectsBrowser::onSelectionChanged(const QItemSelection& theSelected,
477                                        const QItemSelection& theDeselected)
478 {
479   emit selectionChanged();
480 }
481
482 QObjectPtrList XGUI_ObjectsBrowser::selectedObjects(QModelIndexList* theIndexes) const
483 {
484   QObjectPtrList aList;
485   QModelIndexList aIndexes = selectedIndexes();
486   XGUI_DataModel* aModel = dataModel();
487   QModelIndexList::const_iterator aIt;
488   for (aIt = aIndexes.constBegin(); aIt != aIndexes.constEnd(); ++aIt) {
489     if ((*aIt).column() == 0) {
490       ObjectPtr aObject = aModel->object(*aIt);
491       if (aObject) {
492         aList.append(aObject);
493         if (theIndexes)
494           theIndexes->append(*aIt);
495       }
496     }
497   }
498   return aList;
499 }
500
501 void XGUI_ObjectsBrowser::onBeforeReset()
502 {
503   myExpandedItems = expandedItems();
504 }
505
506 void XGUI_ObjectsBrowser::onAfterModelReset()
507 {
508   foreach(QModelIndex aIndex, myExpandedItems) {
509     myTreeView->setExpanded(aIndex, true);
510   }
511 }
512
513 std::list<bool> XGUI_ObjectsBrowser::getStateForDoc(DocumentPtr theDoc) const
514 {
515   std::list<bool> aStates;
516   XGUI_DataModel* aModel = dataModel();
517   QModelIndex aRootIdx = aModel->documentRootIndex(theDoc);
518   int aNbChild = aModel->rowCount(aRootIdx);
519   for (int i = 0; i < aNbChild; i++) {
520     QModelIndex aIdx = aModel->index(i, 0, aRootIdx);
521     aStates.push_back(myTreeView->isExpanded(aIdx));
522   }
523   return aStates;
524 }
525
526 void XGUI_ObjectsBrowser::setStateForDoc(DocumentPtr theDoc, const std::list<bool>& theStates)
527 {
528   if (theStates.size() == 0)
529     return;
530   XGUI_DataModel* aModel = dataModel();
531   QModelIndex aRootIdx = aModel->documentRootIndex(theDoc);
532   int aNbChild = aModel->rowCount(aRootIdx);
533
534   std::list<bool>::const_iterator aIt;
535   int i = 0;
536   for (aIt = theStates.cbegin(); aIt != theStates.cend(); aIt++, i++) {
537     if (i >= aNbChild )
538       break;
539     QModelIndex aIdx = aModel->index(i, 0, aRootIdx);
540     myTreeView->setExpanded(aIdx, (*aIt));
541   }
542 }