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