File indexing completed on 2024-05-19 05:08:28

0001 /*
0002     SPDX-FileCopyrightText: 2021 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "tabordereditor.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QDebug>
0012 #include <QEventLoop>
0013 #include <QMenu>
0014 #include <QMetaObject>
0015 #include <QPaintEvent>
0016 #include <QPainter>
0017 #include <QPointer>
0018 #include <QPushButton>
0019 #include <QStringList>
0020 #include <QVariant>
0021 #include <QVector>
0022 
0023 // ----------------------------------------------------------------------------
0024 // KDE Includes
0025 
0026 // ----------------------------------------------------------------------------
0027 // Project Includes
0028 
0029 #include "ui_tabordereditor.h"
0030 
0031 class TabOrderDialogPrivate
0032 {
0033     Q_DECLARE_PUBLIC(TabOrderDialog)
0034 
0035 public:
0036     explicit TabOrderDialogPrivate(TabOrderDialog* qq)
0037         : q_ptr(qq)
0038         , m_editor(nullptr)
0039         , m_currentIndex(0)
0040     {
0041     }
0042 
0043     void setTabOrder(const QStringList& widgetNames)
0044     {
0045         m_widgetList.clear();
0046         for (const auto& widgetName : widgetNames) {
0047             auto w = ui.m_targetWidget->findChild<QWidget*>(widgetName);
0048             if (w->property("kmm_taborder").toBool() && w->isVisible()) {
0049                 m_widgetList.append(w);
0050             } else {
0051                 qDebug() << "Skip invisible" << widgetName;
0052             }
0053         }
0054         m_currentIndex = 0;
0055     }
0056 
0057     QStringList tabOrder() const
0058     {
0059         QStringList widgetNames;
0060         for (const auto& w : m_widgetList) {
0061             widgetNames += w->objectName();
0062         }
0063         return widgetNames;
0064     }
0065 
0066     QRect indicatorRectangle(const QWidget* w, const QSize& size) const
0067     {
0068         constexpr int horizMargin = 1;
0069         constexpr int vertMargin = 2;
0070 
0071         if (w == nullptr) {
0072             return {};
0073         }
0074 
0075         const QPoint tl = m_editor->mapFromGlobal(w->mapToGlobal(w->rect().topLeft()));
0076         QRect rect(tl - QPoint(size.width() / 2, size.height() / 8), size);
0077         rect = QRect(rect.left() - horizMargin, rect.top() - vertMargin, rect.width() + horizMargin * 2, rect.height() + vertMargin * 2);
0078 
0079         return rect;
0080     }
0081 
0082     void updateIndicatorRegion()
0083     {
0084         const auto widgetCount = m_widgetList.count();
0085         for (int idx = 0; idx < widgetCount; ++idx) {
0086             const auto txt = QString::number(idx + 1);
0087             auto w = m_widgetList[idx];
0088             m_indicatorRegion |= indicatorRectangle(w, m_editor->indicatorFontMetrics().size(Qt::TextSingleLine, txt));
0089         }
0090     }
0091 
0092     TabOrderDialog* q_ptr;
0093     Ui::TabOrderEditor ui;
0094 
0095     TabOrderEditor* m_editor;
0096     QWidgetList bla;
0097     QVector<QWidget*> m_widgetList;
0098     QRegion m_indicatorRegion;
0099     QStringList m_defaultOrder;
0100     QStringList m_currentOrder;
0101     int m_currentIndex;
0102 };
0103 
0104 TabOrderDialog::TabOrderDialog(QWidget* parent)
0105     : QDialog(parent)
0106     , d_ptr(new TabOrderDialogPrivate(this))
0107 {
0108     Q_D(TabOrderDialog);
0109     d->ui.setupUi(this);
0110 }
0111 
0112 TabOrderDialog::~TabOrderDialog()
0113 {
0114     Q_D(TabOrderDialog);
0115     delete d;
0116 }
0117 
0118 void TabOrderDialog::setTarget(TabOrderEditorInterface* targetWidget)
0119 {
0120     Q_D(TabOrderDialog);
0121 
0122     if (d->ui.m_targetWidget) {
0123         auto list = d->ui.m_targetWidget->findChildren<QObject*>();
0124         qDeleteAll(list);
0125     }
0126     targetWidget->setupUi(d->ui.m_targetWidget);
0127 
0128     // force the focus on our own button box (in case the targetWidget
0129     // contains another one which will be found prior to ours when
0130     // scanning over the widget tree
0131     d->ui.buttonBox->button(QDialogButtonBox::Ok)->setFocus();
0132     d->m_defaultOrder.clear();
0133     d->m_widgetList.clear();
0134     d->m_indicatorRegion = QRegion();
0135 }
0136 
0137 void TabOrderDialog::setDefaultTabOrder(const QStringList& widgetNames)
0138 {
0139     Q_D(TabOrderDialog);
0140     d->m_defaultOrder = widgetNames;
0141 }
0142 
0143 void TabOrderDialog::setTabOrder(const QStringList& widgetNames)
0144 {
0145     Q_D(TabOrderDialog);
0146     d->m_currentOrder = widgetNames;
0147 }
0148 
0149 QStringList TabOrderDialog::tabOrder() const
0150 {
0151     Q_D(const TabOrderDialog);
0152     return d->tabOrder();
0153 }
0154 
0155 int TabOrderDialog::exec()
0156 {
0157     Q_D(TabOrderDialog);
0158 
0159     // make everything visible before we
0160     // set the tab order. If we don't do that
0161     // the widgets will not be found (since
0162     // not visible)
0163     show();
0164     d->setTabOrder(d->m_currentOrder);
0165     d->m_editor = new TabOrderEditor(this);
0166 
0167     connect(d->m_editor, &TabOrderEditor::geometryUpdated, this, [&]() {
0168         Q_D(TabOrderDialog);
0169         d->updateIndicatorRegion();
0170     });
0171 
0172     d->updateIndicatorRegion();
0173 
0174     return QDialog::exec();
0175 }
0176 
0177 class TabOrderEditorPrivate
0178 {
0179     Q_DECLARE_PUBLIC(TabOrderEditor)
0180 
0181 public:
0182     explicit TabOrderEditorPrivate(TabOrderEditor* qq)
0183         : q_ptr(qq)
0184         , m_dialog(nullptr)
0185         , m_indicatorFontMetrics(QFont())
0186         , m_noWidgetsSelected(true)
0187     {
0188     }
0189 
0190     void updateGeometry(QPoint pos, QSize size)
0191     {
0192         Q_Q(TabOrderEditor);
0193         // we add 20px at the top so that the indicators
0194         // in the top row won't get cut off
0195         size.setHeight(size.height() + 20);
0196         pos.setY(pos.y() - 20);
0197         q->move(pos);
0198         q->resize(size);
0199         Q_EMIT q->geometryUpdated();
0200     }
0201 
0202     int widgetIndexAt(const QPoint& pos) const
0203     {
0204         const auto dlg_d = m_dialog->d_func();
0205         int target_index = -1;
0206         for (int i = 0; i < dlg_d->m_widgetList.size(); ++i) {
0207             if (indicatorRectangle(i).contains(pos)) {
0208                 target_index = i;
0209                 break;
0210             }
0211         }
0212 
0213         return target_index;
0214     }
0215 
0216     QRect indicatorRectangle(int idx) const
0217     {
0218         const auto dlg_d = m_dialog->d_func();
0219 
0220         const auto txt = QString::number(idx + 1);
0221         auto w = dlg_d->m_widgetList.at(idx);
0222         return dlg_d->indicatorRectangle(w, m_indicatorFontMetrics.size(Qt::TextSingleLine, txt));
0223     }
0224 
0225     TabOrderEditor* q_ptr;
0226     TabOrderDialog* m_dialog;
0227     QFontMetrics m_indicatorFontMetrics;
0228     bool m_noWidgetsSelected;
0229 };
0230 
0231 TabOrderEditor::TabOrderEditor(TabOrderDialog* parent)
0232     : QWidget(parent)
0233     , d_ptr(new TabOrderEditorPrivate(this))
0234 {
0235     Q_D(TabOrderEditor);
0236     d->m_dialog = parent;
0237 
0238     setVisible(true);
0239     raise();
0240 
0241     QFont indicatorFont;
0242     indicatorFont = font();
0243     indicatorFont.setPointSizeF(indicatorFont.pointSizeF() * 1.5);
0244     indicatorFont.setBold(true);
0245     d->m_indicatorFontMetrics = QFontMetrics(indicatorFont);
0246     setFont(indicatorFont);
0247 
0248     d->m_dialog->d_func()->ui.m_targetWidget->installEventFilter(this);
0249 
0250     d->updateGeometry(d->m_dialog->d_func()->ui.m_targetWidget->pos(), d->m_dialog->d_func()->ui.m_targetWidget->size());
0251     setAttribute(Qt::WA_MouseTracking, true);
0252 }
0253 
0254 TabOrderEditor::~TabOrderEditor()
0255 {
0256     Q_D(TabOrderEditor);
0257     delete d;
0258 }
0259 
0260 void TabOrderEditor::paintEvent(QPaintEvent* e)
0261 {
0262     Q_D(TabOrderEditor);
0263     constexpr int backgroundAlpha = 32;
0264     const auto dlg_d = d->m_dialog->d_func();
0265 
0266     QPainter p(this);
0267 
0268     int lastProcessedIndex = dlg_d->m_currentIndex - 1;
0269     if (!d->m_noWidgetsSelected && lastProcessedIndex < 0) {
0270         lastProcessedIndex = dlg_d->m_widgetList.count();
0271     }
0272 
0273     p.setClipRegion(e->region());
0274     const auto widgetCount = dlg_d->m_widgetList.count();
0275     for (int idx = 0; idx < widgetCount; ++idx) {
0276         const auto txt = QString::number(idx + 1);
0277         auto w = dlg_d->m_widgetList[idx];
0278         auto rect = dlg_d->indicatorRectangle(w, d->m_indicatorFontMetrics.size(Qt::TextSingleLine, txt));
0279 
0280         QColor color(Qt::darkGreen);
0281         if (idx == lastProcessedIndex) {
0282             color = Qt::red;
0283         } else if (idx > lastProcessedIndex) {
0284             color = Qt::darkBlue;
0285         }
0286 
0287         p.setPen(color);
0288         color.setAlpha(backgroundAlpha);
0289         p.setBrush(color);
0290         p.drawRect(rect);
0291 
0292         rect.moveLeft(rect.left() + 1);
0293         p.setPen(Qt::white);
0294         p.drawText(rect, txt, QTextOption(Qt::AlignCenter));
0295     }
0296 }
0297 
0298 bool TabOrderEditor::eventFilter(QObject* o, QEvent* e)
0299 {
0300     Q_D(TabOrderEditor);
0301     if (o == d->m_dialog->d_func()->ui.m_targetWidget) {
0302         switch (e->type()) {
0303         case QEvent::Move:
0304             d->updateGeometry(d->m_dialog->d_func()->ui.m_targetWidget->pos(), d->m_dialog->d_func()->ui.m_targetWidget->size());
0305             break;
0306         case QEvent::Resize:
0307             d->updateGeometry(d->m_dialog->d_func()->ui.m_targetWidget->pos(), d->m_dialog->d_func()->ui.m_targetWidget->size());
0308             break;
0309         default:
0310             break;
0311         }
0312     }
0313     return QWidget::eventFilter(o, e);
0314 }
0315 
0316 const QFontMetrics& TabOrderEditor::indicatorFontMetrics() const
0317 {
0318     Q_D(const TabOrderEditor);
0319     return d->m_indicatorFontMetrics;
0320 }
0321 
0322 void TabOrderEditor::mouseMoveEvent(QMouseEvent* event)
0323 {
0324     Q_D(TabOrderEditor);
0325     event->accept();
0326     if (d->m_dialog->d_func()->m_indicatorRegion.contains(event->pos())) {
0327         setCursor(Qt::PointingHandCursor);
0328     } else {
0329         setCursor(QCursor());
0330     }
0331 }
0332 
0333 void TabOrderEditor::mousePressEvent(QMouseEvent* event)
0334 {
0335     Q_D(TabOrderEditor);
0336     const auto dlg_d = d->m_dialog->d_func();
0337 
0338     event->accept();
0339 
0340 #if 0
0341     if (d->m_dialog->d_func()->m_indicatorRegion.contains(event->pos())) {
0342         if (QWidget *child = m_bg_widget->childAt(e->position().toPoint())) {
0343             QDesignerFormEditorInterface *core = m_form_window->core();
0344             if (core->widgetFactory()->isPassiveInteractor(child)) {
0345 
0346                 QMouseEvent event(QEvent::MouseButtonPress,
0347                                   child->mapFromGlobal(e->globalPosition().toPoint()),
0348                                   e->button(), e->buttons(), e->modifiers());
0349 
0350                 qApp->sendEvent(child, &event);
0351 
0352                 QMouseEvent event2(QEvent::MouseButtonRelease,
0353                                    child->mapFromGlobal(e->globalPosition().toPoint()),
0354                                    e->button(), e->buttons(), e->modifiers());
0355 
0356                 qApp->sendEvent(child, &event2);
0357 
0358                 updateBackground();
0359             }
0360         }
0361         return;
0362     }
0363 #endif
0364 
0365     if (event->button() != Qt::LeftButton)
0366         return;
0367 
0368     const int target_index = d->widgetIndexAt(event->pos());
0369     if (target_index == -1)
0370         return;
0371 
0372     d->m_noWidgetsSelected = false;
0373 
0374     if (event->modifiers() & Qt::ControlModifier) {
0375         dlg_d->m_currentIndex = target_index + 1;
0376         if (dlg_d->m_currentIndex >= dlg_d->m_widgetList.size()) {
0377             dlg_d->m_currentIndex = 0;
0378         }
0379         update();
0380         return;
0381     }
0382 
0383     if (dlg_d->m_currentIndex < 0) {
0384         return;
0385     }
0386 
0387     // swap the elements
0388     const auto w = dlg_d->m_widgetList.at(target_index);
0389     dlg_d->m_widgetList[target_index] = dlg_d->m_widgetList.at(dlg_d->m_currentIndex);
0390     dlg_d->m_widgetList[dlg_d->m_currentIndex] = w;
0391 
0392     // continue with next index
0393     ++dlg_d->m_currentIndex;
0394     if (dlg_d->m_currentIndex == dlg_d->m_widgetList.size())
0395         dlg_d->m_currentIndex = 0;
0396 
0397     update();
0398 }
0399 
0400 void TabOrderEditor::mouseDoubleClickEvent(QMouseEvent* event)
0401 {
0402     Q_D(TabOrderEditor);
0403     const auto dlg_d = d->m_dialog->d_func();
0404 
0405     if (event->button() != Qt::LeftButton) {
0406         return;
0407     }
0408 
0409     if (d->widgetIndexAt(event->pos()) >= 0) {
0410         return;
0411     }
0412 
0413     d->m_noWidgetsSelected = true;
0414     dlg_d->m_currentIndex = 0;
0415     update();
0416 }
0417 
0418 void TabOrderEditor::contextMenuEvent(QContextMenuEvent* e)
0419 {
0420     Q_D(TabOrderEditor);
0421     const auto dlg_d = d->m_dialog->d_func();
0422 
0423     QMenu menu(this);
0424     menu.addSection(i18nc("@title:menu", "Tab Editor Options"));
0425 
0426     const int target_index = d->widgetIndexAt(e->pos());
0427     QAction* setIndex = menu.addAction(i18nc("@action:inmenu Move tab editor selector to this widget", "Start from here"));
0428     setIndex->setEnabled(target_index >= 0);
0429     QAction* resetIndex = menu.addAction(i18nc("@action:inmenu Set tab editor selector to first widget", "Restart"));
0430 
0431     menu.addSeparator();
0432     QAction* undoIndex = menu.addAction(i18nc("@action:inmenu Reset all changes", "Undo changes"));
0433     undoIndex->setEnabled(dlg_d->tabOrder() != dlg_d->m_currentOrder);
0434     QAction* defaultIndex = menu.addAction(i18nc("@action:inmenu Reset to application defaults", "Reset to default"));
0435     defaultIndex->setEnabled(dlg_d->tabOrder() != dlg_d->m_defaultOrder);
0436 
0437 #if 0
0438     menu.addSeparator();
0439     QAction *showDialog = menu.addAction(i18nc("@action:inmenu Open tab order dialog", "Tab Order List..."));
0440     showDialog->setEnabled(dlg_d->m_widgetList.count() > 1);
0441 #endif
0442 
0443     QAction* result = menu.exec(e->globalPos());
0444     if (result == resetIndex) {
0445         d->m_noWidgetsSelected = true;
0446         dlg_d->m_currentIndex = 0;
0447         update();
0448     } else if (result == setIndex) {
0449         d->m_noWidgetsSelected = true;
0450         dlg_d->m_currentIndex = target_index + 1;
0451         if (dlg_d->m_currentIndex >= dlg_d->m_widgetList.count()) {
0452             dlg_d->m_currentIndex = 0;
0453         }
0454         update();
0455     } else if (result == undoIndex) {
0456         d->m_noWidgetsSelected = true;
0457         dlg_d->m_currentIndex = 0;
0458         dlg_d->setTabOrder(dlg_d->m_currentOrder);
0459         update();
0460     } else if (result == defaultIndex) {
0461         d->m_noWidgetsSelected = true;
0462         dlg_d->m_currentIndex = 0;
0463         dlg_d->setTabOrder(dlg_d->m_defaultOrder);
0464         update();
0465 #if 0
0466     } else if (result == showDialog) {
0467 #endif
0468     }
0469 }
0470 
0471 void TabOrderEditor::keyPressEvent(QKeyEvent* event)
0472 {
0473     qDebug() << "Keypress event" << event->key();
0474     QWidget::keyPressEvent(event);
0475 }