File indexing completed on 2024-05-12 17:07:11

0001 /*
0002     This file is part of the KDE Control Center Module for Joysticks
0003 
0004     SPDX-FileCopyrightText: 2003, 2012 Martin Koller <kollix@aon.at>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "joywidget.h"
0009 #include "caldialog.h"
0010 #include "joydevice.h"
0011 #include "poswidget.h"
0012 
0013 #include <QApplication>
0014 #include <QCheckBox>
0015 #include <QFontMetrics>
0016 #include <QHBoxLayout>
0017 #include <QHeaderView>
0018 #include <QLabel>
0019 #include <QPushButton>
0020 #include <QStyle>
0021 #include <QTableWidget>
0022 #include <QTimer>
0023 #include <QVBoxLayout>
0024 
0025 #include <KComboBox>
0026 #include <KLocalizedString>
0027 #include <KMessageBox>
0028 #include <KMessageWidget>
0029 #include <KUrlCompletion>
0030 
0031 #include <stdio.h>
0032 
0033 class TableWidget : public QTableWidget
0034 {
0035 public:
0036     TableWidget(int row, int col)
0037         : QTableWidget(row, col)
0038     {
0039     }
0040 
0041     QSize sizeHint() const override
0042     {
0043         return QSize(150, 100); // return a smaller size than the Qt default(256, 192)
0044     }
0045 };
0046 
0047 JoyWidget::JoyWidget(QWidget *parent)
0048     : QWidget(parent)
0049     , idle(nullptr)
0050     , joydev(nullptr)
0051 {
0052     QVBoxLayout *mainVbox = new QVBoxLayout(this);
0053     mainVbox->setContentsMargins(0, 0, 0, 0);
0054 
0055     // create area to show an icon + message if no joystick was detected
0056     {
0057         messageBox = new KMessageWidget(this);
0058         messageBox->setMessageType(KMessageWidget::Information);
0059         messageBox->setCloseButtonVisible(false);
0060         messageBox->hide();
0061         messageBox->setWordWrap(true);
0062 
0063         mainVbox->addWidget(messageBox);
0064     }
0065 
0066     QHBoxLayout *devHbox = new QHBoxLayout;
0067     devHbox->addWidget(new QLabel(i18n("Device:")));
0068     devHbox->addWidget(device = new KComboBox(true));
0069 
0070     device->setInsertPolicy(QComboBox::NoInsert);
0071     KUrlCompletion *kc = new KUrlCompletion(KUrlCompletion::FileCompletion);
0072     device->setCompletionObject(kc);
0073     device->setAutoDeleteCompletionObject(true);
0074     connect(device, SIGNAL(activated(QString)), this, SLOT(deviceChanged(QString)));
0075     connect(device, SIGNAL(returnPressed(QString)), this, SLOT(deviceChanged(QString)));
0076     devHbox->setStretchFactor(device, 3);
0077 
0078     QHBoxLayout *hbox = new QHBoxLayout;
0079 
0080     mainVbox->addLayout(devHbox);
0081     mainVbox->addLayout(hbox);
0082 
0083     QVBoxLayout *vboxLeft = new QVBoxLayout;
0084     vboxLeft->addWidget(new QLabel(i18nc("Cue for deflection of the stick", "Position:")));
0085     vboxLeft->addWidget(xyPos = new PosWidget);
0086 
0087     vboxLeft->addWidget(trace = new QCheckBox(i18n("Show trace")));
0088     connect(trace, &QAbstractButton::toggled, this, &JoyWidget::traceChanged);
0089 
0090     QVBoxLayout *vboxMid = new QVBoxLayout;
0091 
0092     QVBoxLayout *vboxRight = new QVBoxLayout;
0093 
0094     // calculate the column width we need
0095     QFontMetrics fm(font());
0096     int colWidth = qMax(fm.horizontalAdvance(i18n("PRESSED")), fm.horizontalAdvance(QStringLiteral("-32767"))) + 10; // -32767 largest string
0097 
0098     vboxMid->addWidget(new QLabel(i18n("Buttons:")));
0099     buttonTbl = new TableWidget(0, 1);
0100     buttonTbl->setSelectionMode(QAbstractItemView::NoSelection);
0101     buttonTbl->setEditTriggers(QAbstractItemView::NoEditTriggers);
0102     buttonTbl->setHorizontalHeaderLabels(QStringList(i18n("State")));
0103     buttonTbl->setSortingEnabled(false);
0104     buttonTbl->horizontalHeader()->setSectionsClickable(false);
0105     buttonTbl->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
0106     buttonTbl->horizontalHeader()->resizeSection(0, colWidth);
0107     buttonTbl->verticalHeader()->setSectionsClickable(false);
0108     vboxMid->addWidget(buttonTbl);
0109 
0110     vboxRight->addWidget(new QLabel(i18n("Axes:")));
0111     axesTbl = new TableWidget(0, 1);
0112     axesTbl->setSelectionMode(QAbstractItemView::NoSelection);
0113     axesTbl->setEditTriggers(QAbstractItemView::NoEditTriggers);
0114     axesTbl->setHorizontalHeaderLabels(QStringList(i18n("Value")));
0115     axesTbl->setSortingEnabled(false);
0116     axesTbl->horizontalHeader()->setSectionsClickable(false);
0117     axesTbl->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
0118     axesTbl->horizontalHeader()->resizeSection(0, colWidth);
0119     axesTbl->verticalHeader()->setSectionsClickable(false);
0120     vboxRight->addWidget(axesTbl);
0121 
0122     hbox->addLayout(vboxLeft);
0123     hbox->addLayout(vboxMid);
0124     hbox->addLayout(vboxRight);
0125 
0126     // calibrate button
0127     calibrate = new QPushButton(i18n("Calibrate"));
0128     connect(calibrate, &QAbstractButton::clicked, this, &JoyWidget::calibrateDevice);
0129     calibrate->setEnabled(false);
0130 
0131     vboxLeft->addStretch();
0132     vboxLeft->addWidget(calibrate);
0133 
0134     // set up a timer for idle processing of joystick events
0135     idle = new QTimer(this);
0136     connect(idle, &QTimer::timeout, this, &JoyWidget::checkDevice);
0137 
0138     // check which devicefiles we have
0139     init();
0140 }
0141 
0142 JoyWidget::~JoyWidget()
0143 {
0144     delete joydev;
0145 }
0146 
0147 void JoyWidget::init()
0148 {
0149     // check which devicefiles we have
0150     int i;
0151     bool first = true;
0152     char dev[30];
0153 
0154     device->clear();
0155     buttonTbl->setRowCount(0);
0156     axesTbl->setRowCount(0);
0157 
0158     for (i = 0; i < 5; i++) // check the first 5 devices
0159     {
0160         sprintf(dev, "/dev/js%d", i); // first look in /dev
0161         JoyDevice *joy = new JoyDevice(dev);
0162 
0163         if (joy->open() != JoyDevice::SUCCESS) {
0164             delete joy;
0165             sprintf(dev, "/dev/input/js%d", i); // then look in /dev/input
0166             joy = new JoyDevice(dev);
0167 
0168             if (joy->open() != JoyDevice::SUCCESS) {
0169                 delete joy;
0170                 continue; // try next number
0171             }
0172         }
0173 
0174         // we found one
0175 
0176         device->addItem(QStringLiteral("%1 (%2)").arg(joy->text()).arg(joy->device()));
0177 
0178         // display values for first device
0179         if (first) {
0180             showDeviceProps(joy); // this sets the joy object into this->joydev
0181             first = false;
0182         } else
0183             delete joy;
0184     }
0185 
0186     /* KDE 4: Remove this check(and i18n) when all KCM wrappers properly test modules */
0187     if (device->count() == 0) {
0188         messageBox->show();
0189         messageBox->setText(QStringLiteral("<qt>%1</qt>")
0190                                 .arg(i18n("No joystick device automatically found on this computer.<br />"
0191                                           "Checks were done in /dev/js[0-4] and /dev/input/js[0-4]<br />"
0192                                           "If you know that there is one attached, please enter the correct device file.")));
0193     }
0194 }
0195 
0196 void JoyWidget::traceChanged(bool state)
0197 {
0198     xyPos->showTrace(state);
0199 }
0200 
0201 void JoyWidget::restoreCurrDev()
0202 {
0203     if (!joydev) // no device open
0204     {
0205         device->setEditText(QString());
0206         calibrate->setEnabled(false);
0207     } else {
0208         // try to find the current open device in the combobox list
0209         int index = device->findText(joydev->device(), Qt::MatchContains);
0210 
0211         if (index == -1) // the current open device is one the user entered (not in the list)
0212             device->setEditText(joydev->device());
0213         else
0214             device->setEditText(device->itemText(index));
0215     }
0216 }
0217 
0218 void JoyWidget::deviceChanged(const QString &dev)
0219 {
0220     // find "/dev" in given string
0221     int start, stop;
0222     QString devName;
0223 
0224     if ((start = dev.indexOf(QLatin1String("/dev"))) == -1) {
0225         KMessageBox::error(this,
0226                            i18n("The given device name is invalid (does not contain /dev).\n"
0227                                 "Please select a device from the list or\n"
0228                                 "enter a device file, like /dev/js0."),
0229                            i18n("Unknown Device"));
0230 
0231         restoreCurrDev();
0232         return;
0233     }
0234 
0235     if ((stop = dev.indexOf(QLatin1Char(')'), start)) != -1) // seems to be text selected from our list
0236         devName = dev.mid(start, stop - start);
0237     else
0238         devName = dev.mid(start);
0239 
0240     if (joydev && (devName == joydev->device()))
0241         return; // user selected the current device; ignore it
0242 
0243     JoyDevice *joy = new JoyDevice(devName);
0244     JoyDevice::ErrorCode ret = joy->open();
0245 
0246     if (ret != JoyDevice::SUCCESS) {
0247         KMessageBox::error(this, joy->errText(ret), i18n("Device Error"));
0248 
0249         delete joy;
0250         restoreCurrDev();
0251         return;
0252     }
0253 
0254     showDeviceProps(joy);
0255 }
0256 
0257 void JoyWidget::showDeviceProps(JoyDevice *joy)
0258 {
0259     joydev = joy;
0260 
0261     buttonTbl->setRowCount(joydev->numButtons());
0262 
0263     axesTbl->setRowCount(joydev->numAxes());
0264     if (joydev->numAxes() >= 2) {
0265         axesTbl->setVerticalHeaderItem(0, new QTableWidgetItem(i18n("1(x)")));
0266         axesTbl->setVerticalHeaderItem(1, new QTableWidgetItem(i18n("2(y)")));
0267     }
0268 
0269     calibrate->setEnabled(true);
0270     idle->start(0);
0271 
0272     // make both tables use the same space for header; this looks nicer
0273     // TODO: Don't know how to do this in Qt4; the following does no longer work
0274     // Probably by setting a sizeHint for every single header item ?
0275     /*
0276     buttonTbl->verticalHeader()->setFixedWidth(qMax(buttonTbl->verticalHeader()->width(),
0277                                                       axesTbl->verticalHeader()->width()));
0278     axesTbl->verticalHeader()->setFixedWidth(buttonTbl->verticalHeader()->width());
0279     */
0280 }
0281 
0282 void JoyWidget::checkDevice()
0283 {
0284     if (!joydev)
0285         return; // no open device yet
0286 
0287     JoyDevice::EventType type;
0288     int number, value;
0289 
0290     // sleep-wait for the first event
0291     if (!joydev->getEvent(type, number, value, true))
0292         return;
0293 
0294     // process all pending events
0295     do {
0296         if (type == JoyDevice::BUTTON) {
0297             if (!buttonTbl->item(number, 0))
0298                 buttonTbl->setItem(number, 0, new QTableWidgetItem());
0299 
0300             if (value == 0) // button release
0301                 buttonTbl->item(number, 0)->setText(QStringLiteral("-"));
0302             else
0303                 buttonTbl->item(number, 0)->setText(i18n("PRESSED"));
0304         }
0305 
0306         if (type == JoyDevice::AXIS) {
0307             if (number == 0) // x-axis
0308                 xyPos->changeX(value);
0309 
0310             if (number == 1) // y-axis
0311                 xyPos->changeY(value);
0312 
0313             if (!axesTbl->item(number, 0))
0314                 axesTbl->setItem(number, 0, new QTableWidgetItem());
0315 
0316             axesTbl->item(number, 0)->setText(QStringLiteral("%1").arg(int(value)));
0317         }
0318     } while (joydev->getEvent(type, number, value, false)); // return immediately when no events are left
0319 }
0320 
0321 void JoyWidget::calibrateDevice()
0322 {
0323     if (!joydev)
0324         return; // just to be save
0325 
0326     JoyDevice::ErrorCode ret = joydev->initCalibration();
0327 
0328     if (ret != JoyDevice::SUCCESS) {
0329         KMessageBox::error(this, joydev->errText(ret), i18n("Communication Error"));
0330         return;
0331     }
0332 
0333     if (KMessageBox::messageBox(this,
0334                                 KMessageBox::Information,
0335                                 i18n("<qt>Calibration is about to check the precision.<br /><br />"
0336                                      "<b>Please move all axes to their center position and then "
0337                                      "do not touch the joystick anymore.</b><br /><br />"
0338                                      "Click OK to start the calibration.</qt>"),
0339                                 i18n("Calibration"),
0340                                 KStandardGuiItem::ok(),
0341                                 KStandardGuiItem::cancel())
0342         != KMessageBox::Ok)
0343         return;
0344 
0345     idle->stop(); // stop the joystick event getting; this must be done inside the calibrate dialog
0346 
0347     CalDialog dlg(this, joydev);
0348     dlg.calibrate();
0349 
0350     // user canceled somewhere during calibration, therefore the device is in a bad state
0351     if (dlg.result() == QDialog::Rejected)
0352         joydev->restoreCorr();
0353 
0354     idle->start(0); // continue with event getting
0355 }
0356 
0357 void JoyWidget::resetCalibration()
0358 {
0359     if (!joydev)
0360         return; // just to be save
0361 
0362     JoyDevice::ErrorCode ret = joydev->restoreCorr();
0363 
0364     if (ret != JoyDevice::SUCCESS) {
0365         KMessageBox::error(this, joydev->errText(ret), i18n("Communication Error"));
0366     } else {
0367         KMessageBox::information(this, i18n("Restored all calibration values for joystick device %1.", joydev->device()), i18n("Calibration Success"));
0368     }
0369 }