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
99 Q_DECLARE_METATYPE(QWidget*)
100
101 QT_BEGIN_NAMESPACE
102
103 namespace {
104 class BlockSelection
105 {
106 Q_DISABLE_COPY_MOVE(BlockSelection)
107 public:
BlockSelection(qdesigner_internal::FormWindow * fw)108 BlockSelection(qdesigner_internal::FormWindow *fw)
109 : m_formWindow(fw),
110 m_blocked(m_formWindow->blockSelectionChanged(true))
111 {
112 }
113
~BlockSelection()114 ~BlockSelection()
115 {
116 if (m_formWindow)
117 m_formWindow->blockSelectionChanged(m_blocked);
118 }
119
120 private:
121 QPointer<qdesigner_internal::FormWindow> m_formWindow;
122 const bool m_blocked;
123 };
124
125 enum { debugFormWindow = 0 };
126 }
127
128 namespace qdesigner_internal {
129
130 // ------------------------ FormWindow::Selection
131 // Maintains a pool of WidgetSelections to be used for selected widgets.
132
133 class FormWindow::Selection
134 {
135 Q_DISABLE_COPY_MOVE(Selection)
136 public:
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
164 private:
165
166 using SelectionPool = QVector<WidgetSelection *>;
167 SelectionPool m_selectionPool;
168
169 typedef QHash<QWidget *, WidgetSelection *> SelectionHash;
170 SelectionHash m_usedSelections;
171 };
172
173 FormWindow::Selection::Selection() = default;
174
~Selection()175 FormWindow::Selection::~Selection()
176 {
177 clearSelectionPool();
178 }
179
clear()180 void 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
clearSelectionPool()189 void FormWindow::Selection::clearSelectionPool()
190 {
191 clear();
192 qDeleteAll(m_selectionPool);
193 m_selectionPool.clear();
194 }
195
addWidget(FormWindow * fw,QWidget * w)196 WidgetSelection *FormWindow::Selection::addWidget(FormWindow* fw, QWidget *w)
197 {
198 WidgetSelection *rc = m_usedSelections.value(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(rc);
215 }
216
217 m_usedSelections.insert(w, rc);
218 rc->setWidget(w);
219 return rc;
220 }
221
removeWidget(QWidget * w)222 QWidget* FormWindow::Selection::removeWidget(QWidget *w)
223 {
224 WidgetSelection *s = m_usedSelections.value(w);
225 if (!s)
226 return w;
227
228 s->setWidget(nullptr);
229 m_usedSelections.remove(w);
230
231 if (m_usedSelections.isEmpty())
232 return nullptr;
233
234 return (*m_usedSelections.begin())->widget();
235 }
236
repaintSelection(QWidget * w)237 void FormWindow::Selection::repaintSelection(QWidget *w)
238 {
239 if (WidgetSelection *s = m_usedSelections.value(w))
240 s->update();
241 }
242
repaintSelection()243 void FormWindow::Selection::repaintSelection()
244 {
245 for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it)
246 it.value()->update();
247 }
248
isWidgetSelected(QWidget * w) const249 bool FormWindow::Selection::isWidgetSelected(QWidget *w) const{
250 return m_usedSelections.contains(w);
251 }
252
selectedWidgets() const253 QWidgetList FormWindow::Selection::selectedWidgets() const
254 {
255 return m_usedSelections.keys();
256 }
257
raiseList(const QWidgetList & l)258 void 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(w->widget()))
263 w->show();
264 }
265 }
266
raiseWidget(QWidget * w)267 void FormWindow::Selection::raiseWidget(QWidget *w)
268 {
269 if (WidgetSelection *s = m_usedSelections.value(w))
270 s->show();
271 }
272
updateGeometry(QWidget * w)273 void FormWindow::Selection::updateGeometry(QWidget *w)
274 {
275 if (WidgetSelection *s = m_usedSelections.value(w)) {
276 s->updateGeometry();
277 }
278 }
279
hide(QWidget * w)280 void FormWindow::Selection::hide(QWidget *w)
281 {
282 if (WidgetSelection *s = m_usedSelections.value(w))
283 s->hide();
284 }
285
show(QWidget * w)286 void FormWindow::Selection::show(QWidget *w)
287 {
288 if (WidgetSelection *s = m_usedSelections.value(w))
289 s->show();
290 }
291
292 // ------------------------ FormWindow
FormWindow(FormEditor * core,QWidget * parent,Qt::WindowFlags flags)293 FormWindow::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, m_widgetStack->formContainer(), 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(this);
310
311 setDirty(false);
312 setAcceptDrops(true);
313 }
314
~FormWindow()315 FormWindow::~FormWindow()
316 {
317 Q_ASSERT(core() != nullptr);
318 Q_ASSERT(core()->metaDataBase() != nullptr);
319 Q_ASSERT(core()->formWindowManager() != nullptr);
320
321 core()->formWindowManager()->removeFormWindow(this);
322 core()->metaDataBase()->remove(this);
323
324 const QWidgetList &l = widgets();
325 for (QWidget *w : l)
326 core()->metaDataBase()->remove(w);
327
328 m_widgetStack = nullptr;
329 m_rubberBand = nullptr;
330 if (resourceSet())
331 core()->resourceModel()->removeResourceSet(resourceSet());
332 delete m_selection;
333
334 if (FormWindowManager *manager = qobject_cast<FormWindowManager*> (core()->formWindowManager()))
335 manager->undoGroup()->removeStack(&m_undoStack);
336 m_undoStack.disconnect();
337 }
338
core() const339 QDesignerFormEditorInterface *FormWindow::core() const
340 {
341 return m_core;
342 }
343
cursor() const344 QDesignerFormWindowCursorInterface *FormWindow::cursor() const
345 {
346 return m_cursor;
347 }
348
updateWidgets()349 void FormWindow::updateWidgets()
350 {
351 if (!m_mainContainer)
352 return;
353 }
354
widgetDepth(const QWidget * w)355 int 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
isChildOf(const QWidget * c,const QWidget * p)366 bool 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
setCursorToAll(const QCursor & c,QWidget * start)376 void 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*>(widget)) {
383 widget->setCursor(c);
384 }
385 }
386 #endif
387 }
388
init()389 void FormWindow::init()
390 {
391 if (FormWindowManager *manager = qobject_cast<FormWindowManager*> (core()->formWindowManager())) {
392 manager->undoGroup()->addStack(&m_undoStack);
393 }
394
395 m_blockSelectionChanged = false;
396
397 m_defaultMargin = INT_MIN;
398 m_defaultSpacing = INT_MIN;
399
400 connect(m_widgetStack, &FormWindowWidgetStack::currentToolChanged,
401 this, &QDesignerFormWindowInterface::toolChanged);
402
403 m_selectionChangedTimer = new QTimer(this);
404 m_selectionChangedTimer->setSingleShot(true);
405 connect(m_selectionChangedTimer, &QTimer::timeout, this,
406 &FormWindow::selectionChangedTimerDone);
407
408 m_checkSelectionTimer = new QTimer(this);
409 m_checkSelectionTimer->setSingleShot(true);
410 connect(m_checkSelectionTimer, &QTimer::timeout,
411 this, &FormWindow::checkSelectionNow);
412
413 m_geometryChangedTimer = new QTimer(this);
414 m_geometryChangedTimer->setSingleShot(true);
415 connect(m_geometryChangedTimer, &QTimer::timeout,
416 this, &QDesignerFormWindowInterface::geometryChanged);
417
418 m_rubberBand = nullptr;
419
420 setFocusPolicy(Qt::StrongFocus);
421
422 m_mainContainer = nullptr;
423 m_currentWidget = nullptr;
424
425 connect(&m_undoStack, &QUndoStack::indexChanged,
426 this, &QDesignerFormWindowInterface::changed);
427 connect(&m_undoStack, &QUndoStack::cleanChanged,
428 this, &FormWindow::slotCleanChanged);
429 connect(this, &QDesignerFormWindowInterface::changed,
430 this, &FormWindow::checkSelection);
431
432 core()->metaDataBase()->add(this);
433
434 initializeCoreTools();
435
436 QAction *a = new QAction(this);
437 a->setText(tr("Edit contents"));
438 a->setShortcut(tr("F2"));
439 connect(a, &QAction::triggered, this, &FormWindow::editContents);
440 addAction(a);
441 }
442
mainContainer() const443 QWidget *FormWindow::mainContainer() const
444 {
445 return m_mainContainer;
446 }
447
448
clearMainContainer()449 void FormWindow::clearMainContainer()
450 {
451 if (m_mainContainer) {
452 setCurrentTool(0);
453 m_widgetStack->setMainContainer(nullptr);
454 core()->metaDataBase()->remove(m_mainContainer);
455 unmanageWidget(m_mainContainer);
456 delete m_mainContainer;
457 m_mainContainer = nullptr;
458 }
459 }
460
setMainContainer(QWidget * w)461 void 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(m_mainContainer);
478
479 if (QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), m_mainContainer)) {
480 sheet->setVisible(sheet->indexOf(QStringLiteral("windowTitle")), true);
481 sheet->setVisible(sheet->indexOf(QStringLiteral("windowIcon")), true);
482 sheet->setVisible(sheet->indexOf(QStringLiteral("windowModality")), true);
483 sheet->setVisible(sheet->indexOf(QStringLiteral("windowOpacity")), true);
484 sheet->setVisible(sheet->indexOf(QStringLiteral("windowFilePath")), true);
485 // ### generalize
486 }
487
488 m_mainContainer->setFocusPolicy(Qt::StrongFocus);
489 m_mainContainer->resize(sz);
490
491 emit mainContainerChanged(m_mainContainer);
492 }
493
findTargetContainer(QWidget * widget) const494 QWidget *FormWindow::findTargetContainer(QWidget *widget) const
495 {
496 Q_ASSERT(widget);
497
498 while (QWidget *parentWidget = widget->parentWidget()) {
499 if (LayoutInfo::layoutType(m_core, parentWidget) == LayoutInfo::NoLayout && isManaged(widget))
500 return widget;
501
502 widget = parentWidget;
503 }
504
505 return mainContainer();
506 }
507
clearObjectInspectorSelection(const QDesignerFormEditorInterface * core)508 static inline void clearObjectInspectorSelection(const QDesignerFormEditorInterface *core)
509 {
510 if (QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(core->objectInspector()))
511 oi->clearSelection();
512 }
513
514 // Find a parent of a desired selection state
findSelectedParent(QDesignerFormWindowInterface * fw,const QWidget * w,bool selected)515 static 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(p))
521 if (cursor->isWidgetSelected(p) == selected)
522 return p;
523 return nullptr;
524 }
525
526 // Mouse modifiers.
527
528 enum MouseFlags { ToggleSelectionModifier = 0x1, CycleParentModifier=0x2, CopyDragModifier=0x4 };
529
mouseFlags(Qt::KeyboardModifiers mod)530 static 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.
handleClickSelection(QWidget * managedWidget,unsigned mouseMode)551 void FormWindow::handleClickSelection(QWidget *managedWidget, unsigned mouseMode)
552 {
553 const bool sameWidget = managedWidget == m_lastClickedWidget;
554 m_lastClickedWidget = managedWidget;
555
556 const bool selected = isWidgetSelected(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(managedWidget, !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(this, managedWidget, true))
571 selectionCandidate = findSelectedParent(this, currentlySelectedParent, 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(selectionCandidate);
578 }
579
selectSingleWidget(QWidget * w)580 void FormWindow::selectSingleWidget(QWidget *w)
581 {
582 clearSelection(false);
583 selectWidget(w, true);
584 raiseChildSelections(w);
585 }
586
handleMousePressEvent(QWidget * widget,QWidget * managedWidget,QMouseEvent * e)587 bool 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(managedWidget)) { // press was on the formwindow
608 clearObjectInspectorSelection(m_core); // We might have a toolbar or non-widget selected in the object inspector.
609 clearSelection(false);
610
611 m_mouseState = MouseDrawRubber;
612 m_currRect = QRect();
613 startRectDraw(mapFromGlobal(e->globalPos()), this, Rubber);
614 return true;
615 }
616 if (buttons != Qt::LeftButton)
617 return true;
618
619 const unsigned mouseMode = mouseFlags(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(managedWidget) || findSelectedParent(this, managedWidget, 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, effectiveMouseMode);
636 }
637 return true;
638 }
639
640 // We can drag widget in managed layouts except splitter.
canDragWidgetInLayout(const QDesignerFormEditorInterface * core,QWidget * w)641 static bool canDragWidgetInLayout(const QDesignerFormEditorInterface *core, QWidget *w)
642 {
643 bool managed;
644 const LayoutInfo::Type type = LayoutInfo::laidoutWidgetType(core ,w, &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
handleMouseMoveEvent(QWidget *,QWidget *,QMouseEvent * e)658 bool 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(pos, this, 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(true);
686
687 QWidgetList sel = selectedWidgets();
688 const QWidgetList originalSelection = sel;
689 simplifySelection(&sel);
690
691 QSet<QWidget*> widget_set;
692
693 for (QWidget *child : qAsConst(sel)) { // Move parent layout or container?
694 QWidget *current = child;
695
696 bool done = false;
697 while (!isMainContainer(current) && !done) {
698 if (!isManaged(current)) {
699 current = current->parentWidget();
700 continue;
701 }
702 if (LayoutInfo::isWidgetLaidout(core(), current)) {
703 // Go up to parent of layout if shift pressed, else do that only for splitters
704 if (!canDragWidgetInLayout(core(), 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(current);
716 }
717
718 sel = widget_set.values();
719 QDesignerFormWindowCursorInterface *c = cursor();
720 QWidget *current = c->current();
721 if (sel.contains(current)) {
722 sel.removeAll(current);
723 sel.prepend(current);
724 }
725
726 QList<QDesignerDnDItemInterface*> item_list;
727 const QPoint globalPos = mapToGlobal(m_startPos);
728 const QDesignerDnDItemInterface::DropType dropType = (mouseFlags(e->modifiers()) & CopyDragModifier) ?
729 QDesignerDnDItemInterface::CopyDrop : QDesignerDnDItemInterface::MoveDrop;
730 for (QWidget *widget : qAsConst(sel)) {
731 item_list.append(new FormWindowDnDItem(dropType, this, widget, globalPos));
732 if (dropType == QDesignerDnDItemInterface::MoveDrop) {
733 m_selection->hide(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(widget);
742
743 blockSelectionChanged(blocked);
744
745 if (!sel.isEmpty()) // reshow selection?
746 if (QDesignerMimeData::execDrag(item_list, core()->topLevel()) == Qt::IgnoreAction && dropType == QDesignerDnDItemInterface::MoveDrop)
747 for (QWidget *widget : qAsConst(sel))
748 m_selection->show(widget);
749
750 m_startPos = QPoint();
751
752 return true;
753 }
754
handleMouseReleaseEvent(QWidget * w,QWidget * mw,QMouseEvent * e)755 bool 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(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(mw, mouseFlags(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
checkPreviewGeometry(QRect & r)802 void FormWindow::checkPreviewGeometry(QRect &r)
803 {
804 if (!rect().contains(r)) {
805 if (r.left() < rect().left())
806 r.moveTopLeft(QPoint(0, r.top()));
807 if (r.right() > rect().right())
808 r.moveBottomRight(QPoint(rect().right(), r.bottom()));
809 if (r.top() < rect().top())
810 r.moveTopLeft(QPoint(r.left(), rect().top()));
811 if (r.bottom() > rect().bottom())
812 r.moveBottomRight(QPoint(r.right(), rect().bottom()));
813 }
814 }
815
startRectDraw(const QPoint & pos,QWidget *,RectType t)816 void FormWindow::startRectDraw(const QPoint &pos, QWidget *, RectType t)
817 {
818 m_rectAnchor = (t == Insert) ? designerGrid().snapPoint(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
continueRectDraw(const QPoint & pos,QWidget *,RectType t)827 void FormWindow::continueRectDraw(const QPoint &pos, QWidget *, RectType t)
828 {
829 const QPoint p2 = (t == Insert) ? designerGrid().snapPoint(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
endRectDraw()844 void FormWindow::endRectDraw()
845 {
846 if (m_rubberBand) {
847 delete m_rubberBand;
848 m_rubberBand = nullptr;
849 }
850 }
851
currentWidget() const852 QWidget *FormWindow::currentWidget() const
853 {
854 return m_currentWidget;
855 }
856
setCurrentWidget(QWidget * currentWidget)857 bool 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(m_currentWidget);
866 }
867 // set new and repaint
868 m_currentWidget = currentWidget;
869 if (m_currentWidget && m_currentWidget != mainContainer()) {
870 m_selection->repaintSelection(m_currentWidget);
871 }
872 return true;
873 }
874
selectWidget(QWidget * w,bool select)875 void 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.
trySelectWidget(QWidget * w,bool select)882 bool 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(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
clearSelection(bool changePropertyDisplay)912 void 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
emitSelectionChanged()924 void FormWindow::emitSelectionChanged()
925 {
926 if (m_blockSelectionChanged) // nothing to do
927 return;
928
929 m_selectionChangedTimer->start(0);
930 }
931
selectionChangedTimerDone()932 void FormWindow::selectionChangedTimerDone()
933 {
934 emit selectionChanged();
935 }
936
isWidgetSelected(QWidget * w) const937 bool FormWindow::isWidgetSelected(QWidget *w) const
938 {
939 return m_selection->isWidgetSelected(w);
940 }
941
isMainContainer(const QWidget * w) const942 bool FormWindow::isMainContainer(const QWidget *w) const
943 {
944 return w && (w == this || w == mainContainer());
945 }
946
updateChildSelections(QWidget * w)947 void 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
repaintSelection()960 void FormWindow::repaintSelection()
961 {
962 m_selection->repaintSelection();
963 }
964
raiseSelection(QWidget * w)965 void FormWindow::raiseSelection(QWidget *w)
966 {
967 m_selection->raiseWidget(w);
968 }
969
updateSelection(QWidget * w)970 void FormWindow::updateSelection(QWidget *w)
971 {
972 if (!w->isVisibleTo(this)) {
973 selectWidget(w, false);
974 } else {
975 m_selection->updateGeometry(w);
976 }
977 }
978
designerWidget(QWidget * w) const979 QWidget *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
isCentralWidget(QWidget * w) const987 bool FormWindow::isCentralWidget(QWidget *w) const
988 {
989 if (QMainWindow *mainWindow = qobject_cast<QMainWindow*>(mainContainer()))
990 return w == mainWindow->centralWidget();
991
992 return false;
993 }
994
ensureUniqueObjectName(QObject * object)995 void 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(db->indexOfObject(object)))
1001 name = qdesigner_internal::qtify(item->name());
1002 }
1003 unify(object, name, true);
1004 object->setObjectName(name);
1005 }
1006
1007 template <class Iterator>
insertNames(const QDesignerMetaDataBaseInterface * metaDataBase,Iterator it,const Iterator & end,QObject * excludedObject,QSet<QString> & nameSet)1008 static 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(*it))
1014 nameSet.insert((*it)->objectName());
1015 }
1016
languageKeywords()1017 static 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
unify(QObject * w,QString & s,bool changeIt)1145 bool 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(qobject_cast<QWidget*>(w))))
1156 existingNames.insert(main->objectName());
1157
1158 const QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase();
1159 const QWidgetList widgetChildren = main->findChildren<QWidget*>();
1160 if (!widgetChildren.isEmpty())
1161 insertNames(metaDataBase, widgetChildren.constBegin(), widgetChildren.constEnd(), w, existingNames);
1162
1163 const auto layoutChildren = main->findChildren<QLayout*>();
1164 if (!layoutChildren.isEmpty())
1165 insertNames(metaDataBase, layoutChildren.constBegin(), layoutChildren.constEnd(), w, existingNames);
1166
1167 const auto actionChildren = main->findChildren<QAction*>();
1168 if (!actionChildren.isEmpty())
1169 insertNames(metaDataBase, actionChildren.constBegin(), actionChildren.constEnd(), w, existingNames);
1170
1171 const auto buttonGroupChildren = main->findChildren<QButtonGroup*>();
1172 if (!buttonGroupChildren.isEmpty())
1173 insertNames(metaDataBase, buttonGroupChildren.constBegin(), buttonGroupChildren.constEnd(), w, existingNames);
1174
1175 const StringSet::const_iterator enEnd = existingNames.constEnd();
1176 if (existingNames.constFind(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(idx).isDigit(); --idx) {
1187 num += (s.at(idx).unicode() - zeroUnicode) * factor;
1188 factor *= 10;
1189 }
1190 // Position index past '_'.
1191 const QChar underscore = QLatin1Char('_');
1192 if (idx >= 0 && s.at(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(idx);
1202 s += QString::number(num);
1203 if (existingNames.constFind(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
insertWidget(QWidget * w,const QRect & rect,QWidget * container,bool already_in_form)1211 void FormWindow::insertWidget(QWidget *w, const QRect &rect, QWidget *container, bool already_in_form)
1212 {
1213 clearSelection(false);
1214
1215 beginCommand(tr("Insert widget '%1'").arg(WidgetFactory::classNameOf(m_core, 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(w, QStringLiteral("geometry"), r); // ### use rc.size()
1224
1225 if (w->parentWidget() != container) {
1226 ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this);
1227 cmd->init(w, container);
1228 m_undoStack.push(cmd);
1229 }
1230
1231 m_undoStack.push(geom_cmd);
1232
1233 InsertWidgetCommand *cmd = new InsertWidgetCommand(this);
1234 cmd->init(w, already_in_form);
1235 m_undoStack.push(cmd);
1236
1237 endCommand();
1238
1239 w->show();
1240 }
1241
createWidget(DomUI * ui,const QRect & rc,QWidget * target)1242 QWidget *FormWindow::createWidget(DomUI *ui, const QRect &rc, QWidget *target)
1243 {
1244 QWidget *container = findContainer(target, false);
1245 if (!container)
1246 return nullptr;
1247 if (isMainContainer(container)) {
1248 if (QMainWindow *mw = qobject_cast<QMainWindow*>(container)) {
1249 Q_ASSERT(mw->centralWidget() != nullptr);
1250 container = mw->centralWidget();
1251 }
1252 }
1253 QDesignerResource resource(this);
1254 const FormBuilderClipboard clipboard = resource.paste(ui, 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(widget, rc, container);
1259 return widget;
1260 }
1261
isDescendant(const QWidget * parent,const QWidget * child)1262 static 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
resizeWidget(QWidget * widget,const QRect & geometry)1271 void 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(widget, QStringLiteral("geometry"), r);
1278 cmd->setText(tr("Resize"));
1279 m_undoStack.push(cmd);
1280 }
1281
raiseChildSelections(QWidget * w)1282 void 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
containerAt(const QPoint & pos,QWidget * notParentOf)1290 QWidget *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(mapFromGlobal(pos))) {
1296 container = mainContainer();
1297 depth = widgetDepth(container);
1298 }
1299
1300 for (QWidget *wit : qAsConst(m_widgets)) {
1301 if (qobject_cast<QLayoutWidget*>(wit) || qobject_cast<QSplitter*>(wit))
1302 continue;
1303 if (!wit->isVisibleTo(this))
1304 continue;
1305 if (selected.indexOf(wit) != -1)
1306 continue;
1307 if (!core()->widgetDataBase()->isContainer(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((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(wit);
1322 if (wd == depth && container) {
1323 if (wit->parentWidget()->children().indexOf(wit) >
1324 container->parentWidget()->children().indexOf(container))
1325 wd++;
1326 }
1327 if (wd > depth && !isChildOf(wit, notParentOf)) {
1328 depth = wd;
1329 container = wit;
1330 }
1331 }
1332 return container;
1333 }
1334
selectedWidgets() const1335 QWidgetList FormWindow::selectedWidgets() const
1336 {
1337 return m_selection->selectedWidgets();
1338 }
1339
selectWidgets()1340 void 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(selRect) && !r.contains(selRect) && trySelectWidget(w, true))
1350 selectionChanged = true;
1351 }
1352 }
1353
1354 if (selectionChanged)
1355 emitSelectionChanged();
1356 }
1357
handleKeyPressEvent(QWidget * widget,QWidget *,QKeyEvent * e)1358 bool FormWindow::handleKeyPressEvent(QWidget *widget, QWidget *, QKeyEvent *e)
1359 {
1360 if (qobject_cast<const FormWindow*>(widget) || qobject_cast<const QMenu*>(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(QDesignerFormWindowCursorInterface::Next);
1377 break;
1378
1379 case Qt::Key_Backtab:
1380 if (e->modifiers() == Qt::NoModifier)
1381 cursor()->movePosition(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(e->key(), e->modifiers());
1389 break;
1390 }
1391
1392 return true;
1393 }
1394
getValue(const QRect & rect,int key,bool size) const1395 int 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
calcValue(int val,bool forward,bool snap,int snapOffset) const1407 int 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.
1423 struct 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
1434 QT_END_NAMESPACE
1435 Q_DECLARE_METATYPE(qdesigner_internal::ArrowKeyOperation)
1436 QT_BEGIN_NAMESPACE
1437
1438 namespace qdesigner_internal {
1439
apply(const QRect & rect) const1440 QRect 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(r.x() + distance);
1451 else
1452 r.moveTop(r.y() + distance);
1453 }
1454 return r;
1455 }
1456
operator <<(QDebug in,const ArrowKeyOperation & op)1457 QDebug 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
1471 class ArrowKeyPropertyHelper : public PropertyHelper {
1472 public:
ArrowKeyPropertyHelper(QObject * o,SpecialProperty sp,QDesignerPropertySheetExtension * s,int i)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
setValue(QDesignerFormWindowInterface * fw,const QVariant & value,bool changed,unsigned subPropertyMask)1480 PropertyHelper::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*>(object());
1484 const ArrowKeyOperation operation = qvariant_cast<ArrowKeyOperation>(value);
1485 const QRect newGeom = operation.apply(w->geometry());
1486 return PropertyHelper::setValue(fw, 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.
1492 class ArrowKeyPropertyCommand: public SetPropertyCommand {
1493 public:
1494 explicit ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw,
1495 QUndoCommand *p = nullptr);
1496
1497 void init(QWidgetList &l, const ArrowKeyOperation &op);
1498
1499 protected:
createPropertyHelper(QObject * o,SpecialProperty sp,QDesignerPropertySheetExtension * s,int i) const1500 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
ArrowKeyPropertyCommand(QDesignerFormWindowInterface * fw,QUndoCommand * p)1506 ArrowKeyPropertyCommand::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
init(QWidgetList & l,const ArrowKeyOperation & op)1514 void ArrowKeyPropertyCommand::init(QWidgetList &l, const ArrowKeyOperation &op)
1515 {
1516 QObjectList ol;
1517 for (QWidget *w : qAsConst(l))
1518 ol.push_back(w);
1519 SetPropertyCommand::init(ol, QStringLiteral("geometry"), QVariant::fromValue(op));
1520
1521 setText(op.resize ? FormWindow::tr("Key Resize") : FormWindow::tr("Key Move"));
1522 }
1523
mergeValue(const QVariant & newMergeValue)1524 QVariant 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>(newValue());
1530 const ArrowKeyOperation newMergeOperation = qvariant_cast<ArrowKeyOperation>(newMergeValue);
1531 if (mergedOperation.resize != newMergeOperation.resize || mergedOperation.arrowKey != newMergeOperation.arrowKey)
1532 return QVariant();
1533 mergedOperation.distance += newMergeOperation.distance;
1534 return QVariant::fromValue(mergedOperation);
1535 }
1536
handleArrowKeyEvent(int key,Qt::KeyboardModifiers modifiers)1537 void 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(m_core, w))
1550 selection.append(w);
1551 }
1552
1553 simplifySelection(&selection);
1554
1555 if (selection.isEmpty())
1556 return;
1557
1558 QWidget *current = c->current();
1559 if (!current || LayoutInfo::isWidgetLaidout(m_core, 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(current->geometry(), key, size);
1570
1571 const int newValue = calcValue(oldValue, forward, snap, 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(selection, operation);
1580 m_undoStack.push(cmd);
1581 }
1582
handleKeyReleaseEvent(QWidget *,QWidget *,QKeyEvent * e)1583 bool FormWindow::handleKeyReleaseEvent(QWidget *, QWidget *, QKeyEvent *e)
1584 {
1585 e->accept();
1586 return true;
1587 }
1588
selectAll()1589 void FormWindow::selectAll()
1590 {
1591 bool selectionChanged = false;
1592 for (QWidget *widget : qAsConst(m_widgets)) {
1593 if (widget->isVisibleTo(this) && trySelectWidget(widget, true))
1594 selectionChanged = true;
1595 }
1596 if (selectionChanged)
1597 emitSelectionChanged();
1598 }
1599
createLayout(int type,QWidget * container)1600 void FormWindow::createLayout(int type, QWidget *container)
1601 {
1602 if (container) {
1603 layoutContainer(container, type);
1604 } else {
1605 LayoutCommand *cmd = new LayoutCommand(this);
1606 cmd->init(mainContainer(), selectedWidgets(), static_cast<LayoutInfo::Type>(type));
1607 commandHistory()->push(cmd);
1608 }
1609 }
1610
morphLayout(QWidget * container,int newType)1611 void FormWindow::morphLayout(QWidget *container, int newType)
1612 {
1613 MorphLayoutCommand *cmd = new MorphLayoutCommand(this);
1614 if (cmd->init(container, newType)) {
1615 commandHistory()->push(cmd);
1616 } else {
1617 qDebug() << "** WARNING Unable to morph layout.";
1618 delete cmd;
1619 }
1620 }
1621
deleteWidgets()1622 void FormWindow::deleteWidgets()
1623 {
1624 QWidgetList selection = selectedWidgets();
1625 simplifySelection(&selection);
1626
1627 deleteWidgetList(selection);
1628 }
1629
fileName() const1630 QString FormWindow::fileName() const
1631 {
1632 return m_fileName;
1633 }
1634
setFileName(const QString & fileName)1635 void FormWindow::setFileName(const QString &fileName)
1636 {
1637 if (m_fileName == fileName)
1638 return;
1639
1640 m_fileName = fileName;
1641 emit fileNameChanged(fileName);
1642 }
1643
contents() const1644 QString FormWindow::contents() const
1645 {
1646 QBuffer b;
1647 if (!mainContainer() || !b.open(QIODevice::WriteOnly))
1648 return QString();
1649
1650 QDesignerResource resource(const_cast<FormWindow*>(this));
1651 resource.save(&b, mainContainer());
1652
1653 return QString::fromUtf8(b.buffer());
1654 }
1655
1656 #if QT_CONFIG(clipboard)
copy()1657 void FormWindow::copy()
1658 {
1659 QBuffer b;
1660 if (!b.open(QIODevice::WriteOnly))
1661 return;
1662
1663 FormBuilderClipboard clipboard;
1664 QDesignerResource resource(this);
1665 resource.setSaveRelative(false);
1666 clipboard.m_widgets = selectedWidgets();
1667 simplifySelection(&clipboard.m_widgets);
1668 resource.copy(&b, clipboard);
1669
1670 qApp->clipboard()->setText(QString::fromUtf8(b.buffer()), QClipboard::Clipboard);
1671 }
1672
cut()1673 void FormWindow::cut()
1674 {
1675 copy();
1676 deleteWidgets();
1677 }
1678
paste()1679 void FormWindow::paste()
1680 {
1681 paste(PasteAll);
1682 }
1683 #endif
1684
1685 // for cases like QMainWindow (central widget is an inner container) or QStackedWidget (page is an inner container)
innerContainer(QWidget * outerContainer) const1686 QWidget *FormWindow::innerContainer(QWidget *outerContainer) const
1687 {
1688 if (m_core->widgetDataBase()->isContainer(outerContainer))
1689 if (const QDesignerContainerExtension *container = qt_extension<QDesignerContainerExtension*>(m_core->extensionManager(), outerContainer)) {
1690 const int currentIndex = container->currentIndex();
1691 return currentIndex >= 0 ? container->widget(currentIndex) : nullptr;
1692 }
1693 return outerContainer;
1694 }
1695
containerForPaste() const1696 QWidget *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(&selection);
1708
1709 QWidget *containerOfW = findContainer(selection.first(), /* exclude layouts */ true);
1710 if (!containerOfW || containerOfW == mainContainer())
1711 break;
1712 // No layouts, must be container. No empty page-based containers.
1713 containerOfW = innerContainer(containerOfW);
1714 if (!containerOfW)
1715 break;
1716 if (LayoutInfo::layoutType(m_core, containerOfW) != LayoutInfo::NoLayout || !m_core->widgetDataBase()->isContainer(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(w);
1724 if (!w)
1725 return nullptr;
1726 if (LayoutInfo::layoutType(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(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.
domUIFromClipboard(int * widgetCount,int * actionCount)1740 static 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(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(uiElement, Qt::CaseInsensitive) == 0 && !ui) {
1753 ui = new DomUI();
1754 ui->read(reader);
1755 break;
1756 }
1757 reader.raiseError(QCoreApplication::translate("FormWindow", "Unexpected element <%1>").arg(reader.name().toString()));
1758 }
1759 }
1760 if (reader.hasError()) {
1761 delete ui;
1762 ui = nullptr;
1763 designerWarning(QCoreApplication::translate("FormWindow", "Error while pasting clipboard contents at line %1, column %2: %3").
1764 arg(reader.lineNumber()).arg(reader.columnNumber()).arg(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
pasteCommandDescription(int widgetCount,int actionCount)1780 static inline QString pasteCommandDescription(int widgetCount, int actionCount)
1781 {
1782 if (widgetCount == 0)
1783 return FormWindow::tr("Paste %n action(s)", nullptr, actionCount);
1784 if (actionCount == 0)
1785 return FormWindow::tr("Paste %n widget(s)", nullptr, widgetCount);
1786 return FormWindow::tr("Paste (%1 widgets, %2 actions)").arg(widgetCount).arg(actionCount);
1787 }
1788
1789 #if QT_CONFIG(clipboard)
positionPastedWidgetsAtMousePosition(FormWindow * fw,const QPoint & contextMenuPosition,QWidget * parent,const QWidgetList & l)1790 static 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(currentPos);
1798 const QRect parentGeometry = QRect(QPoint(0, 0), parent->size());
1799 const bool outside = !parentGeometry.contains(cursorPos);
1800 if (outside)
1801 cursorPos = grid.snapPoint(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((*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(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
paste(PasteMode pasteMode)1822 void 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, &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("Cannot paste widgets. Designer could not find a container "
1844 "without a layout to paste into.");
1845 const QString infoMessage = tr("Break the layout of the "
1846 "container you want to paste into, select this container "
1847 "and then paste again.");
1848 core()->dialogGui()->message(this, QDesignerDialogGuiInterface::FormEditorMessage, QMessageBox::Information,
1849 tr("Paste error"), message, infoMessage, 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, pasteContainer, this);
1858
1859 clearSelection(false);
1860 // Create command sequence
1861 beginCommand(pasteCommandDescription(widgetCount, actionCount));
1862
1863 if (widgetCount) {
1864 positionPastedWidgetsAtMousePosition(this, m_contextMenuPosition, pasteContainer, clipboard.m_widgets);
1865 for (QWidget *w : clipboard.m_widgets) {
1866 InsertWidgetCommand *cmd = new InsertWidgetCommand(this);
1867 cmd->init(w);
1868 m_undoStack.push(cmd);
1869 selectWidget(w);
1870 }
1871 }
1872
1873 if (actionCount)
1874 for (QAction *a : clipboard.m_actions) {
1875 ensureUniqueObjectName(a);
1876 AddActionCommand *cmd = new AddActionCommand(this);
1877 cmd->init(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
frameNeeded(QWidget * w) const1887 bool FormWindow::frameNeeded(QWidget *w) const
1888 {
1889 if (!core()->widgetDataBase()->isContainer(w))
1890 return false;
1891 if (qobject_cast<QGroupBox *>(w))
1892 return false;
1893 if (qobject_cast<QToolBox *>(w))
1894 return false;
1895 if (qobject_cast<QTabWidget *>(w))
1896 return false;
1897 if (qobject_cast<QStackedWidget *>(w))
1898 return false;
1899 if (qobject_cast<QDockWidget *>(w))
1900 return false;
1901 if (qobject_cast<QDesignerWidget *>(w))
1902 return false;
1903 if (qobject_cast<QMainWindow *>(w))
1904 return false;
1905 if (qobject_cast<QDialog *>(w))
1906 return false;
1907 if (qobject_cast<QLayoutWidget *>(w))
1908 return false;
1909 return true;
1910 }
1911
eventFilter(QObject * watched,QEvent * event)1912 bool 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(widgetRect.adjusted(0, 0, -1, -1));
1932 return ret;
1933 }
1934
manageWidget(QWidget * w)1935 void 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(w);
1946
1947 m_insertedWidgets.insert(w);
1948 m_widgets.append(w);
1949
1950 #if QT_CONFIG(cursor)
1951 setCursorToAll(Qt::ArrowCursor, w);
1952 #endif
1953
1954 emit changed();
1955 emit widgetManaged(w);
1956
1957 if (frameNeeded(w))
1958 w->installEventFilter(this);
1959 }
1960
unmanageWidget(QWidget * w)1961 void FormWindow::unmanageWidget(QWidget *w)
1962 {
1963 if (!isManaged(w))
1964 return;
1965
1966 m_selection->removeWidget(w);
1967
1968 emit aboutToUnmanageWidget(w);
1969
1970 if (w == m_currentWidget)
1971 setCurrentWidget(mainContainer());
1972
1973 core()->metaDataBase()->remove(w);
1974
1975 m_insertedWidgets.remove(w);
1976 m_widgets.removeAt(m_widgets.indexOf(w));
1977
1978 emit changed();
1979 emit widgetUnmanaged(w);
1980
1981 if (frameNeeded(w))
1982 w->removeEventFilter(this);
1983 }
1984
isManaged(QWidget * w) const1985 bool FormWindow::isManaged(QWidget *w) const
1986 {
1987 return m_insertedWidgets.contains(w);
1988 }
1989
breakLayout(QWidget * w)1990 void 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(w))
2004 widgets.push_back(w);
2005 }
2006
2007 BreakLayoutCommand *cmd = new BreakLayoutCommand(this);
2008 cmd->init(widgets, w);
2009 commandHistory()->push(cmd);
2010 clearSelection(false);
2011 }
2012
beginCommand(const QString & description)2013 void FormWindow::beginCommand(const QString &description)
2014 {
2015 m_undoStack.beginMacro(description);
2016 }
2017
endCommand()2018 void FormWindow::endCommand()
2019 {
2020 m_undoStack.endMacro();
2021 }
2022
raiseWidgets()2023 void FormWindow::raiseWidgets()
2024 {
2025 QWidgetList widgets = selectedWidgets();
2026 simplifySelection(&widgets);
2027
2028 if (widgets.isEmpty())
2029 return;
2030
2031 beginCommand(tr("Raise widgets"));
2032 for (QWidget *widget : qAsConst(widgets)) {
2033 RaiseWidgetCommand *cmd = new RaiseWidgetCommand(this);
2034 cmd->init(widget);
2035 m_undoStack.push(cmd);
2036 }
2037 endCommand();
2038 }
2039
lowerWidgets()2040 void FormWindow::lowerWidgets()
2041 {
2042 QWidgetList widgets = selectedWidgets();
2043 simplifySelection(&widgets);
2044
2045 if (widgets.isEmpty())
2046 return;
2047
2048 beginCommand(tr("Lower widgets"));
2049 for (QWidget *widget : qAsConst(widgets)) {
2050 LowerWidgetCommand *cmd = new LowerWidgetCommand(this);
2051 cmd->init(widget);
2052 m_undoStack.push(cmd);
2053 }
2054 endCommand();
2055 }
2056
handleMouseButtonDblClickEvent(QWidget * w,QWidget * managedWidget,QMouseEvent * e)2057 bool 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(managedWidget))
2067 emit activated(managedWidget);
2068
2069 m_mouseState = MouseDoubleClicked;
2070 return true;
2071 }
2072
2073
initializePopupMenu(QWidget * managedWidget)2074 QMenu *FormWindow::initializePopupMenu(QWidget *managedWidget)
2075 {
2076 if (!isManaged(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(managedWidget);
2084 bool update = false;
2085 if (selected) {
2086 update = setCurrentWidget(managedWidget);
2087 } else {
2088 clearObjectInspectorSelection(m_core); // We might have a toolbar or non-widget selected in the object inspector.
2089 clearSelection(false);
2090 update = trySelectWidget(managedWidget, true);
2091 raiseChildSelections(managedWidget); // raise selections and select widget
2092 }
2093
2094 if (update) {
2095 emitSelectionChanged();
2096 QMetaObject::invokeMethod(core()->formWindowManager(), "slotUpdateActions");
2097 }
2098
2099 QWidget *contextMenuWidget = nullptr;
2100
2101 if (isMainContainer(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*>(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(contextMenuWidget);
2119 if (!contextMenu)
2120 return nullptr;
2121
2122 emit contextMenuRequested(contextMenu, contextMenuWidget);
2123 return contextMenu;
2124 }
2125
handleContextMenu(QWidget *,QWidget * managedWidget,QContextMenuEvent * e)2126 bool 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(globalPos);
2134 delete contextMenu;
2135 e->accept();
2136 m_contextMenuPosition = QPoint(-1, -1);
2137 return true;
2138 }
2139
setContents(QIODevice * dev,QString * errorMessageIn)2140 bool 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.data(), formContainer());
2162 if (w) {
2163 setMainContainer(w);
2164 emit changed();
2165 }
2166 if (errorMessageIn)
2167 *errorMessageIn = r.errorString();
2168 return w != nullptr;
2169 }
2170
setContents(const QString & contents)2171 bool FormWindow::setContents(const QString &contents)
2172 {
2173 QString errorMessage;
2174 QByteArray data = contents.toUtf8();
2175 QBuffer b(&data);
2176 const bool success = b.open(QIODevice::ReadOnly) && setContents(&b, &errorMessage);
2177 if (!success && !errorMessage.isEmpty())
2178 designerWarning(errorMessage);
2179 return success;
2180 }
2181
layoutContainer(QWidget * w,int type)2182 void 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(widget))
2197 widgets.append(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(mainContainer(), widgets, static_cast<LayoutInfo::Type>(type), w);
2205 clearSelection(false);
2206 commandHistory()->push(cmd);
2207 }
2208
hasInsertedChildren(QWidget * widget) const2209 bool FormWindow::hasInsertedChildren(QWidget *widget) const // ### move
2210 {
2211 if (QDesignerContainerExtension *container = qt_extension<QDesignerContainerExtension*>(core()->extensionManager(), 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(child) && !LayoutInfo::isWidgetLaidout(core(), child) && child->isVisibleTo(const_cast<FormWindow*>(this)))
2222 return true;
2223 }
2224
2225 return false;
2226 }
2227
2228 // "Select Ancestor" sub menu code
slotSelectWidget(QAction * a)2229 void FormWindow::slotSelectWidget(QAction *a)
2230 {
2231 if (QWidget *w = qvariant_cast<QWidget*>(a->data()))
2232 selectSingleWidget(w);
2233 }
2234
slotCleanChanged(bool clean)2235 void FormWindow::slotCleanChanged(bool clean)
2236 {
2237 if (!clean)
2238 emit changed();
2239 }
2240
objectNameOf(const QWidget * w)2241 static inline QString objectNameOf(const QWidget *w)
2242 {
2243 if (const QLayoutWidget *lw = qobject_cast<const QLayoutWidget *>(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(layout->metaObject()->className());
2250 }
2251 return w->objectName();
2252 }
2253
createSelectAncestorSubMenu(QWidget * w)2254 QAction *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(p) && !isWidgetSelected(p))
2261 parents.push_back(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(ag, &QActionGroup::triggered, this, &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(objectNameOf(w));
2272 a->setData(QVariant::fromValue(w));
2273 menu->addAction(a);
2274 }
2275 QAction *ma = new QAction(tr("Select Ancestor"), nullptr);
2276 ma->setMenu(menu);
2277 return ma;
2278 }
2279
createPopupMenu(QWidget * w)2280 QMenu *FormWindow::createPopupMenu(QWidget *w)
2281 {
2282 QMenu *popup = createExtensionTaskMenu(this, w, 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*>(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*>(w)) {
2294 QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(stackedWidget, popup);
2295 } else if (QTabWidget *tabWidget = qobject_cast<QTabWidget*>(w)) {
2296 QTabWidgetEventFilter::addTabWidgetContextMenuActions(tabWidget, popup);
2297 } else if (QToolBox *toolBox = qobject_cast<QToolBox*>(w)) {
2298 QToolBoxHelper::addToolBoxContextMenuActions(toolBox, popup);
2299 }
2300
2301 if (manager->action(QDesignerFormWindowManagerInterface::LowerAction)->isEnabled()) {
2302 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::LowerAction));
2303 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::RaiseAction));
2304 popup->addSeparator();
2305 }
2306 #if QT_CONFIG(clipboard)
2307 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::CutAction));
2308 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::CopyAction));
2309 #endif
2310 }
2311
2312 #if QT_CONFIG(clipboard)
2313 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::PasteAction));
2314 #endif
2315
2316 if (QAction *selectAncestorAction = createSelectAncestorSubMenu(w))
2317 popup->addAction(selectAncestorAction);
2318 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::SelectAllAction));
2319
2320 if (!isFormWindow) {
2321 popup->addAction(manager->action(QDesignerFormWindowManagerInterface::DeleteAction));
2322 }
2323
2324 popup->addSeparator();
2325 QMenu *layoutMenu = popup->addMenu(tr("Lay out"));
2326 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction));
2327 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction));
2328 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction));
2329 if (!isFormWindow) {
2330 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction));
2331 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction));
2332 }
2333 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::GridLayoutAction));
2334 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::FormLayoutAction));
2335 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction));
2336 layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SimplifyLayoutAction));
2337
2338 return popup;
2339 }
2340
resizeEvent(QResizeEvent * e)2341 void FormWindow::resizeEvent(QResizeEvent *e)
2342 {
2343 m_geometryChangedTimer->start(10);
2344
2345 QWidget::resizeEvent(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 */
mapToForm(const QWidget * w,const QPoint & pos) const2354 QPoint 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(i)) {
2359 p = i->mapToParent(p);
2360 i = i->parentWidget();
2361 }
2362
2363 return mapFromGlobal(w->mapToGlobal(pos));
2364 }
2365
canBeBuddy(QWidget * w) const2366 bool FormWindow::canBeBuddy(QWidget *w) const // ### rename me.
2367 {
2368 if (QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), 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(sheet->property(index), &ok));
2373 return ok && q != Qt::NoFocus;
2374 }
2375 }
2376
2377 return false;
2378 }
2379
findContainer(QWidget * w,bool excludeLayout) const2380 QWidget *FormWindow::findContainer(QWidget *w, bool excludeLayout) const
2381 {
2382 if (!isChildOf(w, 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(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*>(w) || !metaDataBase->item(w)) {
2394 w = w->parentWidget();
2395 continue;
2396 }
2397
2398 const bool isContainer = widgetDataBase->isContainer(w, true) || w == mainContainer();
2399
2400 if (!isContainer || (excludeLayout && qobject_cast<QLayoutWidget*>(w))) { // ### skip QSplitter
2401 w = w->parentWidget();
2402 } else {
2403 container = w;
2404 break;
2405 }
2406 }
2407 }
2408
2409 return container;
2410 }
2411
simplifySelection(QWidgetList * sel) const2412 void 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(mainC)) {
2423 sel->clear();
2424 sel->push_back(mainC);
2425 return;
2426 }
2427 using WidgetVector = QVector<QWidget *>;
2428 WidgetVector toBeRemoved;
2429 toBeRemoved.reserve(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(parent)) {
2438 toBeRemoved.append(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(*it);
2450 }
2451 }
2452
findFormWindow(QWidget * w)2453 FormWindow *FormWindow::findFormWindow(QWidget *w)
2454 {
2455 return qobject_cast<FormWindow*>(QDesignerFormWindowInterface::findFormWindow(w));
2456 }
2457
isDirty() const2458 bool FormWindow::isDirty() const
2459 {
2460 return !m_undoStack.isClean();
2461 }
2462
setDirty(bool dirty)2463 void FormWindow::setDirty(bool dirty)
2464 {
2465 if (dirty)
2466 m_undoStack.resetClean();
2467 else
2468 m_undoStack.setClean();
2469 }
2470
containerAt(const QPoint & pos)2471 QWidget *FormWindow::containerAt(const QPoint &pos)
2472 {
2473 QWidget *widget = widgetAt(pos);
2474 return findContainer(widget, true);
2475 }
2476
childAt_SkipDropLine(QWidget * w,QPoint pos)2477 static 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*>(child_obj) != nullptr)
2483 continue;
2484 QWidget *child = qobject_cast<QWidget*>(child_obj);
2485 if (!child || child->isWindow() || !child->isVisible() ||
2486 !child->geometry().contains(pos) || child->testAttribute(Qt::WA_TransparentForMouseEvents))
2487 continue;
2488 const QPoint childPos = child->mapFromParent(pos);
2489 if (QWidget *res = childAt_SkipDropLine(child, childPos))
2490 return res;
2491 if (child->testAttribute(Qt::WA_MouseNoMask) || child->mask().contains(pos)
2492 || child->mask().isEmpty())
2493 return child;
2494 }
2495
2496 return nullptr;
2497 }
2498
widgetAt(const QPoint & pos)2499 QWidget *FormWindow::widgetAt(const QPoint &pos)
2500 {
2501 QWidget *w = childAt(pos);
2502 if (qobject_cast<const WidgetHandle*>(w) != 0)
2503 w = childAt_SkipDropLine(this, pos);
2504 return (w == nullptr || w == formContainer()) ? this : w;
2505 }
2506
highlightWidget(QWidget * widget,const QPoint & pos,HighlightMode mode)2507 void FormWindow::highlightWidget(QWidget *widget, const QPoint &pos, HighlightMode mode)
2508 {
2509 Q_ASSERT(widget);
2510
2511 if (QMainWindow *mainWindow = qobject_cast<QMainWindow*> (widget)) {
2512 widget = mainWindow->centralWidget();
2513 }
2514
2515 QWidget *container = findContainer(widget, false);
2516
2517 if (container == nullptr || core()->metaDataBase()->item(container) == nullptr)
2518 return;
2519
2520 if (QDesignerActionProviderExtension *g = qt_extension<QDesignerActionProviderExtension*>(core()->extensionManager(), container)) {
2521 if (mode == Restore) {
2522 g->adjustIndicator(QPoint());
2523 } else {
2524 const QPoint pt = widget->mapTo(container, pos);
2525 g->adjustIndicator(pt);
2526 }
2527 } else if (QDesignerLayoutDecorationExtension *g = qt_extension<QDesignerLayoutDecorationExtension*>(core()->extensionManager(), container)) {
2528 if (mode == Restore) {
2529 g->adjustIndicator(QPoint(), -1);
2530 } else {
2531 const QPoint pt = widget->mapTo(container, pos);
2532 const int index = g->findItemAt(pt);
2533 g->adjustIndicator(pt, index);
2534 }
2535 }
2536
2537 QMainWindow *mw = qobject_cast<QMainWindow*> (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(container);
2543 if (pit != m_palettesBeforeHighlight.end()) {
2544 container->setPalette(pit.value().first);
2545 container->setAutoFillBackground(pit.value().second);
2546 m_palettesBeforeHighlight.erase(pit);
2547 }
2548 } else {
2549 QPalette p = container->palette();
2550 if (!m_palettesBeforeHighlight.contains(container)) {
2551 PaletteAndFill paletteAndFill;
2552 if (container->testAttribute(Qt::WA_SetPalette))
2553 paletteAndFill.first = p;
2554 paletteAndFill.second = container->autoFillBackground();
2555 m_palettesBeforeHighlight.insert(container, paletteAndFill);
2556 }
2557
2558 p.setColor(backgroundRole(), p.midlight().color());
2559 container->setPalette(p);
2560 container->setAutoFillBackground(true);
2561 }
2562 }
2563
widgets(QWidget * widget) const2564 QWidgetList 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(w);
2574 }
2575 }
2576 return rc;
2577 }
2578
toolCount() const2579 int FormWindow::toolCount() const
2580 {
2581 return m_widgetStack->count();
2582 }
2583
tool(int index) const2584 QDesignerFormWindowToolInterface *FormWindow::tool(int index) const
2585 {
2586 return m_widgetStack->tool(index);
2587 }
2588
registerTool(QDesignerFormWindowToolInterface * tool)2589 void 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
setCurrentTool(int index)2599 void FormWindow::setCurrentTool(int index)
2600 {
2601 m_widgetStack->setCurrentTool(index);
2602 }
2603
currentTool() const2604 int FormWindow::currentTool() const
2605 {
2606 return m_widgetStack->currentIndex();
2607 }
2608
handleEvent(QWidget * widget,QWidget * managedWidget,QEvent * event)2609 bool 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
initializeCoreTools()2621 void FormWindow::initializeCoreTools()
2622 {
2623 m_widgetEditor = new WidgetEditorTool(this);
2624 registerTool(m_widgetEditor);
2625 }
2626
checkSelection()2627 void FormWindow::checkSelection()
2628 {
2629 m_checkSelectionTimer->start(0);
2630 }
2631
checkSelectionNow()2632 void FormWindow::checkSelectionNow()
2633 {
2634 m_checkSelectionTimer->stop();
2635
2636 const QWidgetList &sel = selectedWidgets();
2637 for (QWidget *widget : sel) {
2638 updateSelection(widget);
2639
2640 if (LayoutInfo::layoutType(core(), widget) != LayoutInfo::NoLayout)
2641 updateChildSelections(widget);
2642 }
2643 }
2644
author() const2645 QString FormWindow::author() const
2646 {
2647 return m_author;
2648 }
2649
comment() const2650 QString FormWindow::comment() const
2651 {
2652 return m_comment;
2653 }
2654
setAuthor(const QString & author)2655 void FormWindow::setAuthor(const QString &author)
2656 {
2657 m_author = author;
2658 }
2659
setComment(const QString & comment)2660 void FormWindow::setComment(const QString &comment)
2661 {
2662 m_comment = comment;
2663 }
2664
editWidgets()2665 void FormWindow::editWidgets()
2666 {
2667 m_widgetEditor->action()->trigger();
2668 }
2669
resourceFiles() const2670 QStringList FormWindow::resourceFiles() const
2671 {
2672 return m_resourceFiles;
2673 }
2674
addResourceFile(const QString & path)2675 void FormWindow::addResourceFile(const QString &path)
2676 {
2677 if (!m_resourceFiles.contains(path)) {
2678 m_resourceFiles.append(path);
2679 setDirty(true);
2680 emit resourceFilesChanged();
2681 }
2682 }
2683
removeResourceFile(const QString & path)2684 void FormWindow::removeResourceFile(const QString &path)
2685 {
2686 if (m_resourceFiles.removeAll(path) > 0) {
2687 setDirty(true);
2688 emit resourceFilesChanged();
2689 }
2690 }
2691
blockSelectionChanged(bool b)2692 bool FormWindow::blockSelectionChanged(bool b)
2693 {
2694 const bool blocked = m_blockSelectionChanged;
2695 m_blockSelectionChanged = b;
2696 return blocked;
2697 }
2698
editContents()2699 void 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(), widget))
2706 a->trigger();
2707 }
2708 }
2709
dragWidgetWithinForm(QWidget * widget,const QRect & targetGeometry,QWidget * targetContainer)2710 void FormWindow::dragWidgetWithinForm(QWidget *widget, const QRect &targetGeometry, QWidget *targetContainer)
2711 {
2712 const bool fromLayout = canDragWidgetInLayout(core(), widget);
2713 const QDesignerLayoutDecorationExtension *targetDeco = qt_extension<QDesignerLayoutDecorationExtension*>(core()->extensionManager(), 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, deleteFlags);
2725 commandHistory()->push(cmd);
2726 }
2727
2728 if (toLayout) {
2729 // Drag from form to layout: just insert. Do not manage
2730 insertWidget(widget, targetGeometry, targetContainer, 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, targetContainer );
2736 commandHistory()->push(cmd);
2737 }
2738 resizeWidget(widget, targetGeometry);
2739 selectWidget(widget, true);
2740 widget->show();
2741 }
2742 }
2743
detectDropArea(QMainWindow * mainWindow,const QRect & area,const QPoint & drop)2744 static Qt::DockWidgetArea detectDropArea(QMainWindow *mainWindow, const QRect &area, const QPoint &drop)
2745 {
2746 QPoint offset = area.topLeft();
2747 QRect rect = area;
2748 rect.moveTopLeft(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(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(Qt::TopLeftCorner);
2775 return y > h ? mainWindow->corner(Qt::BottomLeftCorner) : Qt::LeftDockWidgetArea;
2776 }
2777 if (x > w) {
2778 if (y < 0)
2779 return mainWindow->corner(Qt::TopRightCorner);
2780 return y > h ? mainWindow->corner(Qt::BottomRightCorner) : Qt::RightDockWidgetArea;
2781 }
2782 return y < 0 ? Qt::TopDockWidgetArea :Qt::LeftDockWidgetArea;
2783 }
2784
dropDockWidget(QDesignerDnDItemInterface * item,const QPoint & global_mouse_pos)2785 bool FormWindow::dropDockWidget(QDesignerDnDItemInterface *item, const QPoint &global_mouse_pos)
2786 {
2787 DomUI *dom_ui = item->domUi();
2788
2789 QMainWindow *mw = qobject_cast<QMainWindow *>(mainContainer());
2790 if (!mw)
2791 return false;
2792
2793 QDesignerResource resource(this);
2794 const FormBuilderClipboard clipboard = resource.paste(dom_ui, 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(mw, centralWidgetAreaRect, localPos);
2802
2803 beginCommand(tr("Drop widget"));
2804
2805 clearSelection(false);
2806 highlightWidget(mw, QPoint(0, 0), FormWindow::Restore);
2807
2808 QWidget *widget = clipboard.m_widgets.first();
2809
2810 insertWidget(widget, QRect(0, 0, 1, 1), mw);
2811
2812 selectWidget(widget, 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*>(m_core->extensionManager()->extension(widget, Q_TYPEID(QDesignerPropertySheetExtension)));
2819 if (propertySheet) {
2820 const QString dockWidgetAreaName = QStringLiteral("dockWidgetArea");
2821 PropertySheetEnumValue e = qvariant_cast<PropertySheetEnumValue>(propertySheet->property(propertySheet->indexOf(dockWidgetAreaName)));
2822 e.value = area;
2823 QVariant v;
2824 v.setValue(e);
2825 SetPropertyCommand *cmd = new SetPropertyCommand(this);
2826 cmd->init(widget, dockWidgetAreaName, v);
2827 m_undoStack.push(cmd);
2828 }
2829
2830 endCommand();
2831 return true;
2832 }
2833
dropWidgets(const QList<QDesignerDnDItemInterface * > & item_list,QWidget * target,const QPoint & global_mouse_pos)2834 bool 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*>(target)) {
2844 if (!main_win->centralWidget()) {
2845 designerWarning(tr("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(main_win_pos))
2851 return false;
2852 }
2853
2854 QWidget *container = findContainer(parent, false);
2855 if (container == nullptr)
2856 return false;
2857
2858 beginCommand(tr("Drop widget"));
2859
2860 clearSelection(false);
2861 highlightWidget(target, target->mapFromGlobal(global_mouse_pos), FormWindow::Restore);
2862
2863 QPoint offset;
2864 QDesignerDnDItemInterface *current = nullptr;
2865 QDesignerFormWindowCursorInterface *c = cursor();
2866 for (QDesignerDnDItemInterface *item : qAsConst(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(topLeft) - topLeft;
2879 }
2880
2881 for (QDesignerDnDItemInterface *item : qAsConst(item_list)) {
2882 DomUI *dom_ui = item->domUi();
2883 QRect geometry = item->decoration()->geometry();
2884 Q_ASSERT(dom_ui != nullptr);
2885
2886 geometry.moveTopLeft(container->mapFromGlobal(geometry.topLeft()) + offset);
2887 if (item->type() == QDesignerDnDItemInterface::CopyDrop) { // from widget box or CTRL + mouse move
2888 QWidget *widget = createWidget(dom_ui, geometry, parent);
2889 if (!widget) {
2890 endCommand();
2891 return false;
2892 }
2893 selectWidget(widget, 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(widget);
2899 if (dest == this) {
2900 dragWidgetWithinForm(widget, geometry, container);
2901 } else { // from other form
2902 FormWindow *source = qobject_cast<FormWindow*>(item->source());
2903 Q_ASSERT(source != nullptr);
2904
2905 source->deleteWidgetList(QWidgetList() << widget);
2906 QWidget *new_widget = createWidget(dom_ui, geometry, parent);
2907
2908 selectWidget(new_widget, true);
2909 }
2910 }
2911 }
2912
2913 core()->formWindowManager()->setActiveFormWindow(this);
2914 mainContainer()->activateWindow();
2915 endCommand();
2916 return true;
2917 }
2918
absoluteDir() const2919 QDir FormWindow::absoluteDir() const
2920 {
2921 if (fileName().isEmpty())
2922 return QDir::current();
2923
2924 return QFileInfo(fileName()).absoluteDir();
2925 }
2926
layoutDefault(int * margin,int * spacing)2927 void FormWindow::layoutDefault(int *margin, int *spacing)
2928 {
2929 *margin = m_defaultMargin;
2930 *spacing = m_defaultSpacing;
2931 }
2932
setLayoutDefault(int margin,int spacing)2933 void FormWindow::setLayoutDefault(int margin, int spacing)
2934 {
2935 m_defaultMargin = margin;
2936 m_defaultSpacing = spacing;
2937 }
2938
layoutFunction(QString * margin,QString * spacing)2939 void FormWindow::layoutFunction(QString *margin, QString *spacing)
2940 {
2941 *margin = m_marginFunction;
2942 *spacing = m_spacingFunction;
2943 }
2944
setLayoutFunction(const QString & margin,const QString & spacing)2945 void FormWindow::setLayoutFunction(const QString &margin, const QString &spacing)
2946 {
2947 m_marginFunction = margin;
2948 m_spacingFunction = spacing;
2949 }
2950
pixmapFunction() const2951 QString FormWindow::pixmapFunction() const
2952 {
2953 return m_pixmapFunction;
2954 }
2955
setPixmapFunction(const QString & pixmapFunction)2956 void FormWindow::setPixmapFunction(const QString &pixmapFunction)
2957 {
2958 m_pixmapFunction = pixmapFunction;
2959 }
2960
includeHints() const2961 QStringList FormWindow::includeHints() const
2962 {
2963 return m_includeHints;
2964 }
2965
setIncludeHints(const QStringList & includeHints)2966 void FormWindow::setIncludeHints(const QStringList &includeHints)
2967 {
2968 m_includeHints = includeHints;
2969 }
2970
exportMacro() const2971 QString FormWindow::exportMacro() const
2972 {
2973 return m_exportMacro;
2974 }
2975
setExportMacro(const QString & exportMacro)2976 void FormWindow::setExportMacro(const QString &exportMacro)
2977 {
2978 m_exportMacro = exportMacro;
2979 }
2980
createFormBuilder()2981 QEditorFormBuilder *FormWindow::createFormBuilder()
2982 {
2983 return new QDesignerResource(this);
2984 }
2985
formContainer() const2986 QWidget *FormWindow::formContainer() const
2987 {
2988 return m_widgetStack->formContainer();
2989 }
2990
commandHistory() const2991 QUndoStack *FormWindow::commandHistory() const
2992 {
2993 return &const_cast<QUndoStack &>(m_undoStack);
2994 }
2995
2996 } // namespace
2997
2998 QT_END_NAMESPACE
2999
3000