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
68QT_BEGIN_NAMESPACE
69
70namespace {
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
96static inline SelectionType selectionType(const QDesignerFormWindowInterface *fw, QObject *o)
97{
98 if (!o->isWidgetType())
99 return fw->core()->metaDataBase()->item(object: o) ? QObjectSelection : NoSelection;
100 return fw->isManaged(widget: 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
109static 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
116namespace qdesigner_internal {
117// Delegate with object name validator for the object name column
118class ObjectInspectorDelegate : public QStyledItemDelegate {
119public:
120 using QStyledItemDelegate::QStyledItemDelegate;
121
122 QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
123};
124
125QWidget *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
140class ObjectInspectorTreeView : public QTreeView {
141public:
142 using QTreeView::QTreeView;
143
144protected:
145 void mouseMoveEvent (QMouseEvent * event) override;
146 void keyPressEvent(QKeyEvent *event) override;
147
148};
149
150void ObjectInspectorTreeView::mouseMoveEvent(QMouseEvent *event)
151{
152 event->ignore(); // suppress a range selection by dragging
153}
154
155void 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(parent: 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
184class ObjectInspector::ObjectInspectorPrivate {
185 Q_DISABLE_COPY_MOVE(ObjectInspectorPrivate)
186public:
187 ObjectInspectorPrivate(QDesignerFormEditorInterface *core);
188 ~ObjectInspectorPrivate();
189
190 QLineEdit *filterLineEdit() const { return m_filterLineEdit; }
191 QTreeView *treeView() const { return m_treeView; }
192 QDesignerFormEditorInterface *core() const { return m_core; }
193 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
213 void slotHeaderDoubleClicked(int column) { m_treeView->resizeColumnToContents(column); }
214 void slotPopupContextMenu(QWidget *parent, const QPoint &pos);
215
216private:
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
236ObjectInspector::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(s: "Filter"));
246 m_filterLineEdit->setClearButtonEnabled(true);
247 connect(sender: m_filterLineEdit, signal: &QLineEdit::textChanged,
248 receiver: m_filterModel, slot: &QSortFilterProxyModel::setFilterFixedString);
249 // Filtering text collapses nodes, expand on clear.
250 connect(sender: m_filterLineEdit, signal: &QLineEdit::textChanged,
251 context: m_core, slot: [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(logicalIndex: 1, mode: 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
269ObjectInspector::ObjectInspectorPrivate::~ObjectInspectorPrivate()
270{
271 delete m_treeView->itemDelegate();
272}
273
274void ObjectInspector::ObjectInspectorPrivate::clearSelection()
275{
276 m_withinClearSelection = true;
277 m_treeView->clearSelection();
278 m_withinClearSelection = false;
279}
280
281QWidget *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(index: m_treeView->indexAt(p: pos));
288
289 if (!o || !o->isWidgetType())
290 return nullptr;
291
292 QWidget *rc = qobject_cast<QWidget *>(o);
293 if (!m_formWindow->isManaged(widget: rc))
294 return nullptr;
295 return rc;
296}
297
298void ObjectInspector::ObjectInspectorPrivate::showContainersCurrentPage(QWidget *widget)
299{
300 if (!widget)
301 return;
302
303 FormWindow *fw = FormWindow::findFormWindow(w: 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 *>(object: w)) { // Rule out unmanaged internal scroll areas, for example, on QToolBoxes.
312 if (QDesignerContainerExtension *c = qt_extension<QDesignerContainerExtension*>(manager: m_core->extensionManager(), object: w)) {
313 const int count = c->count();
314 if (count > 1 && !c->widget(index: c->currentIndex())->isAncestorOf(child: widget)) {
315 for (int i = 0; i < count; i++)
316 if (c->widget(index: i)->isAncestorOf(child: widget)) {
317 if (!macroStarted) {
318 macroStarted = true;
319 fw->beginCommand(description: tr(s: "Change Current Page"));
320 }
321 ChangeCurrentPageCommand *cmd = new ChangeCurrentPageCommand(fw);
322 cmd->init(containerWidget: w, newIndex: 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
335void ObjectInspector::ObjectInspectorPrivate::restoreDropHighlighting()
336{
337 if (m_formFakeDropTarget) {
338 if (m_formWindow) {
339 m_formWindow->highlightWidget(w: m_formFakeDropTarget, pos: QPoint(5, 5), mode: FormWindow::Restore);
340 }
341 m_formFakeDropTarget = nullptr;
342 }
343}
344
345void 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 *>(object: 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(global_mouse_pos: objectInspectorWidget->mapToGlobal(event->pos()))) {
361 fakeDropTargetOffset = dropPointOffset(fw: m_formWindow, dropTarget: 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(formPos: fakeFormPos, m: wum);
366 }
367
368 if (m_formFakeDropTarget && dropTarget != m_formFakeDropTarget)
369 m_formWindow->highlightWidget(w: m_formFakeDropTarget, pos: fakeDropTargetOffset, mode: FormWindow::Restore);
370
371 m_formFakeDropTarget = dropTarget;
372 if (m_formFakeDropTarget)
373 m_formWindow->highlightWidget(w: m_formFakeDropTarget, pos: fakeDropTargetOffset, mode: FormWindow::Highlight);
374
375 // Do not refuse drag enter even if the area is not droppable
376 if (isDragEnter || m_formFakeDropTarget)
377 mimeData->acceptEvent(e: event);
378 else
379 event->ignore();
380}
381void 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 *>(object: event->mimeData());
389 if (!mimeData) {
390 event->ignore();
391 return;
392 }
393 const QPoint fakeGlobalDropFormPos = m_formFakeDropTarget->mapToGlobal(dropPointOffset(fw: m_formWindow , dropTarget: m_formFakeDropTarget));
394 mimeData->moveDecoration(globalPos: fakeGlobalDropFormPos + mimeData->hotSpot());
395 if (!m_formWindow->dropWidgets(item_list: mimeData->items(), target: m_formFakeDropTarget, global_mouse_pos: fakeGlobalDropFormPos)) {
396 event->ignore();
397 return;
398 }
399 mimeData->acceptEvent(e: event);
400}
401
402QModelIndexList 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(alloc: srcIndexes.size());
408 for (const auto &srcIndex : srcIndexes)
409 result.append(t: m_filterModel->mapFromSource(sourceIndex: srcIndex));
410 }
411 return result;
412}
413
414QObject *ObjectInspector::ObjectInspectorPrivate::objectAt(const QModelIndex &index) const
415{
416 return m_model->objectAt(index: m_filterModel->mapToSource(proxyIndex: index));
417}
418
419bool ObjectInspector::ObjectInspectorPrivate::selectObject(QObject *o)
420{
421 if (!m_core->metaDataBase()->item(object: 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(column: 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(indexes: objectIndexes, flags: MakeCurrent);
442 return true;
443}
444
445void 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(index: *it, command: selectFlags);
461 selectFlags &= ~(QItemSelectionModel::Clear|QItemSelectionModel::Current);
462 }
463 if (flags & MakeCurrent)
464 m_treeView->scrollTo(index: indexes.constFirst(), hint: QAbstractItemView::EnsureVisible);
465}
466
467void ObjectInspector::ObjectInspectorPrivate::clear()
468{
469 m_formFakeDropTarget = nullptr;
470 m_formWindow = nullptr;
471}
472
473// Form window cursor is in state 'main container only'
474static 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
483void ObjectInspector::ObjectInspectorPrivate::setFormWindow(QDesignerFormWindowInterface *fwi)
484{
485 const bool blocked = m_treeView->selectionModel()->blockSignals(b: true);
486 {
487 UpdateBlocker ub(m_treeView);
488 setFormWindowBlocked(fwi);
489 }
490
491 m_treeView->update();
492 m_treeView->selectionModel()->blockSignals(b: blocked);
493}
494
495void ObjectInspector::ObjectInspectorPrivate::setFormWindowBlocked(QDesignerFormWindowInterface *fwi)
496{
497 FormWindowBase *fw = qobject_cast<FormWindowBase *>(object: fwi);
498 const bool formWindowChanged = m_formWindow != fw;
499
500 m_formWindow = fw;
501
502 const int oldWidth = m_treeView->columnWidth(column: 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(fw: 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(column: 0);
518 } else {
519 m_treeView->setColumnWidth(column: 0, width: 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(fw: m_formWindow);
531 if (!applySelection) {
532 const QModelIndexList currentIndexes = m_treeView->selectionModel()->selectedRows(column: 0);
533 if (currentIndexes.isEmpty()) {
534 applySelection = true;
535 } else {
536 applySelection = selectionType(fw: m_formWindow, o: objectAt(index: 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
547void 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(indexes: indexesOf(o: currentWidget), flags: MakeCurrent);
558 else
559 m_treeView->selectionModel()->clearSelection();
560
561 for (int i = 0;i < count; i++) {
562 QWidget *widget = cursor->selectedWidget(index: i);
563 if (widget != currentWidget)
564 selectIndexRange(indexes: indexesOf(o: widget), flags: AddToSelection);
565 }
566}
567
568// Synchronize managed widget in the form (select in cursor). Block updates
569static int selectInCursor(FormWindowBase *fw, const QObjectVector &objects, bool value)
570{
571 int rc = 0;
572 const bool blocked = fw->blockSelectionChanged(blocked: true);
573 const QObjectVector::const_iterator ocend = objects.constEnd();
574 for (QObjectVector::const_iterator it = objects.constBegin(); it != ocend; ++it)
575 if (selectionType(fw, o: *it) == ManagedWidgetSelection) {
576 fw->selectWidget(w: static_cast<QWidget *>(*it), select: value);
577 rc++;
578 }
579 fw->blockSelectionChanged(blocked);
580 return rc;
581}
582
583void ObjectInspector::ObjectInspectorPrivate::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
584{
585 if (m_formWindow) {
586 synchronizeSelection(selected, deselected);
587 QMetaObject::invokeMethod(obj: m_core->formWindowManager(), member: "slotUpdateActions");
588 }
589}
590
591// Convert indexes to object vectors taking into account that
592// some index lists are multicolumn ranges
593QObjectVector ObjectInspector::ObjectInspectorPrivate::indexesToObjects(const QModelIndexList &indexes) const
594{
595 if (indexes.isEmpty())
596 return QObjectVector();
597 QObjectVector rc;
598 rc.reserve(asize: 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(t: objectAt(index: *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
608bool 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(index: *it);
615 if (selectionType(fw: m_formWindow, o: 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(b: true);
625 for (QModelIndexList::const_iterator it = rowSelection.constBegin(); it != cscend; ++it) {
626 QObject *object = objectAt(index: *it);
627 if (selectionType(fw: m_formWindow, o: object) != ManagedWidgetSelection)
628 selectionModel->select(index: *it, command: QItemSelectionModel::Deselect|QItemSelectionModel::Rows);
629 }
630 selectionModel->blockSignals(b: blocked);
631 return true;
632}
633
634void ObjectInspector::ObjectInspectorPrivate::synchronizeSelection(const QItemSelection & selectedSelection, const QItemSelection &deselectedSelection)
635{
636 // Synchronize form window cursor.
637 const QObjectVector deselected = indexesToObjects(indexes: deselectedSelection.indexes());
638 const QObjectVector newlySelected = indexesToObjects(indexes: selectedSelection.indexes());
639
640 const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(column: 0);
641
642 int deselectedManagedWidgetCount = 0;
643 if (!deselected.isEmpty())
644 deselectedManagedWidgetCount = selectInCursor(fw: m_formWindow, objects: deselected, value: 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(changePropertyDisplay: !m_withinClearSelection);
649 return;
650 }
651
652 const int selectManagedWidgetCount = selectInCursor(fw: m_formWindow, objects: newlySelected, value: 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(rowSelection: 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(changePropertyDisplay: 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(widget: 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(widget: 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(rowSelection: currentSelectedIndexes);
684 // Update form
685 if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0)
686 m_formWindow->emitSelectionChanged();
687}
688
689
690void 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(column: 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(fw: m_formWindow, o: 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(t: object))
711 s.objects.push_back(t: object);
712 break;
713 case UnmanagedWidgetSelection:
714 s.unmanaged.push_back(t: qobject_cast<QWidget *>(o: object));
715 break;
716 case ManagedWidgetSelection:
717 s.managed.push_back(t: qobject_cast<QWidget *>(o: object));
718 break;
719 }
720 }
721 }
722}
723
724// Utility to create a task menu
725static inline QMenu *createTaskMenu(QObject *object, QDesignerFormWindowInterface *fw)
726{
727 // 1) Objects
728 if (!object->isWidgetType())
729 return FormWindowBase::createExtensionTaskMenu(fw, o: object, trailingSeparator: false);
730 // 2) Unmanaged widgets
731 QWidget *w = static_cast<QWidget *>(object);
732 if (!fw->isManaged(widget: w))
733 return FormWindowBase::createExtensionTaskMenu(fw, o: w, trailingSeparator: false);
734 // 3) Mananaged widgets
735 if (qdesigner_internal::FormWindowBase *fwb = qobject_cast<qdesigner_internal::FormWindowBase*>(object: fw))
736 return fwb->initializePopupMenu(managedWidget: w);
737 return nullptr;
738}
739
740void 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(index: m_treeView->indexAt(p: pos))) {
746 if (QMenu *menu = createTaskMenu(object, fw: m_formWindow)) {
747 menu->exec(pos: m_treeView->viewport()->mapToGlobal(pos));
748 delete menu;
749 }
750 }
751}
752
753// ------------ ObjectInspector
754ObjectInspector::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(sender: treeView, signal: &QWidget::customContextMenuRequested,
766 receiver: this, slot: &ObjectInspector::slotPopupContextMenu);
767
768 connect(sender: treeView->selectionModel(), signal: &QItemSelectionModel::selectionChanged,
769 receiver: this, slot: &ObjectInspector::slotSelectionChanged);
770
771 connect(sender: treeView->header(), signal: &QHeaderView::sectionDoubleClicked,
772 receiver: this, slot: &ObjectInspector::slotHeaderDoubleClicked);
773 setAcceptDrops(true);
774}
775
776ObjectInspector::~ObjectInspector()
777{
778 delete m_impl;
779}
780
781QDesignerFormEditorInterface *ObjectInspector::core() const
782{
783 return m_impl->core();
784}
785
786void ObjectInspector::slotPopupContextMenu(const QPoint &pos)
787{
788 m_impl->slotPopupContextMenu(this, pos);
789}
790
791void ObjectInspector::setFormWindow(QDesignerFormWindowInterface *fwi)
792{
793 m_impl->setFormWindow(fwi);
794}
795
796void ObjectInspector::slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected)
797{
798 m_impl->slotSelectionChanged(selected, deselected);
799}
800
801void ObjectInspector::getSelection(Selection &s) const
802{
803 m_impl->getSelection(s);
804}
805
806bool ObjectInspector::selectObject(QObject *o)
807{
808 return m_impl->selectObject(o);
809}
810
811void ObjectInspector::clearSelection()
812{
813 m_impl->clearSelection();
814}
815
816void ObjectInspector::slotHeaderDoubleClicked(int column)
817{
818 m_impl->slotHeaderDoubleClicked(column);
819}
820
821void ObjectInspector::mainContainerChanged()
822{
823 // Invalidate references to objects kept in items
824 if (sender() == m_impl->formWindow())
825 setFormWindow(nullptr);
826}
827
828void ObjectInspector::dragEnterEvent (QDragEnterEvent * event)
829{
830 m_impl->handleDragEnterMoveEvent(objectInspectorWidget: this, event, isDragEnter: true);
831}
832
833void ObjectInspector::dragMoveEvent(QDragMoveEvent * event)
834{
835 m_impl->handleDragEnterMoveEvent(objectInspectorWidget: this, event, isDragEnter: false);
836}
837
838void ObjectInspector::dragLeaveEvent(QDragLeaveEvent * /* event*/)
839{
840 m_impl->restoreDropHighlighting();
841}
842
843void ObjectInspector::dropEvent (QDropEvent * event)
844{
845 m_impl->dropEvent(event);
846
847QT_END_NAMESPACE
848}
849}
850

source code of qttools/src/designer/src/components/objectinspector/objectinspector.cpp