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 | |
96 | static 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 | |
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 | |
125 | 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 | |
150 | void ObjectInspectorTreeView::mouseMoveEvent(QMouseEvent *event) |
151 | { |
152 | event->ignore(); // suppress a range selection by dragging |
153 | } |
154 | |
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(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 | |
184 | class ObjectInspector::ObjectInspectorPrivate { |
185 | Q_DISABLE_COPY_MOVE(ObjectInspectorPrivate) |
186 | public: |
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 (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 | |
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(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 | |
269 | ObjectInspector::ObjectInspectorPrivate::~ObjectInspectorPrivate() |
270 | { |
271 | delete m_treeView->itemDelegate(); |
272 | } |
273 | |
274 | void ObjectInspector::ObjectInspectorPrivate::clearSelection() |
275 | { |
276 | m_withinClearSelection = true; |
277 | m_treeView->clearSelection(); |
278 | m_withinClearSelection = false; |
279 | } |
280 | |
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(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 | |
298 | void 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 | |
335 | void 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 | |
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 *>(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 | } |
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 *>(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 | |
402 | 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(alloc: srcIndexes.size()); |
408 | for (const auto &srcIndex : srcIndexes) |
409 | result.append(t: m_filterModel->mapFromSource(sourceIndex: srcIndex)); |
410 | } |
411 | return result; |
412 | } |
413 | |
414 | QObject *ObjectInspector::ObjectInspectorPrivate::objectAt(const QModelIndex &index) const |
415 | { |
416 | return m_model->objectAt(index: m_filterModel->mapToSource(proxyIndex: index)); |
417 | } |
418 | |
419 | bool 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 | |
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(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 | |
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' |
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 | |
483 | void 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 | |
495 | void 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 |
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(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 |
569 | static 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 | |
583 | void 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 |
593 | QObjectVector 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 |
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(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 | |
634 | void 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 | |
690 | 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(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 |
725 | static inline QMenu *(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 | |
740 | void ObjectInspector::ObjectInspectorPrivate::(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 * = createTaskMenu(object, fw: m_formWindow)) { |
747 | menu->exec(pos: m_treeView->viewport()->mapToGlobal(pos)); |
748 | delete menu; |
749 | } |
750 | } |
751 | } |
752 | |
753 | // ------------ ObjectInspector |
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(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 | |
776 | ObjectInspector::~ObjectInspector() |
777 | { |
778 | delete m_impl; |
779 | } |
780 | |
781 | QDesignerFormEditorInterface *ObjectInspector::core() const |
782 | { |
783 | return m_impl->core(); |
784 | } |
785 | |
786 | void ObjectInspector::(const QPoint &pos) |
787 | { |
788 | m_impl->slotPopupContextMenu(this, pos); |
789 | } |
790 | |
791 | void ObjectInspector::setFormWindow(QDesignerFormWindowInterface *fwi) |
792 | { |
793 | m_impl->setFormWindow(fwi); |
794 | } |
795 | |
796 | void ObjectInspector::slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected) |
797 | { |
798 | m_impl->slotSelectionChanged(selected, deselected); |
799 | } |
800 | |
801 | void ObjectInspector::getSelection(Selection &s) const |
802 | { |
803 | m_impl->getSelection(s); |
804 | } |
805 | |
806 | bool ObjectInspector::selectObject(QObject *o) |
807 | { |
808 | return m_impl->selectObject(o); |
809 | } |
810 | |
811 | void ObjectInspector::clearSelection() |
812 | { |
813 | m_impl->clearSelection(); |
814 | } |
815 | |
816 | void ObjectInspector::(int column) |
817 | { |
818 | m_impl->slotHeaderDoubleClicked(column); |
819 | } |
820 | |
821 | void ObjectInspector::mainContainerChanged() |
822 | { |
823 | // Invalidate references to objects kept in items |
824 | if (sender() == m_impl->formWindow()) |
825 | setFormWindow(nullptr); |
826 | } |
827 | |
828 | void ObjectInspector::dragEnterEvent (QDragEnterEvent * event) |
829 | { |
830 | m_impl->handleDragEnterMoveEvent(objectInspectorWidget: this, event, isDragEnter: true); |
831 | } |
832 | |
833 | void ObjectInspector::dragMoveEvent(QDragMoveEvent * event) |
834 | { |
835 | m_impl->handleDragEnterMoveEvent(objectInspectorWidget: this, event, isDragEnter: false); |
836 | } |
837 | |
838 | void ObjectInspector::dragLeaveEvent(QDragLeaveEvent * /* event*/) |
839 | { |
840 | m_impl->restoreDropHighlighting(); |
841 | } |
842 | |
843 | void ObjectInspector::dropEvent (QDropEvent * event) |
844 | { |
845 | m_impl->dropEvent(event); |
846 | |
847 | QT_END_NAMESPACE |
848 | } |
849 | } |
850 | |