1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "objectinspector.h"
30 #include "objectinspectormodel_p.h"
31 #include "formwindow.h"
32 
33 // sdk
34 #include <QtDesigner/abstractformeditor.h>
35 #include <QtDesigner/taskmenu.h>
36 #include <QtDesigner/qextensionmanager.h>
37 #include <QtDesigner/abstractformwindowcursor.h>
38 #include <QtDesigner/abstractformwindowmanager.h>
39 #include <QtDesigner/container.h>
40 #include <QtDesigner/abstractmetadatabase.h>
41 #include <QtDesigner/abstractpropertyeditor.h>
42 
43 // shared
44 #include <qdesigner_utils_p.h>
45 #include <formwindowbase_p.h>
46 #include <qdesigner_dnditem_p.h>
47 #include <textpropertyeditor_p.h>
48 #include <qdesigner_command_p.h>
49 #include <grid_p.h>
50 
51 // Qt
52 #include <QtWidgets/qapplication.h>
53 #include <QtWidgets/qheaderview.h>
54 #include <QtWidgets/qlineedit.h>
55 #include <QtWidgets/qscrollbar.h>
56 #include <QtGui/qpainter.h>
57 #include <QtWidgets/qboxlayout.h>
58 #include <QtCore/qitemselectionmodel.h>
59 #include <QtWidgets/qmenu.h>
60 #include <QtWidgets/qtreeview.h>
61 #include <QtWidgets/qstyleditemdelegate.h>
62 #include <QtGui/qevent.h>
63 
64 #include <QtCore/qsortfilterproxymodel.h>
65 #include <QtCore/qvector.h>
66 #include <QtCore/qdebug.h>
67 
68 QT_BEGIN_NAMESPACE
69 
70 namespace {
71     // Selections: Basically, ObjectInspector has to ensure a consistent
72     // selection, that is, either form-managed widgets (represented
73     // by the cursor interface selection), or unmanaged widgets/objects,
74     // for example actions, container pages, menu bars, tool bars
75     // and the like. The selection state of the latter is managed only in the object inspector.
76     // As soon as a managed widget is selected, unmanaged objects
77     // have to be unselected
78     // Normally, an empty selection is not allowed, the main container
79     // should be selected in this case (applyCursorSelection()).
80     // An exception is when clearSelection is called directly for example
81     // by the action editor that puts an unassociated action into the property
82     // editor. A hack exists to avoid the update in this case.
83 
84     enum SelectionType {
85         NoSelection,
86         // A QObject that has a meta database entry
87         QObjectSelection,
88         // Unmanaged widget, menu bar or the like
89         UnmanagedWidgetSelection,
90         // A widget managed by the form window cursor
91         ManagedWidgetSelection };
92 
93     using QObjectVector = QVector<QObject *>;
94 }
95 
selectionType(const QDesignerFormWindowInterface * fw,QObject * o)96 static inline SelectionType selectionType(const QDesignerFormWindowInterface *fw, QObject *o)
97 {
98     if (!o->isWidgetType())
99         return fw->core()->metaDataBase()->item(o) ?  QObjectSelection : NoSelection;
100     return fw->isManaged(qobject_cast<QWidget *>(o)) ? ManagedWidgetSelection :  UnmanagedWidgetSelection;
101 }
102 
103 // Return an offset for dropping (when dropping widgets on the object
104 // inspector, we fake a position on the form based on the widget dropped on).
105 // Position the dropped widget with form grid offset to avoid overlapping unless we
106 // drop on a layout. Position doesn't matter in the layout case
107 // and this enables us to drop on a squeezed layout widget of size zero
108 
dropPointOffset(const qdesigner_internal::FormWindowBase * fw,const QWidget * dropTarget)109 static inline QPoint dropPointOffset(const qdesigner_internal::FormWindowBase *fw, const QWidget *dropTarget)
110 {
111     if (!dropTarget || dropTarget->layout())
112         return QPoint(0, 0);
113     return QPoint(fw->designerGrid().deltaX(), fw->designerGrid().deltaY());
114 }
115 
116 namespace qdesigner_internal {
117 // Delegate with object name validator for the object name column
118 class ObjectInspectorDelegate : public QStyledItemDelegate {
119 public:
120     using QStyledItemDelegate::QStyledItemDelegate;
121 
122     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
123 };
124 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const125 QWidget *ObjectInspectorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex &index) const
126 {
127     if (index.column() != ObjectInspectorModel::ObjectNameColumn)
128         return QStyledItemDelegate::createEditor(parent, option, index);
129     // Object name editor
130     const bool isMainContainer = !index.parent().isValid();
131     return new TextPropertyEditor(parent, TextPropertyEditor::EmbeddingTreeView,
132                                   isMainContainer ? ValidationObjectNameScope : ValidationObjectName);
133 }
134 
135 // ------------ ObjectInspectorTreeView:
136 // - Makes the Space key start editing
137 // - Suppresses a range selection by dragging or Shift-up/down, which does not really work due
138 //   to the need to maintain a consistent selection.
139 
140 class ObjectInspectorTreeView : public QTreeView {
141 public:
142     using QTreeView::QTreeView;
143 
144 protected:
145     void mouseMoveEvent (QMouseEvent * event) override;
146     void keyPressEvent(QKeyEvent *event) override;
147 
148 };
149 
mouseMoveEvent(QMouseEvent * event)150 void ObjectInspectorTreeView::mouseMoveEvent(QMouseEvent *event)
151 {
152     event->ignore(); // suppress a range selection by dragging
153 }
154 
keyPressEvent(QKeyEvent * event)155 void ObjectInspectorTreeView::keyPressEvent(QKeyEvent *event)
156 {
157     bool handled = false;
158     switch (event->key()) {
159     case Qt::Key_Up:
160     case Qt::Key_Down: // suppress shift-up/down range selection
161         if (event->modifiers() & Qt::ShiftModifier) {
162             event->ignore();
163             handled = true;
164         }
165         break;
166     case Qt::Key_Space: { // Space pressed: Start editing
167         const QModelIndex index = currentIndex();
168         if (index.isValid() && index.column() == 0 && !model()->hasChildren(index) && model()->flags(index) & Qt::ItemIsEditable) {
169             event->accept();
170             handled = true;
171             edit(index);
172         }
173     }
174         break;
175     default:
176         break;
177     }
178     if (!handled)
179         QTreeView::keyPressEvent(event);
180 }
181 
182 // ------------ ObjectInspectorPrivate
183 
184 class ObjectInspector::ObjectInspectorPrivate {
185     Q_DISABLE_COPY_MOVE(ObjectInspectorPrivate)
186 public:
187     ObjectInspectorPrivate(QDesignerFormEditorInterface *core);
188     ~ObjectInspectorPrivate();
189 
filterLineEdit() const190     QLineEdit *filterLineEdit() const { return m_filterLineEdit; }
treeView() const191     QTreeView *treeView() const { return m_treeView; }
core() const192     QDesignerFormEditorInterface *core() const { return m_core; }
formWindow() const193     const QPointer<FormWindowBase> &formWindow() const { return m_formWindow; }
194 
195     void clear();
196     void setFormWindow(QDesignerFormWindowInterface *fwi);
197 
198     QWidget *managedWidgetAt(const QPoint &global_mouse_pos);
199 
200     void restoreDropHighlighting();
201     void handleDragEnterMoveEvent(const QWidget *objectInspectorWidget, QDragMoveEvent * event, bool isDragEnter);
202     void dropEvent (QDropEvent * event);
203 
204     void clearSelection();
205     bool selectObject(QObject *o);
206     void slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected);
207     void getSelection(Selection &s) const;
208 
209     QModelIndexList indexesOf(QObject *o) const;
210     QObject *objectAt(const QModelIndex &index) const;
211     QObjectVector indexesToObjects(const QModelIndexList &indexes) const;
212 
slotHeaderDoubleClicked(int column)213     void slotHeaderDoubleClicked(int column)       {  m_treeView->resizeColumnToContents(column); }
214     void slotPopupContextMenu(QWidget *parent, const QPoint &pos);
215 
216 private:
217     void setFormWindowBlocked(QDesignerFormWindowInterface *fwi);
218     void applyCursorSelection();
219     void synchronizeSelection(const QItemSelection & selected, const QItemSelection &deselected);
220     bool checkManagedWidgetSelection(const QModelIndexList &selection);
221     void showContainersCurrentPage(QWidget *widget);
222 
223     enum  SelectionFlags { AddToSelection = 1, MakeCurrent = 2};
224     void selectIndexRange(const QModelIndexList &indexes, unsigned flags);
225 
226     QDesignerFormEditorInterface *m_core;
227     QLineEdit *m_filterLineEdit;
228     QTreeView *m_treeView;
229     ObjectInspectorModel *m_model;
230     QSortFilterProxyModel *m_filterModel;
231     QPointer<FormWindowBase> m_formWindow;
232     QPointer<QWidget> m_formFakeDropTarget;
233     bool m_withinClearSelection;
234 };
235 
ObjectInspectorPrivate(QDesignerFormEditorInterface * core)236 ObjectInspector::ObjectInspectorPrivate::ObjectInspectorPrivate(QDesignerFormEditorInterface *core) :
237     m_core(core),
238     m_filterLineEdit(new QLineEdit),
239     m_treeView(new ObjectInspectorTreeView),
240     m_model(new ObjectInspectorModel(m_treeView)),
241     m_filterModel(new QSortFilterProxyModel(m_treeView)),
242     m_withinClearSelection(false)
243 {
244     m_filterModel->setRecursiveFilteringEnabled(true);
245     m_filterLineEdit->setPlaceholderText(ObjectInspector::tr("Filter"));
246     m_filterLineEdit->setClearButtonEnabled(true);
247     connect(m_filterLineEdit, &QLineEdit::textChanged,
248             m_filterModel, &QSortFilterProxyModel::setFilterFixedString);
249     // Filtering text collapses nodes, expand on clear.
250     connect(m_filterLineEdit, &QLineEdit::textChanged,
251             m_core, [this] (const QString &text) {
252                 if (text.isEmpty())
253                     this->m_treeView->expandAll();
254             });
255     m_filterModel->setSourceModel(m_model);
256     m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
257     m_treeView->setModel(m_filterModel);
258     m_treeView->setItemDelegate(new ObjectInspectorDelegate);
259     m_treeView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
260     m_treeView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
261     m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
262     m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
263     m_treeView->setAlternatingRowColors(true);
264     m_treeView->setTextElideMode (Qt::ElideMiddle);
265 
266     m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
267 }
268 
~ObjectInspectorPrivate()269 ObjectInspector::ObjectInspectorPrivate::~ObjectInspectorPrivate()
270 {
271     delete m_treeView->itemDelegate();
272 }
273 
clearSelection()274 void ObjectInspector::ObjectInspectorPrivate::clearSelection()
275 {
276     m_withinClearSelection = true;
277     m_treeView->clearSelection();
278     m_withinClearSelection = false;
279 }
280 
managedWidgetAt(const QPoint & global_mouse_pos)281 QWidget *ObjectInspector::ObjectInspectorPrivate::managedWidgetAt(const QPoint &global_mouse_pos)
282 {
283     if (!m_formWindow)
284         return nullptr;
285 
286     const  QPoint pos = m_treeView->viewport()->mapFromGlobal(global_mouse_pos);
287     QObject *o = objectAt(m_treeView->indexAt(pos));
288 
289     if (!o || !o->isWidgetType())
290         return nullptr;
291 
292     QWidget *rc = qobject_cast<QWidget *>(o);
293     if (!m_formWindow->isManaged(rc))
294         return nullptr;
295     return rc;
296 }
297 
showContainersCurrentPage(QWidget * widget)298 void ObjectInspector::ObjectInspectorPrivate::showContainersCurrentPage(QWidget *widget)
299 {
300     if (!widget)
301         return;
302 
303     FormWindow *fw = FormWindow::findFormWindow(widget);
304     if (!fw)
305         return;
306 
307     QWidget *w = widget->parentWidget();
308     bool macroStarted = false;
309     // Find a multipage container (tab widgets, etc.) in the hierarchy and set the right page.
310     while (w != nullptr) {
311         if (fw->isManaged(w) && !qobject_cast<QMainWindow *>(w)) { // Rule out unmanaged internal scroll areas, for example, on QToolBoxes.
312             if (QDesignerContainerExtension *c = qt_extension<QDesignerContainerExtension*>(m_core->extensionManager(), w)) {
313                 const int count = c->count();
314                 if (count > 1 && !c->widget(c->currentIndex())->isAncestorOf(widget)) {
315                     for (int i = 0; i < count; i++)
316                         if (c->widget(i)->isAncestorOf(widget)) {
317                             if (!macroStarted) {
318                                 macroStarted = true;
319                                 fw->beginCommand(tr("Change Current Page"));
320                             }
321                             ChangeCurrentPageCommand *cmd = new ChangeCurrentPageCommand(fw);
322                             cmd->init(w, i);
323                             fw->commandHistory()->push(cmd);
324                             break;
325                         }
326                 }
327             }
328         }
329         w = w->parentWidget();
330     }
331     if (macroStarted)
332         fw->endCommand();
333 }
334 
restoreDropHighlighting()335 void ObjectInspector::ObjectInspectorPrivate::restoreDropHighlighting()
336 {
337     if (m_formFakeDropTarget) {
338         if (m_formWindow) {
339             m_formWindow->highlightWidget(m_formFakeDropTarget, QPoint(5, 5), FormWindow::Restore);
340         }
341         m_formFakeDropTarget = nullptr;
342     }
343 }
344 
handleDragEnterMoveEvent(const QWidget * objectInspectorWidget,QDragMoveEvent * event,bool isDragEnter)345 void ObjectInspector::ObjectInspectorPrivate::handleDragEnterMoveEvent(const QWidget *objectInspectorWidget, QDragMoveEvent * event, bool isDragEnter)
346 {
347     if (!m_formWindow) {
348         event->ignore();
349         return;
350     }
351 
352     const QDesignerMimeData *mimeData =  qobject_cast<const QDesignerMimeData *>(event->mimeData());
353     if (!mimeData) {
354         event->ignore();
355         return;
356     }
357 
358     QWidget *dropTarget = nullptr;
359     QPoint fakeDropTargetOffset = QPoint(0, 0);
360     if (QWidget *managedWidget = managedWidgetAt(objectInspectorWidget->mapToGlobal(event->pos()))) {
361         fakeDropTargetOffset = dropPointOffset(m_formWindow, managedWidget);
362         // pretend we drag over the managed widget on the form
363         const QPoint fakeFormPos = m_formWindow->mapFromGlobal(managedWidget->mapToGlobal(fakeDropTargetOffset));
364         const FormWindowBase::WidgetUnderMouseMode wum = mimeData->items().size() == 1 ? FormWindowBase::FindSingleSelectionDropTarget : FormWindowBase::FindMultiSelectionDropTarget;
365         dropTarget = m_formWindow->widgetUnderMouse(fakeFormPos, wum);
366     }
367 
368     if (m_formFakeDropTarget && dropTarget != m_formFakeDropTarget)
369         m_formWindow->highlightWidget(m_formFakeDropTarget, fakeDropTargetOffset, FormWindow::Restore);
370 
371     m_formFakeDropTarget =  dropTarget;
372     if (m_formFakeDropTarget)
373         m_formWindow->highlightWidget(m_formFakeDropTarget, fakeDropTargetOffset, FormWindow::Highlight);
374 
375     // Do not refuse drag enter even if the area is not droppable
376     if (isDragEnter || m_formFakeDropTarget)
377         mimeData->acceptEvent(event);
378     else
379         event->ignore();
380 }
dropEvent(QDropEvent * event)381 void  ObjectInspector::ObjectInspectorPrivate::dropEvent (QDropEvent * event)
382 {
383     if (!m_formWindow || !m_formFakeDropTarget) {
384         event->ignore();
385         return;
386     }
387 
388     const QDesignerMimeData *mimeData =  qobject_cast<const QDesignerMimeData *>(event->mimeData());
389     if (!mimeData) {
390         event->ignore();
391         return;
392     }
393     const QPoint fakeGlobalDropFormPos = m_formFakeDropTarget->mapToGlobal(dropPointOffset(m_formWindow , m_formFakeDropTarget));
394     mimeData->moveDecoration(fakeGlobalDropFormPos + mimeData->hotSpot());
395     if (!m_formWindow->dropWidgets(mimeData->items(), m_formFakeDropTarget, fakeGlobalDropFormPos)) {
396         event->ignore();
397         return;
398     }
399     mimeData->acceptEvent(event);
400 }
401 
indexesOf(QObject * o) const402 QModelIndexList ObjectInspector::ObjectInspectorPrivate::indexesOf(QObject *o) const
403 {
404     QModelIndexList result;
405     const auto srcIndexes = m_model->indexesOf(o);
406     if (!srcIndexes.isEmpty()) {
407         result.reserve(srcIndexes.size());
408         for (const auto &srcIndex : srcIndexes)
409             result.append(m_filterModel->mapFromSource(srcIndex));
410     }
411     return result;
412 }
413 
objectAt(const QModelIndex & index) const414 QObject *ObjectInspector::ObjectInspectorPrivate::objectAt(const QModelIndex &index) const
415 {
416     return m_model->objectAt(m_filterModel->mapToSource(index));
417 }
418 
selectObject(QObject * o)419 bool ObjectInspector::ObjectInspectorPrivate::selectObject(QObject *o)
420 {
421     if (!m_core->metaDataBase()->item(o))
422         return false;
423 
424     using ModelIndexSet = QSet<QModelIndex>;
425 
426     const QModelIndexList objectIndexes = indexesOf(o);
427     if (objectIndexes.isEmpty())
428         return false;
429 
430     QItemSelectionModel *selectionModel = m_treeView->selectionModel();
431     const auto currentSelectedItemList = selectionModel->selectedRows(0);
432     const ModelIndexSet currentSelectedItems(currentSelectedItemList.cbegin(), currentSelectedItemList.cend());
433 
434     // Change in selection?
435     if (!currentSelectedItems.isEmpty()
436         && currentSelectedItems == ModelIndexSet(objectIndexes.cbegin(), objectIndexes.cend())) {
437         return true;
438     }
439 
440     // do select and update
441     selectIndexRange(objectIndexes, MakeCurrent);
442     return true;
443 }
444 
selectIndexRange(const QModelIndexList & indexes,unsigned flags)445 void ObjectInspector::ObjectInspectorPrivate::selectIndexRange(const QModelIndexList &indexes, unsigned flags)
446 {
447     if (indexes.isEmpty())
448         return;
449 
450     QItemSelectionModel::SelectionFlags selectFlags = QItemSelectionModel::Select|QItemSelectionModel::Rows;
451     if (!(flags & AddToSelection))
452         selectFlags |= QItemSelectionModel::Clear;
453     if (flags & MakeCurrent)
454         selectFlags |= QItemSelectionModel::Current;
455 
456     QItemSelectionModel *selectionModel = m_treeView->selectionModel();
457     const QModelIndexList::const_iterator cend = indexes.constEnd();
458     for (QModelIndexList::const_iterator it = indexes.constBegin(); it != cend; ++it)
459         if (it->column() == 0) {
460             selectionModel->select(*it, selectFlags);
461             selectFlags &= ~(QItemSelectionModel::Clear|QItemSelectionModel::Current);
462         }
463     if (flags & MakeCurrent)
464         m_treeView->scrollTo(indexes.constFirst(), QAbstractItemView::EnsureVisible);
465 }
466 
clear()467 void ObjectInspector::ObjectInspectorPrivate::clear()
468 {
469     m_formFakeDropTarget = nullptr;
470     m_formWindow = nullptr;
471 }
472 
473 // Form window cursor is in state 'main container only'
mainContainerIsCurrent(const QDesignerFormWindowInterface * fw)474 static inline bool mainContainerIsCurrent(const QDesignerFormWindowInterface *fw)
475 {
476     const QDesignerFormWindowCursorInterface *cursor = fw->cursor();
477     if (cursor->selectedWidgetCount() > 1)
478         return false;
479     const QWidget *current = cursor->current();
480     return current == fw || current == fw->mainContainer();
481 }
482 
setFormWindow(QDesignerFormWindowInterface * fwi)483 void ObjectInspector::ObjectInspectorPrivate::setFormWindow(QDesignerFormWindowInterface *fwi)
484 {
485     const bool blocked = m_treeView->selectionModel()->blockSignals(true);
486     {
487         UpdateBlocker ub(m_treeView);
488         setFormWindowBlocked(fwi);
489     }
490 
491     m_treeView->update();
492     m_treeView->selectionModel()->blockSignals(blocked);
493 }
494 
setFormWindowBlocked(QDesignerFormWindowInterface * fwi)495 void ObjectInspector::ObjectInspectorPrivate::setFormWindowBlocked(QDesignerFormWindowInterface *fwi)
496 {
497     FormWindowBase *fw = qobject_cast<FormWindowBase *>(fwi);
498     const bool formWindowChanged = m_formWindow != fw;
499 
500     m_formWindow = fw;
501 
502     const int oldWidth = m_treeView->columnWidth(0);
503     const int xoffset = m_treeView->horizontalScrollBar()->value();
504     const int yoffset = m_treeView->verticalScrollBar()->value();
505 
506     if (formWindowChanged)
507         m_formFakeDropTarget = nullptr;
508 
509     switch (m_model->update(m_formWindow)) {
510     case ObjectInspectorModel::NoForm:
511         clear();
512         return;
513     case ObjectInspectorModel::Rebuilt: // Complete rebuild: Just apply cursor selection
514         applyCursorSelection();
515         m_treeView->expandAll();
516         if (formWindowChanged) {
517             m_treeView->resizeColumnToContents(0);
518         } else {
519             m_treeView->setColumnWidth(0, oldWidth);
520             m_treeView->horizontalScrollBar()->setValue(xoffset);
521             m_treeView->verticalScrollBar()->setValue(yoffset);
522         }
523         break;
524     case ObjectInspectorModel::Updated: {
525         // Same structure (property changed or click on the form)
526         // We maintain a selection of unmanaged objects
527         // only if the cursor is in state "mainContainer() == current".
528         // and we have a non-managed selection.
529         // Else we take over the cursor selection.
530         bool applySelection = !mainContainerIsCurrent(m_formWindow);
531         if (!applySelection) {
532             const QModelIndexList currentIndexes = m_treeView->selectionModel()->selectedRows(0);
533             if (currentIndexes.isEmpty()) {
534                 applySelection = true;
535             } else {
536                 applySelection = selectionType(m_formWindow, objectAt(currentIndexes.constFirst())) == ManagedWidgetSelection;
537             }
538         }
539         if (applySelection)
540             applyCursorSelection();
541     }
542         break;
543     }
544 }
545 
546 // Apply selection of form window cursor to object inspector, set current
applyCursorSelection()547 void ObjectInspector::ObjectInspectorPrivate::applyCursorSelection()
548 {
549     const QDesignerFormWindowCursorInterface *cursor = m_formWindow->cursor();
550     const int count = cursor->selectedWidgetCount();
551     if (!count)
552         return;
553 
554     // Set the current widget first which also clears the selection
555     QWidget *currentWidget = cursor->current();
556     if (currentWidget)
557         selectIndexRange(indexesOf(currentWidget), MakeCurrent);
558     else
559         m_treeView->selectionModel()->clearSelection();
560 
561     for (int i = 0;i < count; i++) {
562         QWidget *widget = cursor->selectedWidget(i);
563         if (widget != currentWidget)
564             selectIndexRange(indexesOf(widget), AddToSelection);
565     }
566 }
567 
568 // Synchronize managed widget in the form (select in cursor). Block updates
selectInCursor(FormWindowBase * fw,const QObjectVector & objects,bool value)569 static int selectInCursor(FormWindowBase *fw, const QObjectVector &objects, bool value)
570 {
571     int rc = 0;
572     const bool blocked = fw->blockSelectionChanged(true);
573     const QObjectVector::const_iterator ocend = objects.constEnd();
574     for (QObjectVector::const_iterator it = objects.constBegin(); it != ocend; ++it)
575         if (selectionType(fw, *it) == ManagedWidgetSelection) {
576             fw->selectWidget(static_cast<QWidget *>(*it), value);
577             rc++;
578         }
579     fw->blockSelectionChanged(blocked);
580     return rc;
581 }
582 
slotSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)583 void ObjectInspector::ObjectInspectorPrivate::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
584 {
585     if (m_formWindow) {
586         synchronizeSelection(selected, deselected);
587         QMetaObject::invokeMethod(m_core->formWindowManager(), "slotUpdateActions");
588     }
589 }
590 
591 // Convert indexes to object vectors taking into account that
592 // some index lists are multicolumn ranges
indexesToObjects(const QModelIndexList & indexes) const593 QObjectVector ObjectInspector::ObjectInspectorPrivate::indexesToObjects(const QModelIndexList &indexes) const
594 {
595     if (indexes.isEmpty())
596         return  QObjectVector();
597     QObjectVector rc;
598     rc.reserve(indexes.size());
599     const QModelIndexList::const_iterator icend = indexes.constEnd();
600     for (QModelIndexList::const_iterator it = indexes.constBegin(); it != icend; ++it)
601         if (it->column() == 0)
602             rc.append(objectAt(*it));
603     return rc;
604 }
605 
606 // Check if any managed widgets are selected. If so, iterate over
607 // selection and deselect all unmanaged objects
checkManagedWidgetSelection(const QModelIndexList & rowSelection)608 bool ObjectInspector::ObjectInspectorPrivate::checkManagedWidgetSelection(const QModelIndexList &rowSelection)
609 {
610     bool isManagedWidgetSelection = false;
611     QItemSelectionModel *selectionModel = m_treeView->selectionModel();
612     const QModelIndexList::const_iterator cscend = rowSelection.constEnd();
613     for (QModelIndexList::const_iterator it = rowSelection.constBegin(); it != cscend; ++it) {
614         QObject *object = objectAt(*it);
615         if (selectionType(m_formWindow, object) == ManagedWidgetSelection) {
616             isManagedWidgetSelection = true;
617             break;
618         }
619     }
620 
621     if (!isManagedWidgetSelection)
622         return false;
623     // Need to unselect unmanaged ones
624     const bool blocked = selectionModel->blockSignals(true);
625     for (QModelIndexList::const_iterator it = rowSelection.constBegin(); it != cscend; ++it) {
626         QObject *object = objectAt(*it);
627         if (selectionType(m_formWindow, object) != ManagedWidgetSelection)
628             selectionModel->select(*it, QItemSelectionModel::Deselect|QItemSelectionModel::Rows);
629     }
630     selectionModel->blockSignals(blocked);
631     return true;
632 }
633 
synchronizeSelection(const QItemSelection & selectedSelection,const QItemSelection & deselectedSelection)634 void ObjectInspector::ObjectInspectorPrivate::synchronizeSelection(const QItemSelection & selectedSelection, const QItemSelection &deselectedSelection)
635 {
636     // Synchronize form window cursor.
637     const QObjectVector deselected = indexesToObjects(deselectedSelection.indexes());
638     const QObjectVector newlySelected = indexesToObjects(selectedSelection.indexes());
639 
640     const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(0);
641 
642     int deselectedManagedWidgetCount = 0;
643     if (!deselected.isEmpty())
644         deselectedManagedWidgetCount = selectInCursor(m_formWindow, deselected, false);
645 
646     if (newlySelected.isEmpty()) { // Nothing selected
647         if (currentSelectedIndexes.isEmpty()) // Do not allow a null-selection, reset to main container
648             m_formWindow->clearSelection(!m_withinClearSelection);
649         return;
650     }
651 
652     const int selectManagedWidgetCount = selectInCursor(m_formWindow, newlySelected, true);
653     // Check consistency: Make sure either  managed widgets or  unmanaged objects are selected.
654     // No newly-selected managed widgets: Unless there are ones in the (old) current selection,
655     // select the unmanaged object
656     if (selectManagedWidgetCount == 0) {
657         if (checkManagedWidgetSelection(currentSelectedIndexes)) {
658             // Managed selection exists, refuse and update if necessary
659             if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0)
660                 m_formWindow->emitSelectionChanged();
661             return;
662         }
663         // And now for the unmanaged selection
664         m_formWindow->clearSelection(false);
665         QObject *unmanagedObject = newlySelected.constFirst();
666         m_core->propertyEditor()->setObject(unmanagedObject);
667         m_core->propertyEditor()->setEnabled(true);
668         // open container page if it is a single widget
669         if (newlySelected.size() == 1 && unmanagedObject->isWidgetType())
670             showContainersCurrentPage(static_cast<QWidget*>(unmanagedObject));
671         return;
672     }
673     // Open container page if it is a single widget
674     if (newlySelected.size() == 1) {
675         QObject *object = newlySelected.constFirst();
676         if (object->isWidgetType())
677             showContainersCurrentPage(static_cast<QWidget*>(object));
678     }
679 
680     // A managed widget was newly selected. Make sure  there are no unmanaged objects
681     // in the whole unless just single selection
682     if (currentSelectedIndexes.size() > selectManagedWidgetCount)
683         checkManagedWidgetSelection(currentSelectedIndexes);
684     // Update form
685     if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0)
686         m_formWindow->emitSelectionChanged();
687 }
688 
689 
getSelection(Selection & s) const690 void ObjectInspector::ObjectInspectorPrivate::getSelection(Selection &s) const
691 {
692     s.clear();
693 
694     if (!m_formWindow)
695         return;
696 
697     const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(0);
698     if (currentSelectedIndexes.isEmpty())
699         return;
700 
701     // sort objects
702     for (const QModelIndex &index : currentSelectedIndexes) {
703         if (QObject *object = objectAt(index)) {
704             switch (selectionType(m_formWindow, object)) {
705             case NoSelection:
706                 break;
707             case QObjectSelection:
708                 // It is actually possible to select an action twice if it is in a menu bar
709                 // and in a tool bar.
710                 if (!s.objects.contains(object))
711                     s.objects.push_back(object);
712                 break;
713             case UnmanagedWidgetSelection:
714                 s.unmanaged.push_back(qobject_cast<QWidget *>(object));
715                 break;
716             case ManagedWidgetSelection:
717                 s.managed.push_back(qobject_cast<QWidget *>(object));
718                 break;
719             }
720         }
721     }
722 }
723 
724 // Utility to create a task menu
createTaskMenu(QObject * object,QDesignerFormWindowInterface * fw)725 static inline QMenu *createTaskMenu(QObject *object, QDesignerFormWindowInterface *fw)
726 {
727     // 1) Objects
728     if (!object->isWidgetType())
729         return FormWindowBase::createExtensionTaskMenu(fw, object, false);
730     // 2) Unmanaged widgets
731     QWidget *w = static_cast<QWidget *>(object);
732     if (!fw->isManaged(w))
733         return FormWindowBase::createExtensionTaskMenu(fw, w, false);
734     // 3) Mananaged widgets
735     if (qdesigner_internal::FormWindowBase *fwb = qobject_cast<qdesigner_internal::FormWindowBase*>(fw))
736         return fwb->initializePopupMenu(w);
737     return nullptr;
738 }
739 
slotPopupContextMenu(QWidget *,const QPoint & pos)740 void ObjectInspector::ObjectInspectorPrivate::slotPopupContextMenu(QWidget * /*parent*/, const QPoint &pos)
741 {
742     if (m_formWindow == nullptr || m_formWindow->currentTool() != 0)
743         return;
744 
745     if (QObject *object = objectAt(m_treeView->indexAt(pos))) {
746         if (QMenu *menu = createTaskMenu(object, m_formWindow)) {
747             menu->exec(m_treeView->viewport()->mapToGlobal(pos));
748             delete menu;
749         }
750     }
751 }
752 
753 // ------------ ObjectInspector
ObjectInspector(QDesignerFormEditorInterface * core,QWidget * parent)754 ObjectInspector::ObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent) :
755     QDesignerObjectInspector(parent),
756     m_impl(new ObjectInspectorPrivate(core))
757 {
758     QVBoxLayout *vbox = new QVBoxLayout(this);
759     vbox->setContentsMargins(QMargins());
760 
761     vbox->addWidget(m_impl->filterLineEdit());
762     QTreeView *treeView = m_impl->treeView();
763     vbox->addWidget(treeView);
764 
765     connect(treeView, &QWidget::customContextMenuRequested,
766             this, &ObjectInspector::slotPopupContextMenu);
767 
768     connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
769             this, &ObjectInspector::slotSelectionChanged);
770 
771     connect(treeView->header(), &QHeaderView::sectionDoubleClicked,
772             this, &ObjectInspector::slotHeaderDoubleClicked);
773     setAcceptDrops(true);
774 }
775 
~ObjectInspector()776 ObjectInspector::~ObjectInspector()
777 {
778     delete m_impl;
779 }
780 
core() const781 QDesignerFormEditorInterface *ObjectInspector::core() const
782 {
783     return m_impl->core();
784 }
785 
slotPopupContextMenu(const QPoint & pos)786 void ObjectInspector::slotPopupContextMenu(const QPoint &pos)
787 {
788     m_impl->slotPopupContextMenu(this, pos);
789 }
790 
setFormWindow(QDesignerFormWindowInterface * fwi)791 void ObjectInspector::setFormWindow(QDesignerFormWindowInterface *fwi)
792 {
793     m_impl->setFormWindow(fwi);
794 }
795 
slotSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)796 void ObjectInspector::slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected)
797 {
798     m_impl->slotSelectionChanged(selected, deselected);
799 }
800 
getSelection(Selection & s) const801 void ObjectInspector::getSelection(Selection &s) const
802 {
803     m_impl->getSelection(s);
804 }
805 
selectObject(QObject * o)806 bool ObjectInspector::selectObject(QObject *o)
807 {
808    return m_impl->selectObject(o);
809 }
810 
clearSelection()811 void ObjectInspector::clearSelection()
812 {
813     m_impl->clearSelection();
814 }
815 
slotHeaderDoubleClicked(int column)816 void ObjectInspector::slotHeaderDoubleClicked(int column)
817 {
818     m_impl->slotHeaderDoubleClicked(column);
819 }
820 
mainContainerChanged()821 void ObjectInspector::mainContainerChanged()
822 {
823     // Invalidate references to objects kept in items
824     if (sender() == m_impl->formWindow())
825         setFormWindow(nullptr);
826 }
827 
dragEnterEvent(QDragEnterEvent * event)828 void  ObjectInspector::dragEnterEvent (QDragEnterEvent * event)
829 {
830     m_impl->handleDragEnterMoveEvent(this, event, true);
831 }
832 
dragMoveEvent(QDragMoveEvent * event)833 void  ObjectInspector::dragMoveEvent(QDragMoveEvent * event)
834 {
835     m_impl->handleDragEnterMoveEvent(this, event, false);
836 }
837 
dragLeaveEvent(QDragLeaveEvent *)838 void  ObjectInspector::dragLeaveEvent(QDragLeaveEvent * /* event*/)
839 {
840     m_impl->restoreDropHighlighting();
841 }
842 
dropEvent(QDropEvent * event)843 void  ObjectInspector::dropEvent (QDropEvent * event)
844 {
845     m_impl->dropEvent(event);
846 
847 QT_END_NAMESPACE
848 }
849 }
850