File indexing completed on 2024-05-12 04:06:21

0001 /*
0002     SPDX-FileCopyrightText: 2009 Chani Armitage <chani@kde.org>
0003     SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "mouseinputbutton.h"
0009 #include "mouseinputbutton_p.h"
0010 
0011 #include <QApplication>
0012 #include <QHBoxLayout>
0013 #include <QStyleOptionButton>
0014 #include <QIcon>
0015 #include <KLocalizedString>
0016 
0017 static QIcon clearIcon()
0018 {
0019     if (QApplication::isLeftToRight())
0020         return QIcon::fromTheme( QStringLiteral( "edit-clear-locationbar-rtl" ));
0021     else
0022         return QIcon::fromTheme( QStringLiteral( "edit-clear-locationbar-ltr" ));
0023 }
0024 
0025 Palapeli::MouseInputButton::MouseInputButton(QWidget* parent)
0026     : QPushButton(parent)
0027     , m_iconLabel(new QLabel)
0028     , m_mainLabel(new QLabel)
0029     , m_clearButton(new Palapeli::FlatButton(clearIcon()))
0030     , m_mouseAllowed(true)
0031     , m_wheelAllowed(true)
0032     , m_noButtonAllowed(true)
0033     , m_requiresValidation(false)
0034 {
0035     qRegisterMetaType<Palapeli::Trigger>();
0036     connect(this, &QAbstractButton::clicked, this, &MouseInputButton::captureTrigger);
0037     connect(m_clearButton, &FlatButton::clicked, this, &MouseInputButton::clearTrigger);
0038     setCheckable(true);
0039     m_iconLabel->setPixmap(QIcon::fromTheme( QStringLiteral( "input-mouse" )).pixmap(22)); //TODO: respect global icon size configuration
0040     //setup child widgets
0041     m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0042     m_mainLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0043     m_mainLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
0044     m_clearButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0045     m_clearButton->setToolTip(i18n("Remove this trigger"));
0046     //setup layout
0047     QHBoxLayout* layout = new QHBoxLayout;
0048     setLayout(layout);
0049     layout->setContentsMargins(0, 0, 0, 0);
0050     layout->addWidget(m_iconLabel);
0051     layout->addWidget(m_mainLabel);
0052     layout->addWidget(m_clearButton);
0053     updateAppearance();
0054     //calculate margin for layout from QStyle::sizeFromContents
0055     QStyleOptionButton opt;
0056     QPushButton::initStyleOption(&opt);
0057     const QSize bareSizeHint = layout->sizeHint();
0058     const QSize fullSizeHint = style()->sizeFromContents(QStyle::CT_PushButton, &opt, bareSizeHint, this);
0059     const int marginX = (fullSizeHint.width() - bareSizeHint.width()) / 2;
0060     const int marginY = (fullSizeHint.height() - bareSizeHint.height()) / 2;
0061     layout->setContentsMargins(marginX, marginY, marginX, marginY);
0062 }
0063 
0064 QSize Palapeli::MouseInputButton::sizeHint() const
0065 {
0066     //The layout's size hint is the right one, not the one calculated by QPushButton::sizeHint.
0067     return QWidget::sizeHint();
0068 }
0069 
0070 bool Palapeli::MouseInputButton::isMouseAllowed() const
0071 {
0072     return m_mouseAllowed;
0073 }
0074 
0075 void Palapeli::MouseInputButton::setMouseAllowed(bool mouseAllowed)
0076 {
0077     m_mouseAllowed = mouseAllowed;
0078 }
0079 
0080 bool Palapeli::MouseInputButton::isWheelAllowed() const
0081 {
0082     return m_wheelAllowed;
0083 }
0084 
0085 void Palapeli::MouseInputButton::setWheelAllowed(bool wheelAllowed)
0086 {
0087     m_wheelAllowed = wheelAllowed;
0088 }
0089 
0090 bool Palapeli::MouseInputButton::isNoButtonAllowed() const
0091 {
0092     return m_noButtonAllowed;
0093 }
0094 
0095 void Palapeli::MouseInputButton::setNoButtonAllowed(bool noButtonAllowed)
0096 {
0097     m_noButtonAllowed = noButtonAllowed;
0098 }
0099 
0100 bool Palapeli::MouseInputButton::requiresValidation() const
0101 {
0102     return m_requiresValidation;
0103 }
0104 
0105 void Palapeli::MouseInputButton::setRequiresValidation(bool requiresValidation)
0106 {
0107     m_requiresValidation = requiresValidation;
0108 }
0109 
0110 Palapeli::Trigger Palapeli::MouseInputButton::trigger() const
0111 {
0112     return m_trigger;
0113 }
0114 
0115 void Palapeli::MouseInputButton::captureTrigger()
0116 {
0117     setChecked(true);
0118     m_clearButton->setVisible(false); //while capture is in progress
0119     m_mainLabel->setText(i18n("Input here..."));
0120     setToolTip(i18n("Hold down the modifier keys you want, then click a mouse button or scroll a mouse wheel here"));
0121     setFocus(Qt::MouseFocusReason);
0122 }
0123 
0124 void Palapeli::MouseInputButton::clearTrigger()
0125 {
0126     setTrigger(Palapeli::Trigger());
0127 }
0128 
0129 bool Palapeli::MouseInputButton::event(QEvent* event)
0130 {
0131 
0132     if (isChecked())
0133     {
0134         //got a trigger or cancel
0135         switch ((int) event->type())
0136         {
0137             case QEvent::Wheel: {
0138                 if (!m_wheelAllowed)
0139                     return false;
0140                 const QWheelEvent* wEvent = static_cast<QWheelEvent*>(event);
0141                 Palapeli::Trigger newTrigger;
0142                 newTrigger.setModifiers(wEvent->modifiers());
0143                 const QPoint angleDelta = wEvent->angleDelta();
0144                 const Qt::Orientation orientation = (qAbs(angleDelta.x()) > qAbs(angleDelta.y()) ? Qt::Horizontal : Qt::Vertical);
0145                 newTrigger.setWheelDirection(orientation);
0146                 setTrigger(newTrigger);
0147                 event->accept();
0148                 return true;
0149             }
0150             case QEvent::MouseButtonRelease: {
0151                 if (!m_mouseAllowed)
0152                     return false;
0153                 const QMouseEvent* mEvent = static_cast<QMouseEvent*>(event);
0154                 Palapeli::Trigger newTrigger;
0155                 newTrigger.setModifiers(mEvent->modifiers());
0156                 newTrigger.setButton(mEvent->button());
0157                 setTrigger(newTrigger);
0158                 event->accept();
0159                 return true;
0160             }
0161             case QEvent::MouseButtonPress:
0162                 event->accept();
0163                 return true;
0164             case QEvent::KeyPress: {
0165                 const QKeyEvent* kEvent = static_cast<QKeyEvent*>(event);
0166                 if (kEvent->key() == Qt::Key_Escape)
0167                 {
0168                     //cancel
0169                     setTrigger(m_trigger);
0170                     event->accept();
0171                     return true;
0172                 }
0173                 if (kEvent->key() == Qt::Key_Space && m_noButtonAllowed)
0174                 {
0175                     //create trigger with NoButton (TODO: make this functionality more user-visible)
0176                     Palapeli::Trigger newTrigger;
0177                     newTrigger.setModifiers(kEvent->modifiers());
0178                     newTrigger.setButton(Qt::NoButton);
0179                     setTrigger(newTrigger);
0180                     event->accept();
0181                     return true;
0182                 }
0183                 [[fallthrough]];
0184             }
0185             case QEvent::KeyRelease: {
0186                 const QKeyEvent* kEvent = static_cast<QKeyEvent*>(event);
0187                 showModifiers(kEvent->modifiers());
0188                 break;
0189             }
0190         }
0191     }
0192     bool ret = QPushButton::event(event);
0193     if (event->type() == QEvent::MouseButtonRelease)
0194     {
0195         const QMouseEvent* mEvent = static_cast<QMouseEvent*>(event);
0196         //fake a tooltip event
0197         //because otherwise they go away when you click and don't come back until you move the mouse
0198         QHelpEvent tip(QEvent::ToolTip, mEvent->pos(), mEvent->globalPosition().toPoint());
0199         QApplication::sendEvent(this, &tip);
0200     }
0201     return ret;
0202 }
0203 
0204 void Palapeli::MouseInputButton::setTrigger(const Palapeli::Trigger& trigger)
0205 {
0206     //NOTE: Invalid triggers need not be confirmed (esp. calls to clearTrigger()).
0207     if (m_requiresValidation && trigger.isValid() && m_trigger != trigger)
0208     {
0209         m_stagedTrigger = trigger;
0210         Q_EMIT triggerRequest(trigger);
0211     }
0212     else
0213         applyTrigger(trigger);
0214 }
0215 
0216 void Palapeli::MouseInputButton::confirmTrigger(const Palapeli::Trigger& trigger)
0217 {
0218     if (m_stagedTrigger == trigger)
0219         applyTrigger(m_stagedTrigger);
0220 }
0221 
0222 void Palapeli::MouseInputButton::updateAppearance()
0223 {
0224     //find caption
0225     static const QString noTriggerString = i18nc("This is used for describing that no mouse action has been assigned to this interaction plugin.", "None");
0226     QString text = m_trigger.toString();
0227     if (!m_trigger.isValid())
0228         text = text.arg(noTriggerString);
0229     //apply properties
0230     setChecked(false);
0231     setToolTip(i18n("Click to change how an action is triggered"));
0232     m_mainLabel->setText(text);
0233     m_clearButton->setVisible(m_trigger.isValid());
0234 }
0235 
0236 void Palapeli::MouseInputButton::applyTrigger(const Palapeli::Trigger& trigger)
0237 {
0238     const bool announceChange = m_trigger != trigger;
0239     //apply new trigger
0240     m_trigger = trigger;
0241     m_stagedTrigger = Palapeli::Trigger();
0242     updateAppearance();
0243     //announce change
0244     if (announceChange)
0245         Q_EMIT triggerChanged(trigger);
0246 }
0247 
0248 void Palapeli::MouseInputButton::showModifiers(Qt::KeyboardModifiers modifiers)
0249 {
0250     Palapeli::Trigger dummyTrigger;
0251     dummyTrigger.setModifiers(modifiers);
0252     m_mainLabel->setText(dummyTrigger.toString().arg(i18n("Input here...")));
0253 }
0254 
0255 
0256 //
0257 
0258 #include "moc_mouseinputbutton.cpp"
0259 #include "moc_mouseinputbutton_p.cpp"