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