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 "qdesigner_stackedbox_p.h"
30#include "qdesigner_command_p.h"
31#include "qdesigner_propertycommand_p.h"
32#include "orderdialog_p.h"
33#include "promotiontaskmenu_p.h"
34#include "widgetfactory_p.h"
35
36#include <QtDesigner/abstractformwindow.h>
37
38#include <QtWidgets/qtoolbutton.h>
39#include <QtWidgets/qaction.h>
40#include <QtGui/qevent.h>
41#include <QtWidgets/qmenu.h>
42#include <QtWidgets/qstackedwidget.h>
43#include <QtCore/qdebug.h>
44
45QT_BEGIN_NAMESPACE
46
47static QToolButton *createToolButton(QWidget *parent, Qt::ArrowType at, const QString &name) {
48 QToolButton *rc = new QToolButton();
49 rc->setAttribute(Qt::WA_NoChildEventsForParent, on: true);
50 rc->setParent(parent);
51 rc->setObjectName(name);
52 rc->setArrowType(at);
53 rc->setAutoRaise(true);
54 rc->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
55 rc->setFixedSize(QSize(15, 15));
56 return rc;
57}
58
59// --------------- QStackedWidgetPreviewEventFilter
60QStackedWidgetPreviewEventFilter::QStackedWidgetPreviewEventFilter(QStackedWidget *parent) :
61 QObject(parent),
62 m_buttonToolTipEnabled(false), // Not on preview
63 m_stackedWidget(parent),
64 m_prev(createToolButton(parent: m_stackedWidget, at: Qt::LeftArrow, QStringLiteral("__qt__passive_prev"))),
65 m_next(createToolButton(parent: m_stackedWidget, at: Qt::RightArrow, QStringLiteral("__qt__passive_next")))
66{
67 connect(sender: m_prev, signal: &QAbstractButton::clicked, receiver: this, slot: &QStackedWidgetPreviewEventFilter::prevPage);
68 connect(sender: m_next, signal: &QAbstractButton::clicked, receiver: this, slot: &QStackedWidgetPreviewEventFilter::nextPage);
69
70 updateButtons();
71 m_stackedWidget->installEventFilter(filterObj: this);
72 m_prev->installEventFilter(filterObj: this);
73 m_next->installEventFilter(filterObj: this);
74}
75
76void QStackedWidgetPreviewEventFilter::install(QStackedWidget *stackedWidget)
77{
78 new QStackedWidgetPreviewEventFilter(stackedWidget);
79}
80
81void QStackedWidgetPreviewEventFilter::updateButtons()
82{
83 m_prev->move(ax: m_stackedWidget->width() - 31, ay: 1);
84 m_prev->show();
85 m_prev->raise();
86
87 m_next->move(ax: m_stackedWidget->width() - 16, ay: 1);
88 m_next->show();
89 m_next->raise();
90}
91
92void QStackedWidgetPreviewEventFilter::prevPage()
93{
94 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget())) {
95 fw->clearSelection();
96 fw->selectWidget(w: stackedWidget(), select: true);
97 }
98 const int count = m_stackedWidget->count();
99 if (count > 1) {
100 int newIndex = m_stackedWidget->currentIndex() - 1;
101 if (newIndex < 0)
102 newIndex = count - 1;
103 gotoPage(page: newIndex);
104 }
105}
106
107void QStackedWidgetPreviewEventFilter::nextPage()
108{
109 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget())) {
110 fw->clearSelection();
111 fw->selectWidget(w: stackedWidget(), select: true);
112 }
113 const int count = m_stackedWidget->count();
114 if (count > 1)
115 gotoPage(page: (m_stackedWidget->currentIndex() + 1) % count);
116}
117
118bool QStackedWidgetPreviewEventFilter::eventFilter(QObject *watched, QEvent *event)
119{
120 if (watched->isWidgetType()) {
121 if (watched == m_stackedWidget) {
122 switch (event->type()) {
123 case QEvent::LayoutRequest:
124 updateButtons();
125 break;
126 case QEvent::ChildAdded:
127 case QEvent::ChildRemoved:
128 case QEvent::Resize:
129 case QEvent::Show:
130 updateButtons();
131 break;
132 default:
133 break;
134 }
135 }
136 if (m_buttonToolTipEnabled && (watched == m_next || watched == m_prev)) {
137 switch (event->type()) {
138 case QEvent::ToolTip:
139 updateButtonToolTip(o: watched); // Tooltip includes page number, so, refresh on demand
140 break;
141 default:
142 break;
143 }
144 }
145 }
146 return QObject::eventFilter(watched, event);
147}
148
149void QStackedWidgetPreviewEventFilter::gotoPage(int page)
150{
151 m_stackedWidget->setCurrentIndex(page);
152 updateButtons();
153}
154
155static inline QString stackedClassName(QStackedWidget *w)
156{
157 if (const QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w))
158 return qdesigner_internal::WidgetFactory::classNameOf(core: fw->core(), o: w);
159 return QStringLiteral("Stacked widget");
160}
161
162void QStackedWidgetPreviewEventFilter::updateButtonToolTip(QObject *o)
163{
164 if (o == m_prev) {
165 const QString msg = tr(s: "Go to previous page of %1 '%2' (%3/%4).")
166 .arg(args: stackedClassName(w: m_stackedWidget), args: m_stackedWidget->objectName())
167 .arg(a: m_stackedWidget->currentIndex() + 1)
168 .arg(a: m_stackedWidget->count());
169 m_prev->setToolTip(msg);
170 } else {
171 if (o == m_next) {
172 const QString msg = tr(s: "Go to next page of %1 '%2' (%3/%4).")
173 .arg(args: stackedClassName(w: m_stackedWidget), args: m_stackedWidget->objectName())
174 .arg(a: m_stackedWidget->currentIndex() + 1)
175 .arg(a: m_stackedWidget->count());
176 m_next->setToolTip(msg);
177 }
178 }
179}
180
181// --------------- QStackedWidgetEventFilter
182QStackedWidgetEventFilter::QStackedWidgetEventFilter(QStackedWidget *parent) :
183 QStackedWidgetPreviewEventFilter(parent),
184 m_actionPreviousPage(new QAction(tr(s: "Previous Page"), this)),
185 m_actionNextPage(new QAction(tr(s: "Next Page"), this)),
186 m_actionDeletePage(new QAction(tr(s: "Delete"), this)),
187 m_actionInsertPage(new QAction(tr(s: "Before Current Page"), this)),
188 m_actionInsertPageAfter(new QAction(tr(s: "After Current Page"), this)),
189 m_actionChangePageOrder(new QAction(tr(s: "Change Page Order..."), this)),
190 m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this))
191{
192 setButtonToolTipEnabled(true);
193 connect(sender: m_actionPreviousPage, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::prevPage);
194 connect(sender: m_actionNextPage, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::nextPage);
195 connect(sender: m_actionDeletePage, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::removeCurrentPage);
196 connect(sender: m_actionInsertPage, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::addPage);
197 connect(sender: m_actionInsertPageAfter, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::addPageAfter);
198 connect(sender: m_actionChangePageOrder, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::changeOrder);
199}
200
201void QStackedWidgetEventFilter::install(QStackedWidget *stackedWidget)
202{
203 new QStackedWidgetEventFilter(stackedWidget);
204}
205
206QStackedWidgetEventFilter *QStackedWidgetEventFilter::eventFilterOf(const QStackedWidget *stackedWidget)
207{
208 // Look for 1st order children only..otherwise, we might get filters of nested widgets
209 for (QObject *o : stackedWidget->children()) {
210 if (!o->isWidgetType())
211 if (QStackedWidgetEventFilter *ef = qobject_cast<QStackedWidgetEventFilter *>(object: o))
212 return ef;
213 }
214 return nullptr;
215}
216
217QMenu *QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(const QStackedWidget *stackedWidget, QMenu *popup)
218{
219 QStackedWidgetEventFilter *filter = eventFilterOf(stackedWidget);
220 if (!filter)
221 return nullptr;
222 return filter->addContextMenuActions(popup);
223}
224
225void QStackedWidgetEventFilter::removeCurrentPage()
226{
227 if (stackedWidget()->currentIndex() == -1)
228 return;
229
230 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget())) {
231 qdesigner_internal::DeleteStackedWidgetPageCommand *cmd = new qdesigner_internal::DeleteStackedWidgetPageCommand(fw);
232 cmd->init(stackedWidget: stackedWidget());
233 fw->commandHistory()->push(cmd);
234 }
235}
236
237void QStackedWidgetEventFilter::changeOrder()
238{
239 QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget());
240
241 if (!fw)
242 return;
243
244 const QWidgetList oldPages = qdesigner_internal::OrderDialog::pagesOfContainer(core: fw->core(), container: stackedWidget());
245 const int pageCount = oldPages.size();
246 if (pageCount < 2)
247 return;
248
249 qdesigner_internal::OrderDialog dlg(fw);
250 dlg.setPageList(oldPages);
251 if (dlg.exec() == QDialog::Rejected)
252 return;
253
254 const QWidgetList newPages = dlg.pageList();
255 if (newPages == oldPages)
256 return;
257
258 fw->beginCommand(description: tr(s: "Change Page Order"));
259 for(int i=0; i < pageCount; ++i) {
260 if (newPages.at(i) == stackedWidget()->widget(i))
261 continue;
262 qdesigner_internal::MoveStackedWidgetCommand *cmd = new qdesigner_internal::MoveStackedWidgetCommand(fw);
263 cmd->init(stackedWidget: stackedWidget(), page: newPages.at(i), newIndex: i);
264 fw->commandHistory()->push(cmd);
265 }
266 fw->endCommand();
267}
268
269void QStackedWidgetEventFilter::addPage()
270{
271 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget())) {
272 qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw);
273 cmd->init(stackedWidget: stackedWidget(), mode: qdesigner_internal::AddStackedWidgetPageCommand::InsertBefore);
274 fw->commandHistory()->push(cmd);
275 }
276}
277
278void QStackedWidgetEventFilter::addPageAfter()
279{
280 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget())) {
281 qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw);
282 cmd->init(stackedWidget: stackedWidget(), mode: qdesigner_internal::AddStackedWidgetPageCommand::InsertAfter);
283 fw->commandHistory()->push(cmd);
284 }
285}
286
287void QStackedWidgetEventFilter::gotoPage(int page) {
288 // Are we on a form or in a preview?
289 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: stackedWidget())) {
290 qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw);
291 cmd->init(object: stackedWidget(), QStringLiteral("currentIndex"), newValue: page);
292 fw->commandHistory()->push(cmd);
293 fw->emitSelectionChanged(); // Magically prevent an endless loop triggered by auto-repeat.
294 updateButtons();
295 } else {
296 QStackedWidgetPreviewEventFilter::gotoPage(page);
297 }
298}
299
300QMenu *QStackedWidgetEventFilter::addContextMenuActions(QMenu *popup)
301{
302 QMenu *pageMenu = nullptr;
303 const int count = stackedWidget()->count();
304 const bool hasSeveralPages = count > 1;
305 m_actionDeletePage->setEnabled(count);
306 if (count) {
307 const QString pageSubMenuLabel = tr(s: "Page %1 of %2").arg(a: stackedWidget()->currentIndex() + 1).arg(a: count);
308 pageMenu = popup->addMenu(title: pageSubMenuLabel);
309 pageMenu->addAction(action: m_actionDeletePage);
310 // Set up promotion menu for current widget.
311 if (QWidget *page = stackedWidget()->currentWidget ()) {
312 m_pagePromotionTaskMenu->setWidget(page);
313 m_pagePromotionTaskMenu->addActions(fw: QDesignerFormWindowInterface::findFormWindow(w: stackedWidget()),
314 flags: qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit,
315 menu: pageMenu);
316 }
317 QMenu *insertPageMenu = popup->addMenu(title: tr(s: "Insert Page"));
318 insertPageMenu->addAction(action: m_actionInsertPageAfter);
319 insertPageMenu->addAction(action: m_actionInsertPage);
320 } else {
321 QAction *insertPageAction = popup->addAction(text: tr(s: "Insert Page"));
322 connect(sender: insertPageAction, signal: &QAction::triggered, receiver: this, slot: &QStackedWidgetEventFilter::addPage);
323 }
324 popup->addAction(action: m_actionNextPage);
325 m_actionNextPage->setEnabled(hasSeveralPages);
326 popup->addAction(action: m_actionPreviousPage);
327 m_actionPreviousPage->setEnabled(hasSeveralPages);
328 popup->addAction(action: m_actionChangePageOrder);
329 m_actionChangePageOrder->setEnabled(hasSeveralPages);
330 popup->addSeparator();
331 return pageMenu;
332}
333
334// -------- QStackedWidgetPropertySheet
335
336static const char *pagePropertyName = "currentPageName";
337
338QStackedWidgetPropertySheet::QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent) :
339 QDesignerPropertySheet(object, parent),
340 m_stackedWidget(object)
341{
342 createFakeProperty(propertyName: QLatin1String(pagePropertyName), value: QString());
343}
344
345bool QStackedWidgetPropertySheet::isEnabled(int index) const
346{
347 if (propertyName(index) != QLatin1String(pagePropertyName))
348 return QDesignerPropertySheet::isEnabled(index);
349 return m_stackedWidget->currentWidget() != nullptr;
350}
351
352void QStackedWidgetPropertySheet::setProperty(int index, const QVariant &value)
353{
354 if (propertyName(index) == QLatin1String(pagePropertyName)) {
355 if (QWidget *w = m_stackedWidget->currentWidget())
356 w->setObjectName(value.toString());
357 } else {
358 QDesignerPropertySheet::setProperty(index, value);
359 }
360}
361
362QVariant QStackedWidgetPropertySheet::property(int index) const
363{
364 if (propertyName(index) == QLatin1String(pagePropertyName)) {
365 if (const QWidget *w = m_stackedWidget->currentWidget())
366 return w->objectName();
367 return QString();
368 }
369 return QDesignerPropertySheet::property(index);
370}
371
372bool QStackedWidgetPropertySheet::reset(int index)
373{
374 if (propertyName(index) == QLatin1String(pagePropertyName)) {
375 setProperty(index, value: QString());
376 return true;
377 }
378 return QDesignerPropertySheet::reset(index);
379}
380
381bool QStackedWidgetPropertySheet::checkProperty(const QString &propertyName)
382{
383 return propertyName != QLatin1String(pagePropertyName);
384}
385
386QT_END_NAMESPACE
387

source code of qttools/src/designer/src/lib/shared/qdesigner_stackedbox.cpp