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