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
45 QT_BEGIN_NAMESPACE
46
createToolButton(QWidget * parent,Qt::ArrowType at,const QString & name)47 static QToolButton *createToolButton(QWidget *parent, Qt::ArrowType at, const QString &name) {
48 QToolButton *rc = new QToolButton();
49 rc->setAttribute(Qt::WA_NoChildEventsForParent, 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
QStackedWidgetPreviewEventFilter(QStackedWidget * parent)60 QStackedWidgetPreviewEventFilter::QStackedWidgetPreviewEventFilter(QStackedWidget *parent) :
61 QObject(parent),
62 m_buttonToolTipEnabled(false), // Not on preview
63 m_stackedWidget(parent),
64 m_prev(createToolButton(m_stackedWidget, Qt::LeftArrow, QStringLiteral("__qt__passive_prev"))),
65 m_next(createToolButton(m_stackedWidget, Qt::RightArrow, QStringLiteral("__qt__passive_next")))
66 {
67 connect(m_prev, &QAbstractButton::clicked, this, &QStackedWidgetPreviewEventFilter::prevPage);
68 connect(m_next, &QAbstractButton::clicked, this, &QStackedWidgetPreviewEventFilter::nextPage);
69
70 updateButtons();
71 m_stackedWidget->installEventFilter(this);
72 m_prev->installEventFilter(this);
73 m_next->installEventFilter(this);
74 }
75
install(QStackedWidget * stackedWidget)76 void QStackedWidgetPreviewEventFilter::install(QStackedWidget *stackedWidget)
77 {
78 new QStackedWidgetPreviewEventFilter(stackedWidget);
79 }
80
updateButtons()81 void QStackedWidgetPreviewEventFilter::updateButtons()
82 {
83 m_prev->move(m_stackedWidget->width() - 31, 1);
84 m_prev->show();
85 m_prev->raise();
86
87 m_next->move(m_stackedWidget->width() - 16, 1);
88 m_next->show();
89 m_next->raise();
90 }
91
prevPage()92 void QStackedWidgetPreviewEventFilter::prevPage()
93 {
94 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) {
95 fw->clearSelection();
96 fw->selectWidget(stackedWidget(), 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(newIndex);
104 }
105 }
106
nextPage()107 void QStackedWidgetPreviewEventFilter::nextPage()
108 {
109 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) {
110 fw->clearSelection();
111 fw->selectWidget(stackedWidget(), true);
112 }
113 const int count = m_stackedWidget->count();
114 if (count > 1)
115 gotoPage((m_stackedWidget->currentIndex() + 1) % count);
116 }
117
eventFilter(QObject * watched,QEvent * event)118 bool 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(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
gotoPage(int page)149 void QStackedWidgetPreviewEventFilter::gotoPage(int page)
150 {
151 m_stackedWidget->setCurrentIndex(page);
152 updateButtons();
153 }
154
stackedClassName(QStackedWidget * w)155 static inline QString stackedClassName(QStackedWidget *w)
156 {
157 if (const QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w))
158 return qdesigner_internal::WidgetFactory::classNameOf(fw->core(), w);
159 return QStringLiteral("Stacked widget");
160 }
161
updateButtonToolTip(QObject * o)162 void QStackedWidgetPreviewEventFilter::updateButtonToolTip(QObject *o)
163 {
164 if (o == m_prev) {
165 const QString msg = tr("Go to previous page of %1 '%2' (%3/%4).")
166 .arg(stackedClassName(m_stackedWidget), m_stackedWidget->objectName())
167 .arg(m_stackedWidget->currentIndex() + 1)
168 .arg(m_stackedWidget->count());
169 m_prev->setToolTip(msg);
170 } else {
171 if (o == m_next) {
172 const QString msg = tr("Go to next page of %1 '%2' (%3/%4).")
173 .arg(stackedClassName(m_stackedWidget), m_stackedWidget->objectName())
174 .arg(m_stackedWidget->currentIndex() + 1)
175 .arg(m_stackedWidget->count());
176 m_next->setToolTip(msg);
177 }
178 }
179 }
180
181 // --------------- QStackedWidgetEventFilter
QStackedWidgetEventFilter(QStackedWidget * parent)182 QStackedWidgetEventFilter::QStackedWidgetEventFilter(QStackedWidget *parent) :
183 QStackedWidgetPreviewEventFilter(parent),
184 m_actionPreviousPage(new QAction(tr("Previous Page"), this)),
185 m_actionNextPage(new QAction(tr("Next Page"), this)),
186 m_actionDeletePage(new QAction(tr("Delete"), this)),
187 m_actionInsertPage(new QAction(tr("Before Current Page"), this)),
188 m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)),
189 m_actionChangePageOrder(new QAction(tr("Change Page Order..."), this)),
190 m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this))
191 {
192 setButtonToolTipEnabled(true);
193 connect(m_actionPreviousPage, &QAction::triggered, this, &QStackedWidgetEventFilter::prevPage);
194 connect(m_actionNextPage, &QAction::triggered, this, &QStackedWidgetEventFilter::nextPage);
195 connect(m_actionDeletePage, &QAction::triggered, this, &QStackedWidgetEventFilter::removeCurrentPage);
196 connect(m_actionInsertPage, &QAction::triggered, this, &QStackedWidgetEventFilter::addPage);
197 connect(m_actionInsertPageAfter, &QAction::triggered, this, &QStackedWidgetEventFilter::addPageAfter);
198 connect(m_actionChangePageOrder, &QAction::triggered, this, &QStackedWidgetEventFilter::changeOrder);
199 }
200
install(QStackedWidget * stackedWidget)201 void QStackedWidgetEventFilter::install(QStackedWidget *stackedWidget)
202 {
203 new QStackedWidgetEventFilter(stackedWidget);
204 }
205
eventFilterOf(const QStackedWidget * stackedWidget)206 QStackedWidgetEventFilter *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 *>(o))
212 return ef;
213 }
214 return nullptr;
215 }
216
addStackedWidgetContextMenuActions(const QStackedWidget * stackedWidget,QMenu * popup)217 QMenu *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
removeCurrentPage()225 void QStackedWidgetEventFilter::removeCurrentPage()
226 {
227 if (stackedWidget()->currentIndex() == -1)
228 return;
229
230 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) {
231 qdesigner_internal::DeleteStackedWidgetPageCommand *cmd = new qdesigner_internal::DeleteStackedWidgetPageCommand(fw);
232 cmd->init(stackedWidget());
233 fw->commandHistory()->push(cmd);
234 }
235 }
236
changeOrder()237 void QStackedWidgetEventFilter::changeOrder()
238 {
239 QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget());
240
241 if (!fw)
242 return;
243
244 const QWidgetList oldPages = qdesigner_internal::OrderDialog::pagesOfContainer(fw->core(), 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(tr("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(), newPages.at(i), i);
264 fw->commandHistory()->push(cmd);
265 }
266 fw->endCommand();
267 }
268
addPage()269 void QStackedWidgetEventFilter::addPage()
270 {
271 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) {
272 qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw);
273 cmd->init(stackedWidget(), qdesigner_internal::AddStackedWidgetPageCommand::InsertBefore);
274 fw->commandHistory()->push(cmd);
275 }
276 }
277
addPageAfter()278 void QStackedWidgetEventFilter::addPageAfter()
279 {
280 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) {
281 qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw);
282 cmd->init(stackedWidget(), qdesigner_internal::AddStackedWidgetPageCommand::InsertAfter);
283 fw->commandHistory()->push(cmd);
284 }
285 }
286
gotoPage(int page)287 void QStackedWidgetEventFilter::gotoPage(int page) {
288 // Are we on a form or in a preview?
289 if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) {
290 qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw);
291 cmd->init(stackedWidget(), QStringLiteral("currentIndex"), 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
addContextMenuActions(QMenu * popup)300 QMenu *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("Page %1 of %2").arg(stackedWidget()->currentIndex() + 1).arg(count);
308 pageMenu = popup->addMenu(pageSubMenuLabel);
309 pageMenu->addAction(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(QDesignerFormWindowInterface::findFormWindow(stackedWidget()),
314 qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit,
315 pageMenu);
316 }
317 QMenu *insertPageMenu = popup->addMenu(tr("Insert Page"));
318 insertPageMenu->addAction(m_actionInsertPageAfter);
319 insertPageMenu->addAction(m_actionInsertPage);
320 } else {
321 QAction *insertPageAction = popup->addAction(tr("Insert Page"));
322 connect(insertPageAction, &QAction::triggered, this, &QStackedWidgetEventFilter::addPage);
323 }
324 popup->addAction(m_actionNextPage);
325 m_actionNextPage->setEnabled(hasSeveralPages);
326 popup->addAction(m_actionPreviousPage);
327 m_actionPreviousPage->setEnabled(hasSeveralPages);
328 popup->addAction(m_actionChangePageOrder);
329 m_actionChangePageOrder->setEnabled(hasSeveralPages);
330 popup->addSeparator();
331 return pageMenu;
332 }
333
334 // -------- QStackedWidgetPropertySheet
335
336 static const char *pagePropertyName = "currentPageName";
337
QStackedWidgetPropertySheet(QStackedWidget * object,QObject * parent)338 QStackedWidgetPropertySheet::QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent) :
339 QDesignerPropertySheet(object, parent),
340 m_stackedWidget(object)
341 {
342 createFakeProperty(QLatin1String(pagePropertyName), QString());
343 }
344
isEnabled(int index) const345 bool 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
setProperty(int index,const QVariant & value)352 void 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
property(int index) const362 QVariant 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
reset(int index)372 bool QStackedWidgetPropertySheet::reset(int index)
373 {
374 if (propertyName(index) == QLatin1String(pagePropertyName)) {
375 setProperty(index, QString());
376 return true;
377 }
378 return QDesignerPropertySheet::reset(index);
379 }
380
checkProperty(const QString & propertyName)381 bool QStackedWidgetPropertySheet::checkProperty(const QString &propertyName)
382 {
383 return propertyName != QLatin1String(pagePropertyName);
384 }
385
386 QT_END_NAMESPACE
387