File indexing completed on 2024-04-28 04:32:22
0001 /* 0002 * SPDX-FileCopyrightText: 2007-2010 Kare Sars <kare dot sars at iki dot fi> 0003 * SPDX-FileCopyrightText: 2009 Matthias Nagl <matthias at nagl dot info> 0004 * SPDX-FileCopyrightText: 2009 Grzegorz Kurtyka <grzegorz dot kurtyka at gmail dot com> 0005 * SPDX-FileCopyrightText: 2007-2008 Gilles Caulier <caulier dot gilles at gmail dot com> 0006 * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks 0007 * SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net> 0008 * 0009 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0010 */ 0011 0012 #include "ksanewidget.h" 0013 #include "ksanewidget_p.h" 0014 0015 #include <unistd.h> 0016 0017 #include <QApplication> 0018 #include <QList> 0019 #include <QLabel> 0020 #include <QSplitter> 0021 #include <QPointer> 0022 #include <QIcon> 0023 #include <QShortcut> 0024 0025 #include <KPasswordDialog> 0026 #ifdef HAVE_KF5WALLET 0027 #include <KWallet> 0028 #endif 0029 0030 #include <KLocalizedString> 0031 0032 #include "ksanedevicedialog.h" 0033 #include <ksane_debug.h> 0034 0035 namespace KSaneIface 0036 { 0037 0038 KSaneWidget::KSaneWidget(QWidget *parent) 0039 : QWidget(parent), d(new KSaneWidgetPrivate(this)) 0040 { 0041 d->m_ksaneCoreInterface = new KSaneCore::Interface(); 0042 0043 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::scannedImageReady, d, &KSaneWidgetPrivate::imageReady); 0044 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::scanFinished, d, &KSaneWidgetPrivate::scanDone); 0045 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::userMessage, d, &KSaneWidgetPrivate::alertUser); 0046 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::scanProgress, d, &KSaneWidgetPrivate::updateProgress); 0047 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::batchModeCountDown, d, &KSaneWidgetPrivate::updateCountDown); 0048 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::availableDevices, d, &KSaneWidgetPrivate::signalDevListUpdate); 0049 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::buttonPressed, this, &KSaneWidget::buttonPressed); 0050 0051 // Create the static UI 0052 // create the preview 0053 d->m_previewViewer = new KSaneViewer(&(d->m_previewImg), this); 0054 connect(d->m_previewViewer, &KSaneViewer::newSelection, 0055 d, &KSaneWidgetPrivate::handleSelection); 0056 0057 d->m_warmingUp = new QLabel; 0058 d->m_warmingUp->setText(i18n("Waiting for the scan to start.")); 0059 d->m_warmingUp->setAlignment(Qt::AlignCenter); 0060 d->m_warmingUp->hide(); 0061 0062 d->m_countDown = new QLabel; 0063 d->m_countDown->setAlignment(Qt::AlignCenter); 0064 d->m_countDown->hide(); 0065 0066 d->m_progressBar = new QProgressBar; 0067 d->m_progressBar->setMaximum(100); 0068 0069 d->m_cancelBtn = new QPushButton; 0070 d->m_cancelBtn->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); 0071 d->m_cancelBtn->setToolTip(i18n("Cancel current scan operation")); 0072 connect(d->m_cancelBtn, &QPushButton::clicked, this, &KSaneWidget::cancelScan); 0073 0074 d->m_activityFrame = new QWidget; 0075 QHBoxLayout *progress_lay = new QHBoxLayout(d->m_activityFrame); 0076 progress_lay->setContentsMargins(0, 0, 0, 0); 0077 progress_lay->addWidget(d->m_progressBar, 100); 0078 progress_lay->addWidget(d->m_warmingUp, 100); 0079 progress_lay->addWidget(d->m_countDown, 100); 0080 progress_lay->addWidget(d->m_cancelBtn, 0); 0081 d->m_activityFrame->hide(); 0082 0083 d->m_zInBtn = new QToolButton(this); 0084 d->m_zInBtn->setAutoRaise(true); 0085 d->m_zInBtn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); 0086 d->m_zInBtn->setToolTip(i18n("Zoom In")); 0087 connect(d->m_zInBtn, &QToolButton::clicked, d->m_previewViewer, &KSaneViewer::zoomIn); 0088 0089 d->m_zOutBtn = new QToolButton(this); 0090 d->m_zOutBtn->setAutoRaise(true); 0091 d->m_zOutBtn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); 0092 d->m_zOutBtn->setToolTip(i18n("Zoom Out")); 0093 connect(d->m_zOutBtn, &QToolButton::clicked, d->m_previewViewer, &KSaneViewer::zoomOut); 0094 0095 d->m_zSelBtn = new QToolButton(this); 0096 d->m_zSelBtn->setAutoRaise(true); 0097 d->m_zSelBtn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best"))); 0098 d->m_zSelBtn->setToolTip(i18n("Zoom to Selection")); 0099 connect(d->m_zSelBtn, &QToolButton::clicked, d->m_previewViewer, &KSaneViewer::zoomSel); 0100 0101 d->m_zFitBtn = new QToolButton(this); 0102 d->m_zFitBtn->setAutoRaise(true); 0103 d->m_zFitBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); 0104 d->m_zFitBtn->setToolTip(i18n("Zoom to Fit")); 0105 connect(d->m_zFitBtn, &QToolButton::clicked, d->m_previewViewer, &KSaneViewer::zoom2Fit); 0106 0107 d->m_clearSelBtn = new QToolButton(this); 0108 d->m_clearSelBtn->setAutoRaise(true); 0109 d->m_clearSelBtn->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear"))); 0110 d->m_clearSelBtn->setToolTip(i18n("Clear Selections")); 0111 connect(d->m_clearSelBtn, &QToolButton::clicked, d->m_previewViewer, &KSaneViewer::clearSelections); 0112 0113 QShortcut *prevShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+P")), this); 0114 connect(prevShortcut, &QShortcut::activated, d, &KSaneWidgetPrivate::startPreviewScan); 0115 0116 QShortcut *scanShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+S")), this); 0117 connect(scanShortcut, &QShortcut::activated, d, &KSaneWidgetPrivate::startFinalScan); 0118 0119 d->m_prevBtn = new QPushButton(this); 0120 d->m_prevBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); 0121 d->m_prevBtn->setToolTip(i18n("Scan Preview Image (%1)", prevShortcut->key().toString())); 0122 d->m_prevBtn->setText(i18nc("Preview button text", "Preview")); 0123 connect(d->m_prevBtn, &QToolButton::clicked, d, &KSaneWidgetPrivate::startPreviewScan); 0124 0125 d->m_scanBtn = new QPushButton(this); 0126 d->m_scanBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); 0127 d->m_scanBtn->setToolTip(i18n("Scan Final Image (%1)", scanShortcut->key().toString())); 0128 d->m_scanBtn->setText(i18nc("Final scan button text", "Scan")); 0129 d->m_scanBtn->setFocus(Qt::OtherFocusReason); 0130 setFocusProxy(d->m_scanBtn); 0131 connect(d->m_scanBtn, &QToolButton::clicked, d, &KSaneWidgetPrivate::startFinalScan); 0132 0133 d->m_btnFrame = new QWidget; 0134 QHBoxLayout *btn_lay = new QHBoxLayout(d->m_btnFrame); 0135 btn_lay->setContentsMargins(0, 0, 0, 0); 0136 btn_lay->addWidget(d->m_zInBtn); 0137 btn_lay->addWidget(d->m_zOutBtn); 0138 btn_lay->addWidget(d->m_zSelBtn); 0139 btn_lay->addWidget(d->m_zFitBtn); 0140 btn_lay->addWidget(d->m_clearSelBtn); 0141 btn_lay->addStretch(100); 0142 btn_lay->addWidget(d->m_prevBtn); 0143 btn_lay->addWidget(d->m_scanBtn); 0144 0145 // calculate the height of the waiting/scanning/buttons frames to avoid jumpiness. 0146 int minHeight = d->m_btnFrame->sizeHint().height(); 0147 if (d->m_activityFrame->sizeHint().height() > minHeight) { 0148 minHeight = d->m_activityFrame->sizeHint().height(); 0149 } 0150 d->m_btnFrame->setMinimumHeight(minHeight); 0151 d->m_activityFrame->setMinimumHeight(minHeight); 0152 d->m_warmingUp->setMinimumHeight(minHeight); 0153 d->m_countDown->setMinimumHeight(minHeight); 0154 0155 d->m_previewFrame = new QWidget; 0156 QVBoxLayout *preview_layout = new QVBoxLayout(d->m_previewFrame); 0157 preview_layout->setContentsMargins(0, 0, 0, 0); 0158 preview_layout->addWidget(d->m_previewViewer, 100); 0159 preview_layout->addWidget(d->m_activityFrame, 0); 0160 preview_layout->addWidget(d->m_btnFrame, 0); 0161 0162 // Create Options Widget 0163 d->m_optsTabWidget = new QTabWidget(); 0164 0165 // Add the basic options tab 0166 d->m_basicScrollA = new QScrollArea(); 0167 d->m_basicScrollA->setWidgetResizable(true); 0168 d->m_basicScrollA->setFrameShape(QFrame::NoFrame); 0169 d->m_optsTabWidget->addTab(d->m_basicScrollA, i18n("Basic Options")); 0170 0171 // Add the advanced options tab 0172 d->m_advancedScrollA = new QScrollArea(); 0173 d->m_advancedScrollA->setWidgetResizable(true); 0174 d->m_advancedScrollA->setFrameShape(QFrame::NoFrame); 0175 d->m_optsTabWidget->addTab(d->m_advancedScrollA, i18n("Advanced Options")); 0176 0177 // Add the other options tab 0178 d->m_otherScrollA = new QScrollArea; 0179 d->m_otherScrollA->setWidgetResizable(true); 0180 d->m_otherScrollA->setFrameShape(QFrame::NoFrame); 0181 d->m_optsTabWidget->addTab(d->m_otherScrollA, i18n("Scanner Specific Options")); 0182 0183 d->m_splitter = new QSplitter(this); 0184 d->m_splitter->addWidget(d->m_optsTabWidget); 0185 d->m_splitter->setStretchFactor(0, 0); 0186 d->m_splitter->addWidget(d->m_previewFrame); 0187 d->m_splitter->setStretchFactor(1, 100); 0188 0189 d->m_optionsCollapser = new SplitterCollapser(d->m_splitter, d->m_optsTabWidget); 0190 0191 QHBoxLayout *base_layout = new QHBoxLayout(this); 0192 base_layout->addWidget(d->m_splitter); 0193 base_layout->setContentsMargins(0, 0, 0, 0); 0194 0195 // disable the interface in case no device is opened. 0196 d->m_optsTabWidget->setDisabled(true); 0197 d->m_previewViewer->setDisabled(true); 0198 d->m_btnFrame->setDisabled(true); 0199 0200 } 0201 0202 KSaneWidget::~KSaneWidget() 0203 { 0204 delete d->m_ksaneCoreInterface; 0205 delete d; 0206 } 0207 0208 QString KSaneWidget::deviceName() const 0209 { 0210 return d->m_ksaneCoreInterface->deviceName(); 0211 } 0212 0213 QString KSaneWidget::deviceVendor() const 0214 { 0215 return d->m_ksaneCoreInterface->deviceVendor(); 0216 } 0217 0218 QString KSaneWidget::deviceModel() const 0219 { 0220 return d->m_ksaneCoreInterface->deviceModel(); 0221 } 0222 0223 QString KSaneWidget::selectDevice(QWidget *parent) 0224 { 0225 QString selected_name; 0226 QPointer<KSaneDeviceDialog> sel = new KSaneDeviceDialog(parent); 0227 connect(d->m_ksaneCoreInterface, &KSaneCore::Interface::availableDevices, sel, &KSaneDeviceDialog::updateDevicesList); 0228 connect(sel, &KSaneDeviceDialog::requestReloadList, d->m_ksaneCoreInterface, &KSaneCore::Interface::reloadDevicesList); 0229 0230 d->m_ksaneCoreInterface->reloadDevicesList(); 0231 0232 if (sel->exec() == QDialog::Accepted) { 0233 selected_name = sel->getSelectedName(); 0234 } 0235 0236 delete sel; 0237 return selected_name; 0238 } 0239 0240 bool KSaneWidget::openDevice(const QString &deviceName) 0241 { 0242 KPasswordDialog *dlg; 0243 #ifdef HAVE_KF5WALLET 0244 KWallet::Wallet *saneWallet; 0245 #endif 0246 QString myFolderName = QStringLiteral("ksane"); 0247 QMap<QString, QString> wallet_entry; 0248 0249 KSaneCore::Interface::OpenStatus status = d->m_ksaneCoreInterface->openDevice(deviceName); 0250 if (status == KSaneCore::Interface::OpeningFailed) { 0251 return false; 0252 } 0253 0254 bool password_dialog_ok = true; 0255 0256 // prepare wallet for authentication and create password dialog 0257 if (status == KSaneCore::Interface::OpeningDenied) { 0258 #ifdef HAVE_KF5WALLET 0259 saneWallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), winId()); 0260 0261 if (saneWallet) { 0262 dlg = new KPasswordDialog(this, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword); 0263 if (!saneWallet->hasFolder(myFolderName)) { 0264 saneWallet->createFolder(myFolderName); 0265 } 0266 saneWallet->setFolder(myFolderName); 0267 saneWallet->readMap(deviceName, wallet_entry); 0268 if (!wallet_entry.empty() || true) { 0269 dlg->setUsername(wallet_entry[QStringLiteral("username")]); 0270 dlg->setPassword(wallet_entry[QStringLiteral("password")]); 0271 dlg->setKeepPassword(true); 0272 } 0273 } else 0274 #endif 0275 { 0276 dlg = new KPasswordDialog(this, KPasswordDialog::ShowUsernameLine); 0277 } 0278 dlg->setPrompt(i18n("Authentication required for resource: %1", deviceName)); 0279 0280 } 0281 0282 // sane_open failed due to insufficient authorization 0283 // retry opening device with user provided data assisted with kwallet records 0284 while (status == KSaneCore::Interface::OpeningDenied) { 0285 0286 password_dialog_ok = dlg->exec(); 0287 if (!password_dialog_ok) { 0288 delete dlg; 0289 return false; //the user canceled 0290 } 0291 0292 // add/update the device user-name and password for authentication 0293 status = d->m_ksaneCoreInterface->openRestrictedDevice(deviceName, dlg->username(), dlg->password()); 0294 0295 #ifdef HAVE_KF5WALLET 0296 // store password in wallet on successful authentication 0297 if (dlg->keepPassword() && status != KSaneCore::Interface::OpeningDenied) { 0298 QMap<QString, QString> entry; 0299 entry[QStringLiteral("username")] = dlg->username(); 0300 entry[QStringLiteral("password")] = dlg->password(); 0301 if (saneWallet) { 0302 saneWallet->writeMap(deviceName, entry); 0303 } 0304 } 0305 #endif 0306 } 0307 0308 // Create the options interface 0309 d->createOptInterface(); 0310 0311 // Enable the interface 0312 d->m_optsTabWidget->setDisabled(false); 0313 d->m_previewViewer->setDisabled(false); 0314 d->m_btnFrame->setDisabled(false); 0315 0316 // estimate the preview size and create an empty image 0317 // this is done so that you can select scan area without 0318 // having to scan a preview. 0319 d->updatePreviewSize(); 0320 QTimer::singleShot(1000, d->m_previewViewer, &KSaneViewer::zoom2Fit); 0321 return true; 0322 } 0323 0324 bool KSaneWidget::closeDevice() 0325 { 0326 bool result = d->m_ksaneCoreInterface->closeDevice(); 0327 if (!result) { 0328 return false; 0329 } 0330 d->clearDeviceOptions(); 0331 // disable the interface until a new device is opened. 0332 d->m_optsTabWidget->setDisabled(true); 0333 d->m_previewViewer->setDisabled(true); 0334 d->m_btnFrame->setDisabled(true); 0335 0336 return true; 0337 } 0338 0339 void KSaneWidget::startScan() 0340 { 0341 if (d->m_btnFrame->isEnabled()) { 0342 d->m_cancelMultiScan = false; 0343 d->startFinalScan(); 0344 } else { 0345 // if the button frame is disabled, there is no open device to scan from 0346 Q_EMIT scanDone(KSaneWidget::ErrorGeneral, QString()); 0347 } 0348 } 0349 0350 void KSaneWidget::startPreviewScan() 0351 { 0352 if (d->m_btnFrame->isEnabled()) { 0353 d->m_cancelMultiScan = false; 0354 d->startPreviewScan(); 0355 } else { 0356 // if the button frame is disabled, there is no open device to scan from 0357 Q_EMIT scanDone(KSaneWidget::ErrorGeneral, QString()); 0358 } 0359 } 0360 0361 void KSaneWidget::cancelScan() 0362 { 0363 d->m_cancelMultiScan = true; 0364 d->m_ksaneCoreInterface->stopScan(); 0365 } 0366 0367 void KSaneWidget::setPreviewResolution(float dpi) 0368 { 0369 d->m_previewDPI = dpi; 0370 } 0371 0372 void KSaneWidget::getOptionValues(QMap <QString, QString> &opts) 0373 { 0374 opts.clear(); 0375 opts = d->m_ksaneCoreInterface->getOptionsMap(); 0376 } 0377 0378 bool KSaneWidget::getOptionValue(const QString &option, QString &value) 0379 { 0380 const auto optionsMap = d->m_ksaneCoreInterface->getOptionsMap(); 0381 auto it = optionsMap.constBegin(); 0382 while (it != optionsMap.constEnd()) { 0383 if(it.key() == option) { 0384 value = it.value(); 0385 return !value.isEmpty(); 0386 } 0387 it++; 0388 } 0389 return false; 0390 } 0391 0392 int KSaneWidget::setOptionValues(const QMap <QString, QString> &options) 0393 { 0394 int ret = 0; 0395 0396 ret = d->m_ksaneCoreInterface->setOptionsMap(options); 0397 0398 if ((d->m_splitGamChB) && 0399 (d->m_optGamR) && 0400 (d->m_optGamG) && 0401 (d->m_optGamB)) { 0402 // check if the current gamma values are identical. if they are identical, 0403 // uncheck the "Separate color intensity tables" checkbox 0404 QVariant redGamma = d->m_optGamR->value(); 0405 QVariant greenGamma = d->m_optGamG->value(); 0406 QVariant blueGamma = d->m_optGamB->value(); 0407 0408 if ((redGamma == greenGamma) && (greenGamma == blueGamma)) { 0409 d->m_splitGamChB->setChecked(false); 0410 // set the values to the common gamma widget 0411 d->m_commonGamma->setValues(redGamma); 0412 } else { 0413 d->m_splitGamChB->setChecked(true); 0414 } 0415 } 0416 return ret; 0417 } 0418 0419 bool KSaneWidget::setOptionValue(const QString &option, const QString &value) 0420 { 0421 if (d->m_scanOngoing) { 0422 return false; 0423 } 0424 0425 const auto optionsList = d->m_ksaneCoreInterface->getOptionsList(); 0426 for (auto &writeOption : optionsList) { 0427 if (writeOption->name() == option) { 0428 if (writeOption->setValue(value)) { 0429 if ((d->m_splitGamChB) && 0430 (d->m_optGamR) && 0431 (d->m_optGamG) && 0432 (d->m_optGamB) && 0433 ((writeOption == d->m_optGamR) || 0434 (writeOption == d->m_optGamG) || 0435 (writeOption == d->m_optGamB))) { 0436 // check if the current gamma values are identical. if they are identical, 0437 // uncheck the "Separate color intensity tables" checkbox 0438 QVariant redGamma = d->m_optGamR->value(); 0439 QVariant greenGamma = d->m_optGamG->value(); 0440 QVariant blueGamma = d->m_optGamB->value(); 0441 if ((redGamma == greenGamma) && (greenGamma == blueGamma)) { 0442 d->m_splitGamChB->setChecked(false); 0443 // set the values to the common gamma widget 0444 d->m_commonGamma->setValues(redGamma); 0445 } else { 0446 d->m_splitGamChB->setChecked(true); 0447 } 0448 } 0449 return true; 0450 } 0451 } 0452 } 0453 return false; 0454 } 0455 0456 void KSaneWidget::enableAutoSelect(bool enable) 0457 { 0458 d->m_autoSelect = enable; 0459 } 0460 0461 float KSaneWidget::scanAreaWidth() 0462 { 0463 float result = 0.0; 0464 if (d->m_optBrX) { 0465 if (d->m_optBrX->valueUnit() == KSaneCore::Option::UnitPixel) { 0466 result = d->m_optBrX->maximumValue().toFloat(); 0467 float dpi = 0; 0468 if (d->m_optRes) { 0469 dpi = d->m_optRes->value().toFloat(); 0470 } 0471 if (dpi < 1) { 0472 qCDebug(KSANE_LOG) << "Broken DPI value"; 0473 dpi = 1.0; 0474 } 0475 result = result / dpi / 25.4; 0476 } else if (d->m_optBrX->valueUnit() == KSaneCore::Option::UnitMilliMeter) { 0477 result = d->m_optBrX->maximumValue().toFloat(); 0478 } 0479 } 0480 return result; 0481 } 0482 0483 float KSaneWidget::scanAreaHeight() 0484 { 0485 float result = 0.0; 0486 if (d->m_optBrY) { 0487 if (d->m_optBrY->valueUnit() == KSaneCore::Option::UnitPixel) { 0488 result = d->m_optBrY->maximumValue().toFloat(); 0489 float dpi = 0; 0490 if (d->m_optRes) { 0491 dpi = d->m_optRes->value().toFloat(); 0492 } 0493 if (dpi < 1) { 0494 qCDebug(KSANE_LOG) << "Broken DPI value"; 0495 dpi = 1.0; 0496 } 0497 result = result / dpi / 25.4; 0498 } else if (d->m_optBrY->valueUnit() == KSaneCore::Option::UnitMilliMeter) { 0499 result = d->m_optBrY->maximumValue().toFloat(); 0500 } 0501 } 0502 return result; 0503 } 0504 0505 void KSaneWidget::setSelection(QPointF topLeft, QPointF bottomRight) 0506 { 0507 if (!d->m_optBrX || !d->m_optBrY || !d->m_optTlX || !d->m_optTlY) { 0508 return; 0509 } 0510 if (topLeft.x() < 0.0 || topLeft.y() < 0.0 || bottomRight.x() < 0.0 || bottomRight.y() < 0.0) { 0511 d->m_previewViewer->clearActiveSelection(); 0512 return; 0513 } 0514 0515 float tlxRatio = d->scanAreaToRatioX(topLeft.x()); 0516 float tlyRatio = d->scanAreaToRatioY(topLeft.y()); 0517 float brxRatio = d->scanAreaToRatioX(bottomRight.x()); 0518 float bryRatio = d->scanAreaToRatioX(bottomRight.y()); 0519 0520 d->m_previewViewer->setSelection(tlxRatio, tlyRatio, brxRatio, bryRatio); 0521 } 0522 0523 } // NameSpace KSaneIface 0524 0525 #include "moc_ksanewidget.cpp"