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 | |
47 | static 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 |
60 | QStackedWidgetPreviewEventFilter::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 | |
76 | void QStackedWidgetPreviewEventFilter::install(QStackedWidget *stackedWidget) |
77 | { |
78 | new QStackedWidgetPreviewEventFilter(stackedWidget); |
79 | } |
80 | |
81 | void 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 | |
92 | void 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 | |
107 | void 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 | |
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(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 | |
149 | void QStackedWidgetPreviewEventFilter::gotoPage(int page) |
150 | { |
151 | m_stackedWidget->setCurrentIndex(page); |
152 | updateButtons(); |
153 | } |
154 | |
155 | static 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 | |
162 | void 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 |
182 | QStackedWidgetEventFilter::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 | |
201 | void QStackedWidgetEventFilter::install(QStackedWidget *stackedWidget) |
202 | { |
203 | new QStackedWidgetEventFilter(stackedWidget); |
204 | } |
205 | |
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 *>(object: o)) |
212 | return ef; |
213 | } |
214 | return nullptr; |
215 | } |
216 | |
217 | QMenu *QStackedWidgetEventFilter::(const QStackedWidget *stackedWidget, QMenu *) |
218 | { |
219 | QStackedWidgetEventFilter *filter = eventFilterOf(stackedWidget); |
220 | if (!filter) |
221 | return nullptr; |
222 | return filter->addContextMenuActions(popup); |
223 | } |
224 | |
225 | void 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 | |
237 | void 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 | |
269 | void 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 | |
278 | void 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 | |
287 | void 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 | |
300 | QMenu *QStackedWidgetEventFilter::(QMenu *) |
301 | { |
302 | QMenu * = nullptr; |
303 | const int count = stackedWidget()->count(); |
304 | const bool hasSeveralPages = count > 1; |
305 | m_actionDeletePage->setEnabled(count); |
306 | if (count) { |
307 | const QString = 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 * = 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 | |
336 | static const char *pagePropertyName = "currentPageName" ; |
337 | |
338 | QStackedWidgetPropertySheet::QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent) : |
339 | QDesignerPropertySheet(object, parent), |
340 | m_stackedWidget(object) |
341 | { |
342 | createFakeProperty(propertyName: QLatin1String(pagePropertyName), value: QString()); |
343 | } |
344 | |
345 | 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 | |
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 | |
362 | 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 | |
372 | bool 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 | |
381 | bool QStackedWidgetPropertySheet::checkProperty(const QString &propertyName) |
382 | { |
383 | return propertyName != QLatin1String(pagePropertyName); |
384 | } |
385 | |
386 | QT_END_NAMESPACE |
387 | |