File indexing completed on 2025-01-12 12:39:36
0001 /************************************************************************ 0002 * * 0003 * This file is part of Kooka, a scanning/OCR application using * 0004 * Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>. * 0005 * * 0006 * Copyright (C) 1999-2016 Klaas Freitag <freitag@suse.de> * 0007 * Jonathan Marten <jjm@keelhaul.me.uk> * 0008 * * 0009 * Kooka is free software; you can redistribute it and/or modify it * 0010 * under the terms of the GNU Library General Public License as * 0011 * published by the Free Software Foundation and appearing in the * 0012 * file COPYING included in the packaging of this file; either * 0013 * version 2 of the License, or (at your option) any later version. * 0014 * * 0015 * As a special exception, permission is given to link this program * 0016 * with any version of the KADMOS OCR/ICR engine (a product of * 0017 * reRecognition GmbH, Kreuzlingen), and distribute the resulting * 0018 * executable without including the source code for KADMOS in the * 0019 * source distribution. * 0020 * * 0021 * This program is distributed in the hope that it will be useful, * 0022 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0024 * GNU General Public License for more details. * 0025 * * 0026 * You should have received a copy of the GNU General Public * 0027 * License along with this program; see the file COPYING. If * 0028 * not, see <http://www.gnu.org/licenses/>. * 0029 * * 0030 ************************************************************************/ 0031 0032 #include "scanparams.h" 0033 0034 #include <qpushbutton.h> 0035 #include <qimage.h> 0036 #include <qtooltip.h> 0037 #include <qprogressdialog.h> 0038 #include <qcheckbox.h> 0039 #include <qgroupbox.h> 0040 #include <qbuttongroup.h> 0041 #include <qradiobutton.h> 0042 #include <qfileinfo.h> 0043 #include <qgridlayout.h> 0044 #include <qlabel.h> 0045 #include <qscrollarea.h> 0046 #include <qtabwidget.h> 0047 #include <qmimetype.h> 0048 #include <qmimedatabase.h> 0049 #include <qlayout.h> 0050 #include <qdesktopservices.h> 0051 #include <qcombobox.h> 0052 0053 #include <klocalizedstring.h> 0054 #include <kled.h> 0055 #include <kmessagebox.h> 0056 #include <kmessagewidget.h> 0057 0058 extern "C" 0059 { 0060 #include <sane/saneopts.h> 0061 } 0062 0063 #include "scanglobal.h" 0064 #include "scanparamspage.h" 0065 #include "gammadialog.h" 0066 #include "kgammatable.h" 0067 #include "kscancontrols.h" 0068 #include "scansizeselector.h" 0069 #include "kscanoption.h" 0070 #include "kscanoptset.h" 0071 #include "scanicons.h" 0072 #include "dialogbase.h" 0073 #include "libkookascan_logging.h" 0074 0075 // Debugging options 0076 #undef DEBUG_ADF 0077 0078 // SANE testing options 0079 #ifndef SANE_NAME_TEST_PICTURE 0080 #define SANE_NAME_TEST_PICTURE "test-picture" 0081 #endif 0082 #ifndef SANE_NAME_THREE_PASS 0083 #define SANE_NAME_THREE_PASS "three-pass" 0084 #endif 0085 #ifndef SANE_NAME_HAND_SCANNER 0086 #define SANE_NAME_HAND_SCANNER "hand-scanner" 0087 #endif 0088 #ifndef SANE_NAME_GRAYIFY 0089 #define SANE_NAME_GRAYIFY "grayify" 0090 #endif 0091 0092 0093 ScanParams::ScanParams(QWidget *parent) 0094 : QWidget(parent) 0095 { 0096 setObjectName("ScanParams"); 0097 0098 mSaneDevice = nullptr; 0099 mVirtualFile = nullptr; 0100 mGammaEditButt = nullptr; 0101 mResolutionBind = nullptr; 0102 mProgressDialog = nullptr; 0103 mSourceSelect = nullptr; 0104 mLed = nullptr; 0105 0106 mProblemMessage = nullptr; 0107 mNoScannerMessage = nullptr; 0108 } 0109 0110 ScanParams::~ScanParams() 0111 { 0112 delete mProgressDialog; 0113 } 0114 0115 bool ScanParams::connectDevice(KScanDevice *newScanDevice, bool galleryMode) 0116 { 0117 QGridLayout *lay = new QGridLayout(this); 0118 lay->setMargin(0); 0119 lay->setColumnStretch(0, 9); 0120 0121 if (newScanDevice == nullptr) { // no scanner device 0122 qCDebug(LIBKOOKASCAN_LOG) << "No scan device, gallery=" << galleryMode; 0123 mSaneDevice = nullptr; 0124 createNoScannerMsg(galleryMode); 0125 return (true); 0126 } 0127 0128 mSaneDevice = newScanDevice; 0129 mScanMode = ScanParams::NormalMode; 0130 #if 0 0131 // TOTO: port/update 0132 adf = ADF_OFF; 0133 #endif 0134 QLabel *lab = new QLabel(xi18nc("@info", "<title>Scanner Settings</title>"), this); 0135 lay->addWidget(lab, 0, 0, Qt::AlignLeft); 0136 0137 mLed = new KLed(this); 0138 mLed->setState(KLed::Off); 0139 mLed->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); 0140 lay->addWidget(mLed, 0, 1, Qt::AlignRight); 0141 0142 lab = new QLabel(mSaneDevice->scannerDescription(), this); 0143 lay->addWidget(lab, 1, 0, 1, 2, Qt::AlignLeft); 0144 0145 /* load the startup scanoptions */ 0146 0147 /* Now create Widgets for the important scan settings */ 0148 QWidget *sv = createScannerParams(); 0149 lay->addWidget(sv, 3, 0, 1, 2); 0150 lay->setRowStretch(3, 9); 0151 0152 // Load the startup options 0153 // TODO: check whether the saved scanner options apply to the current scanner? 0154 // They may be for a completely different one... 0155 // Or update KScanDevice and here to save/load the startup options 0156 // on a per-scanner basis. 0157 0158 qCDebug(LIBKOOKASCAN_LOG) << "looking for startup options"; 0159 KScanOptSet startupOptions(KScanOptSet::startupSetName()); 0160 if (startupOptions.loadConfig(mSaneDevice->scannerBackendName())) 0161 { 0162 qCDebug(LIBKOOKASCAN_LOG) << "loading startup options"; 0163 mSaneDevice->loadOptionSet(&startupOptions); 0164 } 0165 else qCDebug(LIBKOOKASCAN_LOG) << "no startup options to load"; 0166 0167 // Reload all options, to take account of inactive ones 0168 mSaneDevice->reloadAllOptions(); 0169 0170 // Send the current settings to the previewer 0171 initStartupArea(startupOptions.isEmpty()); // signal newCustomScanSize() 0172 slotNewScanMode(); // signal scanModeChanged() 0173 slotNewResolution(nullptr); // signal scanResolutionChanged 0174 0175 /* Create the Scan Buttons */ 0176 QPushButton *pb = new QPushButton(QIcon::fromTheme("preview"), i18n("Pre&view"), this); 0177 pb->setToolTip(i18n("Start a preview scan and show the preview image")); 0178 pb->setMinimumWidth(100); 0179 connect(pb, &QPushButton::clicked, this, &ScanParams::slotAcquirePreview); 0180 lay->addWidget(pb, 5, 0, Qt::AlignLeft); 0181 0182 pb = new QPushButton(QIcon::fromTheme("scan"), i18n("Star&t Scan"), this); 0183 pb->setToolTip(i18n("Start a scan and save the scanned image")); 0184 pb->setMinimumWidth(100); 0185 connect(pb, &QPushButton::clicked, this, &ScanParams::slotStartScan); 0186 lay->addWidget(pb, 5, 1, Qt::AlignRight); 0187 0188 /* Initialise the progress dialog */ 0189 mProgressDialog = new QProgressDialog(QString(), i18n("Stop"), 0, 100, nullptr); 0190 mProgressDialog->setModal(true); 0191 mProgressDialog->setAutoClose(true); 0192 mProgressDialog->setAutoReset(true); 0193 mProgressDialog->setWindowTitle(i18n("Scanning")); 0194 mProgressDialog->setMinimumDuration(100); 0195 // The next is necessary with Qt5, as otherwise the progress dialogue 0196 // appears to show itself after the default 'minimumDuration' (= 4 seconds), 0197 // even despite the previous and no 'value' being set. 0198 mProgressDialog->reset(); 0199 setScanDestination(KLocalizedString()); // reset destination display 0200 0201 connect(mProgressDialog, &QProgressDialog::canceled, mSaneDevice, &KScanDevice::slotStopScanning); 0202 connect(mSaneDevice, &KScanDevice::sigScanProgress, this, &ScanParams::slotScanProgress); 0203 0204 return (true); 0205 } 0206 0207 KLed *ScanParams::operationLED() const 0208 { 0209 return (mLed); 0210 } 0211 0212 ScanParamsPage *ScanParams::createTab(QTabWidget *tw, const QString &title, const char *name) 0213 { 0214 QScrollArea *scroll = new QScrollArea(this); 0215 scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 0216 scroll->setWidgetResizable(true); // stretch to width 0217 scroll->setFrameStyle(QFrame::NoFrame); 0218 0219 ScanParamsPage *frame = new ScanParamsPage(this, name); 0220 scroll->setWidget(frame); 0221 tw->addTab(scroll, title); 0222 0223 return (frame); 0224 } 0225 0226 QWidget *ScanParams::createScannerParams() 0227 { 0228 QTabWidget *tw = new QTabWidget(this); 0229 tw->setTabsClosable(false); 0230 tw->setTabPosition(QTabWidget::North); 0231 0232 ScanParamsPage *basicFrame = createTab(tw, i18n("&Basic"), "BasicFrame"); 0233 ScanParamsPage *otherFrame = createTab(tw, i18n("Other"), "OtherFrame"); 0234 ScanParamsPage *advancedFrame = createTab(tw, i18n("Advanced"), "AdvancedFrame"); 0235 0236 KScanOption *so; 0237 QLabel *l; 0238 QWidget *w; 0239 QLabel *u; 0240 ScanParamsPage *frame; 0241 0242 // Initial "Basic" options 0243 frame = basicFrame; 0244 0245 // Virtual/debug image file 0246 mVirtualFile = mSaneDevice->getGuiElement(SANE_NAME_FILE, frame); 0247 if (mVirtualFile != nullptr) { 0248 connect(mVirtualFile, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0249 0250 l = mVirtualFile->getLabel(frame, true); 0251 w = mVirtualFile->widget(); 0252 frame->addRow(l, w); 0253 0254 // Selection for either virtual scanner or SANE debug 0255 0256 QWidget *vbg = new QWidget(frame); 0257 QVBoxLayout *vbl = new QVBoxLayout(vbg); 0258 vbl->setMargin(0); 0259 0260 QRadioButton *rb1 = new QRadioButton(i18n("SANE Debug (from PNM image)"), vbg); 0261 rb1->setToolTip(xi18nc("@info:tooltip", "Operate in the same way that a real scanner does (including scan area, image processing etc.), but reading from the specified image file. See <link url=\"man:sane-pnm(5)\">sane-pnm(5)</link> for more information.")); 0262 vbl->addWidget(rb1); 0263 QRadioButton *rb2 = new QRadioButton(i18n("Virtual Scanner (any image format)"), vbg); 0264 rb2->setToolTip(xi18nc("@info:tooltip", "Do not perform any scanning or processing, but simply read the specified image file. This is for testing the image saving, etc.")); 0265 vbl->addWidget(rb2); 0266 0267 if (mScanMode == ScanParams::NormalMode) mScanMode = ScanParams::SaneDebugMode; 0268 rb1->setChecked(mScanMode == ScanParams::SaneDebugMode); 0269 rb2->setChecked(mScanMode == ScanParams::VirtualScannerMode); 0270 0271 // needed for new 'buttonClicked' signal: 0272 QButtonGroup *vbgGroup = new QButtonGroup(vbg); 0273 vbgGroup->addButton(rb1, 0); 0274 vbgGroup->addButton(rb2, 1); 0275 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 0276 connect(vbgGroup, &QButtonGroup::idClicked, this, &ScanParams::slotVirtScanModeSelect); 0277 #else 0278 connect(vbgGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &ScanParams::slotVirtScanModeSelect); 0279 #endif 0280 0281 l = new QLabel(i18n("Reading mode:"), frame); 0282 frame->addRow(l, vbg, nullptr, Qt::AlignTop); 0283 0284 // Separator line after these. Using a KScanGroup with a null text, 0285 // so that it looks the same as any real group separators following. 0286 frame->addGroup(new KScanGroup(frame, QString())); 0287 } 0288 0289 // Mode setting 0290 so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_MODE, frame); 0291 if (so != nullptr) { 0292 KScanCombo *cb = (KScanCombo *) so->widget(); 0293 0294 // Set icons for all possible scan modes which the combo box may contain. 0295 // Modes which the scanner does not support and hence which are not in 0296 // the combo box don't matter. See ScanIcons and KScanCombo::setIcon(). 0297 // 0298 // The combo has already been populated with the current list of SANE values 0299 // at the KScanOption initialisation (by KScanOption::updateList() calling 0300 // KScanOption::getList() which reads the list values from SANE). But 0301 // this may not work properly if the list of scan modes changes at runtime! 0302 const QList<QByteArray> scanModes = ScanIcons::self()->allModes(); 0303 for (const QByteArray &scanMode : scanModes) 0304 { 0305 cb->setIcon(ScanIcons::self()->icon(scanMode), scanMode.constData()); 0306 } 0307 0308 connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0309 connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode); 0310 0311 l = so->getLabel(frame, true); 0312 frame->addRow(l, cb); 0313 } 0314 0315 // Resolution setting. Try "X-Resolution" setting first, this is the 0316 // option we want if the resolutions are split up. If there is no such 0317 // option then try just "Resolution", this may not be the same as 0318 // "X-Resolution" even though this was the case in SANE<=1.0.19. 0319 so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_X_RESOLUTION, frame); 0320 if (so == nullptr) { 0321 so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_RESOLUTION, frame); 0322 } 0323 if (so != nullptr) { 0324 connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0325 // Connection that passes the resolution to the previewer 0326 connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution); 0327 0328 l = so->getLabel(frame, true); 0329 w = so->widget(); 0330 u = so->getUnit(frame); 0331 frame->addRow(l, w, u); 0332 0333 // Same X/Y resolution option (if present) 0334 mResolutionBind = mSaneDevice->getGuiElement(SANE_NAME_RESOLUTION_BIND, frame); 0335 if (mResolutionBind != nullptr) { 0336 connect(mResolutionBind, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0337 0338 l = so->getLabel(frame, true); 0339 w = so->widget(); 0340 frame->addRow(l, w); 0341 } 0342 0343 // Now the "Y-Resolution" setting, if there is a separate one 0344 so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_Y_RESOLUTION, frame); 0345 if (so!=nullptr) 0346 { 0347 // Connection that passes the resolution to the previewer 0348 connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution); 0349 0350 l = so->getLabel(frame, true); 0351 w = so->widget(); 0352 u = so->getUnit(frame); 0353 frame->addRow(l, w, u); 0354 } 0355 } else { 0356 qCWarning(LIBKOOKASCAN_LOG) << "No resolution option available!"; 0357 } 0358 0359 // Scan size setting 0360 mAreaSelect = new ScanSizeSelector(frame, mSaneDevice->getMaxScanSize()); 0361 connect(mAreaSelect, &ScanSizeSelector::sizeSelected, this, &ScanParams::slotScanSizeSelected); 0362 l = new QLabel("Scan &area:", frame); // make sure it gets an accel 0363 l->setBuddy(mAreaSelect->focusProxy()); 0364 frame->addRow(l, mAreaSelect, nullptr, Qt::AlignTop); 0365 0366 // Insert another beautification line 0367 frame->addGroup(new KScanGroup(frame, QString())); 0368 0369 // Scan destination 0370 const int originalRow = frame->currentRow(); // note what has been built so far 0371 createScanDestinationGUI(frame); // call out to the application 0372 if (frame->currentRow()>originalRow) // did that create anything? 0373 { // if so then add a separator 0374 frame->addGroup(new KScanGroup(frame, QString())); 0375 } 0376 0377 // Source selection 0378 mSourceSelect = mSaneDevice->getGuiElement(SANE_NAME_SCAN_SOURCE, frame); 0379 if (mSourceSelect != nullptr) { 0380 connect(mSourceSelect, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0381 0382 l = mSourceSelect->getLabel(frame, true); 0383 w = mSourceSelect->widget(); 0384 frame->addRow(l, w); 0385 0386 // TODO: enable the "Advanced" dialogue, because that 0387 // contains other ADF options. They are not implemented at the moment 0388 // but they may be some day... 0389 //QPushButton *pb = new QPushButton( i18n("Source && ADF Options..."), frame); 0390 //connect(pb, &QAbstractButton::clicked, this, &ScanParams::slotSourceSelect); 0391 //lay->addWidget(pb,row,2,1,-1,Qt::AlignRight); 0392 //++row; 0393 } 0394 0395 // SANE testing options, for the "test" device 0396 so = mSaneDevice->getGuiElement(SANE_NAME_TEST_PICTURE, frame); 0397 if (so!=nullptr) 0398 { 0399 connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0400 0401 l = so->getLabel(frame); 0402 w = so->widget(); 0403 frame->addRow(l, w); 0404 0405 // This control allows the image type presented to the application to be 0406 // tested. It does not actually affect the format of the scanned image, 0407 // so it will only affect the image type in Kooka's "Save Assistant" 0408 // dialogue if it is set to ask for a filename before the scan starts. 0409 // The setting is not persistent. 0410 QComboBox *fitCombo = new QComboBox(frame); 0411 fitCombo->addItem(i18n("Default (from scan settings)"), ScanImage::None); 0412 fitCombo->addItem(i18n("Black/white bitmap"), ScanImage::BlackWhite); 0413 fitCombo->addItem(i18n("Grey scale"), ScanImage::Greyscale); 0414 fitCombo->addItem(i18n("Low colour"), ScanImage::LowColour); 0415 fitCombo->addItem(i18n("High colour"), ScanImage::HighColour); 0416 fitCombo->setCurrentIndex(0); 0417 0418 connect(fitCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), 0419 this, [=](int index) 0420 { 0421 ScanImage::ImageType fmt = static_cast<ScanImage::ImageType>(fitCombo->currentData().toInt()); 0422 mSaneDevice->setTestFormat(fmt); 0423 }); 0424 0425 l = new QLabel(i18n("Force image format:"), frame); 0426 frame->addRow(l, fitCombo); 0427 } 0428 0429 // Now all of the other options which have not been accounted for yet. 0430 // Split them up into "Other" and "Advanced". 0431 const QList<QByteArray> opts = mSaneDevice->getAllOptions(); 0432 for (QList<QByteArray>::const_iterator it = opts.constBegin(); 0433 it != opts.constEnd(); ++it) { 0434 const QByteArray opt = (*it); 0435 0436 if (opt == SANE_NAME_SCAN_TL_X || // ignore these (scan area) 0437 opt == SANE_NAME_SCAN_TL_Y || 0438 opt == SANE_NAME_SCAN_BR_X || 0439 opt == SANE_NAME_SCAN_BR_Y) 0440 { 0441 continue; 0442 } 0443 0444 so = mSaneDevice->getExistingGuiElement(opt); // see if already created 0445 if (so != nullptr) { 0446 continue; // if so ignore, don't duplicate 0447 } 0448 0449 so = mSaneDevice->getGuiElement(opt, frame); 0450 if (so != nullptr) { 0451 //qCDebug(LIBKOOKASCAN_LOG) << "creating" << (so->isCommonOption() ? "OTHER" : "ADVANCED") << "option" << opt; 0452 connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); 0453 0454 if (so->isCommonOption()) { 0455 frame = otherFrame; 0456 } else { 0457 frame = advancedFrame; 0458 } 0459 0460 w = so->widget(); 0461 if (so->isGroup()) { 0462 frame->addGroup(w); 0463 } else { 0464 l = so->getLabel(frame); 0465 u = so->getUnit(frame); 0466 frame->addRow(l, w, u); 0467 } 0468 0469 // Some special things to do for particular options 0470 if (opt == SANE_NAME_BIT_DEPTH) 0471 { 0472 connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode); 0473 } 0474 else if (opt == SANE_NAME_CUSTOM_GAMMA) 0475 { 0476 mGammaEditButt = new QPushButton(i18n("Edit Gamma Table..."), this); 0477 mGammaEditButt->setIcon(QIcon::fromTheme("document-edit")); 0478 connect(mGammaEditButt, &QPushButton::clicked, this, &ScanParams::slotEditCustGamma); 0479 // Enabling/disabling the edit button is handled by 0480 // slotOptionChanged() calling setEditCustomGammaTableState() 0481 setEditCustomGammaTableState(); 0482 0483 frame->addRow(nullptr, mGammaEditButt, nullptr, Qt::AlignRight); 0484 } 0485 } 0486 } 0487 0488 basicFrame->lastRow(); // final stretch row 0489 if (!otherFrame->lastRow()) { 0490 tw->setTabEnabled(1, false); 0491 } 0492 if (!advancedFrame->lastRow()) { 0493 tw->setTabEnabled(2, false); 0494 } 0495 0496 return (tw); // top-level (tab) widget 0497 } 0498 0499 0500 void ScanParams::initStartupArea(bool dontRestore) 0501 { 0502 // TODO: restore area a user preference 0503 #ifdef RESTORE_AREA 0504 if (dontRestore) // no saved options available 0505 #endif 0506 { 0507 applyRect(QRect()); // set maximum scan area 0508 return; 0509 } 0510 // set scan area from saved 0511 KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X); 0512 KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y); 0513 KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X); 0514 KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y); 0515 0516 QRect rect; 0517 int val1, val2; 0518 tl_x->get(&val1); rect.setLeft(val1); // pass area to previewer 0519 br_x->get(&val2); rect.setWidth(val2 - val1); 0520 tl_y->get(&val1); rect.setTop(val1); 0521 br_y->get(&val2); rect.setHeight(val2 - val1); 0522 emit newCustomScanSize(rect); 0523 0524 mAreaSelect->selectSize(rect); // set selector to match 0525 } 0526 0527 void ScanParams::createNoScannerMsg(bool galleryMode) 0528 { 0529 QWidget *lab; 0530 if (galleryMode) { 0531 lab = messageScannerNotSelected(); 0532 } else { 0533 lab = messageScannerProblem(); 0534 } 0535 0536 QGridLayout *lay = dynamic_cast<QGridLayout *>(layout()); 0537 if (lay != nullptr) { 0538 lay->addWidget(lab, 0, 0, Qt::AlignTop); 0539 } 0540 } 0541 0542 QWidget *ScanParams::messageScannerNotSelected() 0543 { 0544 if (mNoScannerMessage==nullptr) 0545 { 0546 mNoScannerMessage = new KMessageWidget( 0547 xi18nc("@info", 0548 "<title>No scanner selected</title>" 0549 "<nl/><nl/>" 0550 "Select a scanner device to perform scanning.")); 0551 0552 mNoScannerMessage->setMessageType(KMessageWidget::Information); 0553 mNoScannerMessage->setIcon(QIcon::fromTheme("dialog-information")); 0554 mNoScannerMessage->setCloseButtonVisible(false); 0555 mNoScannerMessage->setWordWrap(true); 0556 } 0557 0558 return (mNoScannerMessage); 0559 } 0560 0561 0562 QWidget *ScanParams::messageScannerProblem() 0563 { 0564 if (mProblemMessage==nullptr) 0565 { 0566 mProblemMessage = new KMessageWidget( 0567 xi18nc("@info", 0568 "<title>No scanner found, or unable to access it</title>" 0569 "<nl/><nl/>" 0570 "There was a problem using the SANE (Scanner Access Now Easy) library to access " 0571 "the scanner device. There may be a problem with your SANE installation, or it " 0572 "may not be configured to support your scanner." 0573 "<nl/><nl/>" 0574 "Check that SANE is correctly installed and configured on your system, and " 0575 "also that the scanner device name and settings are correct." 0576 "<nl/><nl/>" 0577 "See the SANE project home page " 0578 "(<link url=\"http://www.sane-project.org\">www.sane-project.org</link>) " 0579 "for more information on SANE installation and setup.")); 0580 0581 mProblemMessage->setMessageType(KMessageWidget::Warning); 0582 mProblemMessage->setIcon(QIcon::fromTheme("dialog-warning")); 0583 mProblemMessage->setCloseButtonVisible(false); 0584 mProblemMessage->setWordWrap(true); 0585 connect(mProblemMessage, &KMessageWidget::linkActivated, 0586 [](const QString &link){ QDesktopServices::openUrl(QUrl(link)); }); 0587 } 0588 0589 return (mProblemMessage); 0590 } 0591 0592 0593 void ScanParams::slotSourceSelect() 0594 { 0595 #if 0 0596 // TODO: port/update 0597 AdfBehaviour adf = ADF_OFF; 0598 0599 if (mSourceSelect == nullptr) { 0600 return; // no source selection GUI 0601 } 0602 if (!mSourceSelect->isValid()) { 0603 return; // no option on scanner 0604 } 0605 0606 const QByteArray &currSource = mSourceSelect->get(); 0607 //qCDebug(LIBKOOKASCAN_LOG) << "Current source is" << currSource; 0608 0609 QList<QByteArray> sources = mSourceSelect->getList(); 0610 #ifdef DEBUG_ADF 0611 if (!sources.contains("Automatic Document Feeder")) { 0612 sources.append("Automatic Document Feeder"); 0613 } 0614 #endif 0615 0616 // TODO: the 'sources' list has exactly the same options as the 0617 // scan source combo (apart from the debugging hack above), so 0618 // what's the point of repeating it in this dialogue? 0619 ScanSourceDialog d(this, sources, adf); 0620 d.slotSetSource(currSource); 0621 0622 if (d.exec() != QDialog::Accepted) { 0623 return; 0624 } 0625 0626 QString sel_source = d.getText(); 0627 adf = d.getAdfBehave(); 0628 //qCDebug(LIBKOOKASCAN_LOG) << "new source" << sel_source << "ADF" << adf; 0629 0630 /* set the selected Document source, the behavior is stored in a membervar */ 0631 mSourceSelect->set(sel_source.toLatin1()); // TODO: FIX in ScanSourceDialog, then here 0632 mSourceSelect->apply(); 0633 mSourceSelect->reload(); 0634 mSourceSelect->redrawWidget(); 0635 #endif 0636 } 0637 0638 /* Slot which is called if the user switches in the gui between 0639 * the SANE-Debug-Mode and the qt image reading 0640 */ 0641 void ScanParams::slotVirtScanModeSelect(int but) 0642 { 0643 if (but == 0) mScanMode = ScanParams::SaneDebugMode; // SANE Debug 0644 else mScanMode = ScanParams::VirtualScannerMode; // Virtual Scanner 0645 const bool enable = (mScanMode == ScanParams::SaneDebugMode); 0646 0647 mSaneDevice->guiSetEnabled(SANE_NAME_HAND_SCANNER, enable); 0648 mSaneDevice->guiSetEnabled(SANE_NAME_THREE_PASS, enable); 0649 mSaneDevice->guiSetEnabled(SANE_NAME_GRAYIFY, enable); 0650 mSaneDevice->guiSetEnabled(SANE_NAME_CONTRAST, enable); 0651 mSaneDevice->guiSetEnabled(SANE_NAME_BRIGHTNESS, enable); 0652 mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_RESOLUTION, enable); 0653 mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_X_RESOLUTION, enable); 0654 mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_Y_RESOLUTION, enable); 0655 0656 mAreaSelect->setEnabled(enable); 0657 } 0658 0659 KScanDevice::Status ScanParams::prepareScan(QString *vfp) 0660 { 0661 qCDebug(LIBKOOKASCAN_LOG) << "scan mode=" << mScanMode; 0662 0663 setScanDestination(KLocalizedString()); // reset progress display 0664 0665 // Check compatibility of scan settings 0666 int format; 0667 int depth; 0668 mSaneDevice->getCurrentFormat(&format, &depth); 0669 if (depth == 1 && format != SANE_FRAME_GRAY) { // 1-bit scan depth in colour? 0670 KMessageBox::error(this, i18n("1-bit depth scan cannot be done in color")); 0671 return (KScanDevice::ParamError); 0672 } else if (depth == 16) { 0673 KMessageBox::error(this, i18n("16-bit depth scans are not supported")); 0674 return (KScanDevice::ParamError); 0675 } 0676 0677 QString virtfile; 0678 if (mScanMode == ScanParams::SaneDebugMode || mScanMode == ScanParams::VirtualScannerMode) { 0679 if (mVirtualFile != nullptr) { 0680 virtfile = QFile::decodeName(mVirtualFile->get()); 0681 } 0682 if (virtfile.isEmpty()) { 0683 KMessageBox::error(this, i18n("A file must be entered for testing or virtual scanning")); 0684 return (KScanDevice::ParamError); 0685 } 0686 0687 QFileInfo fi(virtfile); 0688 if (!fi.exists()) { 0689 KMessageBox::error(this, xi18nc("@info", "The testing or virtual file <filename>%1</filename><nl/>was not found or is not readable.", virtfile)); 0690 return (KScanDevice::ParamError); 0691 } 0692 0693 if (mScanMode == ScanParams::SaneDebugMode) { 0694 QMimeDatabase db; 0695 QMimeType mime = db.mimeTypeForFile(virtfile); 0696 if (!(mime.inherits("image/x-portable-bitmap") || 0697 mime.inherits("image/x-portable-greymap") || 0698 mime.inherits("image/x-portable-pixmap"))) { 0699 KMessageBox::error(this, xi18nc("@info", "SANE Debug can only read PNM files.<nl/>" 0700 "The specified file is type <icode>%1</icode>.", mime.name())); 0701 return (KScanDevice::ParamError); 0702 } 0703 } 0704 } 0705 0706 if (vfp != nullptr) { 0707 *vfp = virtfile; 0708 } 0709 return (KScanDevice::Ok); 0710 } 0711 0712 0713 void ScanParams::setScanDestination(const KLocalizedString &dest) 0714 { 0715 KLocalizedString labelText; 0716 if (dest.isEmpty()) labelText = kxi18n("Scan in progress"); 0717 else labelText = kxi18n("Scan in progress<nl/><nl/>%1").subs(dest); 0718 mProgressDialog->setLabelText(labelText.toString(Kuit::RichText)); 0719 } 0720 0721 0722 void ScanParams::slotScanProgress(int value) 0723 { 0724 mProgressDialog->setValue(value); 0725 } 0726 0727 0728 /* Slot called to start acquiring a preview */ 0729 void ScanParams::slotAcquirePreview() 0730 { 0731 0732 // TODO: should be able to preview in Virtual Scanner mode, it just means 0733 // that the preview image will be the same size as the final image (which 0734 // doesn't matter). 0735 0736 if (mScanMode == ScanParams::VirtualScannerMode) { 0737 KMessageBox::error(this, i18n("Cannot preview in Virtual Scanner mode")); 0738 return; 0739 } 0740 0741 QString virtfile; 0742 KScanDevice::Status stat = prepareScan(&virtfile); 0743 if (stat != KScanDevice::Ok) { 0744 return; 0745 } 0746 0747 //qCDebug(LIBKOOKASCAN_LOG) << "scan mode=" << mScanMode << "virtfile" << virtfile; 0748 0749 KScanOption *greyPreview = mSaneDevice->getExistingGuiElement(SANE_NAME_GRAY_PREVIEW); 0750 int gp = 0; 0751 if (greyPreview != nullptr) { 0752 greyPreview->get(&gp); 0753 } 0754 0755 setMaximalScanSize(); // always preview at maximal size 0756 mAreaSelect->selectCustomSize(QRect()); // reset selector to reflect that 0757 0758 stat = mSaneDevice->acquirePreview(gp); 0759 if (stat != KScanDevice::Ok) { 0760 qCWarning(LIBKOOKASCAN_LOG) << "Error, preview status " << stat; 0761 } 0762 } 0763 0764 /* Slot called to start scanning */ 0765 void ScanParams::slotStartScan() 0766 { 0767 QString virtfile; 0768 KScanDevice::Status stat = prepareScan(&virtfile); 0769 if (stat != KScanDevice::Ok) { 0770 return; 0771 } 0772 0773 //qCDebug(LIBKOOKASCAN_LOG) << "scan mode=" << mScanMode << "virtfile" << virtfile; 0774 0775 if (mScanMode != ScanParams::VirtualScannerMode) { // acquire via SANE 0776 #if 0 0777 // TODO: port/update 0778 if (adf == ADF_OFF) { 0779 #endif 0780 qCDebug(LIBKOOKASCAN_LOG) << "Start to acquire image"; 0781 stat = mSaneDevice->acquireScan(); 0782 #if 0 0783 } else { 0784 //qCDebug(LIBKOOKASCAN_LOG) << "ADF Scan not yet implemented :-/"; 0785 // stat = performADFScan(); 0786 } 0787 #endif 0788 } else { // acquire via Qt-IMGIO 0789 qCDebug(LIBKOOKASCAN_LOG) << "Acquiring from virtual file"; 0790 stat = mSaneDevice->acquireScan(virtfile); 0791 } 0792 0793 if (stat != KScanDevice::Ok) { 0794 qCDebug(LIBKOOKASCAN_LOG) << "Error, scan status " << stat; 0795 } 0796 } 0797 0798 bool ScanParams::getGammaTableFrom(const QByteArray &opt, KGammaTable *gt) 0799 { 0800 KScanOption *so = mSaneDevice->getOption(opt, false); 0801 if (so == nullptr) { 0802 return (false); 0803 } 0804 0805 if (!so->get(gt)) { 0806 return (false); 0807 } 0808 0809 qCDebug(LIBKOOKASCAN_LOG) << "got from" << so->getName() << "=" << gt->toString(); 0810 return (true); 0811 } 0812 0813 bool ScanParams::setGammaTableTo(const QByteArray &opt, const KGammaTable *gt) 0814 { 0815 KScanOption *so = mSaneDevice->getOption(opt, false); 0816 if (so == nullptr) { 0817 return (false); 0818 } 0819 0820 qCDebug(LIBKOOKASCAN_LOG) << "set" << so->getName() << "=" << gt->toString(); 0821 so->set(gt); 0822 return (so->apply()); 0823 } 0824 0825 void ScanParams::slotEditCustGamma() 0826 { 0827 KGammaTable gt; // start with default values 0828 0829 // Get the current gamma table from either the combined gamma table 0830 // option, or any one of the colour channel gamma tables. 0831 if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR, >)) { 0832 if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_R, >)) { 0833 if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_G, >)) { 0834 if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_B, >)) { 0835 // Should not happen... but if it does, no problem 0836 // the dialogue will just use the default values 0837 // for an empty gamma table. 0838 qCWarning(LIBKOOKASCAN_LOG) << "no existing/active gamma option!"; 0839 } 0840 } 0841 } 0842 } 0843 qCDebug(LIBKOOKASCAN_LOG) << "initial gamma table" << gt.toString(); 0844 0845 // TODO; Maybe need to have a separate GUI widget for each gamma table, a 0846 // little preview of the gamma curve (a GammaWidget) with an edit button. 0847 // Will avoid the special case for the SANE_NAME_CUSTOM_GAMMA button followed 0848 // by the edit button, and will allow separate editing of the R/G/B gamma 0849 // tables if the scanner has them. 0850 0851 GammaDialog gdiag(>, this); 0852 connect(&gdiag, &GammaDialog::gammaToApply, this, &ScanParams::slotApplyGamma); 0853 gdiag.exec(); 0854 } 0855 0856 void ScanParams::slotApplyGamma(const KGammaTable *gt) 0857 { 0858 if (gt == nullptr) { 0859 return; 0860 } 0861 0862 bool reload = false; 0863 0864 KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA); 0865 if (so != nullptr) { // do we have a gamma switch? 0866 int cg = 0; 0867 if (so->get(&cg) && !cg) { // yes, see if already on 0868 // if not, set it on now 0869 qCDebug(LIBKOOKASCAN_LOG) << "Setting gamma switch on"; 0870 so->set(true); 0871 reload = so->apply(); 0872 } 0873 } 0874 0875 qCDebug(LIBKOOKASCAN_LOG) << "Applying gamma table" << gt->toString(); 0876 reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR, gt); 0877 reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_R, gt); 0878 reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_G, gt); 0879 reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_B, gt); 0880 0881 if (reload) { 0882 mSaneDevice->reloadAllOptions(); // reload is needed 0883 } 0884 } 0885 0886 // The user has changed an option. Apply that; as a result of doing so, 0887 // it may be necessary to reload every other scanner option apart from 0888 // this one. 0889 0890 void ScanParams::slotOptionChanged(KScanOption *so) 0891 { 0892 if (so == nullptr || mSaneDevice == nullptr) { 0893 return; 0894 } 0895 mSaneDevice->applyOption(so); 0896 0897 // Update the gamma edit button state, if the option exists 0898 setEditCustomGammaTableState(); 0899 } 0900 0901 // Enable editing of the gamma tables if any one of the gamma tables 0902 // exists and is currently active. 0903 0904 void ScanParams::setEditCustomGammaTableState() 0905 { 0906 if (mSaneDevice == nullptr) { 0907 return; 0908 } 0909 if (mGammaEditButt == nullptr) { 0910 return; 0911 } 0912 0913 bool butState = false; 0914 0915 KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA, false); 0916 if (so != nullptr) { 0917 butState = so->isActive(); 0918 } 0919 0920 if (!butState) { 0921 KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR, false); 0922 if (so != nullptr) { 0923 butState = so->isActive(); 0924 } 0925 } 0926 0927 if (!butState) { 0928 KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_R, false); 0929 if (so != nullptr) { 0930 butState = so->isActive(); 0931 } 0932 } 0933 0934 if (!butState) { 0935 KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_G, false); 0936 if (so != nullptr) { 0937 butState = so->isActive(); 0938 } 0939 } 0940 0941 if (!butState) { 0942 KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_B, false); 0943 if (so != nullptr) { 0944 butState = so->isActive(); 0945 } 0946 } 0947 0948 qCDebug(LIBKOOKASCAN_LOG) << "Set state to" << butState; 0949 mGammaEditButt->setEnabled(butState); 0950 } 0951 0952 // This assumes that the SANE unit for the scan area is millimetres. 0953 // All scanners out there appear to do this. 0954 void ScanParams::applyRect(const QRect &rect) 0955 { 0956 qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect; 0957 0958 KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X); 0959 KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y); 0960 KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X); 0961 KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y); 0962 0963 double min1, max1; 0964 double min2, max2; 0965 0966 if (!rect.isValid()) { // set full scan area 0967 tl_x->getRange(&min1, &max1); tl_x->set(min1); 0968 br_x->getRange(&min1, &max1); br_x->set(max1); 0969 tl_y->getRange(&min2, &max2); tl_y->set(min2); 0970 br_y->getRange(&min2, &max2); br_y->set(max2); 0971 0972 qCDebug(LIBKOOKASCAN_LOG) << "setting full area" << min1 << min2 << "-" << max1 << max2; 0973 } else { 0974 double tlx = rect.left(); 0975 double tly = rect.top(); 0976 double brx = rect.right(); 0977 double bry = rect.bottom(); 0978 0979 tl_x->getRange(&min1, &max1); 0980 if (tlx < min1) { 0981 brx += (min1 - tlx); 0982 tlx = min1; 0983 } 0984 tl_x->set(tlx); br_x->set(brx); 0985 0986 tl_y->getRange(&min2, &max2); 0987 if (tly < min2) { 0988 bry += (min2 - tly); 0989 tly = min2; 0990 } 0991 tl_y->set(tly); br_y->set(bry); 0992 0993 qCDebug(LIBKOOKASCAN_LOG) << "setting area" << tlx << tly << "-" << brx << bry; 0994 } 0995 0996 tl_x->apply(); 0997 tl_y->apply(); 0998 br_x->apply(); 0999 br_y->apply(); 1000 } 1001 1002 // The previewer is telling us that the user has drawn or auto-selected a 1003 // new preview area (specified in millimetres). 1004 void ScanParams::slotNewPreviewRect(const QRect &rect) 1005 { 1006 qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect; 1007 applyRect(rect); 1008 mAreaSelect->selectSize(rect); 1009 } 1010 1011 // A new preset scan size or orientation chosen by the user 1012 void ScanParams::slotScanSizeSelected(const QRect &rect) 1013 { 1014 qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect << "full=" << !rect.isValid(); 1015 applyRect(rect); 1016 emit newCustomScanSize(rect); 1017 } 1018 1019 /* 1020 * sets the scan area to the default, which is the whole area. 1021 */ 1022 void ScanParams::setMaximalScanSize() 1023 { 1024 qCDebug(LIBKOOKASCAN_LOG) << "Setting to default"; 1025 slotScanSizeSelected(QRect()); 1026 } 1027 1028 void ScanParams::slotNewResolution(KScanOption *opt) 1029 { 1030 KScanOption *opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_X_RESOLUTION); 1031 if (opt_x == nullptr) { 1032 opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_RESOLUTION); 1033 } 1034 KScanOption *opt_y = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_Y_RESOLUTION); 1035 1036 int x_res = 0; // get the X resolution 1037 if (opt_x != nullptr && opt_x->isValid()) { 1038 opt_x->get(&x_res); 1039 } 1040 1041 int y_res = 0; // get the Y resolution 1042 if (opt_y != nullptr && opt_y->isValid()) { 1043 opt_y->get(&y_res); 1044 } 1045 1046 qCDebug(LIBKOOKASCAN_LOG) << "X/Y resolution" << x_res << y_res; 1047 1048 if (y_res == 0) { 1049 y_res = x_res; // use X res if Y unavailable 1050 } 1051 if (x_res == 0) { 1052 x_res = y_res; // unlikely, but orthogonal 1053 } 1054 1055 if (x_res == 0 && y_res == 0) { 1056 qCWarning(LIBKOOKASCAN_LOG) << "resolution not available!"; 1057 } else { 1058 emit scanResolutionChanged(x_res, y_res); 1059 } 1060 } 1061 1062 void ScanParams::slotNewScanMode() 1063 { 1064 int format = SANE_FRAME_RGB; 1065 int depth = 8; 1066 mSaneDevice->getCurrentFormat(&format, &depth); 1067 1068 int strips = (format == SANE_FRAME_GRAY ? 1 : 3); 1069 qCDebug(LIBKOOKASCAN_LOG) << "format" << format << "depth" << depth << "-> strips" << strips; 1070 1071 if (strips == 1 && depth == 1) { // bitmap scan 1072 emit scanModeChanged(0); // 8 pixels per byte 1073 } else { 1074 // bytes per pixel 1075 emit scanModeChanged(strips * (depth == 16 ? 2 : 1)); 1076 } 1077 } 1078 1079 KScanDevice::Status ScanParams::performADFScan() 1080 { 1081 KScanDevice::Status stat = KScanDevice::Ok; 1082 bool scan_on = true; 1083 1084 /* The scan source should be set to ADF by the SourceSelect-Dialog */ 1085 1086 while (scan_on) { 1087 scan_on = false; 1088 } 1089 return (stat); 1090 }