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