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 "formwindow.h"
30#include "formeditor.h"
31#include "formwindow_dnditem.h"
32#include "formwindow_widgetstack.h"
33#include "formwindowcursor.h"
34#include "formwindowmanager.h"
35#include "tool_widgeteditor.h"
36#include "widgetselection.h"
37#include "qtresourcemodel_p.h"
38#include "widgetfactory_p.h"
39
40// shared
41#include <metadatabase_p.h>
42#include <qdesigner_tabwidget_p.h>
43#include <qdesigner_toolbox_p.h>
44#include <qdesigner_stackedbox_p.h>
45#include <qdesigner_resource.h>
46#include <qdesigner_command_p.h>
47#include <qdesigner_command2_p.h>
48#include <qdesigner_propertycommand_p.h>
49#include <qdesigner_taskmenu_p.h>
50#include <qdesigner_widget_p.h>
51#include <qdesigner_utils_p.h>
52#include <qlayout_widget_p.h>
53#include <spacer_widget_p.h>
54#include <invisible_widget_p.h>
55#include <layoutinfo_p.h>
56#include <qdesigner_objectinspector_p.h>
57#include <connectionedit_p.h>
58#include <actionprovider_p.h>
59#include <private/ui4_p.h>
60#include <deviceprofile_p.h>
61#include <shared_settings_p.h>
62#include <grid_p.h>
63
64#include <QtDesigner/qextensionmanager.h>
65#include <QtDesigner/abstractwidgetdatabase.h>
66#include <QtDesigner/propertysheet.h>
67#include <QtDesigner/abstractwidgetfactory.h>
68#include <QtDesigner/container.h>
69#include <QtDesigner/taskmenu.h>
70#include <QtDesigner/abstractwidgetbox.h>
71#include <QtDesigner/private/ui4_p.h>
72
73#include <abstractdialoggui_p.h>
74
75#include <QtCore/qdebug.h>
76#include <QtCore/qbuffer.h>
77#include <QtCore/qtimer.h>
78#include <QtCore/qvector.h>
79#include <QtCore/qxmlstream.h>
80#include <QtWidgets/qmenu.h>
81#include <QtWidgets/qaction.h>
82#include <QtWidgets/qactiongroup.h>
83#if QT_CONFIG(clipboard)
84#include <QtGui/qclipboard.h>
85#endif
86#include <QtWidgets/qundogroup.h>
87#include <QtWidgets/qscrollarea.h>
88#include <QtWidgets/qrubberband.h>
89#include <QtWidgets/qapplication.h>
90#include <QtWidgets/qsplitter.h>
91#include <QtGui/qpainter.h>
92#include <QtWidgets/qgroupbox.h>
93#include <QtWidgets/qdockwidget.h>
94#include <QtWidgets/qtoolbox.h>
95#include <QtWidgets/qstackedwidget.h>
96#include <QtWidgets/qtabwidget.h>
97#include <QtWidgets/qbuttongroup.h>
98
99Q_DECLARE_METATYPE(QWidget*)
100
101QT_BEGIN_NAMESPACE
102
103namespace {
104class BlockSelection
105{
106 Q_DISABLE_COPY_MOVE(BlockSelection)
107public:
108 BlockSelection(qdesigner_internal::FormWindow *fw)
109 : m_formWindow(fw),
110 m_blocked(m_formWindow->blockSelectionChanged(blocked: true))
111 {
112 }
113
114 ~BlockSelection()
115 {
116 if (m_formWindow)
117 m_formWindow->blockSelectionChanged(blocked: m_blocked);
118 }
119
120private:
121 QPointer<qdesigner_internal::FormWindow> m_formWindow;
122 const bool m_blocked;
123};
124
125enum { debugFormWindow = 0 };
126}
127
128namespace qdesigner_internal {
129
130// ------------------------ FormWindow::Selection
131// Maintains a pool of WidgetSelections to be used for selected widgets.
132
133class FormWindow::Selection
134{
135 Q_DISABLE_COPY_MOVE(Selection)
136public:
137 Selection();
138 ~Selection();
139
140 // Clear
141 void clear();
142
143 // Also clear out the pool. Call if reparenting of the main container occurs.
144 void clearSelectionPool();
145
146 void repaintSelection(QWidget *w);
147 void repaintSelection();
148
149 bool isWidgetSelected(QWidget *w) const;
150 QWidgetList selectedWidgets() const;
151
152 WidgetSelection *addWidget(FormWindow* fw, QWidget *w);
153 // remove widget, return new current widget or 0
154 QWidget* removeWidget(QWidget *w);
155
156 void raiseList(const QWidgetList& l);
157 void raiseWidget(QWidget *w);
158
159 void updateGeometry(QWidget *w);
160
161 void hide(QWidget *w);
162 void show(QWidget *w);
163
164private:
165
166 using SelectionPool = QVector<WidgetSelection *>;
167 SelectionPool m_selectionPool;
168
169 typedef QHash<QWidget *, WidgetSelection *> SelectionHash;
170 SelectionHash m_usedSelections;
171};
172
173FormWindow::Selection::Selection() = default;
174
175FormWindow::Selection::~Selection()
176{
177 clearSelectionPool();
178}
179
180void FormWindow::Selection::clear()
181{
182 if (!m_usedSelections.isEmpty()) {
183 for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it)
184 it.value()->setWidget(nullptr);
185 m_usedSelections.clear();
186 }
187}
188
189void FormWindow::Selection::clearSelectionPool()
190{
191 clear();
192 qDeleteAll(c: m_selectionPool);
193 m_selectionPool.clear();
194}
195
196WidgetSelection *FormWindow::Selection::addWidget(FormWindow* fw, QWidget *w)
197{
198 WidgetSelection *rc = m_usedSelections.value(akey: w);
199 if (rc != nullptr) {
200 rc->show();
201 rc->updateActive();
202 return rc;
203 }
204 // find a free one in the pool
205 for (auto it = m_selectionPool.constBegin(), pend = m_selectionPool.constEnd(); it != pend; ++it) {
206 if (! (*it)->isUsed()) {
207 rc = *it;
208 break;
209 }
210 }
211
212 if (rc == nullptr) {
213 rc = new WidgetSelection(fw);
214 m_selectionPool.push_back(t: rc);
215 }
216
217 m_usedSelections.insert(akey: w, avalue: rc);
218 rc->setWidget(w);
219 return rc;
220}
221
222QWidget* FormWindow::Selection::removeWidget(QWidget *w)
223{
224 WidgetSelection *s = m_usedSelections.value(akey: w);
225 if (!s)
226 return w;
227
228 s->setWidget(nullptr);
229 m_usedSelections.remove(akey: w);
230
231 if (m_usedSelections.isEmpty())
232 return nullptr;
233
234 return (*m_usedSelections.begin())->widget();
235}
236
237void FormWindow::Selection::repaintSelection(QWidget *w)
238{
239 if (WidgetSelection *s = m_usedSelections.value(akey: w))
240 s->update();
241}
242
243void FormWindow::Selection::repaintSelection()
244{
245 for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it)
246 it.value()->update();
247}
248
249bool FormWindow::Selection::isWidgetSelected(QWidget *w) const{
250 return m_usedSelections.contains(akey: w);
251}
252
253QWidgetList FormWindow::Selection::selectedWidgets() const
254{
255 return m_usedSelections.keys();
256}
257
258void FormWindow::Selection::raiseList(const QWidgetList& l)
259{
260 for (auto it = m_usedSelections.constBegin(), mend = m_usedSelections.constEnd(); it != mend; ++it) {
261 WidgetSelection *w = it.value();
262 if (l.contains(t: w->widget()))
263 w->show();
264 }
265}
266
267void FormWindow::Selection::raiseWidget(QWidget *w)
268{
269 if (WidgetSelection *s = m_usedSelections.value(akey: w))
270 s->show();
271}
272
273void FormWindow::Selection::updateGeometry(QWidget *w)
274{
275 if (WidgetSelection *s = m_usedSelections.value(akey: w)) {
276 s->updateGeometry();
277 }
278}
279
280void FormWindow::Selection::hide(QWidget *w)
281{
282 if (WidgetSelection *s = m_usedSelections.value(akey: w))
283 s->hide();
284}
285
286void FormWindow::Selection::show(QWidget *w)
287{
288 if (WidgetSelection *s = m_usedSelections.value(akey: w))
289 s->show();
290}
291
292// ------------------------ FormWindow
293FormWindow::FormWindow(FormEditor *core, QWidget *parent, Qt::WindowFlags flags) :
294 FormWindowBase(core, parent, flags),
295 m_mouseState(NoMouseState),
296 m_core(core),
297 m_selection(new Selection),
298 m_widgetStack(new FormWindowWidgetStack(this)),
299 m_contextMenuPosition(-1, -1)
300{
301 // Apply settings to formcontainer
302 deviceProfile().apply(core, widget: m_widgetStack->formContainer(), am: qdesigner_internal::DeviceProfile::ApplyFormParent);
303
304 setLayout(m_widgetStack->layout());
305 init();
306
307 m_cursor = new FormWindowCursor(this, this);
308
309 core->formWindowManager()->addFormWindow(formWindow: this);
310
311 setDirty(false);
312 setAcceptDrops(true);
313}
314
315FormWindow::~FormWindow()
316{
317 Q_ASSERT(core() != nullptr);
318 Q_ASSERT(core()->metaDataBase() != nullptr);
319 Q_ASSERT(core()->formWindowManager() != nullptr);
320
321 core()->formWindowManager()->removeFormWindow(formWindow: this);
322 core()->metaDataBase()->remove(object: this);
323
324 const QWidgetList &l = widgets();
325 for (QWidget *w : l)
326 core()->metaDataBase()->remove(object: w);
327
328 m_widgetStack = nullptr;
329 m_rubberBand = nullptr;
330 if (resourceSet())
331 core()->resourceModel()->removeResourceSet(resourceSet: resourceSet());
332 delete m_selection;
333
334 if (FormWindowManager *manager = qobject_cast<FormWindowManager*> (object: core()->formWindowManager()))
335 manager->undoGroup()->removeStack(stack: &m_undoStack);
336 m_undoStack.disconnect();
337}
338
339QDesignerFormEditorInterface *FormWindow::core() const
340{
341 return m_core;
342}
343
344QDesignerFormWindowCursorInterface *FormWindow::cursor() const
345{
346 return m_cursor;
347}
348
349void FormWindow::updateWidgets()
350{
351 if (!m_mainContainer)
352 return;
353}
354
355int FormWindow::widgetDepth(const QWidget *w)
356{
357 int d = -1;
358 while (w && !w->isWindow()) {
359 d++;
360 w = w->parentWidget();
361 }
362
363 return d;
364}
365
366bool FormWindow::isChildOf(const QWidget *c, const QWidget *p)
367{
368 while (c) {
369 if (c == p)
370 return true;
371 c = c->parentWidget();
372 }
373 return false;
374}
375
376void FormWindow::setCursorToAll(const QCursor &c, QWidget *start)
377{
378#if QT_CONFIG(cursor)
379 start->setCursor(c);
380 const QWidgetList widgets = start->findChildren<QWidget*>();
381 for (QWidget *widget : widgets) {
382 if (!qobject_cast<WidgetHandle*>(object: widget)) {
383 widget->setCursor(c);
384 }
385 }
386#endif
387}
388
389void FormWindow::init()
390{
391 if (FormWindowManager *manager = qobject_cast<FormWindowManager*> (object: core()->formWindowManager())) {
392 manager->undoGroup()->addStack(stack: &m_undoStack);
393 }
394
395 m_blockSelectionChanged = false;
396
397 m_defaultMargin = INT_MIN;
398 m_defaultSpacing = INT_MIN;
399
400 connect(sender: m_widgetStack, signal: &FormWindowWidgetStack::currentToolChanged,
401 receiver: this, slot: &QDesignerFormWindowInterface::toolChanged);
402
403 m_selectionChangedTimer = new QTimer(this);
404 m_selectionChangedTimer->setSingleShot(true);
405 connect(sender: m_selectionChangedTimer, signal: &QTimer::timeout, receiver: this,
406 slot: &FormWindow::selectionChangedTimerDone);
407
408 m_checkSelectionTimer = new QTimer(this);
409 m_checkSelectionTimer->setSingleShot(true);
410 connect(sender: m_checkSelectionTimer, signal: &QTimer::timeout,
411 receiver: this, slot: &FormWindow::checkSelectionNow);
412
413 m_geometryChangedTimer = new QTimer(this);
414 m_geometryChangedTimer->setSingleShot(true);
415 connect(sender: m_geometryChangedTimer, signal: &QTimer::timeout,
416 receiver: this, slot: &QDesignerFormWindowInterface::geometryChanged);
417
418 m_rubberBand = nullptr;
419
420 setFocusPolicy(Qt::StrongFocus);
421
422 m_mainContainer = nullptr;
423 m_currentWidget = nullptr;
424
425 connect(sender: &m_undoStack, signal: &QUndoStack::indexChanged,
426 receiver: this, slot: &QDesignerFormWindowInterface::changed);
427 connect(sender: &m_undoStack, signal: &QUndoStack::cleanChanged,
428 receiver: this, slot: &FormWindow::slotCleanChanged);
429 connect(sender: this, signal: &QDesignerFormWindowInterface::changed,
430 receiver: this, slot: &FormWindow::checkSelection);
431
432 core()->metaDataBase()->add(object: this);
433
434 initializeCoreTools();
435
436 QAction *a = new QAction(this);
437 a->setText(tr(s: "Edit contents"));
438 a->setShortcut(tr(s: "F2"));
439 connect(sender: a, signal: &QAction::triggered, receiver: this, slot: &FormWindow::editContents);
440 addAction(action: a);
441}
442
443QWidget *FormWindow::mainContainer() const
444{
445 return m_mainContainer;
446}
447
448
449void FormWindow::clearMainContainer()
450{
451 if (m_mainContainer) {
452 setCurrentTool(0);
453 m_widgetStack->setMainContainer(nullptr);
454 core()->metaDataBase()->remove(object: m_mainContainer);
455 unmanageWidget(w: m_mainContainer);
456 delete m_mainContainer;
457 m_mainContainer = nullptr;
458 }
459}
460
461void FormWindow::setMainContainer(QWidget *w)
462{
463 if (w == m_mainContainer) {
464 // nothing to do
465 return;
466 }
467
468 clearMainContainer();
469
470 m_mainContainer = w;
471 const QSize sz = m_mainContainer->size();
472
473 m_widgetStack->setMainContainer(m_mainContainer);
474 m_widgetStack->setCurrentTool(m_widgetEditor);
475
476 setCurrentWidget(m_mainContainer);
477 manageWidget(w: m_mainContainer);
478
479 if (QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: m_mainContainer)) {
480 sheet->setVisible(index: sheet->indexOf(QStringLiteral("windowTitle")), b: true);
481 sheet->setVisible(index: sheet->indexOf(QStringLiteral("windowIcon")), b: true);
482 sheet->setVisible(index: sheet->indexOf(QStringLiteral("windowModality")), b: true);
483 sheet->setVisible(index: sheet->indexOf(QStringLiteral("windowOpacity")), b: true);
484 sheet->setVisible(index: sheet->indexOf(QStringLiteral("windowFilePath")), b: true);
485 // ### generalize
486 }
487
488 m_mainContainer->setFocusPolicy(Qt::StrongFocus);
489 m_mainContainer->resize(sz);
490
491 emit mainContainerChanged(mainContainer: m_mainContainer);
492}
493
494QWidget *FormWindow::findTargetContainer(QWidget *widget) const
495{
496 Q_ASSERT(widget);
497
498 while (QWidget *parentWidget = widget->parentWidget()) {
499 if (LayoutInfo::layoutType(core: m_core, w: parentWidget) == LayoutInfo::NoLayout && isManaged(w: widget))
500 return widget;
501
502 widget = parentWidget;
503 }
504
505 return mainContainer();
506}
507
508static inline void clearObjectInspectorSelection(const QDesignerFormEditorInterface *core)
509{
510 if (QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(object: core->objectInspector()))
511 oi->clearSelection();
512}
513
514// Find a parent of a desired selection state
515static QWidget *findSelectedParent(QDesignerFormWindowInterface *fw, const QWidget *w, bool selected)
516{
517 const QDesignerFormWindowCursorInterface *cursor = fw->cursor();
518 QWidget *mainContainer = fw->mainContainer();
519 for (QWidget *p = w->parentWidget(); p && p != mainContainer; p = p->parentWidget())
520 if (fw->isManaged(widget: p))
521 if (cursor->isWidgetSelected(widget: p) == selected)
522 return p;
523 return nullptr;
524}
525
526// Mouse modifiers.
527
528enum MouseFlags { ToggleSelectionModifier = 0x1, CycleParentModifier=0x2, CopyDragModifier=0x4 };
529
530static inline unsigned mouseFlags(Qt::KeyboardModifiers mod)
531{
532 switch (mod) {
533 case Qt::ShiftModifier:
534 return CycleParentModifier;
535 break;
536#ifdef Q_OS_MACOS
537 case Qt::AltModifier: // "Alt" or "option" key on Mac means copy
538 return CopyDragModifier;
539#endif
540 case Qt::ControlModifier:
541 return CopyDragModifier|ToggleSelectionModifier;
542 break;
543 default:
544 break;
545 }
546 return 0;
547}
548
549// Handle the click selection: Do toggling/cycling
550// of parents according to the modifiers.
551void FormWindow::handleClickSelection(QWidget *managedWidget, unsigned mouseMode)
552{
553 const bool sameWidget = managedWidget == m_lastClickedWidget;
554 m_lastClickedWidget = managedWidget;
555
556 const bool selected = isWidgetSelected(w: managedWidget);
557 if (debugFormWindow)
558 qDebug() << "handleClickSelection" << managedWidget << " same=" << sameWidget << " mouse= " << mouseMode << " selected=" << selected;
559
560 // // toggle selection state of widget
561 if (mouseMode & ToggleSelectionModifier) {
562 selectWidget(w: managedWidget, select: !selected);
563 return;
564 }
565
566 QWidget *selectionCandidate = nullptr;
567 // Hierarchy cycling: If the same widget clicked again: Attempt to cycle
568 // trough the hierarchy. Find the next currently selected parent
569 if (sameWidget && (mouseMode & CycleParentModifier))
570 if (QWidget *currentlySelectedParent = selected ? managedWidget : findSelectedParent(fw: this, w: managedWidget, selected: true))
571 selectionCandidate = findSelectedParent(fw: this, w: currentlySelectedParent, selected: false);
572 // Not the same widget, list wrapped over or there was no unselected parent
573 if (!selectionCandidate && !selected)
574 selectionCandidate = managedWidget;
575
576 if (selectionCandidate)
577 selectSingleWidget(w: selectionCandidate);
578}
579
580void FormWindow::selectSingleWidget(QWidget *w)
581{
582 clearSelection(changePropertyDisplay: false);
583 selectWidget(w, select: true);
584 raiseChildSelections(w);
585}
586
587bool FormWindow::handleMousePressEvent(QWidget * widget, QWidget *managedWidget, QMouseEvent *e)
588{
589 m_mouseState = NoMouseState;
590 m_startPos = QPoint();
591 e->accept();
592
593 BlockSelection blocker(this);
594
595 if (core()->formWindowManager()->activeFormWindow() != this)
596 core()->formWindowManager()->setActiveFormWindow(this);
597
598 const Qt::MouseButtons buttons = e->buttons();
599 if (buttons != Qt::LeftButton && buttons != Qt::MiddleButton)
600 return true;
601
602 m_startPos = mapFromGlobal(e->globalPos());
603
604 if (debugFormWindow)
605 qDebug() << "handleMousePressEvent:" << widget << ',' << managedWidget;
606
607 if (buttons == Qt::MiddleButton || isMainContainer(w: managedWidget)) { // press was on the formwindow
608 clearObjectInspectorSelection(core: m_core); // We might have a toolbar or non-widget selected in the object inspector.
609 clearSelection(changePropertyDisplay: false);
610
611 m_mouseState = MouseDrawRubber;
612 m_currRect = QRect();
613 startRectDraw(global: mapFromGlobal(e->globalPos()), this, t: Rubber);
614 return true;
615 }
616 if (buttons != Qt::LeftButton)
617 return true;
618
619 const unsigned mouseMode = mouseFlags(mod: e->modifiers());
620
621 /* Normally, we want to be able to click /select-on-press to drag away
622 * the widget in the next step. However, in the case of a widget which
623 * itself or whose parent is selected, we defer the selection to the
624 * release event.
625 * This is to prevent children from being dragged away from layouts
626 * when their layouts are selected and one wants to move the layout.
627 * Note that toggle selection is only deferred if the widget is already
628 * selected, so, it is still possible to just Ctrl+Click and CopyDrag. */
629 const bool deferSelection = isWidgetSelected(w: managedWidget) || findSelectedParent(fw: this, w: managedWidget, selected: true);
630 if (deferSelection) {
631 m_mouseState = MouseDeferredSelection;
632 } else {
633 // Cycle the parent unless we explicitly want toggle
634 const unsigned effectiveMouseMode = (mouseMode & ToggleSelectionModifier) ? mouseMode : static_cast<unsigned>(CycleParentModifier);
635 handleClickSelection(managedWidget, mouseMode: effectiveMouseMode);
636 }
637 return true;
638}
639
640// We can drag widget in managed layouts except splitter.
641static bool canDragWidgetInLayout(const QDesignerFormEditorInterface *core, QWidget *w)
642{
643 bool managed;
644 const LayoutInfo::Type type = LayoutInfo::laidoutWidgetType(core ,widget: w, isManaged: &managed);
645 if (!managed)
646 return false;
647 switch (type) {
648 case LayoutInfo::NoLayout:
649 case LayoutInfo::HSplitter:
650 case LayoutInfo::VSplitter:
651 return false;
652 default:
653 break;
654 }
655 return true;
656}
657
658bool FormWindow::handleMouseMoveEvent(QWidget *, QWidget *, QMouseEvent *e)
659{
660 e->accept();
661 if (m_startPos.isNull())
662 return true;
663
664 const QPoint pos = mapFromGlobal(e->globalPos());
665
666 switch (m_mouseState) {
667 case MouseDrawRubber: // Rubber band with left/middle mouse
668 continueRectDraw(global: pos, this, t: Rubber);
669 return true;
670 case MouseMoveDrag: // Spurious move event after drag started?
671 return true;
672 default:
673 break;
674 }
675
676 if (e->buttons() != Qt::LeftButton)
677 return true;
678
679 const bool canStartDrag = (m_startPos - pos).manhattanLength() > QApplication::startDragDistance();
680
681 if (!canStartDrag) // nothing to do
682 return true;
683
684 m_mouseState = MouseMoveDrag;
685 const bool blocked = blockSelectionChanged(blocked: true);
686
687 QWidgetList sel = selectedWidgets();
688 const QWidgetList originalSelection = sel;
689 simplifySelection(sel: &sel);
690
691 QSet<QWidget*> widget_set;
692
693 for (QWidget *child : qAsConst(t&: sel)) { // Move parent layout or container?
694 QWidget *current = child;
695
696 bool done = false;
697 while (!isMainContainer(w: current) && !done) {
698 if (!isManaged(w: current)) {
699 current = current->parentWidget();
700 continue;
701 }
702 if (LayoutInfo::isWidgetLaidout(core: core(), widget: current)) {
703 // Go up to parent of layout if shift pressed, else do that only for splitters
704 if (!canDragWidgetInLayout(core: core(), w: current)) {
705 current = current->parentWidget();
706 continue;
707 }
708 }
709 done = true;
710 }
711
712 if (current == mainContainer())
713 continue;
714
715 widget_set.insert(value: current);
716 }
717
718 sel = widget_set.values();
719 QDesignerFormWindowCursorInterface *c = cursor();
720 QWidget *current = c->current();
721 if (sel.contains(t: current)) {
722 sel.removeAll(t: current);
723 sel.prepend(t: current);
724 }
725
726 QList<QDesignerDnDItemInterface*> item_list;
727 const QPoint globalPos = mapToGlobal(m_startPos);
728 const QDesignerDnDItemInterface::DropType dropType = (mouseFlags(mod: e->modifiers()) & CopyDragModifier) ?
729 QDesignerDnDItemInterface::CopyDrop : QDesignerDnDItemInterface::MoveDrop;
730 for (QWidget *widget : qAsConst(t&: sel)) {
731 item_list.append(t: new FormWindowDnDItem(dropType, this, widget, globalPos));
732 if (dropType == QDesignerDnDItemInterface::MoveDrop) {
733 m_selection->hide(w: widget);
734 widget->hide();
735 }
736 }
737
738 // In case when we have reduced the selection (by calling simplifySelection()
739 // beforehand) we still need to hide selection handles for children widgets
740 for (auto *widget : originalSelection)
741 m_selection->hide(w: widget);
742
743 blockSelectionChanged(blocked);
744
745 if (!sel.isEmpty()) // reshow selection?
746 if (QDesignerMimeData::execDrag(items: item_list, dragSource: core()->topLevel()) == Qt::IgnoreAction && dropType == QDesignerDnDItemInterface::MoveDrop)
747 for (QWidget *widget : qAsConst(t&: sel))
748 m_selection->show(w: widget);
749
750 m_startPos = QPoint();
751
752 return true;
753}
754
755bool FormWindow::handleMouseReleaseEvent(QWidget *w, QWidget *mw, QMouseEvent *e)
756{
757 const MouseState oldState = m_mouseState;
758 m_mouseState = NoMouseState;
759
760 if (debugFormWindow)
761 qDebug() << "handleMouseeleaseEvent:" << w << ',' << mw << "state=" << oldState;
762
763 if (oldState == MouseDoubleClicked)
764 return true;
765
766 e->accept();
767
768 switch (oldState) {
769 case MouseDrawRubber: { // we were drawing a rubber selection
770 endRectDraw(); // get rid of the rectangle
771 const bool blocked = blockSelectionChanged(blocked: true);
772 selectWidgets(); // select widgets which intersect the rect
773 blockSelectionChanged(blocked);
774 }
775 break;
776 // Deferred select: Select the child here unless the parent was moved.
777 case MouseDeferredSelection:
778 handleClickSelection(managedWidget: mw, mouseMode: mouseFlags(mod: e->modifiers()));
779 break;
780 default:
781 break;
782 }
783
784 m_startPos = QPoint();
785
786 /* Inform about selection changes (left/mid or context menu). Also triggers
787 * in the case of an empty rubber drag that cleared the selection in
788 * MousePressEvent. */
789 switch (e->button()) {
790 case Qt::LeftButton:
791 case Qt::MiddleButton:
792 case Qt::RightButton:
793 emitSelectionChanged();
794 break;
795 default:
796 break;
797 }
798
799 return true;
800}
801
802void FormWindow::checkPreviewGeometry(QRect &r)
803{
804 if (!rect().contains(r)) {
805 if (r.left() < rect().left())
806 r.moveTopLeft(p: QPoint(0, r.top()));
807 if (r.right() > rect().right())
808 r.moveBottomRight(p: QPoint(rect().right(), r.bottom()));
809 if (r.top() < rect().top())
810 r.moveTopLeft(p: QPoint(r.left(), rect().top()));
811 if (r.bottom() > rect().bottom())
812 r.moveBottomRight(p: QPoint(r.right(), rect().bottom()));
813 }
814}
815
816void FormWindow::startRectDraw(const QPoint &pos, QWidget *, RectType t)
817{
818 m_rectAnchor = (t == Insert) ? designerGrid().snapPoint(p: pos) : pos;
819
820 m_currRect = QRect(m_rectAnchor, QSize(0, 0));
821 if (!m_rubberBand)
822 m_rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
823 m_rubberBand->setGeometry(m_currRect);
824 m_rubberBand->show();
825}
826
827void FormWindow::continueRectDraw(const QPoint &pos, QWidget *, RectType t)
828{
829 const QPoint p2 = (t == Insert) ? designerGrid().snapPoint(p: pos) : pos;
830
831 QRect r(m_rectAnchor, p2);
832 r = r.normalized();
833
834 if (m_currRect == r)
835 return;
836
837 if (r.width() > 1 || r.height() > 1) {
838 m_currRect = r;
839 if (m_rubberBand)
840 m_rubberBand->setGeometry(m_currRect);
841 }
842}
843
844void FormWindow::endRectDraw()
845{
846 if (m_rubberBand) {
847 delete m_rubberBand;
848 m_rubberBand = nullptr;
849 }
850}
851
852QWidget *FormWindow::currentWidget() const
853{
854 return m_currentWidget;
855}
856
857bool FormWindow::setCurrentWidget(QWidget *currentWidget)
858{
859 if (debugFormWindow)
860 qDebug() << "setCurrentWidget:" << m_currentWidget << " --> " << currentWidget;
861 if (currentWidget == m_currentWidget)
862 return false;
863 // repaint the old widget unless it is the main window
864 if (m_currentWidget && m_currentWidget != mainContainer()) {
865 m_selection->repaintSelection(w: m_currentWidget);
866 }
867 // set new and repaint
868 m_currentWidget = currentWidget;
869 if (m_currentWidget && m_currentWidget != mainContainer()) {
870 m_selection->repaintSelection(w: m_currentWidget);
871 }
872 return true;
873}
874
875void FormWindow::selectWidget(QWidget* w, bool select)
876{
877 if (trySelectWidget(w, select))
878 emitSelectionChanged();
879}
880
881// Selects a widget and determines the new current one. Returns true if a change occurs.
882bool FormWindow::trySelectWidget(QWidget *w, bool select)
883{
884 if (debugFormWindow)
885 qDebug() << "trySelectWidget:" << w << select;
886 if (!isManaged(w) && !isCentralWidget(w))
887 return false;
888
889 if (!select && !isWidgetSelected(w))
890 return false;
891
892 if (!mainContainer())
893 return false;
894
895 if (isMainContainer(w) || isCentralWidget(w)) {
896 setCurrentWidget(mainContainer());
897 return true;
898 }
899
900 if (select) {
901 setCurrentWidget(w);
902 m_selection->addWidget(fw: this, w);
903 } else {
904 QWidget *newCurrent = m_selection->removeWidget(w);
905 if (!newCurrent)
906 newCurrent = mainContainer();
907 setCurrentWidget(newCurrent);
908 }
909 return true;
910}
911
912void FormWindow::clearSelection(bool changePropertyDisplay)
913{
914 if (debugFormWindow)
915 qDebug() << "clearSelection(" << changePropertyDisplay << ')';
916 // At all events, we need a current widget.
917 m_selection->clear();
918 setCurrentWidget(mainContainer());
919
920 if (changePropertyDisplay)
921 emitSelectionChanged();
922}
923
924void FormWindow::emitSelectionChanged()
925{
926 if (m_blockSelectionChanged) // nothing to do
927 return;
928
929 m_selectionChangedTimer->start(msec: 0);
930}
931
932void FormWindow::selectionChangedTimerDone()
933{
934 emit selectionChanged();
935}
936
937bool FormWindow::isWidgetSelected(QWidget *w) const
938{
939 return m_selection->isWidgetSelected(w);
940}
941
942bool FormWindow::isMainContainer(const QWidget *w) const
943{
944 return w && (w == this || w == mainContainer());
945}
946
947void FormWindow::updateChildSelections(QWidget *w)
948{
949 const QWidgetList l = w->findChildren<QWidget*>();
950 if (!l.isEmpty()) {
951 const QWidgetList::const_iterator lcend = l.constEnd();
952 for (QWidgetList::const_iterator it = l.constBegin(); it != lcend; ++it) {
953 QWidget *w = *it;
954 if (isManaged(w))
955 updateSelection(w);
956 }
957 }
958}
959
960void FormWindow::repaintSelection()
961{
962 m_selection->repaintSelection();
963}
964
965void FormWindow::raiseSelection(QWidget *w)
966{
967 m_selection->raiseWidget(w);
968}
969
970void FormWindow::updateSelection(QWidget *w)
971{
972 if (!w->isVisibleTo(this)) {
973 selectWidget(w, select: false);
974 } else {
975 m_selection->updateGeometry(w);
976 }
977}
978
979QWidget *FormWindow::designerWidget(QWidget *w) const
980{
981 while ((w && !isMainContainer(w) && !isManaged(w)) || isCentralWidget(w))
982 w = w->parentWidget();
983
984 return w;
985}
986
987bool FormWindow::isCentralWidget(QWidget *w) const
988{
989 if (QMainWindow *mainWindow = qobject_cast<QMainWindow*>(object: mainContainer()))
990 return w == mainWindow->centralWidget();
991
992 return false;
993}
994
995void FormWindow::ensureUniqueObjectName(QObject *object)
996{
997 QString name = object->objectName();
998 if (name.isEmpty()) {
999 QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase();
1000 if (QDesignerWidgetDataBaseItemInterface *item = db->item(index: db->indexOfObject(object)))
1001 name = qdesigner_internal::qtify(name: item->name());
1002 }
1003 unify(w: object, s&: name, changeIt: true);
1004 object->setObjectName(name);
1005}
1006
1007template <class Iterator>
1008static inline void insertNames(const QDesignerMetaDataBaseInterface *metaDataBase,
1009 Iterator it, const Iterator &end,
1010 QObject *excludedObject, QSet<QString> &nameSet)
1011{
1012 for ( ; it != end; ++it)
1013 if (excludedObject != *it && metaDataBase->item(object: *it))
1014 nameSet.insert(value: (*it)->objectName());
1015}
1016
1017static QSet<QString> languageKeywords()
1018{
1019 static QSet<QString> keywords;
1020 if (keywords.isEmpty()) {
1021 // C++ keywords
1022 keywords.insert(QStringLiteral("asm"));
1023 keywords.insert(QStringLiteral("auto"));
1024 keywords.insert(QStringLiteral("bool"));
1025 keywords.insert(QStringLiteral("break"));
1026 keywords.insert(QStringLiteral("case"));
1027 keywords.insert(QStringLiteral("catch"));
1028 keywords.insert(QStringLiteral("char"));
1029 keywords.insert(QStringLiteral("class"));
1030 keywords.insert(QStringLiteral("const"));
1031 keywords.insert(QStringLiteral("const_cast"));
1032 keywords.insert(QStringLiteral("continue"));
1033 keywords.insert(QStringLiteral("default"));
1034 keywords.insert(QStringLiteral("delete"));
1035 keywords.insert(QStringLiteral("do"));
1036 keywords.insert(QStringLiteral("double"));
1037 keywords.insert(QStringLiteral("dynamic_cast"));
1038 keywords.insert(QStringLiteral("else"));
1039 keywords.insert(QStringLiteral("enum"));
1040 keywords.insert(QStringLiteral("explicit"));
1041 keywords.insert(QStringLiteral("export"));
1042 keywords.insert(QStringLiteral("extern"));
1043 keywords.insert(QStringLiteral("false"));
1044 keywords.insert(QStringLiteral("float"));
1045 keywords.insert(QStringLiteral("for"));
1046 keywords.insert(QStringLiteral("friend"));
1047 keywords.insert(QStringLiteral("goto"));
1048 keywords.insert(QStringLiteral("if"));
1049 keywords.insert(QStringLiteral("inline"));
1050 keywords.insert(QStringLiteral("int"));
1051 keywords.insert(QStringLiteral("long"));
1052 keywords.insert(QStringLiteral("mutable"));
1053 keywords.insert(QStringLiteral("namespace"));
1054 keywords.insert(QStringLiteral("new"));
1055 keywords.insert(QStringLiteral("NULL"));
1056 keywords.insert(QStringLiteral("operator"));
1057 keywords.insert(QStringLiteral("private"));
1058 keywords.insert(QStringLiteral("protected"));
1059 keywords.insert(QStringLiteral("public"));
1060 keywords.insert(QStringLiteral("register"));
1061 keywords.insert(QStringLiteral("reinterpret_cast"));
1062 keywords.insert(QStringLiteral("return"));
1063 keywords.insert(QStringLiteral("short"));
1064 keywords.insert(QStringLiteral("signed"));
1065 keywords.insert(QStringLiteral("sizeof"));
1066 keywords.insert(QStringLiteral("static"));
1067 keywords.insert(QStringLiteral("static_cast"));
1068 keywords.insert(QStringLiteral("struct"));
1069 keywords.insert(QStringLiteral("switch"));
1070 keywords.insert(QStringLiteral("template"));
1071 keywords.insert(QStringLiteral("this"));
1072 keywords.insert(QStringLiteral("throw"));
1073 keywords.insert(QStringLiteral("true"));
1074 keywords.insert(QStringLiteral("try"));
1075 keywords.insert(QStringLiteral("typedef"));
1076 keywords.insert(QStringLiteral("typeid"));
1077 keywords.insert(QStringLiteral("typename"));
1078 keywords.insert(QStringLiteral("union"));
1079 keywords.insert(QStringLiteral("unsigned"));
1080 keywords.insert(QStringLiteral("using"));
1081 keywords.insert(QStringLiteral("virtual"));
1082 keywords.insert(QStringLiteral("void"));
1083 keywords.insert(QStringLiteral("volatile"));
1084 keywords.insert(QStringLiteral("wchar_t"));
1085 keywords.insert(QStringLiteral("while"));
1086
1087 // java keywords
1088 keywords.insert(QStringLiteral("abstract"));
1089 keywords.insert(QStringLiteral("assert"));
1090 keywords.insert(QStringLiteral("boolean"));
1091 keywords.insert(QStringLiteral("break"));
1092 keywords.insert(QStringLiteral("byte"));
1093 keywords.insert(QStringLiteral("case"));
1094 keywords.insert(QStringLiteral("catch"));
1095 keywords.insert(QStringLiteral("char"));
1096 keywords.insert(QStringLiteral("class"));
1097 keywords.insert(QStringLiteral("const"));
1098 keywords.insert(QStringLiteral("continue"));
1099 keywords.insert(QStringLiteral("default"));
1100 keywords.insert(QStringLiteral("do"));
1101 keywords.insert(QStringLiteral("double"));
1102 keywords.insert(QStringLiteral("else"));
1103 keywords.insert(QStringLiteral("enum"));
1104 keywords.insert(QStringLiteral("extends"));
1105 keywords.insert(QStringLiteral("false"));
1106 keywords.insert(QStringLiteral("final"));
1107 keywords.insert(QStringLiteral("finality"));
1108 keywords.insert(QStringLiteral("float"));
1109 keywords.insert(QStringLiteral("for"));
1110 keywords.insert(QStringLiteral("goto"));
1111 keywords.insert(QStringLiteral("if"));
1112 keywords.insert(QStringLiteral("implements"));
1113 keywords.insert(QStringLiteral("import"));
1114 keywords.insert(QStringLiteral("instanceof"));
1115 keywords.insert(QStringLiteral("int"));
1116 keywords.insert(QStringLiteral("interface"));
1117 keywords.insert(QStringLiteral("long"));
1118 keywords.insert(QStringLiteral("native"));
1119 keywords.insert(QStringLiteral("new"));
1120 keywords.insert(QStringLiteral("null"));
1121 keywords.insert(QStringLiteral("package"));
1122 keywords.insert(QStringLiteral("private"));
1123 keywords.insert(QStringLiteral("protected"));
1124 keywords.insert(QStringLiteral("public"));
1125 keywords.insert(QStringLiteral("return"));
1126 keywords.insert(QStringLiteral("short"));
1127 keywords.insert(QStringLiteral("static"));
1128 keywords.insert(QStringLiteral("strictfp"));
1129 keywords.insert(QStringLiteral("super"));
1130 keywords.insert(QStringLiteral("switch"));
1131 keywords.insert(QStringLiteral("synchronized"));
1132 keywords.insert(QStringLiteral("this"));
1133 keywords.insert(QStringLiteral("throw"));
1134 keywords.insert(QStringLiteral("throws"));
1135 keywords.insert(QStringLiteral("transient"));
1136 keywords.insert(QStringLiteral("true"));
1137 keywords.insert(QStringLiteral("try"));
1138 keywords.insert(QStringLiteral("void"));
1139 keywords.insert(QStringLiteral("volatile"));
1140 keywords.insert(QStringLiteral("while"));
1141 }
1142 return keywords;
1143}
1144
1145bool FormWindow::unify(QObject *w, QString &s, bool changeIt)
1146{
1147 using StringSet = QSet<QString>;
1148
1149 QWidget *main = mainContainer();
1150 if (!main)
1151 return true;
1152
1153 StringSet existingNames = languageKeywords();
1154 // build a set of existing names of other widget excluding self
1155 if (!(w->isWidgetType() && isMainContainer(w: qobject_cast<QWidget*>(o: w))))
1156 existingNames.insert(value: main->objectName());
1157
1158 const QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase();
1159 const QWidgetList widgetChildren = main->findChildren<QWidget*>();
1160 if (!widgetChildren.isEmpty())
1161 insertNames(metaDataBase, it: widgetChildren.constBegin(), end: widgetChildren.constEnd(), excludedObject: w, nameSet&: existingNames);
1162
1163 const auto layoutChildren = main->findChildren<QLayout*>();
1164 if (!layoutChildren.isEmpty())
1165 insertNames(metaDataBase, it: layoutChildren.constBegin(), end: layoutChildren.constEnd(), excludedObject: w, nameSet&: existingNames);
1166
1167 const auto actionChildren = main->findChildren<QAction*>();
1168 if (!actionChildren.isEmpty())
1169 insertNames(metaDataBase, it: actionChildren.constBegin(), end: actionChildren.constEnd(), excludedObject: w, nameSet&: existingNames);
1170
1171 const auto buttonGroupChildren = main->findChildren<QButtonGroup*>();
1172 if (!buttonGroupChildren.isEmpty())
1173 insertNames(metaDataBase, it: buttonGroupChildren.constBegin(), end: buttonGroupChildren.constEnd(), excludedObject: w, nameSet&: existingNames);
1174
1175 const StringSet::const_iterator enEnd = existingNames.constEnd();
1176 if (existingNames.constFind(value: s) == enEnd)
1177 return true;
1178 if (!changeIt)
1179 return false;
1180
1181 // split 'name_number'
1182 qlonglong num = 0;
1183 qlonglong factor = 1;
1184 int idx = s.length()-1;
1185 const ushort zeroUnicode = QLatin1Char('0').unicode();
1186 for ( ; idx > 0 && s.at(i: idx).isDigit(); --idx) {
1187 num += (s.at(i: idx).unicode() - zeroUnicode) * factor;
1188 factor *= 10;
1189 }
1190 // Position index past '_'.
1191 const QChar underscore = QLatin1Char('_');
1192 if (idx >= 0 && s.at(i: idx) == underscore) {
1193 idx++;
1194 } else {
1195 num = 1;
1196 s += underscore;
1197 idx = s.length();
1198 }
1199 // try 'name_n', 'name_n+1'
1200 for (num++ ; ;num++) {
1201 s.truncate(pos: idx);
1202 s += QString::number(num);
1203 if (existingNames.constFind(value: s) == enEnd)
1204 break;
1205 }
1206 return false;
1207}
1208/* already_in_form is true when we are moving a widget from one parent to another inside the same
1209 * form. All this means is that InsertWidgetCommand::undo() must not unmanage it. */
1210
1211void FormWindow::insertWidget(QWidget *w, const QRect &rect, QWidget *container, bool already_in_form)
1212{
1213 clearSelection(changePropertyDisplay: false);
1214
1215 beginCommand(description: tr(s: "Insert widget '%1'").arg(a: WidgetFactory::classNameOf(core: m_core, o: w))); // ### use the WidgetDatabaseItem
1216
1217 /* Reparenting into a QSplitter automatically adjusts child's geometry. We create the geometry
1218 * command before we push the reparent command, so that the geometry command has the original
1219 * geometry of the widget. */
1220 QRect r = rect;
1221 Q_ASSERT(r.isValid());
1222 SetPropertyCommand *geom_cmd = new SetPropertyCommand(this);
1223 geom_cmd->init(object: w, QStringLiteral("geometry"), newValue: r); // ### use rc.size()
1224
1225 if (w->parentWidget() != container) {
1226 ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this);
1227 cmd->init(widget: w, parentWidget: container);
1228 m_undoStack.push(cmd);
1229 }
1230
1231 m_undoStack.push(cmd: geom_cmd);
1232
1233 InsertWidgetCommand *cmd = new InsertWidgetCommand(this);
1234 cmd->init(widget: w, already_in_form);
1235 m_undoStack.push(cmd);
1236
1237 endCommand();
1238
1239 w->show();
1240}
1241
1242QWidget *FormWindow::createWidget(DomUI *ui, const QRect &rc, QWidget *target)
1243{
1244 QWidget *container = findContainer(w: target, excludeLayout: false);
1245 if (!container)
1246 return nullptr;
1247 if (isMainContainer(w: container)) {
1248 if (QMainWindow *mw = qobject_cast<QMainWindow*>(object: container)) {
1249 Q_ASSERT(mw->centralWidget() != nullptr);
1250 container = mw->centralWidget();
1251 }
1252 }
1253 QDesignerResource resource(this);
1254 const FormBuilderClipboard clipboard = resource.paste(ui, widgetParent: container);
1255 if (clipboard.m_widgets.size() != 1) // multiple-paste from DomUI not supported yet
1256 return nullptr;
1257 QWidget *widget = clipboard.m_widgets.first();
1258 insertWidget(w: widget, rect: rc, container);
1259 return widget;
1260}
1261
1262static bool isDescendant(const QWidget *parent, const QWidget *child)
1263{
1264 for (; child != nullptr; child = child->parentWidget()) {
1265 if (child == parent)
1266 return true;
1267 }
1268 return false;
1269}
1270
1271void FormWindow::resizeWidget(QWidget *widget, const QRect &geometry)
1272{
1273 Q_ASSERT(isDescendant(this, widget));
1274
1275 QRect r = geometry;
1276 SetPropertyCommand *cmd = new SetPropertyCommand(this);
1277 cmd->init(object: widget, QStringLiteral("geometry"), newValue: r);
1278 cmd->setText(tr(s: "Resize"));
1279 m_undoStack.push(cmd);
1280}
1281
1282void FormWindow::raiseChildSelections(QWidget *w)
1283{
1284 const QWidgetList l = w->findChildren<QWidget*>();
1285 if (l.isEmpty())
1286 return;
1287 m_selection->raiseList(l);
1288}
1289
1290QWidget *FormWindow::containerAt(const QPoint &pos, QWidget *notParentOf)
1291{
1292 QWidget *container = nullptr;
1293 int depth = -1;
1294 const QWidgetList selected = selectedWidgets();
1295 if (rect().contains(p: mapFromGlobal(pos))) {
1296 container = mainContainer();
1297 depth = widgetDepth(w: container);
1298 }
1299
1300 for (QWidget *wit : qAsConst(t&: m_widgets)) {
1301 if (qobject_cast<QLayoutWidget*>(object: wit) || qobject_cast<QSplitter*>(object: wit))
1302 continue;
1303 if (!wit->isVisibleTo(this))
1304 continue;
1305 if (selected.indexOf(t: wit) != -1)
1306 continue;
1307 if (!core()->widgetDataBase()->isContainer(object: wit) &&
1308 wit != mainContainer())
1309 continue;
1310
1311 // the rectangles of all ancestors of the container must contain the insert position
1312 QWidget *w = wit;
1313 while (w && !w->isWindow()) {
1314 if (!w->rect().contains(p: (w->mapFromGlobal(pos))))
1315 break;
1316 w = w->parentWidget();
1317 }
1318 if (!(w == nullptr || w->isWindow()))
1319 continue; // we did not get through the full while loop
1320
1321 int wd = widgetDepth(w: wit);
1322 if (wd == depth && container) {
1323 if (wit->parentWidget()->children().indexOf(t: wit) >
1324 container->parentWidget()->children().indexOf(t: container))
1325 wd++;
1326 }
1327 if (wd > depth && !isChildOf(c: wit, p: notParentOf)) {
1328 depth = wd;
1329 container = wit;
1330 }
1331 }
1332 return container;
1333}
1334
1335QWidgetList FormWindow::selectedWidgets() const
1336{
1337 return m_selection->selectedWidgets();
1338}
1339
1340void FormWindow::selectWidgets()
1341{
1342 bool selectionChanged = false;
1343 const QWidgetList l = mainContainer()->findChildren<QWidget*>();
1344 const QRect selRect(mapToGlobal(m_currRect.topLeft()), m_currRect.size());
1345 for (QWidget *w : l) {
1346 if (w->isVisibleTo(this) && isManaged(w)) {
1347 const QPoint p = w->mapToGlobal(QPoint(0,0));
1348 const QRect r(p, w->size());
1349 if (r.intersects(r: selRect) && !r.contains(r: selRect) && trySelectWidget(w, select: true))
1350 selectionChanged = true;
1351 }
1352 }
1353
1354 if (selectionChanged)
1355 emitSelectionChanged();
1356}
1357
1358bool FormWindow::handleKeyPressEvent(QWidget *widget, QWidget *, QKeyEvent *e)
1359{
1360 if (qobject_cast<const FormWindow*>(object: widget) || qobject_cast<const QMenu*>(object: widget))
1361 return false;
1362
1363 e->accept(); // we always accept!
1364
1365 switch (e->key()) {
1366 default: break; // we don't care about the other keys
1367
1368 case Qt::Key_Delete:
1369 case Qt::Key_Backspace:
1370 if (e->modifiers() == Qt::NoModifier)
1371 deleteWidgets();
1372 break;
1373
1374 case Qt::Key_Tab:
1375 if (e->modifiers() == Qt::NoModifier)
1376 cursor()->movePosition(op: QDesignerFormWindowCursorInterface::Next);
1377 break;
1378
1379 case Qt::Key_Backtab:
1380 if (e->modifiers() == Qt::NoModifier)
1381 cursor()->movePosition(op: QDesignerFormWindowCursorInterface::Prev);
1382 break;
1383
1384 case Qt::Key_Left:
1385 case Qt::Key_Right:
1386 case Qt::Key_Up:
1387 case Qt::Key_Down:
1388 handleArrowKeyEvent(key: e->key(), modifiers: e->modifiers());
1389 break;
1390 }
1391
1392 return true;
1393}
1394
1395int FormWindow::getValue(const QRect &rect, int key, bool size) const
1396{
1397 if (size) {
1398 if (key == Qt::Key_Left || key == Qt::Key_Right)
1399 return rect.width();
1400 return rect.height();
1401 }
1402 if (key == Qt::Key_Left || key == Qt::Key_Right)
1403 return rect.x();
1404 return rect.y();
1405}
1406
1407int FormWindow::calcValue(int val, bool forward, bool snap, int snapOffset) const
1408{
1409 if (snap) {
1410 const int rest = val % snapOffset;
1411 if (rest) {
1412 const int offset = forward ? snapOffset : 0;
1413 const int newOffset = rest < 0 ? offset - snapOffset : offset;
1414 return val + newOffset - rest;
1415 }
1416 return (forward ? val + snapOffset : val - snapOffset);
1417 }
1418 return (forward ? val + 1 : val - 1);
1419}
1420
1421// ArrowKeyOperation: Stores a keyboard move or resize (Shift pressed)
1422// operation.
1423struct ArrowKeyOperation
1424{
1425 QRect apply(const QRect &rect) const;
1426
1427 bool resize = false; // Resize: Shift-Key->drag bottom/right corner, else just move
1428 int distance = 0;
1429 int arrowKey = Qt::Key_Left;
1430};
1431
1432} // namespace
1433
1434QT_END_NAMESPACE
1435Q_DECLARE_METATYPE(qdesigner_internal::ArrowKeyOperation)
1436QT_BEGIN_NAMESPACE
1437
1438namespace qdesigner_internal {
1439
1440QRect ArrowKeyOperation::apply(const QRect &rect) const
1441{
1442 QRect r = rect;
1443 if (resize) {
1444 if (arrowKey == Qt::Key_Left || arrowKey == Qt::Key_Right)
1445 r.setWidth(r.width() + distance);
1446 else
1447 r.setHeight(r.height() + distance);
1448 } else {
1449 if (arrowKey == Qt::Key_Left || arrowKey == Qt::Key_Right)
1450 r.moveLeft(pos: r.x() + distance);
1451 else
1452 r.moveTop(pos: r.y() + distance);
1453 }
1454 return r;
1455}
1456
1457QDebug operator<<(QDebug in, const ArrowKeyOperation &op)
1458{
1459 in.nospace() << "Resize=" << op.resize << " dist=" << op.distance << " Key=" << op.arrowKey << ' ';
1460 return in;
1461}
1462
1463// ArrowKeyPropertyHelper: Applies a struct ArrowKeyOperation
1464// (stored as new value) to a list of widgets using to calculate the
1465// changed geometry of the widget in setValue(). Thus, the 'newValue'
1466// of the property command is the relative move distance, which is the same
1467// for all widgets (although resulting in different geometries for the widgets).
1468// The command merging can then work as it would when applying the same text
1469// to all QLabels.
1470
1471class ArrowKeyPropertyHelper : public PropertyHelper {
1472public:
1473 ArrowKeyPropertyHelper(QObject* o, SpecialProperty sp,
1474 QDesignerPropertySheetExtension *s, int i) :
1475 PropertyHelper(o, sp, s, i) {}
1476
1477 Value setValue(QDesignerFormWindowInterface *fw, const QVariant &value, bool changed, unsigned subPropertyMask) override;
1478};
1479
1480PropertyHelper::Value ArrowKeyPropertyHelper::setValue(QDesignerFormWindowInterface *fw, const QVariant &value, bool changed, unsigned subPropertyMask)
1481{
1482 // Apply operation to obtain the new geometry value.
1483 QWidget *w = qobject_cast<QWidget*>(o: object());
1484 const ArrowKeyOperation operation = qvariant_cast<ArrowKeyOperation>(v: value);
1485 const QRect newGeom = operation.apply(rect: w->geometry());
1486 return PropertyHelper::setValue(fw, value: QVariant(newGeom), changed, subPropertyMask);
1487}
1488
1489// ArrowKeyPropertyCommand: Helper factory overwritten to create
1490// ArrowKeyPropertyHelper and a merge operation that merges values of
1491// the same direction.
1492class ArrowKeyPropertyCommand: public SetPropertyCommand {
1493public:
1494 explicit ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw,
1495 QUndoCommand *p = nullptr);
1496
1497 void init(QWidgetList &l, const ArrowKeyOperation &op);
1498
1499protected:
1500 PropertyHelper *createPropertyHelper(QObject *o, SpecialProperty sp,
1501 QDesignerPropertySheetExtension *s, int i) const override
1502 { return new ArrowKeyPropertyHelper(o, sp, s, i); }
1503 QVariant mergeValue(const QVariant &newValue) override;
1504};
1505
1506ArrowKeyPropertyCommand::ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw,
1507 QUndoCommand *p) :
1508 SetPropertyCommand(fw, p)
1509{
1510 static const int mid = qRegisterMetaType<qdesigner_internal::ArrowKeyOperation>();
1511 Q_UNUSED(mid);
1512}
1513
1514void ArrowKeyPropertyCommand::init(QWidgetList &l, const ArrowKeyOperation &op)
1515{
1516 QObjectList ol;
1517 for (QWidget *w : qAsConst(t&: l))
1518 ol.push_back(t: w);
1519 SetPropertyCommand::init(list: ol, QStringLiteral("geometry"), newValue: QVariant::fromValue(value: op));
1520
1521 setText(op.resize ? FormWindow::tr(s: "Key Resize") : FormWindow::tr(s: "Key Move"));
1522}
1523
1524QVariant ArrowKeyPropertyCommand::mergeValue(const QVariant &newMergeValue)
1525{
1526 // Merge move operations of the same arrow key
1527 if (!newMergeValue.canConvert<ArrowKeyOperation>())
1528 return QVariant();
1529 ArrowKeyOperation mergedOperation = qvariant_cast<ArrowKeyOperation>(v: newValue());
1530 const ArrowKeyOperation newMergeOperation = qvariant_cast<ArrowKeyOperation>(v: newMergeValue);
1531 if (mergedOperation.resize != newMergeOperation.resize || mergedOperation.arrowKey != newMergeOperation.arrowKey)
1532 return QVariant();
1533 mergedOperation.distance += newMergeOperation.distance;
1534 return QVariant::fromValue(value: mergedOperation);
1535}
1536
1537void FormWindow::handleArrowKeyEvent(int key, Qt::KeyboardModifiers modifiers)
1538{
1539 const QDesignerFormWindowCursorInterface *c = cursor();
1540 if (!c->hasSelection())
1541 return;
1542
1543 QWidgetList selection;
1544
1545 // check if a laid out widget is selected
1546 const int count = c->selectedWidgetCount();
1547 for (int index = 0; index < count; ++index) {
1548 QWidget *w = c->selectedWidget(index);
1549 if (!LayoutInfo::isWidgetLaidout(core: m_core, widget: w))
1550 selection.append(t: w);
1551 }
1552
1553 simplifySelection(sel: &selection);
1554
1555 if (selection.isEmpty())
1556 return;
1557
1558 QWidget *current = c->current();
1559 if (!current || LayoutInfo::isWidgetLaidout(core: m_core, widget: current)) {
1560 current = selection.first();
1561 }
1562
1563 const bool size = modifiers & Qt::ShiftModifier;
1564
1565 const bool snap = !(modifiers & Qt::ControlModifier);
1566 const bool forward = (key == Qt::Key_Right || key == Qt::Key_Down);
1567 const int snapPoint = (key == Qt::Key_Left || key == Qt::Key_Right) ? grid().x() : grid().y();
1568
1569 const int oldValue = getValue(rect: current->geometry(), key, size);
1570
1571 const int newValue = calcValue(val: oldValue, forward, snap, snapOffset: snapPoint);
1572
1573 ArrowKeyOperation operation;
1574 operation.resize = modifiers & Qt::ShiftModifier;
1575 operation.distance = newValue - oldValue;
1576 operation.arrowKey = key;
1577
1578 ArrowKeyPropertyCommand *cmd = new ArrowKeyPropertyCommand(this);
1579 cmd->init(l&: selection, op: operation);
1580 m_undoStack.push(cmd);
1581}
1582
1583bool FormWindow::handleKeyReleaseEvent(QWidget *, QWidget *, QKeyEvent *e)
1584{
1585 e->accept();
1586 return true;
1587}
1588
1589void FormWindow::selectAll()
1590{
1591 bool selectionChanged = false;
1592 for (QWidget *widget : qAsConst(t&: m_widgets)) {
1593 if (widget->isVisibleTo(this) && trySelectWidget(w: widget, select: true))
1594 selectionChanged = true;
1595 }
1596 if (selectionChanged)
1597 emitSelectionChanged();
1598}
1599
1600void FormWindow::createLayout(int type, QWidget *container)
1601{
1602 if (container) {
1603 layoutContainer(w: container, type);
1604 } else {
1605 LayoutCommand *cmd = new LayoutCommand(this);
1606 cmd->init(parentWidget: mainContainer(), widgets: selectedWidgets(), layoutType: static_cast<LayoutInfo::Type>(type));
1607 commandHistory()->push(cmd);
1608 }
1609}
1610
1611void FormWindow::morphLayout(QWidget *container, int newType)
1612{
1613 MorphLayoutCommand *cmd = new MorphLayoutCommand(this);
1614 if (cmd->init(w: container, newType)) {
1615 commandHistory()->push(cmd);
1616 } else {
1617 qDebug() << "** WARNING Unable to morph layout.";
1618 delete cmd;
1619 }
1620}
1621
1622void FormWindow::deleteWidgets()
1623{
1624 QWidgetList selection = selectedWidgets();
1625 simplifySelection(sel: &selection);
1626
1627 deleteWidgetList(widget_list: selection);
1628}
1629
1630QString FormWindow::fileName() const
1631{
1632 return m_fileName;
1633}
1634
1635void FormWindow::setFileName(const QString &fileName)
1636{
1637 if (m_fileName == fileName)
1638 return;
1639
1640 m_fileName = fileName;
1641 emit fileNameChanged(fileName);
1642}
1643
1644QString FormWindow::contents() const
1645{
1646 QBuffer b;
1647 if (!mainContainer() || !b.open(openMode: QIODevice::WriteOnly))
1648 return QString();
1649
1650 QDesignerResource resource(const_cast<FormWindow*>(this));
1651 resource.save(dev: &b, widget: mainContainer());
1652
1653 return QString::fromUtf8(str: b.buffer());
1654}
1655
1656#if QT_CONFIG(clipboard)
1657void FormWindow::copy()
1658{
1659 QBuffer b;
1660 if (!b.open(openMode: QIODevice::WriteOnly))
1661 return;
1662
1663 FormBuilderClipboard clipboard;
1664 QDesignerResource resource(this);
1665 resource.setSaveRelative(false);
1666 clipboard.m_widgets = selectedWidgets();
1667 simplifySelection(sel: &clipboard.m_widgets);
1668 resource.copy(dev: &b, selection: clipboard);
1669
1670 qApp->clipboard()->setText(QString::fromUtf8(str: b.buffer()), mode: QClipboard::Clipboard);
1671}
1672
1673void FormWindow::cut()
1674{
1675 copy();
1676 deleteWidgets();
1677}
1678
1679void FormWindow::paste()
1680{
1681 paste(pasteMode: PasteAll);
1682}
1683#endif
1684
1685// for cases like QMainWindow (central widget is an inner container) or QStackedWidget (page is an inner container)
1686QWidget *FormWindow::innerContainer(QWidget *outerContainer) const
1687{
1688 if (m_core->widgetDataBase()->isContainer(object: outerContainer))
1689 if (const QDesignerContainerExtension *container = qt_extension<QDesignerContainerExtension*>(manager: m_core->extensionManager(), object: outerContainer)) {
1690 const int currentIndex = container->currentIndex();
1691 return currentIndex >= 0 ? container->widget(index: currentIndex) : nullptr;
1692 }
1693 return outerContainer;
1694}
1695
1696QWidget *FormWindow::containerForPaste() const
1697{
1698 QWidget *w = mainContainer();
1699 if (!w)
1700 return nullptr;
1701 do {
1702 // Try to find a close parent, for example a non-laid-out
1703 // QFrame/QGroupBox when a widget within it is selected.
1704 QWidgetList selection = selectedWidgets();
1705 if (selection.isEmpty())
1706 break;
1707 simplifySelection(sel: &selection);
1708
1709 QWidget *containerOfW = findContainer(w: selection.first(), /* exclude layouts */ excludeLayout: true);
1710 if (!containerOfW || containerOfW == mainContainer())
1711 break;
1712 // No layouts, must be container. No empty page-based containers.
1713 containerOfW = innerContainer(outerContainer: containerOfW);
1714 if (!containerOfW)
1715 break;
1716 if (LayoutInfo::layoutType(core: m_core, w: containerOfW) != LayoutInfo::NoLayout || !m_core->widgetDataBase()->isContainer(object: containerOfW))
1717 break;
1718 w = containerOfW;
1719 } while (false);
1720 // First check for layout (note that it does not cover QMainWindow
1721 // and the like as the central widget has the layout).
1722
1723 w = innerContainer(outerContainer: w);
1724 if (!w)
1725 return nullptr;
1726 if (LayoutInfo::layoutType(core: m_core, w) != LayoutInfo::NoLayout)
1727 return nullptr;
1728 // Go up via container extension (also includes step from QMainWindow to its central widget)
1729 w = m_core->widgetFactory()->containerOfWidget(w);
1730 if (w == nullptr || LayoutInfo::layoutType(core: m_core, w) != LayoutInfo::NoLayout)
1731 return nullptr;
1732
1733 if (debugFormWindow)
1734 qDebug() <<"containerForPaste() " << w;
1735 return w;
1736}
1737
1738#if QT_CONFIG(clipboard)
1739// Construct DomUI from clipboard (paste) and determine number of widgets/actions.
1740static inline DomUI *domUIFromClipboard(int *widgetCount, int *actionCount)
1741{
1742 *widgetCount = *actionCount = 0;
1743 const QString clipboardText = qApp->clipboard()->text();
1744 if (clipboardText.isEmpty() || clipboardText.indexOf(c: QLatin1Char('<')) == -1)
1745 return nullptr;
1746
1747 QXmlStreamReader reader(clipboardText);
1748 DomUI *ui = nullptr;
1749 const QString uiElement = QStringLiteral("ui");
1750 while (!reader.atEnd()) {
1751 if (reader.readNext() == QXmlStreamReader::StartElement) {
1752 if (reader.name().compare(s: uiElement, cs: Qt::CaseInsensitive) == 0 && !ui) {
1753 ui = new DomUI();
1754 ui->read(reader);
1755 break;
1756 }
1757 reader.raiseError(message: QCoreApplication::translate(context: "FormWindow", key: "Unexpected element <%1>").arg(a: reader.name().toString()));
1758 }
1759 }
1760 if (reader.hasError()) {
1761 delete ui;
1762 ui = nullptr;
1763 designerWarning(message: QCoreApplication::translate(context: "FormWindow", key: "Error while pasting clipboard contents at line %1, column %2: %3").
1764 arg(a: reader.lineNumber()).arg(a: reader.columnNumber()).arg(a: reader.errorString()));
1765 return nullptr;
1766 }
1767
1768 if (const DomWidget *topLevel = ui->elementWidget()) {
1769 *widgetCount = topLevel->elementWidget().size();
1770 *actionCount = topLevel->elementAction().size();
1771 }
1772 if (*widgetCount == 0 && *actionCount == 0) {
1773 delete ui;
1774 return nullptr;
1775 }
1776 return ui;
1777}
1778#endif
1779
1780static inline QString pasteCommandDescription(int widgetCount, int actionCount)
1781{
1782 if (widgetCount == 0)
1783 return FormWindow::tr(s: "Paste %n action(s)", c: nullptr, n: actionCount);
1784 if (actionCount == 0)
1785 return FormWindow::tr(s: "Paste %n widget(s)", c: nullptr, n: widgetCount);
1786 return FormWindow::tr(s: "Paste (%1 widgets, %2 actions)").arg(a: widgetCount).arg(a: actionCount);
1787}
1788
1789#if QT_CONFIG(clipboard)
1790static void positionPastedWidgetsAtMousePosition(FormWindow *fw, const QPoint &contextMenuPosition, QWidget *parent, const QWidgetList &l)
1791{
1792 // Try to position pasted widgets at mouse position (current mouse position for Ctrl-V or position of context menu)
1793 // if it fits. If it is completely outside, force it to 0,0
1794 // If it fails, the old coordinates relative to the previous parent will be used.
1795 QPoint currentPos = contextMenuPosition.x() >=0 ? parent->mapFrom(fw, contextMenuPosition) : parent->mapFromGlobal(QCursor::pos());
1796 const Grid &grid = fw->designerGrid();
1797 QPoint cursorPos = grid.snapPoint(p: currentPos);
1798 const QRect parentGeometry = QRect(QPoint(0, 0), parent->size());
1799 const bool outside = !parentGeometry.contains(p: cursorPos);
1800 if (outside)
1801 cursorPos = grid.snapPoint(p: QPoint(0, 0));
1802 // Determine area of pasted widgets
1803 QRect pasteArea;
1804 const QWidgetList::const_iterator lcend = l.constEnd();
1805 for (QWidgetList::const_iterator it = l.constBegin(); it != lcend; ++it)
1806 pasteArea =pasteArea.isNull() ? (*it)->geometry() : pasteArea.united(r: (*it)->geometry());
1807
1808 // Mouse on some child? (try to position bottomRight on a free spot to
1809 // get the stacked-offset effect of Designer 4.3, that is, offset by grid if Ctrl-V is pressed continuously
1810 do {
1811 const QPoint bottomRight = cursorPos + QPoint(pasteArea.width(), pasteArea.height()) - QPoint(1, 1);
1812 if (bottomRight.y() > parentGeometry.bottom() || parent->childAt(p: bottomRight) == nullptr)
1813 break;
1814 cursorPos += QPoint(grid.deltaX(), grid.deltaY());
1815 } while (true);
1816 // Move.
1817 const QPoint offset = cursorPos - pasteArea.topLeft();
1818 for (QWidgetList::const_iterator it = l.constBegin(); it != lcend; ++it)
1819 (*it)->move((*it)->pos() + offset);
1820}
1821
1822void FormWindow::paste(PasteMode pasteMode)
1823{
1824 // Avoid QDesignerResource constructing widgets that are not used as
1825 // QDesignerResource manages the widgets it creates (creating havoc if one remains unused)
1826 DomUI *ui = nullptr;
1827 do {
1828 int widgetCount;
1829 int actionCount;
1830 ui = domUIFromClipboard(widgetCount: &widgetCount, actionCount: &actionCount);
1831 if (!ui)
1832 break;
1833
1834 // Check for actions
1835 if (pasteMode == PasteActionsOnly)
1836 if (widgetCount != 0 || actionCount == 0)
1837 break;
1838
1839 // Check for widgets: need a container
1840 QWidget *pasteContainer = widgetCount ? containerForPaste() : nullptr;
1841 if (widgetCount && pasteContainer == nullptr) {
1842
1843 const QString message = tr(s: "Cannot paste widgets. Designer could not find a container "
1844 "without a layout to paste into.");
1845 const QString infoMessage = tr(s: "Break the layout of the "
1846 "container you want to paste into, select this container "
1847 "and then paste again.");
1848 core()->dialogGui()->message(parent: this, context: QDesignerDialogGuiInterface::FormEditorMessage, icon: QMessageBox::Information,
1849 title: tr(s: "Paste error"), text: message, informativeText: infoMessage, buttons: QMessageBox::Ok);
1850 break;
1851 }
1852
1853 QDesignerResource resource(this);
1854 // Note that the widget factory must be able to locate the
1855 // form window (us) via parent, otherwise, it will not able to construct QLayoutWidgets
1856 // (It will then default to widgets) among other issues.
1857 const FormBuilderClipboard clipboard = resource.paste(ui, widgetParent: pasteContainer, actionParent: this);
1858
1859 clearSelection(changePropertyDisplay: false);
1860 // Create command sequence
1861 beginCommand(description: pasteCommandDescription(widgetCount, actionCount));
1862
1863 if (widgetCount) {
1864 positionPastedWidgetsAtMousePosition(fw: this, contextMenuPosition: m_contextMenuPosition, parent: pasteContainer, l: clipboard.m_widgets);
1865 for (QWidget *w : clipboard.m_widgets) {
1866 InsertWidgetCommand *cmd = new InsertWidgetCommand(this);
1867 cmd->init(widget: w);
1868 m_undoStack.push(cmd);
1869 selectWidget(w);
1870 }
1871 }
1872
1873 if (actionCount)
1874 for (QAction *a : clipboard.m_actions) {
1875 ensureUniqueObjectName(object: a);
1876 AddActionCommand *cmd = new AddActionCommand(this);
1877 cmd->init(action: a);
1878 m_undoStack.push(cmd);
1879 }
1880 endCommand();
1881 } while (false);
1882 delete ui;
1883}
1884#endif
1885
1886// Draw a dotted frame around containers
1887bool FormWindow::frameNeeded(QWidget *w) const
1888{
1889 if (!core()->widgetDataBase()->isContainer(object: w))
1890 return false;
1891 if (qobject_cast<QGroupBox *>(object: w))
1892 return false;
1893 if (qobject_cast<QToolBox *>(object: w))
1894 return false;
1895 if (qobject_cast<QTabWidget *>(object: w))
1896 return false;
1897 if (qobject_cast<QStackedWidget *>(object: w))
1898 return false;
1899 if (qobject_cast<QDockWidget *>(object: w))
1900 return false;
1901 if (qobject_cast<QDesignerWidget *>(object: w))
1902 return false;
1903 if (qobject_cast<QMainWindow *>(object: w))
1904 return false;
1905 if (qobject_cast<QDialog *>(object: w))
1906 return false;
1907 if (qobject_cast<QLayoutWidget *>(object: w))
1908 return false;
1909 return true;
1910}
1911
1912bool FormWindow::eventFilter(QObject *watched, QEvent *event)
1913{
1914 const bool ret = FormWindowBase::eventFilter(watched, event);
1915 if (event->type() != QEvent::Paint)
1916 return ret;
1917
1918 Q_ASSERT(watched->isWidgetType());
1919 QWidget *w = static_cast<QWidget *>(watched);
1920 QPaintEvent *pe = static_cast<QPaintEvent*>(event);
1921 const QRect widgetRect = w->rect();
1922 const QRect paintRect = pe->rect();
1923 // Does the paint rectangle touch the borders of the widget rectangle
1924 if (paintRect.x() > widgetRect.x() && paintRect.y() > widgetRect.y() &&
1925 paintRect.right() < widgetRect.right() && paintRect.bottom() < widgetRect.bottom())
1926 return ret;
1927 QPainter p(w);
1928 const QPen pen(QColor(0, 0, 0, 32), 0, Qt::DotLine);
1929 p.setPen(pen);
1930 p.setBrush(QBrush(Qt::NoBrush));
1931 p.drawRect(r: widgetRect.adjusted(xp1: 0, yp1: 0, xp2: -1, yp2: -1));
1932 return ret;
1933}
1934
1935void FormWindow::manageWidget(QWidget *w)
1936{
1937 if (isManaged(w))
1938 return;
1939
1940 Q_ASSERT(qobject_cast<QMenu*>(w) == 0);
1941
1942 if (w->hasFocus())
1943 setFocus();
1944
1945 core()->metaDataBase()->add(object: w);
1946
1947 m_insertedWidgets.insert(value: w);
1948 m_widgets.append(t: w);
1949
1950#if QT_CONFIG(cursor)
1951 setCursorToAll(c: Qt::ArrowCursor, start: w);
1952#endif
1953
1954 emit changed();
1955 emit widgetManaged(widget: w);
1956
1957 if (frameNeeded(w))
1958 w->installEventFilter(filterObj: this);
1959}
1960
1961void FormWindow::unmanageWidget(QWidget *w)
1962{
1963 if (!isManaged(w))
1964 return;
1965
1966 m_selection->removeWidget(w);
1967
1968 emit aboutToUnmanageWidget(widget: w);
1969
1970 if (w == m_currentWidget)
1971 setCurrentWidget(mainContainer());
1972
1973 core()->metaDataBase()->remove(object: w);
1974
1975 m_insertedWidgets.remove(value: w);
1976 m_widgets.removeAt(i: m_widgets.indexOf(t: w));
1977
1978 emit changed();
1979 emit widgetUnmanaged(widget: w);
1980
1981 if (frameNeeded(w))
1982 w->removeEventFilter(obj: this);
1983}
1984
1985bool FormWindow::isManaged(QWidget *w) const
1986{
1987 return m_insertedWidgets.contains(value: w);
1988}
1989
1990void FormWindow::breakLayout(QWidget *w)
1991{
1992 if (w == this)
1993 w = mainContainer();
1994 // Find the first-order managed child widgets
1995 QWidgetList widgets;
1996
1997 const QObjectList children = w->children();
1998 const QObjectList::const_iterator cend = children.constEnd();
1999 const QDesignerMetaDataBaseInterface *mdb = core()->metaDataBase();
2000 for (QObjectList::const_iterator it = children.constBegin(); it != cend; ++it)
2001 if ( (*it)->isWidgetType()) {
2002 QWidget *w = static_cast<QWidget*>(*it);
2003 if (mdb->item(object: w))
2004 widgets.push_back(t: w);
2005 }
2006
2007 BreakLayoutCommand *cmd = new BreakLayoutCommand(this);
2008 cmd->init(widgets, layoutBase: w);
2009 commandHistory()->push(cmd);
2010 clearSelection(changePropertyDisplay: false);
2011}
2012
2013void FormWindow::beginCommand(const QString &description)
2014{
2015 m_undoStack.beginMacro(text: description);
2016}
2017
2018void FormWindow::endCommand()
2019{
2020 m_undoStack.endMacro();
2021}
2022
2023void FormWindow::raiseWidgets()
2024{
2025 QWidgetList widgets = selectedWidgets();
2026 simplifySelection(sel: &widgets);
2027
2028 if (widgets.isEmpty())
2029 return;
2030
2031 beginCommand(description: tr(s: "Raise widgets"));
2032 for (QWidget *widget : qAsConst(t&: widgets)) {
2033 RaiseWidgetCommand *cmd = new RaiseWidgetCommand(this);
2034 cmd->init(widget);
2035 m_undoStack.push(cmd);
2036 }
2037 endCommand();
2038}
2039
2040void FormWindow::lowerWidgets()
2041{
2042 QWidgetList widgets = selectedWidgets();
2043 simplifySelection(sel: &widgets);
2044
2045 if (widgets.isEmpty())
2046 return;
2047
2048 beginCommand(description: tr(s: "Lower widgets"));
2049 for (QWidget *widget : qAsConst(t&: widgets)) {
2050 LowerWidgetCommand *cmd = new LowerWidgetCommand(this);
2051 cmd->init(widget);
2052 m_undoStack.push(cmd);
2053 }
2054 endCommand();
2055}
2056
2057bool FormWindow::handleMouseButtonDblClickEvent(QWidget *w, QWidget *managedWidget, QMouseEvent *e)
2058{
2059 if (debugFormWindow)
2060 qDebug() << "handleMouseButtonDblClickEvent:" << w << ',' << managedWidget << "state=" << m_mouseState;
2061
2062 e->accept();
2063
2064 // Might be out of sync due cycling of the parent selection
2065 // In that case, do nothing
2066 if (isWidgetSelected(w: managedWidget))
2067 emit activated(widget: managedWidget);
2068
2069 m_mouseState = MouseDoubleClicked;
2070 return true;
2071}
2072
2073
2074QMenu *FormWindow::initializePopupMenu(QWidget *managedWidget)
2075{
2076 if (!isManaged(w: managedWidget) || currentTool())
2077 return nullptr;
2078
2079 // Make sure the managedWidget is selected and current since
2080 // the SetPropertyCommands must use the right reference
2081 // object obtained from the property editor for the property group
2082 // of a multiselection to be correct.
2083 const bool selected = isWidgetSelected(w: managedWidget);
2084 bool update = false;
2085 if (selected) {
2086 update = setCurrentWidget(managedWidget);
2087 } else {
2088 clearObjectInspectorSelection(core: m_core); // We might have a toolbar or non-widget selected in the object inspector.
2089 clearSelection(changePropertyDisplay: false);
2090 update = trySelectWidget(w: managedWidget, select: true);
2091 raiseChildSelections(w: managedWidget); // raise selections and select widget
2092 }
2093
2094 if (update) {
2095 emitSelectionChanged();
2096 QMetaObject::invokeMethod(obj: core()->formWindowManager(), member: "slotUpdateActions");
2097 }
2098
2099 QWidget *contextMenuWidget = nullptr;
2100
2101 if (isMainContainer(w: managedWidget)) { // press on a child widget
2102 contextMenuWidget = mainContainer();
2103 } else { // press on a child widget
2104 // if widget is laid out, find the first non-laid out super-widget
2105 QWidget *realWidget = managedWidget; // but store the original one
2106 QMainWindow *mw = qobject_cast<QMainWindow*>(object: mainContainer());
2107
2108 if (mw && mw->centralWidget() == realWidget) {
2109 contextMenuWidget = managedWidget;
2110 } else {
2111 contextMenuWidget = realWidget;
2112 }
2113 }
2114
2115 if (!contextMenuWidget)
2116 return nullptr;
2117
2118 QMenu *contextMenu = createPopupMenu(w: contextMenuWidget);
2119 if (!contextMenu)
2120 return nullptr;
2121
2122 emit contextMenuRequested(menu: contextMenu, widget: contextMenuWidget);
2123 return contextMenu;
2124}
2125
2126bool FormWindow::handleContextMenu(QWidget *, QWidget *managedWidget, QContextMenuEvent *e)
2127{
2128 QMenu *contextMenu = initializePopupMenu(managedWidget);
2129 if (!contextMenu)
2130 return false;
2131 const QPoint globalPos = e->globalPos();
2132 m_contextMenuPosition = mapFromGlobal (globalPos);
2133 contextMenu->exec(pos: globalPos);
2134 delete contextMenu;
2135 e->accept();
2136 m_contextMenuPosition = QPoint(-1, -1);
2137 return true;
2138}
2139
2140bool FormWindow::setContents(QIODevice *dev, QString *errorMessageIn /* = 0 */)
2141{
2142 QDesignerResource r(this);
2143 QScopedPointer<DomUI> ui(r.readUi(dev));
2144 if (ui.isNull()) {
2145 if (errorMessageIn)
2146 *errorMessageIn = r.errorString();
2147 return false;
2148 }
2149
2150 UpdateBlocker ub(this);
2151 clearSelection();
2152 m_selection->clearSelectionPool();
2153 m_insertedWidgets.clear();
2154 m_widgets.clear();
2155 // The main container is cleared as otherwise
2156 // the names of the newly loaded objects will be unified.
2157 clearMainContainer();
2158 m_undoStack.clear();
2159 emit changed();
2160
2161 QWidget *w = r.loadUi(ui: ui.data(), parentWidget: formContainer());
2162 if (w) {
2163 setMainContainer(w);
2164 emit changed();
2165 }
2166 if (errorMessageIn)
2167 *errorMessageIn = r.errorString();
2168 return w != nullptr;
2169}
2170
2171bool FormWindow::setContents(const QString &contents)
2172{
2173 QString errorMessage;
2174 QByteArray data = contents.toUtf8();
2175 QBuffer b(&data);
2176 const bool success = b.open(openMode: QIODevice::ReadOnly) && setContents(dev: &b, errorMessageIn: &errorMessage);
2177 if (!success && !errorMessage.isEmpty())
2178 designerWarning(message: errorMessage);
2179 return success;
2180}
2181
2182void FormWindow::layoutContainer(QWidget *w, int type)
2183{
2184 if (w == this)
2185 w = mainContainer();
2186
2187 w = core()->widgetFactory()->containerOfWidget(w);
2188
2189 const QObjectList l = w->children();
2190 // find managed widget children
2191 QWidgetList widgets;
2192 const QObjectList::const_iterator ocend = l.constEnd();
2193 for (QObjectList::const_iterator it = l.constBegin(); it != ocend; ++it)
2194 if ( (*it)->isWidgetType() ) {
2195 QWidget *widget = static_cast<QWidget*>(*it);
2196 if (widget->isVisibleTo(this) && isManaged(w: widget))
2197 widgets.append(t: widget);
2198 }
2199
2200 if (widgets.isEmpty()) // QTBUG-50563, observed when using hand-edited forms.
2201 return;
2202
2203 LayoutCommand *cmd = new LayoutCommand(this);
2204 cmd->init(parentWidget: mainContainer(), widgets, layoutType: static_cast<LayoutInfo::Type>(type), layoutBase: w);
2205 clearSelection(changePropertyDisplay: false);
2206 commandHistory()->push(cmd);
2207}
2208
2209bool FormWindow::hasInsertedChildren(QWidget *widget) const // ### move
2210{
2211 if (QDesignerContainerExtension *container = qt_extension<QDesignerContainerExtension*>(manager: core()->extensionManager(), object: widget)) {
2212 const int index = container->currentIndex();
2213 if (index < 0)
2214 return false;
2215 widget = container->widget(index);
2216 }
2217
2218 const QWidgetList l = widgets(widget);
2219
2220 for (QWidget *child : l) {
2221 if (isManaged(w: child) && !LayoutInfo::isWidgetLaidout(core: core(), widget: child) && child->isVisibleTo(const_cast<FormWindow*>(this)))
2222 return true;
2223 }
2224
2225 return false;
2226}
2227
2228// "Select Ancestor" sub menu code
2229void FormWindow::slotSelectWidget(QAction *a)
2230{
2231 if (QWidget *w = qvariant_cast<QWidget*>(v: a->data()))
2232 selectSingleWidget(w);
2233}
2234
2235void FormWindow::slotCleanChanged(bool clean)
2236{
2237 if (!clean)
2238 emit changed();
2239}
2240
2241static inline QString objectNameOf(const QWidget *w)
2242{
2243 if (const QLayoutWidget *lw = qobject_cast<const QLayoutWidget *>(object: w)) {
2244 const QLayout *layout = lw->layout();
2245 const QString rc = layout->objectName();
2246 if (!rc.isEmpty())
2247 return rc;
2248 // Fall thru for 4.3 forms which have a name on the widget: Display the class name
2249 return QString::fromUtf8(str: layout->metaObject()->className());
2250 }
2251 return w->objectName();
2252}
2253
2254QAction *FormWindow::createSelectAncestorSubMenu(QWidget *w)
2255{
2256 // Find the managed, unselected parents
2257 QWidgetList parents;
2258 QWidget *mc = mainContainer();
2259 for (QWidget *p = w->parentWidget(); p && p != mc; p = p->parentWidget())
2260 if (isManaged(w: p) && !isWidgetSelected(w: p))
2261 parents.push_back(t: p);
2262 if (parents.isEmpty())
2263 return nullptr;
2264 // Create a submenu listing the managed, unselected parents
2265 QMenu *menu = new QMenu;
2266 QActionGroup *ag = new QActionGroup(menu);
2267 QObject::connect(sender: ag, signal: &QActionGroup::triggered, receiver: this, slot: &FormWindow::slotSelectWidget);
2268 const int size = parents.size();
2269 for (int i = 0; i < size; i++) {
2270 QWidget *w = parents.at(i);
2271 QAction *a = ag->addAction(text: objectNameOf(w));
2272 a->setData(QVariant::fromValue(value: w));
2273 menu->addAction(action: a);
2274 }
2275 QAction *ma = new QAction(tr(s: "Select Ancestor"), nullptr);
2276 ma->setMenu(menu);
2277 return ma;
2278}
2279
2280QMenu *FormWindow::createPopupMenu(QWidget *w)
2281{
2282 QMenu *popup = createExtensionTaskMenu(fw: this, o: w, trailingSeparator: true);
2283 if (!popup)
2284 popup = new QMenu;
2285 // if w doesn't have a QDesignerTaskMenu as a child create one and make it a child.
2286 // insert actions from QDesignerTaskMenu
2287
2288 QDesignerFormWindowManagerInterface *manager = core()->formWindowManager();
2289 const bool isFormWindow = qobject_cast<const FormWindow*>(object: w);
2290
2291 // Check for special containers and obtain the page menu from them to add layout actions.
2292 if (!isFormWindow) {
2293 if (QStackedWidget *stackedWidget = qobject_cast<QStackedWidget*>(object: w)) {
2294 QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(stackedWidget, popup);
2295 } else if (QTabWidget *tabWidget = qobject_cast<QTabWidget*>(object: w)) {
2296 QTabWidgetEventFilter::addTabWidgetContextMenuActions(tabWidget, popup);
2297 } else if (QToolBox *toolBox = qobject_cast<QToolBox*>(object: w)) {
2298 QToolBoxHelper::addToolBoxContextMenuActions(toolbox: toolBox, popup);
2299 }
2300
2301 if (manager->action(action: QDesignerFormWindowManagerInterface::LowerAction)->isEnabled()) {
2302 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::LowerAction));
2303 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::RaiseAction));
2304 popup->addSeparator();
2305 }
2306#if QT_CONFIG(clipboard)
2307 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::CutAction));
2308 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::CopyAction));
2309#endif
2310 }
2311
2312#if QT_CONFIG(clipboard)
2313 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::PasteAction));
2314#endif
2315
2316 if (QAction *selectAncestorAction = createSelectAncestorSubMenu(w))
2317 popup->addAction(action: selectAncestorAction);
2318 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::SelectAllAction));
2319
2320 if (!isFormWindow) {
2321 popup->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::DeleteAction));
2322 }
2323
2324 popup->addSeparator();
2325 QMenu *layoutMenu = popup->addMenu(title: tr(s: "Lay out"));
2326 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::AdjustSizeAction));
2327 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::HorizontalLayoutAction));
2328 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::VerticalLayoutAction));
2329 if (!isFormWindow) {
2330 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::SplitHorizontalAction));
2331 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::SplitVerticalAction));
2332 }
2333 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::GridLayoutAction));
2334 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::FormLayoutAction));
2335 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::BreakLayoutAction));
2336 layoutMenu->addAction(action: manager->action(action: QDesignerFormWindowManagerInterface::SimplifyLayoutAction));
2337
2338 return popup;
2339}
2340
2341void FormWindow::resizeEvent(QResizeEvent *e)
2342{
2343 m_geometryChangedTimer->start(msec: 10);
2344
2345 QWidget::resizeEvent(event: e);
2346}
2347
2348/*!
2349 Maps \a pos in \a w's coordinates to the form's coordinate system.
2350
2351 This is the equivalent to mapFromGlobal(w->mapToGlobal(pos)) but
2352 avoids the two roundtrips to the X-Server on Unix/X11.
2353 */
2354QPoint FormWindow::mapToForm(const QWidget *w, const QPoint &pos) const
2355{
2356 QPoint p = pos;
2357 const QWidget* i = w;
2358 while (i && !i->isWindow() && !isMainContainer(w: i)) {
2359 p = i->mapToParent(p);
2360 i = i->parentWidget();
2361 }
2362
2363 return mapFromGlobal(w->mapToGlobal(pos));
2364}
2365
2366bool FormWindow::canBeBuddy(QWidget *w) const // ### rename me.
2367{
2368 if (QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: w)) {
2369 const int index = sheet->indexOf(QStringLiteral("focusPolicy"));
2370 if (index != -1) {
2371 bool ok = false;
2372 const Qt::FocusPolicy q = static_cast<Qt::FocusPolicy>(Utils::valueOf(value: sheet->property(index), ok: &ok));
2373 return ok && q != Qt::NoFocus;
2374 }
2375 }
2376
2377 return false;
2378}
2379
2380QWidget *FormWindow::findContainer(QWidget *w, bool excludeLayout) const
2381{
2382 if (!isChildOf(c: w, p: this)
2383 || const_cast<const QWidget *>(w) == this)
2384 return nullptr;
2385
2386 QDesignerWidgetFactoryInterface *widgetFactory = core()->widgetFactory();
2387 QDesignerWidgetDataBaseInterface *widgetDataBase = core()->widgetDataBase();
2388 QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase();
2389
2390 QWidget *container = widgetFactory->containerOfWidget(w: mainContainer()); // default parent for new widget is the formwindow
2391 if (!isMainContainer(w)) { // press was not on formwindow, check if we can find another parent
2392 while (w) {
2393 if (qobject_cast<InvisibleWidget*>(object: w) || !metaDataBase->item(object: w)) {
2394 w = w->parentWidget();
2395 continue;
2396 }
2397
2398 const bool isContainer = widgetDataBase->isContainer(object: w, resolveName: true) || w == mainContainer();
2399
2400 if (!isContainer || (excludeLayout && qobject_cast<QLayoutWidget*>(object: w))) { // ### skip QSplitter
2401 w = w->parentWidget();
2402 } else {
2403 container = w;
2404 break;
2405 }
2406 }
2407 }
2408
2409 return container;
2410}
2411
2412void FormWindow::simplifySelection(QWidgetList *sel) const
2413{
2414 if (sel->size() < 2)
2415 return;
2416 // Figure out which widgets should be removed from selection.
2417 // We want to remove those whose parent widget is also in the
2418 // selection (because the child widgets are contained by
2419 // their parent, they shouldn't be in the selection --
2420 // they are "implicitly" selected).
2421 QWidget *mainC = mainContainer(); // Quick check for main container first
2422 if (sel->contains(t: mainC)) {
2423 sel->clear();
2424 sel->push_back(t: mainC);
2425 return;
2426 }
2427 using WidgetVector = QVector<QWidget *>;
2428 WidgetVector toBeRemoved;
2429 toBeRemoved.reserve(asize: sel->size());
2430 const QWidgetList::const_iterator scend = sel->constEnd();
2431 for (QWidgetList::const_iterator it = sel->constBegin(); it != scend; ++it) {
2432 QWidget *child = *it;
2433 for (QWidget *w = child; true ; ) { // Is any of the parents also selected?
2434 QWidget *parent = w->parentWidget();
2435 if (!parent || parent == mainC)
2436 break;
2437 if (sel->contains(t: parent)) {
2438 toBeRemoved.append(t: child);
2439 break;
2440 }
2441 w = parent;
2442 }
2443 }
2444 // Now we can actually remove the widgets that were marked
2445 // for removal in the previous pass.
2446 if (!toBeRemoved.isEmpty()) {
2447 const WidgetVector::const_iterator rcend = toBeRemoved.constEnd();
2448 for (WidgetVector::const_iterator it = toBeRemoved.constBegin(); it != rcend; ++it)
2449 sel->removeAll(t: *it);
2450 }
2451}
2452
2453FormWindow *FormWindow::findFormWindow(QWidget *w)
2454{
2455 return qobject_cast<FormWindow*>(object: QDesignerFormWindowInterface::findFormWindow(w));
2456}
2457
2458bool FormWindow::isDirty() const
2459{
2460 return !m_undoStack.isClean();
2461}
2462
2463void FormWindow::setDirty(bool dirty)
2464{
2465 if (dirty)
2466 m_undoStack.resetClean();
2467 else
2468 m_undoStack.setClean();
2469}
2470
2471QWidget *FormWindow::containerAt(const QPoint &pos)
2472{
2473 QWidget *widget = widgetAt(pos);
2474 return findContainer(w: widget, excludeLayout: true);
2475}
2476
2477static QWidget *childAt_SkipDropLine(QWidget *w, QPoint pos)
2478{
2479 const QObjectList &child_list = w->children();
2480 for (int i = child_list.size() - 1; i >= 0; --i) {
2481 QObject *child_obj = child_list[i];
2482 if (qobject_cast<WidgetHandle*>(object: child_obj) != nullptr)
2483 continue;
2484 QWidget *child = qobject_cast<QWidget*>(o: child_obj);
2485 if (!child || child->isWindow() || !child->isVisible() ||
2486 !child->geometry().contains(p: pos) || child->testAttribute(attribute: Qt::WA_TransparentForMouseEvents))
2487 continue;
2488 const QPoint childPos = child->mapFromParent(pos);
2489 if (QWidget *res = childAt_SkipDropLine(w: child, pos: childPos))
2490 return res;
2491 if (child->testAttribute(attribute: Qt::WA_MouseNoMask) || child->mask().contains(p: pos)
2492 || child->mask().isEmpty())
2493 return child;
2494 }
2495
2496 return nullptr;
2497}
2498
2499QWidget *FormWindow::widgetAt(const QPoint &pos)
2500{
2501 QWidget *w = childAt(p: pos);
2502 if (qobject_cast<const WidgetHandle*>(object: w) != 0)
2503 w = childAt_SkipDropLine(w: this, pos);
2504 return (w == nullptr || w == formContainer()) ? this : w;
2505}
2506
2507void FormWindow::highlightWidget(QWidget *widget, const QPoint &pos, HighlightMode mode)
2508{
2509 Q_ASSERT(widget);
2510
2511 if (QMainWindow *mainWindow = qobject_cast<QMainWindow*> (object: widget)) {
2512 widget = mainWindow->centralWidget();
2513 }
2514
2515 QWidget *container = findContainer(w: widget, excludeLayout: false);
2516
2517 if (container == nullptr || core()->metaDataBase()->item(object: container) == nullptr)
2518 return;
2519
2520 if (QDesignerActionProviderExtension *g = qt_extension<QDesignerActionProviderExtension*>(manager: core()->extensionManager(), object: container)) {
2521 if (mode == Restore) {
2522 g->adjustIndicator(pos: QPoint());
2523 } else {
2524 const QPoint pt = widget->mapTo(container, pos);
2525 g->adjustIndicator(pos: pt);
2526 }
2527 } else if (QDesignerLayoutDecorationExtension *g = qt_extension<QDesignerLayoutDecorationExtension*>(manager: core()->extensionManager(), object: container)) {
2528 if (mode == Restore) {
2529 g->adjustIndicator(pos: QPoint(), index: -1);
2530 } else {
2531 const QPoint pt = widget->mapTo(container, pos);
2532 const int index = g->findItemAt(pos: pt);
2533 g->adjustIndicator(pos: pt, index);
2534 }
2535 }
2536
2537 QMainWindow *mw = qobject_cast<QMainWindow*> (object: container);
2538 if (container == mainContainer() || (mw && mw->centralWidget() && mw->centralWidget() == container))
2539 return;
2540
2541 if (mode == Restore) {
2542 const WidgetPaletteMap::iterator pit = m_palettesBeforeHighlight.find(akey: container);
2543 if (pit != m_palettesBeforeHighlight.end()) {
2544 container->setPalette(pit.value().first);
2545 container->setAutoFillBackground(pit.value().second);
2546 m_palettesBeforeHighlight.erase(it: pit);
2547 }
2548 } else {
2549 QPalette p = container->palette();
2550 if (!m_palettesBeforeHighlight.contains(akey: container)) {
2551 PaletteAndFill paletteAndFill;
2552 if (container->testAttribute(attribute: Qt::WA_SetPalette))
2553 paletteAndFill.first = p;
2554 paletteAndFill.second = container->autoFillBackground();
2555 m_palettesBeforeHighlight.insert(akey: container, avalue: paletteAndFill);
2556 }
2557
2558 p.setColor(acr: backgroundRole(), acolor: p.midlight().color());
2559 container->setPalette(p);
2560 container->setAutoFillBackground(true);
2561 }
2562}
2563
2564QWidgetList FormWindow::widgets(QWidget *widget) const
2565{
2566 if (widget->children().isEmpty())
2567 return QWidgetList();
2568 QWidgetList rc;
2569 for (QObject *o : widget->children()) {
2570 if (o->isWidgetType()) {
2571 QWidget *w = qobject_cast<QWidget*>(o);
2572 if (isManaged(w))
2573 rc.push_back(t: w);
2574 }
2575 }
2576 return rc;
2577}
2578
2579int FormWindow::toolCount() const
2580{
2581 return m_widgetStack->count();
2582}
2583
2584QDesignerFormWindowToolInterface *FormWindow::tool(int index) const
2585{
2586 return m_widgetStack->tool(index);
2587}
2588
2589void FormWindow::registerTool(QDesignerFormWindowToolInterface *tool)
2590{
2591 Q_ASSERT(tool != nullptr);
2592
2593 m_widgetStack->addTool(tool);
2594
2595 if (m_mainContainer)
2596 m_mainContainer->update();
2597}
2598
2599void FormWindow::setCurrentTool(int index)
2600{
2601 m_widgetStack->setCurrentTool(index);
2602}
2603
2604int FormWindow::currentTool() const
2605{
2606 return m_widgetStack->currentIndex();
2607}
2608
2609bool FormWindow::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event)
2610{
2611 if (m_widgetStack == nullptr)
2612 return false;
2613
2614 QDesignerFormWindowToolInterface *tool = m_widgetStack->currentTool();
2615 if (tool == nullptr)
2616 return false;
2617
2618 return tool->handleEvent(widget, managedWidget, event);
2619}
2620
2621void FormWindow::initializeCoreTools()
2622{
2623 m_widgetEditor = new WidgetEditorTool(this);
2624 registerTool(tool: m_widgetEditor);
2625}
2626
2627void FormWindow::checkSelection()
2628{
2629 m_checkSelectionTimer->start(msec: 0);
2630}
2631
2632void FormWindow::checkSelectionNow()
2633{
2634 m_checkSelectionTimer->stop();
2635
2636 const QWidgetList &sel = selectedWidgets();
2637 for (QWidget *widget : sel) {
2638 updateSelection(w: widget);
2639
2640 if (LayoutInfo::layoutType(core: core(), w: widget) != LayoutInfo::NoLayout)
2641 updateChildSelections(w: widget);
2642 }
2643}
2644
2645QString FormWindow::author() const
2646{
2647 return m_author;
2648}
2649
2650QString FormWindow::comment() const
2651{
2652 return m_comment;
2653}
2654
2655void FormWindow::setAuthor(const QString &author)
2656{
2657 m_author = author;
2658}
2659
2660void FormWindow::setComment(const QString &comment)
2661{
2662 m_comment = comment;
2663}
2664
2665void FormWindow::editWidgets()
2666{
2667 m_widgetEditor->action()->trigger();
2668}
2669
2670QStringList FormWindow::resourceFiles() const
2671{
2672 return m_resourceFiles;
2673}
2674
2675void FormWindow::addResourceFile(const QString &path)
2676{
2677 if (!m_resourceFiles.contains(str: path)) {
2678 m_resourceFiles.append(t: path);
2679 setDirty(true);
2680 emit resourceFilesChanged();
2681 }
2682}
2683
2684void FormWindow::removeResourceFile(const QString &path)
2685{
2686 if (m_resourceFiles.removeAll(t: path) > 0) {
2687 setDirty(true);
2688 emit resourceFilesChanged();
2689 }
2690}
2691
2692bool FormWindow::blockSelectionChanged(bool b)
2693{
2694 const bool blocked = m_blockSelectionChanged;
2695 m_blockSelectionChanged = b;
2696 return blocked;
2697}
2698
2699void FormWindow::editContents()
2700{
2701 const QWidgetList sel = selectedWidgets();
2702 if (sel.count() == 1) {
2703 QWidget *widget = sel.first();
2704
2705 if (QAction *a = preferredEditAction(core: core(), managedWidget: widget))
2706 a->trigger();
2707 }
2708}
2709
2710void FormWindow::dragWidgetWithinForm(QWidget *widget, const QRect &targetGeometry, QWidget *targetContainer)
2711{
2712 const bool fromLayout = canDragWidgetInLayout(core: core(), w: widget);
2713 const QDesignerLayoutDecorationExtension *targetDeco = qt_extension<QDesignerLayoutDecorationExtension*>(manager: core()->extensionManager(), object: targetContainer);
2714 const bool toLayout = targetDeco != nullptr;
2715
2716 if (fromLayout) {
2717 // Drag from Layout: We need to delete the widget properly to store the layout state
2718 // Do not simplify the layout when dragging onto a layout
2719 // as this might invalidate the insertion position if it is the same layout
2720 DeleteWidgetCommand *cmd = new DeleteWidgetCommand(this);
2721 unsigned deleteFlags = DeleteWidgetCommand::DoNotUnmanage;
2722 if (toLayout)
2723 deleteFlags |= DeleteWidgetCommand::DoNotSimplifyLayout;
2724 cmd->init(widget, flags: deleteFlags);
2725 commandHistory()->push(cmd);
2726 }
2727
2728 if (toLayout) {
2729 // Drag from form to layout: just insert. Do not manage
2730 insertWidget(w: widget, rect: targetGeometry, container: targetContainer, already_in_form: true);
2731 } else {
2732 // into container without layout
2733 if (targetContainer != widget->parent()) { // different parent
2734 ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this);
2735 cmd->init(widget, parentWidget: targetContainer );
2736 commandHistory()->push(cmd);
2737 }
2738 resizeWidget(widget, geometry: targetGeometry);
2739 selectWidget(w: widget, select: true);
2740 widget->show();
2741 }
2742}
2743
2744static Qt::DockWidgetArea detectDropArea(QMainWindow *mainWindow, const QRect &area, const QPoint &drop)
2745{
2746 QPoint offset = area.topLeft();
2747 QRect rect = area;
2748 rect.moveTopLeft(p: QPoint(0, 0));
2749 QPoint point = drop - offset;
2750 const int x = point.x();
2751 const int y = point.y();
2752 const int w = rect.width();
2753 const int h = rect.height();
2754
2755 if (rect.contains(p: point)) {
2756 bool topRight = false;
2757 bool topLeft = false;
2758 if (w * y < h * x) // top and right, oterwise bottom and left
2759 topRight = true;
2760 if (w * y < h * (w - x)) // top and left, otherwise bottom and right
2761 topLeft = true;
2762
2763 if (topRight && topLeft)
2764 return Qt::TopDockWidgetArea;
2765 if (topRight && !topLeft)
2766 return Qt::RightDockWidgetArea;
2767 if (!topRight && topLeft)
2768 return Qt::LeftDockWidgetArea;
2769 return Qt::BottomDockWidgetArea;
2770 }
2771
2772 if (x < 0) {
2773 if (y < 0)
2774 return mainWindow->corner(corner: Qt::TopLeftCorner);
2775 return y > h ? mainWindow->corner(corner: Qt::BottomLeftCorner) : Qt::LeftDockWidgetArea;
2776 }
2777 if (x > w) {
2778 if (y < 0)
2779 return mainWindow->corner(corner: Qt::TopRightCorner);
2780 return y > h ? mainWindow->corner(corner: Qt::BottomRightCorner) : Qt::RightDockWidgetArea;
2781 }
2782 return y < 0 ? Qt::TopDockWidgetArea :Qt::LeftDockWidgetArea;
2783}
2784
2785bool FormWindow::dropDockWidget(QDesignerDnDItemInterface *item, const QPoint &global_mouse_pos)
2786{
2787 DomUI *dom_ui = item->domUi();
2788
2789 QMainWindow *mw = qobject_cast<QMainWindow *>(object: mainContainer());
2790 if (!mw)
2791 return false;
2792
2793 QDesignerResource resource(this);
2794 const FormBuilderClipboard clipboard = resource.paste(ui: dom_ui, widgetParent: mw);
2795 if (clipboard.m_widgets.size() != 1) // multiple-paste from DomUI not supported yet
2796 return false;
2797
2798 QWidget *centralWidget = mw->centralWidget();
2799 QPoint localPos = centralWidget->mapFromGlobal(global_mouse_pos);
2800 const QRect centralWidgetAreaRect = centralWidget->rect();
2801 Qt::DockWidgetArea area = detectDropArea(mainWindow: mw, area: centralWidgetAreaRect, drop: localPos);
2802
2803 beginCommand(description: tr(s: "Drop widget"));
2804
2805 clearSelection(changePropertyDisplay: false);
2806 highlightWidget(widget: mw, pos: QPoint(0, 0), mode: FormWindow::Restore);
2807
2808 QWidget *widget = clipboard.m_widgets.first();
2809
2810 insertWidget(w: widget, rect: QRect(0, 0, 1, 1), container: mw);
2811
2812 selectWidget(w: widget, select: true);
2813 mw->setFocus(Qt::MouseFocusReason); // in case focus was in e.g. object inspector
2814
2815 core()->formWindowManager()->setActiveFormWindow(this);
2816 mainContainer()->activateWindow();
2817
2818 QDesignerPropertySheetExtension *propertySheet = qobject_cast<QDesignerPropertySheetExtension*>(object: m_core->extensionManager()->extension(object: widget, Q_TYPEID(QDesignerPropertySheetExtension)));
2819 if (propertySheet) {
2820 const QString dockWidgetAreaName = QStringLiteral("dockWidgetArea");
2821 PropertySheetEnumValue e = qvariant_cast<PropertySheetEnumValue>(v: propertySheet->property(index: propertySheet->indexOf(name: dockWidgetAreaName)));
2822 e.value = area;
2823 QVariant v;
2824 v.setValue(e);
2825 SetPropertyCommand *cmd = new SetPropertyCommand(this);
2826 cmd->init(object: widget, propertyName: dockWidgetAreaName, newValue: v);
2827 m_undoStack.push(cmd);
2828 }
2829
2830 endCommand();
2831 return true;
2832}
2833
2834bool FormWindow::dropWidgets(const QList<QDesignerDnDItemInterface*> &item_list, QWidget *target,
2835 const QPoint &global_mouse_pos)
2836{
2837
2838 QWidget *parent = target;
2839 if (parent == nullptr)
2840 parent = mainContainer();
2841 // You can only drop stuff onto the central widget of a QMainWindow
2842 // ### generalize to use container extension
2843 if (QMainWindow *main_win = qobject_cast<QMainWindow*>(object: target)) {
2844 if (!main_win->centralWidget()) {
2845 designerWarning(message: tr(s: "A QMainWindow-based form does not contain a central widget."));
2846 return false;
2847 }
2848 const QPoint main_win_pos = main_win->mapFromGlobal(global_mouse_pos);
2849 const QRect central_wgt_geo = main_win->centralWidget()->geometry();
2850 if (!central_wgt_geo.contains(p: main_win_pos))
2851 return false;
2852 }
2853
2854 QWidget *container = findContainer(w: parent, excludeLayout: false);
2855 if (container == nullptr)
2856 return false;
2857
2858 beginCommand(description: tr(s: "Drop widget"));
2859
2860 clearSelection(changePropertyDisplay: false);
2861 highlightWidget(widget: target, pos: target->mapFromGlobal(global_mouse_pos), mode: FormWindow::Restore);
2862
2863 QPoint offset;
2864 QDesignerDnDItemInterface *current = nullptr;
2865 QDesignerFormWindowCursorInterface *c = cursor();
2866 for (QDesignerDnDItemInterface *item : qAsConst(t: item_list)) {
2867 QWidget *w = item->widget();
2868 if (!current)
2869 current = item;
2870 if (c->current() == w) {
2871 current = item;
2872 break;
2873 }
2874 }
2875 if (current) {
2876 QRect geom = current->decoration()->geometry();
2877 QPoint topLeft = container->mapFromGlobal(geom.topLeft());
2878 offset = designerGrid().snapPoint(p: topLeft) - topLeft;
2879 }
2880
2881 for (QDesignerDnDItemInterface *item : qAsConst(t: item_list)) {
2882 DomUI *dom_ui = item->domUi();
2883 QRect geometry = item->decoration()->geometry();
2884 Q_ASSERT(dom_ui != nullptr);
2885
2886 geometry.moveTopLeft(p: container->mapFromGlobal(geometry.topLeft()) + offset);
2887 if (item->type() == QDesignerDnDItemInterface::CopyDrop) { // from widget box or CTRL + mouse move
2888 QWidget *widget = createWidget(ui: dom_ui, rc: geometry, target: parent);
2889 if (!widget) {
2890 endCommand();
2891 return false;
2892 }
2893 selectWidget(w: widget, select: true);
2894 mainContainer()->setFocus(Qt::MouseFocusReason); // in case focus was in e.g. object inspector
2895 } else { // same form move
2896 QWidget *widget = item->widget();
2897 Q_ASSERT(widget != nullptr);
2898 QDesignerFormWindowInterface *dest = findFormWindow(w: widget);
2899 if (dest == this) {
2900 dragWidgetWithinForm(widget, targetGeometry: geometry, targetContainer: container);
2901 } else { // from other form
2902 FormWindow *source = qobject_cast<FormWindow*>(object: item->source());
2903 Q_ASSERT(source != nullptr);
2904
2905 source->deleteWidgetList(widget_list: QWidgetList() << widget);
2906 QWidget *new_widget = createWidget(ui: dom_ui, rc: geometry, target: parent);
2907
2908 selectWidget(w: new_widget, select: true);
2909 }
2910 }
2911 }
2912
2913 core()->formWindowManager()->setActiveFormWindow(this);
2914 mainContainer()->activateWindow();
2915 endCommand();
2916 return true;
2917}
2918
2919QDir FormWindow::absoluteDir() const
2920{
2921 if (fileName().isEmpty())
2922 return QDir::current();
2923
2924 return QFileInfo(fileName()).absoluteDir();
2925}
2926
2927void FormWindow::layoutDefault(int *margin, int *spacing)
2928{
2929 *margin = m_defaultMargin;
2930 *spacing = m_defaultSpacing;
2931}
2932
2933void FormWindow::setLayoutDefault(int margin, int spacing)
2934{
2935 m_defaultMargin = margin;
2936 m_defaultSpacing = spacing;
2937}
2938
2939void FormWindow::layoutFunction(QString *margin, QString *spacing)
2940{
2941 *margin = m_marginFunction;
2942 *spacing = m_spacingFunction;
2943}
2944
2945void FormWindow::setLayoutFunction(const QString &margin, const QString &spacing)
2946{
2947 m_marginFunction = margin;
2948 m_spacingFunction = spacing;
2949}
2950
2951QString FormWindow::pixmapFunction() const
2952{
2953 return m_pixmapFunction;
2954}
2955
2956void FormWindow::setPixmapFunction(const QString &pixmapFunction)
2957{
2958 m_pixmapFunction = pixmapFunction;
2959}
2960
2961QStringList FormWindow::includeHints() const
2962{
2963 return m_includeHints;
2964}
2965
2966void FormWindow::setIncludeHints(const QStringList &includeHints)
2967{
2968 m_includeHints = includeHints;
2969}
2970
2971QString FormWindow::exportMacro() const
2972{
2973 return m_exportMacro;
2974}
2975
2976void FormWindow::setExportMacro(const QString &exportMacro)
2977{
2978 m_exportMacro = exportMacro;
2979}
2980
2981QEditorFormBuilder *FormWindow::createFormBuilder()
2982{
2983 return new QDesignerResource(this);
2984}
2985
2986QWidget *FormWindow::formContainer() const
2987{
2988 return m_widgetStack->formContainer();
2989}
2990
2991QUndoStack *FormWindow::commandHistory() const
2992{
2993 return &const_cast<QUndoStack &>(m_undoStack);
2994}
2995
2996} // namespace
2997
2998QT_END_NAMESPACE
2999
3000

source code of qttools/src/designer/src/components/formeditor/formwindow.cpp