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 }