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 }