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_tabwidget_p.h"
30#include "qdesigner_command_p.h"
31#include "qdesigner_propertycommand_p.h"
32#include "promotiontaskmenu_p.h"
33#include "formwindowbase_p.h"
34
35#include <QtDesigner/abstractformwindow.h>
36
37#include <QtWidgets/qapplication.h>
38#include <QtWidgets/qtabbar.h>
39#include <QtWidgets/qaction.h>
40#include <QtGui/qevent.h>
41#include <QtGui/qdrag.h>
42#include <QtWidgets/qmenu.h>
43#include <QtWidgets/qlabel.h>
44#include <QtWidgets/qtabwidget.h>
45
46#include <QtCore/qdebug.h>
47#include <QtCore/qmimedata.h>
48
49QT_BEGIN_NAMESPACE
50
51namespace qdesigner_internal {
52// Store tab widget as drag source
53class MyMimeData : public QMimeData
54{
55 Q_OBJECT
56public:
57 MyMimeData(const QTabWidget *tab) : m_tab(tab) {}
58 static bool fromMyTab(const QMimeData *mimeData, const QTabWidget *tab) {
59 if (!mimeData)
60 return false;
61 const MyMimeData *m = qobject_cast<const MyMimeData *>(object: mimeData);
62 return m && m->m_tab == tab;
63 }
64private:
65 const QTabWidget *m_tab;
66};
67
68} // namespace qdesigner_internal
69
70// ------------- QTabWidgetEventFilter
71
72QTabWidgetEventFilter::QTabWidgetEventFilter(QTabWidget *parent) :
73 QObject(parent),
74 m_tabWidget(parent),
75 m_dropIndicator(nullptr),
76 m_dragPage(nullptr),
77 m_mousePressed(false),
78 m_actionDeletePage(new QAction(tr(s: "Delete"), this)),
79 m_actionInsertPage(new QAction(tr(s: "Before Current Page"), this)),
80 m_actionInsertPageAfter(new QAction(tr(s: "After Current Page"), this)),
81 m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this))
82{
83 tabBar()->setAcceptDrops(true);
84 tabBar()->installEventFilter(filterObj: this);
85
86 connect(sender: m_actionInsertPage, signal: &QAction::triggered, receiver: this, slot: &QTabWidgetEventFilter::addPage);
87 connect(sender: m_actionInsertPageAfter, signal: &QAction::triggered, receiver: this, slot: &QTabWidgetEventFilter::addPageAfter);
88 connect(sender: m_actionDeletePage, signal: &QAction::triggered, receiver: this, slot: &QTabWidgetEventFilter::removeCurrentPage);
89}
90
91QTabWidgetEventFilter::~QTabWidgetEventFilter() = default;
92
93void QTabWidgetEventFilter::install(QTabWidget *tabWidget)
94{
95 new QTabWidgetEventFilter(tabWidget);
96}
97
98QTabWidgetEventFilter *QTabWidgetEventFilter::eventFilterOf(const QTabWidget *tabWidget)
99{
100 // Look for 1st order children only..otherwise, we might get filters of nested tab widgets
101 for (QObject *o : tabWidget->children()) {
102 if (!o->isWidgetType())
103 if (QTabWidgetEventFilter *ef = qobject_cast<QTabWidgetEventFilter*>(object: o))
104 return ef;
105 }
106 return nullptr;
107}
108
109QMenu *QTabWidgetEventFilter::addTabWidgetContextMenuActions(const QTabWidget *tabWidget, QMenu *popup)
110{
111 QTabWidgetEventFilter *filter = eventFilterOf(tabWidget);
112 if (!filter)
113 return nullptr;
114 return filter->addContextMenuActions(popup);
115}
116
117QTabBar *QTabWidgetEventFilter::tabBar() const
118{
119 // QTabWidget::tabBar() accessor is protected, grmbl...
120 if (!m_cachedTabBar) {
121 const auto tabBars = m_tabWidget->findChildren<QTabBar *>();
122 Q_ASSERT(tabBars.size() == 1);
123 m_cachedTabBar = tabBars.constFirst();
124 }
125 return m_cachedTabBar;
126
127}
128
129static bool canMove(const QPoint &pressPoint, const QMouseEvent *e)
130{
131 const QPoint pt = pressPoint - e->pos();
132 return pt.manhattanLength() > QApplication::startDragDistance();
133}
134
135bool QTabWidgetEventFilter::eventFilter(QObject *o, QEvent *e)
136{
137 const QEvent::Type type = e->type();
138 // Do not try to locate tab bar and form window, etc. for uninteresting events and
139 // avoid asserts about missing tab bars when being destroyed
140 switch (type) {
141 case QEvent::MouseButtonDblClick:
142 case QEvent::MouseButtonPress:
143 case QEvent::MouseButtonRelease:
144 case QEvent::MouseMove:
145 case QEvent::DragLeave:
146 case QEvent::DragEnter:
147 case QEvent::DragMove:
148 case QEvent::Drop:
149 break;
150 default:
151 return false;
152 }
153
154 if (o != tabBar())
155 return false;
156
157 QDesignerFormWindowInterface *fw = formWindow();
158 if (!fw)
159 return false;
160
161 bool handled = true;
162 switch (type) {
163 case QEvent::MouseButtonDblClick:
164 break;
165 case QEvent::MouseButtonPress: {
166 QMouseEvent *mev = static_cast<QMouseEvent*>(e);
167 if (QDesignerFormWindowInterface *fw = formWindow()) {
168 fw->clearSelection();
169 fw->selectWidget(w: m_tabWidget, select: true);
170 }
171 if (mev->button() & Qt::LeftButton) {
172 m_mousePressed = true;
173 m_pressPoint = mev->pos();
174
175 QTabBar *tabbar = tabBar();
176 const int count = tabbar->count();
177 for (int i = 0; i < count; ++i) {
178 if (tabbar->tabRect(index: i).contains(p: m_pressPoint)) {
179 if (i != tabbar->currentIndex()) {
180 qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw);
181 cmd->init(object: m_tabWidget, QStringLiteral("currentIndex"), newValue: i);
182 fw->commandHistory()->push(cmd);
183 }
184 break;
185 }
186 }
187 }
188 } break;
189
190 case QEvent::MouseButtonRelease:
191 m_mousePressed = false;
192 break;
193
194 case QEvent::MouseMove: {
195 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(e);
196 if (m_mousePressed && canMove(pressPoint: m_pressPoint, e: mouseEvent)) {
197 const int index = m_tabWidget->currentIndex();
198 if (index == -1)
199 break;
200
201 m_mousePressed = false;
202 QDrag *drg = new QDrag(m_tabWidget);
203 drg->setMimeData(new qdesigner_internal::MyMimeData(m_tabWidget));
204
205 m_dragIndex = index;
206 m_dragPage = m_tabWidget->currentWidget();
207 m_dragLabel = m_tabWidget->tabText(index);
208 m_dragIcon = m_tabWidget->tabIcon(index);
209 if (m_dragIcon.isNull()) {
210 QLabel *label = new QLabel(m_dragLabel);
211 label->adjustSize();
212 drg->setPixmap(label->grab(rectangle: QRect(0, 0, -1, -1)));
213 label->deleteLater();
214 } else {
215 drg->setPixmap(m_dragIcon.pixmap(w: 22, h: 22));
216 }
217
218 m_tabWidget->removeTab(index: m_dragIndex);
219
220 const Qt::DropActions dropAction = drg->exec(supportedActions: Qt::MoveAction);
221
222 if (dropAction == Qt::IgnoreAction) {
223 // abort
224 m_tabWidget->insertTab(index: m_dragIndex, widget: m_dragPage, icon: m_dragIcon, label: m_dragLabel);
225 m_tabWidget->setCurrentIndex(m_dragIndex);
226 }
227
228 if (m_dropIndicator)
229 m_dropIndicator->hide();
230 }
231 } break;
232
233 case QEvent::DragLeave: {
234 if (m_dropIndicator)
235 m_dropIndicator->hide();
236 } break;
237
238 case QEvent::DragEnter:
239 case QEvent::DragMove: {
240 QDragMoveEvent *de = static_cast<QDragMoveEvent*>(e);
241 if (!qdesigner_internal::MyMimeData::fromMyTab(mimeData: de->mimeData(), tab: m_tabWidget))
242 return false;
243
244 if (de->proposedAction() == Qt::MoveAction)
245 de->acceptProposedAction();
246 else {
247 de->setDropAction(Qt::MoveAction);
248 de->accept();
249 }
250
251 QRect rect;
252 const int index = pageFromPosition(pos: de->pos(), rect);
253
254 if (!m_dropIndicator) {
255 m_dropIndicator = new QWidget(m_tabWidget);
256 QPalette p = m_dropIndicator->palette();
257 p.setColor(acr: m_tabWidget->backgroundRole(), acolor: Qt::red);
258 m_dropIndicator->setPalette(p);
259 }
260
261 QPoint pos;
262 if (index == m_tabWidget->count())
263 pos = tabBar()->mapToParent(QPoint(rect.x() + rect.width(), rect.y()));
264 else
265 pos = tabBar()->mapToParent(QPoint(rect.x(), rect.y()));
266
267 m_dropIndicator->setGeometry(ax: pos.x(), ay: pos.y() , aw: 3, ah: rect.height());
268 m_dropIndicator->show();
269 } break;
270
271 case QEvent::Drop: {
272 QDropEvent *de = static_cast<QDropEvent*>(e);
273 if (!qdesigner_internal::MyMimeData::fromMyTab(mimeData: de->mimeData(), tab: m_tabWidget))
274 return false;
275 de->acceptProposedAction();
276 de->accept();
277
278 QRect rect;
279 const int newIndex = pageFromPosition(pos: de->pos(), rect);
280
281 qdesigner_internal::MoveTabPageCommand *cmd = new qdesigner_internal::MoveTabPageCommand(fw);
282 m_tabWidget->insertTab(index: m_dragIndex, widget: m_dragPage, icon: m_dragIcon, label: m_dragLabel);
283 cmd->init(tabWidget: m_tabWidget, page: m_dragPage, icon: m_dragIcon, label: m_dragLabel, index: m_dragIndex, newIndex);
284 fw->commandHistory()->push(cmd);
285 } break;
286
287 default:
288 handled = false;
289 break;
290 }
291
292 return handled;
293}
294
295void QTabWidgetEventFilter::removeCurrentPage()
296{
297 if (!m_tabWidget->currentWidget())
298 return;
299
300 if (QDesignerFormWindowInterface *fw = formWindow()) {
301 qdesigner_internal::DeleteTabPageCommand *cmd = new qdesigner_internal::DeleteTabPageCommand(fw);
302 cmd->init(tabWidget: m_tabWidget);
303 fw->commandHistory()->push(cmd);
304 }
305}
306
307void QTabWidgetEventFilter::addPage()
308{
309 if (QDesignerFormWindowInterface *fw = formWindow()) {
310 qdesigner_internal::AddTabPageCommand *cmd = new qdesigner_internal::AddTabPageCommand(fw);
311 cmd->init(tabWidget: m_tabWidget, mode: qdesigner_internal::AddTabPageCommand::InsertBefore);
312 fw->commandHistory()->push(cmd);
313 }
314}
315
316void QTabWidgetEventFilter::addPageAfter()
317{
318 if (QDesignerFormWindowInterface *fw = formWindow()) {
319 qdesigner_internal::AddTabPageCommand *cmd = new qdesigner_internal::AddTabPageCommand(fw);
320 cmd->init(tabWidget: m_tabWidget, mode: qdesigner_internal::AddTabPageCommand::InsertAfter);
321 fw->commandHistory()->push(cmd);
322 }
323}
324
325QDesignerFormWindowInterface *QTabWidgetEventFilter::formWindow() const
326{
327 return QDesignerFormWindowInterface::findFormWindow(w: const_cast<QTabWidget*>(m_tabWidget));
328}
329
330// Get page from mouse position. Default to new page if in right half of last page?
331int QTabWidgetEventFilter::pageFromPosition(const QPoint &pos, QRect &rect) const
332{
333 int index = 0;
334 const QTabBar *tabbar = tabBar();
335 const int count = m_tabWidget->count();
336 for (; index < count; index++) {
337 const QRect rc = tabbar->tabRect(index);
338 if (rc.contains(p: pos)) {
339 rect = rc;
340 break;
341 }
342 }
343
344 if (index == count -1) {
345 QRect rect2 = rect;
346 rect2.setLeft(rect2.left() + rect2.width() / 2);
347 if (rect2.contains(p: pos))
348 index++;
349 }
350 return index;
351}
352
353QMenu *QTabWidgetEventFilter::addContextMenuActions(QMenu *popup)
354{
355 QMenu *pageMenu = nullptr;
356 const int count = m_tabWidget->count();
357 m_actionDeletePage->setEnabled(count);
358 if (count) {
359 const int currentIndex = m_tabWidget->currentIndex();
360 const QString pageSubMenuLabel = tr(s: "Page %1 of %2").arg(a: currentIndex + 1).arg(a: count);
361 pageMenu = popup->addMenu(title: pageSubMenuLabel);
362 pageMenu->addAction(action: m_actionDeletePage);
363 // Set up promotion menu for current widget.
364 if (QWidget *page = m_tabWidget->currentWidget ()) {
365 m_pagePromotionTaskMenu->setWidget(page);
366 m_pagePromotionTaskMenu->addActions(fw: QDesignerFormWindowInterface::findFormWindow(w: m_tabWidget),
367 flags: qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit,
368 menu: pageMenu);
369 }
370 QMenu *insertPageMenu = popup->addMenu(title: tr(s: "Insert Page"));
371 insertPageMenu->addAction(action: m_actionInsertPageAfter);
372 insertPageMenu->addAction(action: m_actionInsertPage);
373 } else {
374 QAction *insertPageAction = popup->addAction(text: tr(s: "Insert Page"));
375 connect(sender: insertPageAction, signal: &QAction::triggered, receiver: this, slot: &QTabWidgetEventFilter::addPage);
376 }
377 popup->addSeparator();
378 return pageMenu;
379}
380
381// ----------- QTabWidgetPropertySheet
382
383static const char *currentTabTextKey = "currentTabText";
384static const char *currentTabNameKey = "currentTabName";
385static const char *currentTabIconKey = "currentTabIcon";
386static const char *currentTabToolTipKey = "currentTabToolTip";
387static const char *currentTabWhatsThisKey = "currentTabWhatsThis";
388static const char *tabMovableKey = "movable";
389
390QTabWidgetPropertySheet::QTabWidgetPropertySheet(QTabWidget *object, QObject *parent) :
391 QDesignerPropertySheet(object, parent),
392 m_tabWidget(object)
393{
394 createFakeProperty(propertyName: QLatin1String(currentTabTextKey), value: QVariant::fromValue(value: qdesigner_internal::PropertySheetStringValue()));
395 createFakeProperty(propertyName: QLatin1String(currentTabNameKey), value: QString());
396 createFakeProperty(propertyName: QLatin1String(currentTabIconKey), value: QVariant::fromValue(value: qdesigner_internal::PropertySheetIconValue()));
397 if (formWindowBase())
398 formWindowBase()->addReloadableProperty(sheet: this, index: indexOf(name: QLatin1String(currentTabIconKey)));
399 createFakeProperty(propertyName: QLatin1String(currentTabToolTipKey), value: QVariant::fromValue(value: qdesigner_internal::PropertySheetStringValue()));
400 createFakeProperty(propertyName: QLatin1String(currentTabWhatsThisKey), value: QVariant::fromValue(value: qdesigner_internal::PropertySheetStringValue()));
401 // Prevent the tab widget's drag and drop handling from interfering with Designer's
402 createFakeProperty(propertyName: QLatin1String(tabMovableKey), value: QVariant(false));
403}
404
405QTabWidgetPropertySheet::TabWidgetProperty QTabWidgetPropertySheet::tabWidgetPropertyFromName(const QString &name)
406{
407 using TabWidgetPropertyHash = QHash<QString, TabWidgetProperty>;
408 static TabWidgetPropertyHash tabWidgetPropertyHash;
409 if (tabWidgetPropertyHash.isEmpty()) {
410 tabWidgetPropertyHash.insert(akey: QLatin1String(currentTabTextKey), avalue: PropertyCurrentTabText);
411 tabWidgetPropertyHash.insert(akey: QLatin1String(currentTabNameKey), avalue: PropertyCurrentTabName);
412 tabWidgetPropertyHash.insert(akey: QLatin1String(currentTabIconKey), avalue: PropertyCurrentTabIcon);
413 tabWidgetPropertyHash.insert(akey: QLatin1String(currentTabToolTipKey), avalue: PropertyCurrentTabToolTip);
414 tabWidgetPropertyHash.insert(akey: QLatin1String(currentTabWhatsThisKey), avalue: PropertyCurrentTabWhatsThis);
415 }
416 return tabWidgetPropertyHash.value(akey: name, adefaultValue: PropertyTabWidgetNone);
417}
418
419void QTabWidgetPropertySheet::setProperty(int index, const QVariant &value)
420{
421 const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(name: propertyName(index));
422 if (tabWidgetProperty == PropertyTabWidgetNone) {
423 QDesignerPropertySheet::setProperty(index, value);
424 return;
425 }
426
427 // index-dependent
428 const int currentIndex = m_tabWidget->currentIndex();
429 QWidget *currentWidget = m_tabWidget->currentWidget();
430 if (!currentWidget)
431 return;
432
433 switch (tabWidgetProperty) {
434 case PropertyCurrentTabText:
435 m_tabWidget->setTabText(index: currentIndex, text: qvariant_cast<QString>(v: resolvePropertyValue(index, value)));
436 m_pageToData[currentWidget].text = qvariant_cast<qdesigner_internal::PropertySheetStringValue>(v: value);
437 break;
438 case PropertyCurrentTabName:
439 currentWidget->setObjectName(value.toString());
440 break;
441 case PropertyCurrentTabIcon:
442 m_tabWidget->setTabIcon(index: currentIndex, icon: qvariant_cast<QIcon>(v: resolvePropertyValue(index, value)));
443 m_pageToData[currentWidget].icon = qvariant_cast<qdesigner_internal::PropertySheetIconValue>(v: value);
444 break;
445 case PropertyCurrentTabToolTip:
446 m_tabWidget->setTabToolTip(index: currentIndex, tip: qvariant_cast<QString>(v: resolvePropertyValue(index, value)));
447 m_pageToData[currentWidget].tooltip = qvariant_cast<qdesigner_internal::PropertySheetStringValue>(v: value);
448 break;
449 case PropertyCurrentTabWhatsThis:
450 m_tabWidget->setTabWhatsThis(index: currentIndex, text: qvariant_cast<QString>(v: resolvePropertyValue(index, value)));
451 m_pageToData[currentWidget].whatsthis = qvariant_cast<qdesigner_internal::PropertySheetStringValue>(v: value);
452 break;
453 case PropertyTabWidgetNone:
454 break;
455 }
456}
457
458bool QTabWidgetPropertySheet::isEnabled(int index) const
459{
460 if (tabWidgetPropertyFromName(name: propertyName(index)) == PropertyTabWidgetNone)
461 return QDesignerPropertySheet::isEnabled(index);
462 return m_tabWidget->currentIndex() != -1;
463}
464
465QVariant QTabWidgetPropertySheet::property(int index) const
466{
467 const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(name: propertyName(index));
468 if (tabWidgetProperty == PropertyTabWidgetNone)
469 return QDesignerPropertySheet::property(index);
470
471 // index-dependent
472 QWidget *currentWidget = m_tabWidget->currentWidget();
473 if (!currentWidget) {
474 if (tabWidgetProperty == PropertyCurrentTabIcon)
475 return QVariant::fromValue(value: qdesigner_internal::PropertySheetIconValue());
476 if (tabWidgetProperty == PropertyCurrentTabText)
477 return QVariant::fromValue(value: qdesigner_internal::PropertySheetStringValue());
478 if (tabWidgetProperty == PropertyCurrentTabToolTip)
479 return QVariant::fromValue(value: qdesigner_internal::PropertySheetStringValue());
480 if (tabWidgetProperty == PropertyCurrentTabWhatsThis)
481 return QVariant::fromValue(value: qdesigner_internal::PropertySheetStringValue());
482 return QVariant(QString());
483 }
484
485 // index-dependent
486 switch (tabWidgetProperty) {
487 case PropertyCurrentTabText:
488 return QVariant::fromValue(value: m_pageToData.value(akey: currentWidget).text);
489 case PropertyCurrentTabName:
490 return currentWidget->objectName();
491 case PropertyCurrentTabIcon:
492 return QVariant::fromValue(value: m_pageToData.value(akey: currentWidget).icon);
493 case PropertyCurrentTabToolTip:
494 return QVariant::fromValue(value: m_pageToData.value(akey: currentWidget).tooltip);
495 case PropertyCurrentTabWhatsThis:
496 return QVariant::fromValue(value: m_pageToData.value(akey: currentWidget).whatsthis);
497 case PropertyTabWidgetNone:
498 break;
499 }
500 return QVariant();
501}
502
503bool QTabWidgetPropertySheet::reset(int index)
504{
505 const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(name: propertyName(index));
506 if (tabWidgetProperty == PropertyTabWidgetNone)
507 return QDesignerPropertySheet::reset(index);
508
509 // index-dependent
510 QWidget *currentWidget = m_tabWidget->currentWidget();
511 if (!currentWidget)
512 return false;
513
514 // index-dependent
515 switch (tabWidgetProperty) {
516 case PropertyCurrentTabName:
517 setProperty(index, value: QString());
518 break;
519 case PropertyCurrentTabToolTip:
520 m_pageToData[currentWidget].tooltip = qdesigner_internal::PropertySheetStringValue();
521 setProperty(index, value: QString());
522 break;
523 case PropertyCurrentTabWhatsThis:
524 m_pageToData[currentWidget].whatsthis = qdesigner_internal::PropertySheetStringValue();
525 setProperty(index, value: QString());
526 break;
527 case PropertyCurrentTabText:
528 m_pageToData[currentWidget].text = qdesigner_internal::PropertySheetStringValue();
529 setProperty(index, value: QString());
530 break;
531 case PropertyCurrentTabIcon:
532 m_pageToData[currentWidget].icon = qdesigner_internal::PropertySheetIconValue();
533 setProperty(index, value: QIcon());
534 break;
535 case PropertyTabWidgetNone:
536 break;
537 }
538 return true;
539}
540
541bool QTabWidgetPropertySheet::checkProperty(const QString &propertyName)
542{
543 switch (tabWidgetPropertyFromName(name: propertyName)) {
544 case PropertyCurrentTabText:
545 case PropertyCurrentTabName:
546 case PropertyCurrentTabToolTip:
547 case PropertyCurrentTabWhatsThis:
548 case PropertyCurrentTabIcon:
549 return false;
550 default:
551 break;
552 }
553 return true;
554}
555
556QT_END_NAMESPACE
557
558#include "qdesigner_tabwidget.moc" // required for MyMimeData
559

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