diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dc6396..6f061a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,106 +1,111 @@ ########################################################################## ## ## ## This CMake file is part of Kooka, a KDE scanning/OCR application. ## ## ## ## This file may be distributed and/or modified under the terms of ## ## the GNU General Public License version 2, as published by the ## ## Free Software Foundation and appearing in the file COPYING ## ## included in the packaging of this file. ## ## ## ## Author: Jonathan Marten ## ## ## ########################################################################## cmake_minimum_required(VERSION 2.8.12) project(kooka5) set(VERSION "0.90") message(STATUS "Configuring for Kooka/libkookascan version ${VERSION}") cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.10.0") set(ECM_MIN_VERSION "1.2.0") # ECM setup (Extra Cmake Modules) find_package(ECM ${ECM_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(FeatureSummary) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMPackageConfigHelpers) include(CheckFunctionExists) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(GenerateExportHeader) include(ECMInstallIcons) # Required Qt5 components to build this package find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Widgets) -# REMOVE IT -remove_definitions(-DQT_NO_CAST_FROM_ASCII) -#add_definitions(-DQT_RESTRICTED_CAST_FROM_ASCII) -remove_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS) - -add_definitions(-DQT_NO_URL_CAST_FROM_STRING) +# Rigourousness +add_definitions("-DQT_USE_FAST_CONCATENATION") +add_definitions("-DQT_USE_FAST_OPERATOR_PLUS") +add_definitions("-DQT_NO_CAST_FROM_BYTEARRAY") +add_definitions("-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT") +add_definitions("-DQT_NO_CAST_TO_ASCII") +add_definitions("-DQT_NO_URL_CAST_FROM_STRING") + +# Permissiveness +remove_definitions("-DQT_NO_CAST_FROM_ASCII") +remove_definitions("-DQT_NO_SIGNALS_SLOTS_KEYWORDS") # Support for SANE, here because library and sanedump both need it if (SANECONFIG_BIN) set(SANECONFIG_PROG ${SANECONFIG_BIN}) message(STATUS "Specified sane-config(1), ${SANECONFIG_PROG}") else (SANECONFIG_BIN) find_program(SANECONFIG_PROG NAMES sane-config) message(STATUS "Found sane-config(1), ${SANECONFIG_PROG}") endif (SANECONFIG_BIN) if (SANECONFIG_PROG) set(HAVE_SANE true) else (SANECONFIG_PROG) message(SEND_ERROR "libkookascan needs SANE (http://www.sane-project.org) - specify location of sane-config(1) with SANECONFIG_BIN") endif (SANECONFIG_PROG) if (HAVE_SANE) execute_process(COMMAND ${SANECONFIG_PROG} --version OUTPUT_VARIABLE SANE_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Found SANE, version ${SANE_VERSION}") execute_process(COMMAND ${SANECONFIG_PROG} --cflags OUTPUT_VARIABLE SANE_INCLUDES OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "SANE includes: ${SANE_INCLUDES}") execute_process(COMMAND ${SANECONFIG_PROG} --libs OUTPUT_VARIABLE SANE_LIBRARIES OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "SANE libraries: ${SANE_LIBRARIES}") endif (HAVE_SANE) ############### Now, we add the Kooka components ############### add_subdirectory(libdialogutil) add_subdirectory(libkookascan) add_subdirectory(libfiletree) add_subdirectory(kooka) add_subdirectory(doc) add_subdirectory(tools EXCLUDE_FROM_ALL) ############### VCS revision number in vcsversion.h ############### add_custom_target(vcsversion ALL COMMENT "Checking VCS version" VERBATIM COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/vcsversion.sh ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${VERSION} ) # ########### documentation ############### # # if (HAVE_APIDOX) # add_custom_target(apidox # COMMENT "Generating API documentation in ${CMAKE_CURRENT_BINARY_DIR}..." # VERBATIM # COMMAND sh -c "${KDELIBS_SOURCE_DIR}/doc/api/doxygen.sh --no-modulename --recurse --doxdatadir=${KDELIBS_SOURCE_DIR}/doc/common ${CMAKE_CURRENT_SOURCE_DIR}; echo 'API documentation at file://${CMAKE_CURRENT_BINARY_DIR}/apidocs/index.html';") # endif (HAVE_APIDOX) ############### Configuration information ############### feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kooka/formatdialog.cpp b/kooka/formatdialog.cpp index 14469b4..263cd9b 100644 --- a/kooka/formatdialog.cpp +++ b/kooka/formatdialog.cpp @@ -1,619 +1,619 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2008-2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "formatdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imageformat.h" #include "kookasettings.h" struct FormatInfo { const char *mime; const char *helpString; ImageMetaInfo::ImageTypes recForTypes; ImageMetaInfo::ImageTypes okForTypes; }; static struct FormatInfo formats[] = { { "image/bmp", // BMP I18N_NOOP( "Bitmap Picture is a widely used format for images under MS Windows. \ It is suitable for color, grayscale and line art images.\

This format is widely supported but is not recommended, use an open format \ instead."), 0, 0 }, { "image/x-portable-bitmap", // PBM I18N_NOOP( "Portable Bitmap, as used by Netpbm, is an uncompressed format for line art \ (bitmap) images. Only 1 bit per pixel depth is supported."), ImageMetaInfo::BlackWhite, ImageMetaInfo::BlackWhite }, { "image/x-portable-graymap", // PGM I18N_NOOP( "Portable Greymap, as used by Netpbm, is an uncompressed format for grayscale \ images. Only 8 bit per pixel depth is supported."), ImageMetaInfo::Greyscale, ImageMetaInfo::Greyscale }, { "image/x-portable-pixmap", // PPM I18N_NOOP( "Portable Pixmap, as used by Netpbm, is an uncompressed format for full color \ images. Only 24 bit per pixel RGB is supported."), ImageMetaInfo::LowColour | ImageMetaInfo::HighColour, ImageMetaInfo::LowColour | ImageMetaInfo::HighColour }, { "image/x-pcx", // PCX I18N_NOOP( "PCX is a lossless compressed format which is often supported by PC imaging \ applications, although it is rather old and unsophisticated. It is suitable for \ color and grayscale images.\

This format is not recommended, use an open format instead."), 0, 0 }, { "image/x-xbitmap", // XBM I18N_NOOP( "X Bitmap is often used by the X Window System to store cursor and icon bitmaps.\

Unless required for this purpose, use a general purpose format instead."), 0, ImageMetaInfo::BlackWhite }, { "image/x-xpixmap", // XPM I18N_NOOP( "X Pixmap is often used by the X Window System for color icons and other images.\

Unless required for this purpose, use a general purpose format instead."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::HighColour }, { "image/png", // PNG I18N_NOOP( "Portable Network Graphics is a lossless compressed format designed to be \ portable and extensible. It is suitable for any type of color or grayscale images, \ indexed or true color.\

PNG is an open format which is widely supported."), ImageMetaInfo::BlackWhite | ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour, 0 }, { "image/jpeg", // JPEG JPG I18N_NOOP( "JPEG is a compressed format suitable for true color or grayscale images. \ It is a lossy format, so it is not recommended for archiving or for repeated loading \ and saving.\

This is an open format which is widely supported."), ImageMetaInfo::HighColour | ImageMetaInfo::Greyscale, ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour }, { "image/jp2", // JP2 I18N_NOOP( "JPEG 2000 was intended as an update to the JPEG format, with the option of \ lossless compression, but so far is not widely supported. It is suitable for true \ color or grayscale images."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour }, { "image/x-eps", // EPS EPSF EPSI I18N_NOOP( "Encapsulated PostScript is derived from the PostScript™ \ page description language. Use this format for importing into other \ applications, or to use with (e.g.) TeX."), 0, 0 }, { "image/x-tga", // TGA I18N_NOOP( "Truevision Targa can store full color images with an alpha channel, and is \ used extensively by animation and video applications.\

This format is not recommended, use an open format instead."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour }, { "image/gif", // GIF I18N_NOOP( // writing may not be supported "Graphics Interchange Format is a popular but patent-encumbered format often \ used for web graphics. It uses lossless compression with up to 256 colors and \ optional transparency.\

For legal reasons this format is not recommended, use an open format instead."), 0, 0 }, { "image/tiff", // TIF TIFF I18N_NOOP( // writing may not be supported "Tagged Image File Format is a versatile and extensible file format widely \ supported by imaging and publishing applications. It supports indexed and true color \ images with alpha transparency.\

Because there are many variations, there may sometimes be compatibility problems. \ Unless required for use with other applications, use an open format instead."), ImageMetaInfo::BlackWhite | ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour, 0 }, { "video/x-mng", // MNG I18N_NOOP( "Multiple-image Network Graphics is derived from the PNG standard and is \ intended for animated images. It is an open format suitable for all types of \ images.\

Images produced by a scanner will not be animated, so unless specifically \ required for use with other applications use PNG instead."), 0, 0 }, { "image/x-sgi", // SGI I18N_NOOP( "This is the Silicon Graphics native image file format, supporting 24 bit \ true color images with optional lossless compression.\

Unless specifically required, use an open format instead."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::HighColour }, { NULL, NULL, 0, 0 } }; static QString sLastFormat = QString::null; // format last used, whether // remembered or not FormatDialog::FormatDialog(QWidget *parent, ImageMetaInfo::ImageType type, bool askForFormat, const ImageFormat &format, bool askForFilename, const QString &filename) : DialogBase(parent), mFormat(format), // save these to return, if mFilename(filename) // they are not requested { setObjectName("FormatDialog"); //qDebug(); setModal(true); // KDE4 buttons: Ok Cancel User1=Select Format // KF5 buttons: Ok Cancel Yes=SelectFormat setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Yes); setWindowTitle(askForFormat ? i18n("Save Assistant") : i18n("Save Scan")); QWidget *page = new QWidget(this); setMainWidget(page); mHelpLabel = NULL; mSubformatCombo = NULL; mFormatList = NULL; mSubformatLabel = NULL; mDontAskCheck = NULL; mRecOnlyCheck = NULL; mExtensionLabel = NULL; mFilenameEdit = NULL; if (!mFormat.isValid()) askForFormat = true; // must ask if none mWantAssistant = false; QGridLayout *gl = new QGridLayout(page); gl->setMargin(0); int row = 0; QLabel *l1; KSeparator *sep; if (askForFormat) // format selector section { l1 = new QLabel(xi18nc("@info", "Select a format to save the scanned image.This is a %1.", ImgSaver::picTypeAsString(type)), page); gl->addWidget(l1, row, 0, 1, 3); ++row; sep = new KSeparator(Qt::Horizontal, page); gl->addWidget(sep, row, 0, 1, 3); ++row; // Insert scrolled list for formats l1 = new QLabel(i18n("File format:"), page); gl->addWidget(l1, row, 0, Qt::AlignLeft); mFormatList = new QListWidget(page); // The list box is filled later. mImageType = type; connect(mFormatList, &QListWidget::currentItemChanged, this, &FormatDialog::formatSelected); l1->setBuddy(mFormatList); gl->addWidget(mFormatList, row + 1, 0); gl->setRowStretch(row + 1, 1); // Insert label for help text mHelpLabel = new QLabel(page); mHelpLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); mHelpLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); mHelpLabel->setMinimumSize(230, 200); mHelpLabel->setWordWrap(true); mHelpLabel->setMargin(4); gl->addWidget(mHelpLabel, row, 1, 4, 2); // Insert selection box for subformat mSubformatLabel = new QLabel(i18n("Image sub-format:"), page); mSubformatLabel->setEnabled(false); gl->addWidget(mSubformatLabel, row + 2, 0, Qt::AlignLeft); mSubformatCombo = new QComboBox(page); mSubformatCombo->setEnabled(false); // not yet implemented gl->addWidget(mSubformatCombo, row + 3, 0); mSubformatLabel->setBuddy(mSubformatCombo); row += 4; sep = new KSeparator(Qt::Horizontal, page); gl->addWidget(sep, row, 0, 1, 3); ++row; // Checkbox to store setting const KConfigSkeletonItem *ski = KookaSettings::self()->saverOnlyRecommendedTypesItem(); Q_ASSERT(ski!=NULL); mRecOnlyCheck = new QCheckBox(ski->label(), page); mRecOnlyCheck->setToolTip(ski->toolTip()); mRecOnlyCheck->setChecked(KookaSettings::saverOnlyRecommendedTypes()); connect(mRecOnlyCheck, &QCheckBox::toggled, this, &FormatDialog::buildFormatList); gl->addWidget(mRecOnlyCheck, row, 0, 1, 3, Qt::AlignLeft); ++row; ski = KookaSettings::self()->saverAlwaysUseFormatItem(); Q_ASSERT(ski!=NULL); mDontAskCheck = new QCheckBox(ski->label(), page); mDontAskCheck->setToolTip(ski->toolTip()); gl->addWidget(mDontAskCheck, row, 0, 1, 3, Qt::AlignLeft); ++row; buildFormatList(mRecOnlyCheck->isChecked()); // now have this setting // don't want this button buttonBox()->button(QDialogButtonBox::Yes)->setVisible(false); } gl->setColumnStretch(1, 1); gl->setColumnMinimumWidth(1, horizontalSpacing()); if (askForFormat && askForFilename) { sep = new KSeparator(Qt::Horizontal, page); gl->addWidget(sep, row, 0, 1, 3); ++row; } if (askForFilename) { // file name section l1 = new QLabel(i18n("File name:"), page); gl->addWidget(l1, row, 0, 1, 3); ++row; mFilenameEdit = new QLineEdit(filename, page); connect(mFilenameEdit, &QLineEdit::textChanged, this, &FormatDialog::checkValid); l1->setBuddy(mFilenameEdit); gl->addWidget(mFilenameEdit, row, 0, 1, 2); mExtensionLabel = new QLabel("", page); gl->addWidget(mExtensionLabel, row, 2, Qt::AlignLeft); ++row; if (!askForFormat) { buttonBox()->button(QDialogButtonBox::Yes)->setText(i18n("Select Format...")); } } if (mFormatList != NULL) // have the format selector { setSelectedFormat(format); // preselect the remembered format } else // no format selector, but { // asking for a file name showExtension(format); // show extension it will have } connect(buttonBox()->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &FormatDialog::slotOk); connect(buttonBox()->button(QDialogButtonBox::Yes), &QPushButton::clicked, this, &FormatDialog::slotUser1); } void FormatDialog::showEvent(QShowEvent *ev) { DialogBase::showEvent(ev); if (mFilenameEdit != NULL) { // asking for a file name mFilenameEdit->setFocus(); // set focus to that mFilenameEdit->selectAll(); // highight for editing } } void FormatDialog::showExtension(const ImageFormat &format) { if (mExtensionLabel == NULL) return; // not showing this mExtensionLabel->setText("." + format.extension()); // show extension it will have } void FormatDialog::formatSelected(QListWidgetItem *item) { if (mHelpLabel == NULL) return; // not showing this if (item == NULL) { // nothing is selected mHelpLabel->setText(i18n("No format selected.")); setButtonEnabled(QDialogButtonBox::Ok, false); mFormatList->clearSelection(); if (mExtensionLabel != NULL) { mExtensionLabel->setText(".???"); } return; } mFormatList->setCurrentItem(item); // focus highlight -> select const char *helptxt = NULL; - const QString mimename = item->data(Qt::UserRole).toString(); + const QByteArray mimename = item->data(Qt::UserRole).toByteArray(); for (FormatInfo *ip = &formats[0]; ip->mime != NULL; ++ip) { // locate help text for format if (ip->mime == mimename) { helptxt = ip->helpString; break; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimename); ImageFormat format = ImageFormat::formatForMime(mime); if (helptxt != NULL) { // found some help text mHelpLabel->setText(i18n(helptxt)); // set the hint check_subformat(format); // and check subformats } else { mHelpLabel->setText(i18n("No information is available for this format.")); } if (mDontAskCheck != NULL) { mDontAskCheck->setChecked(ImgSaver::isRememberedFormat(mImageType, format)); } showExtension(format); checkValid(); } // TODO: implement subtypes void FormatDialog::check_subformat(const ImageFormat &format) { if (mSubformatCombo == NULL) return; // not showing this mSubformatCombo->setEnabled(false); // not yet implemented mSubformatLabel->setEnabled(false); } void FormatDialog::setSelectedFormat(const ImageFormat &format) { if (mFormatList == NULL) return; // not showing this if (format.isValid()) { // valid format to select const QMimeType mime = format.mime(); if (!mime.isValid()) return; for (int i = 0; i < mFormatList->count(); ++i) { QListWidgetItem *item = mFormatList->item(i); if (item == NULL) { continue; } QString mimename = item->data(Qt::UserRole).toString(); if (mime.inherits(mimename)) { mFormatList->setCurrentItem(item); return; } } } // If that selected nothing, then select the last-used format (regardless // of the "always use" option setting). The last-used format is saved // in getFormat() for that purpose. This helps when scanning a series of // similar images yet where the user doesn't want to permanently save the // format for some reason. // // This is safe if the last-used format is not applicable to and so is not // displayed for the current image type - it just does nothing. if (!sLastFormat.isEmpty()) { for (int i = 0; i < mFormatList->count(); ++i) { QListWidgetItem *item = mFormatList->item(i); if (item == NULL) { continue; } QString mimename = item->data(Qt::UserRole).toString(); // We know the MIME name is canonical here, so can use string compare if (mimename == sLastFormat) { mFormatList->setCurrentItem(item); return; } } } } ImageFormat FormatDialog::getFormat() const { if (mFormatList == NULL) return (mFormat); // no UI for this QMimeDatabase db; const QListWidgetItem *item = mFormatList->currentItem(); if (item != NULL) { QString mimename = item->data(Qt::UserRole).toString(); const QMimeType mime = db.mimeTypeForName(mimename); if (mime.isValid()) { sLastFormat = mime.name(); return (ImageFormat::formatForMime(mime)); } } return (ImageFormat("PNG")); // a sensible default } QString FormatDialog::getFilename() const { if (mFilenameEdit == NULL) return (mFilename); // no UI for this return (mFilenameEdit->text()); } QByteArray FormatDialog::getSubFormat() const { return (""); // Not supported yet... } void FormatDialog::checkValid() { bool ok = true; // so far, anyway if (mFormatList != NULL && mFormatList->selectedItems().count() == 0) ok = false; if (mFilenameEdit != NULL && mFilenameEdit->text().isEmpty()) ok = false; setButtonEnabled(QDialogButtonBox::Ok, ok); } static const FormatInfo *findKnownFormat(const QMimeType &mime) { for (const FormatInfo *fi = &formats[0]; fi->mime != NULL; ++fi) { // search for format info if (mime.inherits(fi->mime)) return (fi); // matching that MIME type } return (NULL); // nothing found } void FormatDialog::buildFormatList(bool recOnly) { if (mFormatList == NULL) return; // not showing this qDebug() << "recOnly" << recOnly << "for type" << mImageType; mFormatList->clear(); const QList *mimeTypes = ImageFormat::mimeTypes(); foreach (const QMimeType &mime, *mimeTypes) // for all known MIME types { const FormatInfo *fi = findKnownFormat(mime); // look for format information if (fi==NULL) // nothing for that MIME type { if (recOnly) continue; // never show for recommended } // but always show otherwise else { ImageMetaInfo::ImageTypes okTypes = fi->okForTypes; if (okTypes!=0) // format has allowed types { if (!(okTypes & mImageType)) continue; // but not for this image type } if (recOnly) // want only recommended types { if (!(fi->recForTypes & mImageType)) // check for recommended format { continue; // not for this image type } } } // add format to list QListWidgetItem *item = new QListWidgetItem(QIcon::fromTheme(mime.iconName()), mime.comment(), mFormatList); // Not sure whether a QMimeType can safely be stored in a // QVariant, so storing the MIME type name instead. item->setData(Qt::UserRole, mime.name()); mFormatList->addItem(item); } formatSelected(NULL); // selection has been cleared } void FormatDialog::slotOk() { if (mRecOnlyCheck != NULL) { // have UI for this KookaSettings::setSaverOnlyRecommendedTypes(mRecOnlyCheck->isChecked()); KookaSettings::self()->save(); // save state of this option } } void FormatDialog::slotUser1() { mWantAssistant = true; accept(); } void FormatDialog::forgetRemembered() { const KConfigSkeletonItem *ski = KookaSettings::self()->saverOnlyRecommendedTypesItem(); Q_ASSERT(ski!=NULL); KConfigGroup grp = KookaSettings::self()->config()->group(ski->group()); grp.deleteGroup(); grp.sync(); } bool FormatDialog::alwaysUseFormat() const { return (mDontAskCheck != NULL ? mDontAskCheck->isChecked() : false); } diff --git a/kooka/kookapref.cpp b/kooka/kookapref.cpp index 9d41eed..4a2e9c0 100644 --- a/kooka/kookapref.cpp +++ b/kooka/kookapref.cpp @@ -1,306 +1,306 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kookapref.h" #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STRERROR #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #include #include #include #include #include #include "prefspages.h" #include "kookasettings.h" #include "dialogstatesaver.h" KookaPref::KookaPref(QWidget *parent) : KPageDialog(parent) { setObjectName("KookaPref"); setModal(true); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); buttonBox()->button(QDialogButtonBox::Ok)->setDefault(true); buttonBox()->button(QDialogButtonBox::RestoreDefaults)->setIcon(QIcon::fromTheme("edit-undo")); setWindowTitle(i18n("Preferences")); createPage(new KookaGeneralPage(this), i18n("General"), i18n("General Options"), "configure"); createPage(new KookaStartupPage(this), i18n("Startup"), i18n("Startup Options"), "system-run"); createPage(new KookaSavingPage(this), i18n("Image Saving"), i18n("Image Saving Options"), "document-save"); createPage(new KookaThumbnailPage(this), i18n("Gallery & Thumbnails"), i18n("Image Gallery and Thumbnail View"), "view-list-icons"); createPage(new KookaOcrPage(this), i18n("OCR"), i18n("Optical Character Recognition"), "ocr"); connect(buttonBox()->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &KookaPref::slotSaveSettings); connect(buttonBox()->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &KookaPref::slotSaveSettings); connect(buttonBox()->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &KookaPref::slotSetDefaults); setMinimumSize(670, 380); new DialogStateSaver(this); } int KookaPref::createPage(KookaPrefsPage *page, const QString &name, const QString &header, const char *icon) { QVBoxLayout *top = static_cast(page->layout()); if (top != NULL) { top->addStretch(1); } KPageWidgetItem *item = addPage(page, name); item->setHeader(header); item->setIcon(QIcon::fromTheme(icon)); int idx = mPages.count(); // index of new item mPages.append(item); return (idx); // index of item added } void KookaPref::slotSaveSettings() { for (int i = 0; i < mPages.size(); ++i) { KookaPrefsPage *page = static_cast(mPages[i]->widget()); page->saveSettings(); } KSharedConfig::openConfig()->sync(); emit dataSaved(); } void KookaPref::slotSetDefaults() { for (int i = 0; i < mPages.size(); ++i) { KookaPrefsPage *page = static_cast(mPages[i]->widget()); page->defaultSettings(); } } void KookaPref::showPageIndex(int page) { if (page >= 0 && page < mPages.size()) { setCurrentPage(mPages[page]); } } int KookaPref::currentPageIndex() { return (mPages.indexOf(currentPage())); } static QString tryFindBinary(const QString &bin, const QString &configPath) { // First check for a full path in the config file. // Not sure what the point of the 'contains' test is here. if (!configPath.isEmpty() && configPath.contains(bin)) { QFileInfo fi(configPath); // check for valid executable if (fi.exists() && fi.isExecutable() && !fi.isDir()) return (fi.absoluteFilePath()); } // Otherwise try to find the program on the user's search PATH return (QStandardPaths::findExecutable(bin)); } QString KookaPref::tryFindGocr() { return (tryFindBinary("gocr", KookaSettings::ocrGocrBinary())); } QString KookaPref::tryFindOcrad(void) { return (tryFindBinary("ocrad", KookaSettings::ocrOcradBinary())); } // Support for the gallery location - moved here from Previewer class in libkscan QString KookaPref::sGalleryRoot = QString::null; // global resolved location // The static variable above ensures that the user is only asked // at most once in an application run. QString KookaPref::galleryRoot() { if (sGalleryRoot.isNull()) { sGalleryRoot = findGalleryRoot(); } return (sGalleryRoot); } // Get the user's configured KDE documents path. It may not exist yet, in // which case QDir::canonicalPath() will fail - try QDir::absolutePath() in // this case. If all else fails then the last resort is the home directory. static QString docsPath() { QString docpath = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).canonicalPath(); if (docpath.isEmpty()) { docpath = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).absolutePath(); } if (docpath.isEmpty()) { - docpath = getenv("HOME"); + docpath = QFile::decodeName(getenv("HOME")); } return (docpath); } // TODO: maybe save a .directory file there which shows a 'scanner' logo? static QString createGallery(const QDir &d, bool *success = NULL) { if (!d.exists()) // does not already exist { if (mkdir(QFile::encodeName(d.path()).constData(), 0755) != 0) { // using mkdir(2) so that we can // get the errno if it fails #ifdef HAVE_STRERROR const char *reason = strerror(errno); #else const char *reason = ""; #endif QString docs = docsPath(); KMessageBox::error(NULL, xi18nc("@info", "Unable to create the directory %1" "for the Kooka gallery" #ifdef HAVE_STRERROR " - %3." #else "." #endif "Your document directory %2" "will be used." "Check the document directory setting and permissions.", d.absolutePath(), docs, reason), i18n("Error creating gallery")); if (success != NULL) *success = false; return (docs); } } if (success != NULL) { *success = true; } return (d.absolutePath()); } QString KookaPref::findGalleryRoot() { QString galleryName = KookaSettings::galleryName(); // may be base name or absolute path if (galleryName.isEmpty()) { qWarning() << "Gallery name not configured"; return (QString::null); } QString oldpath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "ScanImages", QStandardPaths::LocateDirectory); bool oldexists = !oldpath.isEmpty(); QString newpath(galleryName); if (!QDir::isAbsolutePath(galleryName)) { QString docpath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); newpath = docpath+'/'+galleryName; // already an absolute path } QDir newdir(newpath); bool newexists = newdir.exists(); qDebug() << "old" << oldpath << "exists" << oldexists; qDebug() << "new" << newpath << "exists" << newexists; QString dir; if (!oldexists && !newexists) { // no directories present dir = createGallery(newdir); // create and use new } else if (!oldexists && newexists) { // only new exists dir = newpath; // fine, just use that } else if (oldexists && !newexists) { // only old exists if (KMessageBox::questionYesNo(NULL, xi18nc("@info", "An old Kooka gallery was found at %1." "The preferred new location is now %2." "Do you want to create a new gallery at the new location?", oldpath, newpath), i18n("Create New Gallery"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "GalleryNoMigrate") == KMessageBox::Yes) { // yes, create new bool created; dir = createGallery(newdir, &created); if (created) { // new created OK KMessageBox::information(NULL, xi18nc("@info", "Kooka will use the new gallery, %1." "If you wish to add the images from your old gallery %2," "then you may do so by simply copying or moving the files.", newpath, oldpath), i18n("New Gallery Created"), QString::null, KMessageBox::Notify | KMessageBox::AllowLink); } } else { // no, don't create dir = oldpath; // stay with old location } } else { // both exist KMessageBox::information(NULL, xi18nc("@info", "Kooka will use the new gallery, %1." "If you wish to add the images from your old gallery %2," "then you may do so by simply copying or moving the files.", newpath, oldpath), i18n("Old Gallery Exists"), "GalleryNoRemind", KMessageBox::Notify | KMessageBox::AllowLink); dir = newpath; // just use new one } if (!dir.endsWith("/")) dir += "/"; qDebug() << "using" << dir; return (dir); } diff --git a/libkookascan/imagemetainfo.h b/libkookascan/imagemetainfo.h index 8be5e3d..d6775ee 100644 --- a/libkookascan/imagemetainfo.h +++ b/libkookascan/imagemetainfo.h @@ -1,213 +1,213 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2004-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef IMAGEMETAINFO_H #define IMAGEMETAINFO_H #include "kookascan_export.h" #include class QImage; /** * @short Image meta-information. * * Information about an image which may be useful to an application, but * is not stored with or is not readily available from a @c QImage. * * @author Klaas Freitag * @author Jonathan Marten **/ class KOOKASCAN_EXPORT ImageMetaInfo { public: /** * An image type. This is not the detailed format of the image * data (which is available via @c QImage::Format), but a general * category which an application or its user will be interested in. **/ enum ImageType { Unknown = 0x00, ///< Unknown or not resolved yet BlackWhite = 0x01, ///< Black/white bitmap Greyscale = 0x02, ///< Grey scale (indexed with palette) LowColour = 0x04, ///< Low colour (indexed with palette) HighColour = 0x08, ///< High colour (RGB) Preview = 0x10, ///< A preview image (application defined) Thumbnail = 0x20 ///< A thumbnail image (application defined) }; Q_DECLARE_FLAGS(ImageTypes, ImageType) /** * Constructor. * * The image type is initialised to @c Unknown, and the * X and Y resolutions to -1. **/ explicit ImageMetaInfo(); // TODO: can use QImage::text(key,value) to replace this? /** * Set the X resolution of the image. * * @param res The new X resolution * @see getXResolution **/ void setXResolution(int res) { m_xRes = res; } /** * Set the Y resolution of the image. * * @param res The new Y resolution * @see getYResolution **/ void setYResolution(int res) { m_yRes = res; } /** * Set the mode of the image. * * This is intended to be a user readable description. * * @param mode The new mode string * @see getMode **/ void setMode(const QString &mode) { m_mode = mode; } /** * Set the name of the scanner that was used to generate the image. * * @param scanner The new scanner name * @see getScannerName **/ - void setScannerName(const QString &scanner) + void setScannerName(const QByteArray &scanner) { m_scanner = scanner; } /** * Set the image type. * * @param type The new image type * @see getImageType **/ void setImageType(ImageMetaInfo::ImageType type) { m_type = type; } /** * Get the X resolution of the image. * * @return The X resolution, or -1 if it has not been set. * @see setXResolution **/ int getXResolution() const { return (m_xRes); } /** * Get the Y resolution of the image. * * @return The Y resolution, or -1 if it has not been set. * @see setYResolution **/ int getYResolution() const { return (m_yRes); } /** * Get the mode description of the image. * * @return The mode string * @see setMode **/ QString getMode() const { return (m_mode); } /** * Get the scanner name for the image. * * @return The scanner name * @see setScannerName **/ - QString getScannerName() const + QByteArray getScannerName() const { return (m_scanner); } /** * Get the image type. * * @return The set image type. * @see setImageType **/ ImageMetaInfo::ImageType getImageType() const { return (m_type); } /** * Deduce the image type for an image. * * @param image The image to examine * @return The image type * * @note This will never return the type as @c Preview or @c Thumbnail. **/ static ImageType findImageType(const QImage *image); private: int m_xRes; int m_yRes; QString m_mode; - QString m_scanner; + QByteArray m_scanner; ImageMetaInfo::ImageType m_type; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ImageMetaInfo::ImageTypes) #endif // IMAGEMETAINFO_H diff --git a/libkookascan/kscandevice.cpp b/libkookascan/kscandevice.cpp index 958f274..06b5d9a 100644 --- a/libkookascan/kscandevice.cpp +++ b/libkookascan/kscandevice.cpp @@ -1,1525 +1,1525 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kscandevice.h" #include #include #include #include #include #include #include #include #include #include "scanglobal.h" #include "scandevices.h" #include "kgammatable.h" #include "kscancontrols.h" #include "kscanoption.h" #include "kscanoptset.h" #include "deviceselector.h" #include "imagemetainfo.h" #include "scansettings.h" extern "C" { #include } #define MIN_PREVIEW_DPI 75 #define MAX_PROGRESS 100 // Debugging options #define DEBUG_OPTIONS #undef DEBUG_RELOAD #undef DEBUG_CREATE #define DEBUG_PARAMS #ifdef DEBUG_OPTIONS #include #endif // DEBUG_OPTIONS // Accessing GUI options // --------------------- // Used only by ScanParams::slotVirtScanModeSelect() void KScanDevice::guiSetEnabled(const QByteArray &name, bool state) { KScanOption *so = getExistingGuiElement(name); if (so==NULL) return; QWidget *w = so->widget(); if (w==NULL) return; w->setEnabled(state && so->isSoftwareSettable()); } KScanOption *KScanDevice::getOption(const QByteArray &name, bool create) { QByteArray alias = aliasName(name); if (mCreatedOptions.contains(alias)) { #ifdef DEBUG_CREATE qDebug() << "already exists" << alias; #endif // DEBUG_CREATE return (mCreatedOptions.value(alias)); } if (!create) { #ifdef DEBUG_CREATE qDebug() << "does not exist" << alias; #endif // DEBUG_CREATE return (NULL); } #ifdef DEBUG_CREATE qDebug() << "creating new" << alias; #endif // DEBUG_CREATE KScanOption *so = new KScanOption(alias, this); mCreatedOptions.insert(alias, so); return (so); } KScanOption *KScanDevice::getExistingGuiElement(const QByteArray &name) const { KScanOption *ret = NULL; QByteArray alias = aliasName(name); for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { KScanOption *opt = it.value(); if (opt->isGuiElement() && opt->getName()==alias) { ret = opt; break; } } return (ret); } KScanOption *KScanDevice::getGuiElement(const QByteArray &name, QWidget *parent) { if (name.isEmpty()) return (NULL); if (!optionExists(name)) return (NULL); //qDebug() << "for" << name; KScanOption *so = getExistingGuiElement(name); // see if already exists if (so!=NULL) return (so); // if so, just return that so = getOption(name); // create a new scan option if (so->isValid()) // option was created { QWidget *w = so->createWidget(parent); // create widget for option if (w!=NULL) w->setEnabled(so->isActive() && so->isSoftwareSettable()); //else qDebug() << "no widget created for" << name; } else // option not valid { // (not known by scanner?) //qDebug() << "option invalid" << name; so = NULL; } return (so); } // Constructor & destructor // ------------------------ KScanDevice::KScanDevice(QObject *parent) : QObject(parent) { //qDebug(); ScanGlobal::self()->init(); // do sane_init() first of all mScannerHandle = NULL; mScannerInitialised = false; // is device opened yet? mScannerName = ""; mScanningState = KScanDevice::ScanIdle; mScanBuf = NULL; // image data buffer while scanning mScanImage = NULL; // temporary image to scan into mImageInfo = NULL; // scanned image information mSocketNotifier = NULL; // socket notifier for async scanning mSavedOptions = NULL; // options to save during preview mBytesRead = 0; mBytesUsed = 0; mPixelX = 0; mPixelY = 0; // TODO: make this just a function call, for predictable order // (sigScanFinished before sigNewImage) connect(this, &KScanDevice::sigScanFinished, this, &KScanDevice::slotScanFinished); } KScanDevice::~KScanDevice() { delete mSavedOptions; delete mImageInfo; // TODO: need to check and do closeDevice() here? ScanGlobal::self()->setScanDevice(NULL); // going away, don't call me //qDebug(); } // Opening/closing the scanner device // ---------------------------------- KScanDevice::Status KScanDevice::openDevice(const QByteArray &backend) { KScanDevice::Status stat = KScanDevice::Ok; //qDebug() << "backend" << backend; mSaneStatus = SANE_STATUS_UNSUPPORTED; if (backend.isEmpty()) return (KScanDevice::ParamError); // search for scanner if (ScanDevices::self()->deviceInfo(backend)==NULL) return (KScanDevice::NoDevice); mScannerName = backend; // set now for authentication QApplication::setOverrideCursor(Qt::WaitCursor); // potential lengthy operation ScanGlobal::self()->setScanDevice(this); // for possible authentication mSaneStatus = sane_open(backend.constData(), &mScannerHandle); if (mSaneStatus==SANE_STATUS_ACCESS_DENIED) // authentication failed? { clearSavedAuth(); // clear any saved password //qDebug() << "retrying authentication"; // try again once more mSaneStatus = sane_open(backend.constData(), &mScannerHandle); } if (mSaneStatus==SANE_STATUS_GOOD) { stat = findOptions(); // fill dictionary with options mScannerInitialised = true; // note scanner opened OK } else { stat = KScanDevice::OpenDevice; mScannerName = ""; } QApplication::restoreOverrideCursor(); return (stat); } void KScanDevice::closeDevice() { emit sigCloseDevice(); // tell callers we're closing //qDebug() << "Saving default scan settings"; saveStartupConfig(); // save config for next startup if (mScannerHandle!=NULL) { if (mScanningState!=KScanDevice::ScanIdle) { //qDebug() << "Scanner is still active, calling sane_cancel()"; sane_cancel(mScannerHandle); } sane_close(mScannerHandle); // close the SANE device mScannerHandle = NULL; // scanner no longer open } // clear lists of options QList opts = mCreatedOptions.values(); while (!opts.isEmpty()) delete opts.takeFirst(); mCreatedOptions.clear(); mKnownOptions.clear(); mScannerName = ""; mScannerInitialised = false; } // Scanner and image information // ----------------------------- QString KScanDevice::scannerDescription() const { QString ret; if (!mScannerName.isNull() && mScannerInitialised) { ret = ScanDevices::self()->deviceDescription(mScannerName); } else { ret = i18n("No scanner selected"); } //qDebug() << "returning" << ret; return (ret); } QSize KScanDevice::getMaxScanSize() { QSize s; double min, max; KScanOption *so_w = getOption(SANE_NAME_SCAN_BR_X); so_w->getRange(&min, &max); s.setWidth(static_cast(max)); KScanOption *so_h = getOption(SANE_NAME_SCAN_BR_Y); so_h->getRange(&min, &max); s.setHeight(static_cast(max)); return (s); } void KScanDevice::getCurrentFormat(int *format, int *depth) { sane_get_parameters(mScannerHandle, &mSaneParameters); *format = mSaneParameters.format; *depth = mSaneParameters.depth; } // Listing the available options // ----------------------------- KScanDevice::Status KScanDevice::findOptions() { SANE_Int n; SANE_Int opt; if (sane_control_option(mScannerHandle, 0, SANE_ACTION_GET_VALUE, &n, &opt)!=SANE_STATUS_GOOD) { qWarning() << "cannot read option 0 (count)"; return (KScanDevice::ControlError); } mKnownOptions.clear(); for (int i = 1; iname!=NULL && strlen(d->name)>0) name = d->name; if (d->type==SANE_TYPE_GROUP) // option is a group, { // give it a dummy name name = "group-"; name += QByteArray::number(i); } if (!name.isEmpty()) // must now have a name { #ifdef DEBUG_OPTIONS qDebug() << "Option" << i << "is" << name; #endif // DEBUG_OPTIONS mKnownOptions.insert(i, name); } else qWarning() << "Invalid option" << i << "(no name and not a group)"; } return (KScanDevice::Ok); } QList KScanDevice::getAllOptions() const { return (mKnownOptions.values()); } QList KScanDevice::getCommonOptions() const { QList opts; for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { KScanOption *so = it.value(); if (so->isCommonOption()) opts.append(it.key()); } return (opts); } QList KScanDevice::getAdvancedOptions() const { QList opts; for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { KScanOption *so = it.value(); if (!so->isCommonOption()) opts.append(it.key()); } return (opts); } // Controlling options // ------------------- int KScanDevice::getOptionIndex(const QByteArray &name) const { return (mKnownOptions.key(name)); } bool KScanDevice::optionExists(const QByteArray &name) const { if (name.isEmpty()) return (false); QByteArray alias = aliasName(name); return (mKnownOptions.key(alias)!=0); } /* This function tries to find name aliases which appear from backend to backend. * Example: Custom-Gamma is for epson backends 'gamma-correction' - not a really * cool thing :-| * Maybe this helps us out ? */ QByteArray KScanDevice::aliasName( const QByteArray& name ) const { if (mCreatedOptions.contains(name)) return (name); QByteArray ret = name; if( name == SANE_NAME_CUSTOM_GAMMA ) { if (mCreatedOptions.contains("gamma-correction")) ret = "gamma-correction"; } //if( ret != name ) //qDebug() << "Found alias for" << name << "which is" << ret; return( ret ); } void KScanDevice::applyOption(KScanOption *opt) { bool reload = true; // is a reload needed? if (opt!=NULL) // an option is specified { #ifdef DEBUG_RELOAD qDebug() << "option" << opt->getName(); #endif // DEBUG_APPLY reload = opt->apply(); // apply this option } if (!reload) // need to reload now? { #ifdef DEBUG_RELOAD qDebug() << "Reload of others not needed"; #endif // DEBUG_RELOAD return; } // reload of all others needed for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { KScanOption *so = it.value(); if (!so->isGuiElement()) continue; if (opt==NULL || so!=opt) { #ifdef DEBUG_RELOAD qDebug() << "Reloading" << so->getName(); #endif // DEBUG_RELOAD so->reload(); so->redrawWidget(); } } #ifdef DEBUG_RELOAD qDebug() << "Finished"; #endif // DEBUG_RELOAD } void KScanDevice::reloadAllOptions() { #ifdef DEBUG_RELOAD qDebug(); #endif // DEBUG_RELOAD applyOption(NULL); } // Scanning control // ---------------- void KScanDevice::slotStopScanning() { //qDebug() << "Attempt to stop scanning"; // TODO: needed? will be done by acquireData() if (mScanningState==KScanDevice::ScanInProgress) emit sigScanFinished(KScanDevice::Cancelled); mScanningState = KScanDevice::ScanStopNow; } // Preview image // ------------- const QString KScanDevice::previewFile() const { // TODO: this doesn't work if that directory doesn't exist, // and nothing ever creates that directory! // // Do we want this feature to work? // If so, create the directory in savePreviewImage() below QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)+"/previews/"; QString sname(scannerDescription()); sname.replace('/', "_"); return (dir+sname); } QImage KScanDevice::loadPreviewImage() { const QString prevFile = previewFile(); //qDebug() << "Loading preview from" << prevFile; return (QImage(prevFile)); } bool KScanDevice::savePreviewImage(const QImage &image) const { if (image.isNull()) return (false); const QString prevFile = previewFile(); //qDebug() << "Saving preview to" << prevFile; return (image.save(prevFile, "BMP")); } // Displaying scan options // ----------------------- // // For debugging. Originally showOptions() was called prepareScan() and had // the comment: // // prepareScan tries to set as much as parameters as possible. // // Function which applies all Options which need to be applied. // See SANE-Documentation Table 4.5, description for SANE_CAP_SOFT_DETECT. // The function sets the options which have SANE_CAP_AUTOMATIC set // to automatic adjust. // // But this wasn't true - it only reports the current state of the options. #ifdef DEBUG_OPTIONS inline const char *optionNotifyString(int opt) { return (opt!=0 ? " X |" : " - |"); } void KScanDevice::showOptions() { qDebug() << "for" << mScannerName; std::cerr << "----------------------------------+---+---+---+---+---+---+---+---+-------" << std::endl; std::cerr << " Option |SSL|HSL|SDT|EMU|AUT|INA|ADV|PRI| Value" << std::endl; std::cerr << "----------------------------------+---+---+---+---+---+---+---+---+-------" << std::endl; for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { const KScanOption *so = it.value(); if (so->isGroup()) continue; int cap = so->getCapabilities(); QString s = QString(it.key()).left(31).leftJustified(32); std::cerr << " " << qPrintable(s) << " |" << optionNotifyString((cap & SANE_CAP_SOFT_SELECT)) << optionNotifyString((cap & SANE_CAP_HARD_SELECT)) << optionNotifyString((cap & SANE_CAP_SOFT_DETECT)) << optionNotifyString((cap & SANE_CAP_EMULATED)) << optionNotifyString((cap & SANE_CAP_AUTOMATIC)) << optionNotifyString((cap & SANE_CAP_INACTIVE)) << optionNotifyString((cap & SANE_CAP_ADVANCED)) << optionNotifyString(so->isPriorityOption()) << " " << qPrintable(so->get()) << std::endl; } std::cerr << "----------------------------------+---+---+---+---+---+---+---+---+-------" << std::endl; } #endif // DEBUG_OPTIONS // Creating a new image to receive the scan/preview // ------------------------------------------------ static ImageMetaInfo::ImageType getImageFormat(const SANE_Parameters *p) { if (p==NULL) return (ImageMetaInfo::Unknown); if (p->depth==1) // Line art (bitmap) { return (ImageMetaInfo::BlackWhite); } else if (p->depth==8) // 8 bit RGB { if (p->format==SANE_FRAME_GRAY) // Grey scale { return (ImageMetaInfo::Greyscale); } else // True colour { return (ImageMetaInfo::HighColour); } } else // Error, no others supported { //qDebug() << "Only bit depths 1 or 8 supported!"; return (ImageMetaInfo::Unknown); } } KScanDevice::Status KScanDevice::createNewImage(const SANE_Parameters *p) { QImage::Format fmt; ImageMetaInfo::ImageType itype = getImageFormat(p); // what format should this be? switch (itype) // choose QImage option for that { default: case ImageMetaInfo::Unknown: return (KScanDevice::ParamError); case ImageMetaInfo::BlackWhite: fmt = QImage::Format_Mono; break; case ImageMetaInfo::Greyscale: fmt = QImage::Format_Indexed8; break; case ImageMetaInfo::HighColour: fmt = QImage::Format_RGB32; break; } delete mScanImage; // recreate new image mScanImage = new QImage(p->pixels_per_line,p->lines,fmt); if (mScanImage==NULL) return (KScanDevice::NoMemory); if (itype==ImageMetaInfo::BlackWhite) // Line art (bitmap) { mScanImage->setColor(0,qRgb(0x00,0x00,0x00)); // set black/white palette mScanImage->setColor(1,qRgb(0xFF,0xFF,0xFF)); } else if (itype==ImageMetaInfo::Greyscale) // 8 bit grey { // set grey scale palette for (int i = 0; i<256; i++) mScanImage->setColor(i,qRgb(i,i,i)); } return (KScanDevice::Ok); } // Acquiring preview/scan image // ---------------------------- KScanDevice::Status KScanDevice::acquirePreview( bool forceGray, int dpi ) { if (mSavedOptions!=NULL) mSavedOptions->clear(); else mSavedOptions = new KScanOptSet("TempStore"); /* set Preview = ON if exists */ KScanOption *prev = getOption(SANE_NAME_PREVIEW, false); if (prev!=NULL) { prev->set(true); prev->apply(); prev->set(false); // Ensure that it gets restored mSavedOptions->backupOption(prev); // back to 'false' after previewing } // TODO: this block doesn't make sense (set the option to true if // it is already true?) /* Gray-Preview only done by widget? */ KScanOption *so = getOption(SANE_NAME_GRAY_PREVIEW, false); if (so!=NULL) { if (so->get()=="true") { /* Gray preview on */ so->set(true); //qDebug() << "Setting GrayPreview ON"; } else { so->set(false); //qDebug() << "Setting GrayPreview OFF"; } so->apply(); } KScanOption *mode = getOption(SANE_NAME_SCAN_MODE, false); if (mode!=NULL) { //qDebug() << "Scan mode before preview is" << mode->get(); mSavedOptions->backupOption(mode); /* apply if it has a widget, or apply always? */ if (mode->isGuiElement()) mode->apply(); } /* Some sort of Scan Resolution option should always exist */ KScanOption *xres = getOption(SANE_NAME_SCAN_X_RESOLUTION, false); if (xres==NULL) xres = getOption(SANE_NAME_SCAN_RESOLUTION, false); if (xres!=NULL) { //qDebug() << "Scan resolution before preview is" << xres->get(); mSavedOptions->backupOption(xres); int preview_dpi = dpi; if (dpi==0) // preview DPI not specified { double min, max; if (!xres->getRange(&min, &max)) { //qDebug() << "Could not retrieve resolution range!"; min = 75.0; // hope every scanner can do this } preview_dpi = (int) min; if (preview_dpibackupOption(yres); yres->set(preview_dpi); yres->apply(); yres->get(&mCurrScanResolutionY); } else mCurrScanResolutionY = 0; /* Resolution bind switch? */ KScanOption *bind = getOption(SANE_NAME_RESOLUTION_BIND, false); if (bind!=NULL) { /* Switch binding on if available */ mSavedOptions->backupOption(bind); bind->set(true); bind->apply(); } xres->set(preview_dpi); xres->apply(); /* Store the resulting preview for additional image information */ xres->get(&mCurrScanResolutionX); if (mCurrScanResolutionY==0) mCurrScanResolutionY = mCurrScanResolutionX; } return (acquireData(true)); // perform the preview } void KScanDevice::applyAllOptions(bool prio) { for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { KScanOption *so = it.value(); if (!so->isGuiElement()) continue; if (so->isPriorityOption() ^ prio) continue; if (so->isActive() && so->isSoftwareSettable()) so->apply(); } } /* Starts scanning * depending on if a filename is given or not, the function tries to open * the file using the Qt-Image-IO or really scans the image. */ KScanDevice::Status KScanDevice::acquireScan(const QString &filename) { if (filename.isEmpty()) // real scan { applyAllOptions(true); // apply priority options applyAllOptions(false); // apply non-priority options // One of the Scan Resolution parameters should always exist KScanOption *xres = getOption(SANE_NAME_SCAN_X_RESOLUTION, false); if (xres==NULL) xres = getOption(SANE_NAME_SCAN_RESOLUTION, false); if (xres!=NULL) { xres->get(&mCurrScanResolutionX); KScanOption *yres = getOption(SANE_NAME_SCAN_Y_RESOLUTION, false); if (yres!=NULL) yres->get(&mCurrScanResolutionY); else mCurrScanResolutionY = mCurrScanResolutionX; } return (acquireData(false)); // perform the scan } else // virtual scan from image file { QFileInfo file(filename); if (!file.exists()) { //qDebug() << "virtual file" << filename << "does not exist"; return (KScanDevice::ParamError); } QImage img(filename); if (img.isNull()) { //qDebug() << "virtual file" << filename << "could not load"; return (KScanDevice::ParamError); } ImageMetaInfo info; info.setXResolution(img.dotsPerMeterX()); // TODO: *2.54/100 info.setYResolution(img.dotsPerMeterY()); // TODO: *2.54/100 - info.setScannerName(filename); + info.setScannerName(QFile::encodeName(filename)); emit sigNewImage(&img, &info); return (KScanDevice::Ok); } } #ifdef DEBUG_PARAMS static void dumpParams(const QString &msg, const SANE_Parameters *p) { QByteArray formatName("UNKNOWN"); switch (p->format) { case SANE_FRAME_GRAY: formatName = "GREY"; break; case SANE_FRAME_RGB: formatName = "RGB"; break; case SANE_FRAME_RED: formatName = "RED"; break; case SANE_FRAME_GREEN: formatName = "GREEN"; break; case SANE_FRAME_BLUE: formatName = "BLUE"; break; } qDebug() << msg.toLatin1().constData(); qDebug() << " format: " << p->format << "=" << formatName.constData(); qDebug() << " last_frame: " << p->last_frame; qDebug() << " lines: " << p->lines; qDebug() << " depth: " << p->depth; qDebug() << " pixels_per_line: " << p->pixels_per_line; qDebug() << " bytes_per_line: " << p->bytes_per_line; } #endif // DEBUG_PARAMS KScanDevice::Status KScanDevice::acquireData(bool isPreview) { KScanDevice::Status stat = KScanDevice::Ok; int frames = 0; #ifdef DEBUG_OPTIONS showOptions(); // dump the current noptions #endif mScanningPreview = isPreview; mScanningState = KScanDevice::ScanStarting; mBytesRead = 0; mBlocksRead = 0; ScanGlobal::self()->setScanDevice(this); // for possible authentication if (mImageInfo!=NULL) delete mImageInfo; // start with this clean mImageInfo = NULL; if (!isPreview) // scanning to eventually save { mImageInfo = new ImageMetaInfo; // create for image information mSaneStatus = sane_get_parameters(mScannerHandle, &mSaneParameters); if (mSaneStatus==SANE_STATUS_GOOD) // get pre-scan parameters { #ifdef DEBUG_PARAMS dumpParams("Before scan:", &mSaneParameters); #endif // DEBUG_PARAMS if (mSaneParameters.lines>=1 && mSaneParameters.pixels_per_line>0) { // check for a plausible image ImageMetaInfo::ImageType fmt = getImageFormat(&mSaneParameters); if (fmt==ImageMetaInfo::Unknown) // find format it will have { // scan format not recognised? stat = KScanDevice::ParamError; // no point starting scan emit sigScanFinished(stat); // scan is now finished return (stat); } mImageInfo->setImageType(fmt); // save result for later } } } // Tell the application that scanning is about to start. emit sigScanStart(mImageInfo); // If the image information was available, the application may have // prompted for a filename. If the user cancelled that, it will have // called our slotStopScanning() which set mScanningState to // KScanDevice::ScanStopNow. If that is the case, then finish here. if (mScanningState==KScanDevice::ScanStopNow) { // user cancelled save dialogue //qDebug() << "user cancelled before start"; stat = KScanDevice::Cancelled; emit sigScanFinished(stat); return (stat); } while (true) // loop while frames available { QApplication::setOverrideCursor(Qt::WaitCursor); // potential lengthy operation mSaneStatus = sane_start(mScannerHandle); if (mSaneStatus==SANE_STATUS_ACCESS_DENIED) // authentication failed? { //qDebug() << "retrying authentication"; clearSavedAuth(); // clear any saved password mSaneStatus = sane_start(mScannerHandle); // try again once more } if (mSaneStatus==SANE_STATUS_GOOD) { mSaneStatus = sane_get_parameters(mScannerHandle, &mSaneParameters); if (mSaneStatus==SANE_STATUS_GOOD) { #ifdef DEBUG_PARAMS dumpParams(QString("For frame %1:").arg(frames+1), &mSaneParameters); #endif // DEBUG_PARAMS // TODO: implement "Hand Scanner" support if (mSaneParameters.lines<1) { //qDebug() << "Hand Scanner not supported"; stat = KScanDevice::NotSupported; } else if (mSaneParameters.pixels_per_line==0) { //qDebug() << "Nothing to acquire!"; stat = KScanDevice::EmptyPic; } } else { stat = KScanDevice::OpenDevice; //qDebug() << "sane_get_parameters() error" << lastSaneErrorMessage(); } } else { stat = KScanDevice::OpenDevice; //qDebug() << "sane_start() error" << lastSaneErrorMessage(); } QApplication::restoreOverrideCursor(); if (stat==KScanDevice::Ok && mScanningState==KScanDevice::ScanStarting) { // first time through loop // Create image to receive scan, based on real SANE parameters stat = createNewImage(&mSaneParameters); // Create/reinitialise buffer for scanning one line if (stat==KScanDevice::Ok) { if (mScanBuf!=NULL) delete [] mScanBuf; mScanBuf = new SANE_Byte[mSaneParameters.bytes_per_line+4]; if (mScanBuf==NULL) stat = KScanDevice::NoMemory; } // can this ever happen? if (stat==KScanDevice::Ok) { int fd = 0; // Don't assume that sane_get_select_fd() will succeed even if // sane_set_io_mode() successfully sets I/O mode to noblocking - // bug 159300 if (sane_set_io_mode(mScannerHandle, SANE_TRUE)==SANE_STATUS_GOOD) { if (sane_get_select_fd(mScannerHandle, &fd)==SANE_STATUS_GOOD) { //qDebug() << "using read socket notifier"; mSocketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(mSocketNotifier, &QSocketNotifier::activated, this, &KScanDevice::doProcessABlock); } //else qDebug() << "not using socket notifier (sane_get_select_fd() failed)"; } //else qDebug() << "not using socket notifier (sane_set_io_mode() failed)"; } } if (stat!=KScanDevice::Ok) // some problem getting started { // Scanning could not start - give up now //qDebug() << "Scanning failed to start, status" << stat; emit sigScanFinished(stat); return (stat); } if (mScanningState==KScanDevice::ScanStarting) // first time through loop { QApplication::setOverrideCursor(Qt::BusyCursor); emit sigAcquireStart(); } emit sigScanProgress(0); // signal the progress dialog qApp->processEvents(); // update the progress window mPixelX = 0; mPixelY = 0; mBytesUsed = 0; // Set internal status to indicate scanning in progress. // This status might be changed, e.g. by pressing Stop on a // GUI dialog displayed during scanning. mScanningState = KScanDevice::ScanInProgress; // As originally coded, if using the socket notifier // sane_get_parameters() was only called once at the beginning of // the scan - just after sane_start() above. If not using the socket // notifier on the other hand, sane_get_parameters() was called after // each doProcessABlock() in the loop below. // // According to the SANE documentation text, sane_get_parameters() // needs to be called once after sane_start() to get the exact // parameters, but not necessarily in the reading loop that just // needs to call sane_read() repeatedly. The diagram above, though, // seems to imply that sane_get_parameters() should be done in the // reading loop. // // Doing the sane_get_parameters() just once seems to work with all // of the scanners that I have available to test, both using the // socket notifier and not. So that is what we now do, in this // much simpler loop. while (true) // loop for one scanned frame { if (mSocketNotifier!=NULL) // using the socket notifier { qApp->processEvents(); // let it fire away } else // not using socket notifier { doProcessABlock(); // may block while reading } // exit loop when frame done if (mScanningState==KScanDevice::ScanIdle || mScanningState==KScanDevice::ScanStopNow || mScanningState==KScanDevice::ScanNextFrame) break; } ++frames; // count up this frame // more frames to do if (mScanningState==KScanDevice::ScanNextFrame) continue; break; // scan done, exit loop } if (mScanningState==KScanDevice::ScanStopNow) { stat = KScanDevice::Cancelled; } else { if (mSaneStatus!=SANE_STATUS_GOOD && mSaneStatus!=SANE_STATUS_EOF) { stat = KScanDevice::ScanError; } } //qDebug() << "Scan read" << mBytesRead << "bytes in" //<< mBlocksRead << "blocks," << frames << "frames - status" << stat; emit sigScanFinished(stat); // scan is now finished return (stat); } /* This function calls at least sane_read and converts the read data from the scanner * to the qimage. * The function needs: * QImage img valid * the data-buffer set to a appropriate size **/ // TODO: probably needs to be extended for 16-bit scanner support void KScanDevice::doProcessABlock() { int val,i; QRgb col, newCol; SANE_Byte *rptr = NULL; SANE_Int bytes_read = 0; int chan = 0; mSaneStatus = SANE_STATUS_GOOD; uchar eight_pix = 0; if (mScanningState==KScanDevice::ScanIdle) return; // scan finished, no more to do // block notifications while working if (mSocketNotifier!=NULL) mSocketNotifier->setEnabled(false); while (true) { mSaneStatus = sane_read(mScannerHandle, (mScanBuf+mBytesUsed), mSaneParameters.bytes_per_line, &bytes_read); if (mSaneStatus!=SANE_STATUS_GOOD) { if (mSaneStatus!=SANE_STATUS_EOF) // this is OK, just stop { // any other error //qDebug() << "sane_read() error" << lastSaneErrorMessage() //<< "bytes read" << bytes_read; } break; } if (bytes_read<1) break; // no data, finish loop ++mBlocksRead; mBytesRead += bytes_read; // qDebug( "Bytes read: %d, bytes written: %d", bytes_read, mBytesUsed ); int red = 0; int green = 0; int blue = 0; rptr = mScanBuf; // start of scan data switch (mSaneParameters.format) { case SANE_FRAME_RGB: if (mSaneParameters.lines<1) break; bytes_read += mBytesUsed; // die übergebliebenen Bytes dazu mBytesUsed = bytes_read % 3; for (val = 0; val<((bytes_read-mBytesUsed)/3); val++) { red = *rptr++; green = *rptr++; blue = *rptr++; if (mPixelX>=mSaneParameters.pixels_per_line) { // reached end of a row mPixelX = 0; mPixelY++; } if (mPixelYheight()) // within image height { mScanImage->setPixel(mPixelX, mPixelY, qRgb(red, green, blue)); } mPixelX++; } for (val = 0; val=mSaneParameters.lines) break; if (mSaneParameters.depth==8) // Greyscale { if (mPixelX>=mSaneParameters.pixels_per_line) { // reached end of a row mPixelX = 0; mPixelY++; } mScanImage->setPixel(mPixelX, mPixelY, *rptr++); mPixelX++; } else // Lineart (bitmap) { // needs to be converted to byte eight_pix = *rptr++; for (i = 0; i<8; i++) { if (mPixelY0 ? 0 : 1; eight_pix = eight_pix << 1; mScanImage->setPixel(mPixelX, mPixelY, chan); mPixelX++; if( mPixelX>=mSaneParameters.pixels_per_line) { mPixelX = 0; mPixelY++; break; } } } } } break; case SANE_FRAME_RED: case SANE_FRAME_GREEN: case SANE_FRAME_BLUE: for (val = 0; val=mSaneParameters.pixels_per_line) { // reached end of a row mPixelX = 0; mPixelY++; } if (mPixelYpixel(mPixelX, mPixelY); red = qRed(col); green = qGreen(col); blue = qBlue(col); chan = *rptr++; switch (mSaneParameters.format) { case SANE_FRAME_RED: newCol = qRgba(chan, green, blue, 0xFF); break; case SANE_FRAME_GREEN: newCol = qRgba(red, chan, blue, 0xFF); break; case SANE_FRAME_BLUE: newCol = qRgba(red, green, chan, 0xFF); break; default: newCol = qRgba(0xFF, 0xFF, 0xFF, 0xFF); break; } mScanImage->setPixel(mPixelX, mPixelY, newCol); mPixelX++; } } break; default: //qDebug() << "Undefined SANE format" << mSaneParameters.format; break; } // switch of scan format if ((mSaneParameters.lines>0) && ((mSaneParameters.lines*mPixelY)>0)) { int progress = (int)(((double)MAX_PROGRESS)/mSaneParameters.lines*mPixelY); if (progress Parameter neu belegen und neu starten **/ mScanningState = KScanDevice::ScanNextFrame; //qDebug() << "EOF, but another frame to scan"; } } else if (mSaneStatus!=SANE_STATUS_GOOD) { mScanningState = KScanDevice::ScanIdle; //qDebug() << "Scan error or cancelled, status" << mSaneStatus; } if (mSocketNotifier!=NULL) mSocketNotifier->setEnabled(true); } void KScanDevice::slotScanFinished(KScanDevice::Status status) { if (mSocketNotifier!=NULL) // clean up if in use { delete mSocketNotifier; mSocketNotifier = NULL; } emit sigScanProgress(MAX_PROGRESS); QApplication::restoreOverrideCursor(); //qDebug() << "status" << status; if (mScanBuf!=NULL) { delete[] mScanBuf; mScanBuf = NULL; } if (status==KScanDevice::Ok && mScanImage!=NULL) { ImageMetaInfo info; info.setXResolution(mCurrScanResolutionX); info.setYResolution(mCurrScanResolutionY); info.setScannerName(mScannerName); // put the resolution also into the image itself // TODO: use qRound() mScanImage->setDotsPerMeterX(static_cast(mCurrScanResolutionX / 0.0254 + 0.5)); mScanImage->setDotsPerMeterY(static_cast(mCurrScanResolutionY / 0.0254 + 0.5)); if (mScanningPreview) { savePreviewImage(*mScanImage); emit sigNewPreview(mScanImage, &info); loadOptionSet(mSavedOptions); // restore original scan settings } else { emit sigNewImage(mScanImage, &info); } } sane_cancel(mScannerHandle); /* This follows after sending the signal */ if (mScanImage!=NULL) { delete mScanImage; mScanImage = NULL; } mScanningState = KScanDevice::ScanIdle; } // Configuration // ------------- void KScanDevice::saveStartupConfig() { if (mScannerName.isNull()) return; // do not save for no scanner KScanOptSet optSet(KScanOptSet::startupSetName()); getCurrentOptions(&optSet); optSet.saveConfig(mScannerName, i18n("Default startup configuration")); } void KScanDevice::loadOptionSetInternal(const KScanOptSet *optSet, bool prio) { for (KScanOptSet::const_iterator it = optSet->constBegin(); it!=optSet->constEnd(); ++it) { const QByteArray name = it.key(); if (!optionExists(name)) continue; // only for options that exist KScanOption *so = getOption(name, false); if (so==NULL) continue; // we don't have this option if (so->isGroup()) continue; // nothing to do here if (so->isPriorityOption() ^ prio) continue; // check whether requested priority so->set(it.value()); if (so->isInitialised() && so->isSoftwareSettable() && so->isActive()) so->apply(); } } void KScanDevice::loadOptionSet(const KScanOptSet *optSet) { if (optSet==NULL) return; //qDebug() << "Loading set" << optSet->getSetName() << "with" << optSet->count() << "options"; loadOptionSetInternal(optSet, true); loadOptionSetInternal(optSet, false); } // Retrieve the current options from the scanner, i.e. all of those that // have an associated GUI element and also any others (e.g. the TL_X and // other scan area settings) that have been apply()'ed but do not have // a GUI. void KScanDevice::getCurrentOptions(KScanOptSet *optSet) const { if (optSet==NULL) return; for (OptionHash::const_iterator it = mCreatedOptions.constBegin(); it!=mCreatedOptions.constEnd(); ++it) { KScanOption *so = it.value(); if (!so->isReadable()) continue; if (so->isGuiElement() || so->isApplied()) { if (so->isActive()) optSet->backupOption(so); so->setApplied(false); } } } KConfigGroup KScanDevice::configGroup(const QString &groupName) { Q_ASSERT(!groupName.isEmpty()); return (ScanSettings::self()->config()->group(groupName)); } // SANE Authentication // ------------------- // // According to the SANE documentation, this may be requested for any use of // sane_open(), sane_control_option() or sane_start() on a scanner device // that requires authentication. // // The only uses of sane_open() and sane_start() are here in this file, and // they set the current scanner using setScanDevice() before performing the // SANE operation. // // This does not happen for all uses of sane_control_option(), either here or // in KScanOption, so there is a slight possibility that if authentication is // needed for those (and has not been previously requested by sane_open() or // sane_start()) then it will use the wrong scanner device or will not prompt // at all. However, Kooka only supports one scanner open at a time, and does // sane_open() before any use of sane_control_option(). So hopefully this // will not be a problem. bool KScanDevice::authenticate(QByteArray *retuser, QByteArray *retpass) { //qDebug() << "for" << mScannerName; // TODO: use KWallet for username/password? KConfigGroup grp = configGroup(mScannerName); QByteArray user = QByteArray::fromBase64(grp.readEntry("user", QString()).toLocal8Bit()); QByteArray pass = QByteArray::fromBase64(grp.readEntry("pass", QString()).toLocal8Bit()); if (!user.isEmpty() && !pass.isEmpty()) { //qDebug() << "have saved username/password"; } else { //qDebug() << "asking for username/password"; KPasswordDialog dlg(NULL, KPasswordDialog::ShowKeepPassword|KPasswordDialog::ShowUsernameLine); dlg.setPrompt(xi18nc("@info", "The scanner%1requires authentication.", mScannerName.constData())); dlg.setWindowTitle(i18n("Scanner Authentication")); if (!user.isEmpty()) dlg.setUsername(user); if (!pass.isEmpty()) dlg.setPassword(pass); if (!dlg.exec()) return (false); user = dlg.username().toLocal8Bit(); pass = dlg.password().toLocal8Bit(); if (dlg.keepPassword()) { grp.writeEntry("user", user.toBase64()); grp.writeEntry("pass", pass.toBase64()); } } *retuser = user; *retpass = pass; return (true); } void KScanDevice::clearSavedAuth() { KConfigGroup grp = configGroup(mScannerName); grp.deleteEntry("user"); grp.deleteEntry("pass"); grp.sync(); } // Error reporting // --------------- QString KScanDevice::lastSaneErrorMessage() const { return (sane_strstatus(mSaneStatus)); } QString KScanDevice::statusMessage(KScanDevice::Status stat) { switch (stat) { case KScanDevice::Ok: return (i18n("OK")); // shouldn't be reported case KScanDevice::NoDevice: return (i18n("No device")); // never during scanning case KScanDevice::ParamError: return (i18n("Bad parameter")); case KScanDevice::OpenDevice: return (i18n("Cannot open device")); case KScanDevice::ControlError: return (i18n("sane_control_option() failed")); case KScanDevice::ScanError: return (i18n("Error while scanning")); case KScanDevice::EmptyPic: return (i18n("Empty picture")); case KScanDevice::NoMemory: return (i18n("Out of memory")); case KScanDevice::Reload: return (i18n("Needs reload")); // never during scanning case KScanDevice::Cancelled: return (i18n("Cancelled")); // shouldn't be reported case KScanDevice::OptionNotActive: return (i18n("Not active")); // never during scanning case KScanDevice::NotSupported: return (i18n("Not supported")); default: return (i18n("Unknown status %1", stat)); } } diff --git a/libkookascan/kscandevice.h b/libkookascan/kscandevice.h index cef82a1..fe75f56 100644 --- a/libkookascan/kscandevice.h +++ b/libkookascan/kscandevice.h @@ -1,628 +1,628 @@ /* This file is part of the KDE Project -*- mode:c++; -*- Copyright (C) 1999 Klaas Freitag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSCANDEVICE_H #define KSCANDEVICE_H #include "kookascan_export.h" #include #include #include #include #include #include #include extern "C" { #include } class QWidget; class QSocketNotifier; class KConfigSkeletonItem; class KScanOption; class KScanOptSet; class ImageMetaInfo; /** * @short Access and control a scanner. * * After constructing a @c KScanDevice, a scanner may be opened * via @c openDevice() by specifying its SANE backend name. Having * done so, the scanner options supported by the scanner may be set * and read individually using a @c KScanOption. * * A scan or preview may be initiated, once the scan is finished the * scanned image data is made available via a signal. * * The scan device may be closed (using @c closeDevice()) and opened * again to change the scanner that is accessed. * * @author Klaas Freitag * @author Jonathan Marten **/ class KOOKASCAN_EXPORT KScanDevice : public QObject { Q_OBJECT public: /** * Scanning/control status. **/ enum Status { Ok, NoDevice, ParamError, OpenDevice, ControlError, ScanError, EmptyPic, NoMemory, Reload, Cancelled, OptionNotActive, NotSupported }; /** * Construct a new scanner device object. * * @param parent The parent object **/ explicit KScanDevice(QObject *parent); /** * Destructor. **/ ~KScanDevice(); /** * Open a scanner device. * * @param backend SANE name of the backend to open * @return The status of the operation * * @note This operation may prompt for SANE authentication if * it is required. **/ KScanDevice::Status openDevice(const QByteArray &backend); /** * Close the scanner device and frees all related data. * After doing so, a new scanner may be opened. **/ void closeDevice(); /** * Get the device name of the current scanner backend. * It has the format, for example, "umax:/dev/sg1". * * @return The scanner backend name, or a null string if no scanner * is currently open. **/ const QByteArray &scannerBackendName() const { return (mScannerName); } /** * Get a readable name/description of the current scanner * backend. It may read, for example, "Mustek SP1200 Flatbed scanner". * * @return The scanner description. If no scanner is currently open, * the (I18N'ed) string "No scanner connected" is returned. **/ QString scannerDescription() const; /** * Check whether a scanner device is opened and connected: that is, * whether the @c openDevice() returned @c KScanDevice::Ok). **/ bool isScannerConnected() const { return (mScannerInitialised); } /** * Get the SANE scanner handle of the current scanner. * * @return The scanner handle, or @c NULL if no scanner is * currently open. **/ SANE_Handle scannerHandle() const { return (mScannerHandle); } /** * Acquires a preview from the current scanner device. * When the scan is complete, a result image will be sent by * @c sigNewPreview. * * @param forceGray If this is @c true, the preview is acquired * in greyscale regardless of any other settings. * @param dpi Resolution setting for the preview. If this is 0, * a suitable low resolution is selected. * @return The status of the operation **/ KScanDevice::Status acquirePreview(bool forceGray = false, int dpi = 0); /** * Acquires a scan from the current scanner device. * When the scan is complete, a result image will be sent by * @c sigNewImage. * * @param filename File name to load, for virtual scanner test mode. * If this is a null or empty string, a real scan is performed. * @return The status of the operation **/ KScanDevice::Status acquireScan(const QString &filename = QString::null); /** * Get the standard file name for saving the preview image for the * current scanner. * * @return Preview file name * * @note Saving the preview image this does not actually work unless a * subdirectory called @c previews exists within the calling application's * @c data resource directory. * @see loadPreviewImage * @see savePreviewImage * @see KStandardDirs **/ const QString previewFile() const; /** * Load the last saved preview image for this device from the saved file. * * @return The preview image, or a null image if none could be loaded. * * @see previewFile * @see savePreviewImage **/ QImage loadPreviewImage(); /** * Saves a preview image for this device to the standard save file. * * @param image Preview image which is to be saved * @return @c true if the saving was successful * * @see previewFile * @see loadPreviewImage * @note For historical reasons, the image is saved in BMP format. **/ bool savePreviewImage(const QImage &image) const; /** * Get the names of all of the parameters supported by the * current scanner device. * * @return A list of all known parameter names, in order of their SANE * index. * @see getCommonOptions * @see getAdvancedOptions **/ QList getAllOptions() const; /** * Get the common options of the device. An application will normally * display these options in a convenient and easy to access way. * * @return A list of the names of the common options. * @see getAllOptions * @see getAdvancedOptions **/ QList getCommonOptions() const; /** * Get the advanced options of the device. An application may hide * these options away in a less obvious place. * * @return A list of the names of the advanced options. * @see getAllOptions * @see getCommonOptions **/ QList getAdvancedOptions() const; /** * Retrieve the currently active parameters from the scanner. * This includes all of those that have an associated GUI element, * and also any others (e.g. the TL_X and 3 other scan area settings) * that have been @c apply()'ed but do not have a GUI. * * @param optSet An option set which will receive the options **/ void getCurrentOptions(KScanOptSet *optSet) const; /** * Load a saved parameter set. All options that exist in the set * and which the current scanner supports will be @c set() * with the values from the @c KScanOptSet and @c apply()'ed. * * @param optSet An option set with the options to be loaded **/ void loadOptionSet(const KScanOptSet *optSet); /** * Check whether the current scanner device supports an option. * * @param name The name of a scanner parameter * @return @c true if the option is known by this scanner **/ bool optionExists(const QByteArray &name) const; /** * Resolve a backend-specific option name into a generally known * SANE option name. See the source code for those names supported. * * @param name The backend-specific parameter name * @return The generally known SANE name, or the input @p name * unchanged if it has no known alias. **/ QByteArray aliasName(const QByteArray &name) const; /** * Create a @c KScanOption and GUI widget suitable for the specified * scanner parameter. * * @param name Name of the SANE parameter. * @param parent Parent for widget. * @return A @c KScanOption for the parameter, or @c NULL if none * could be created. * * @see KScanOption::createWidget * @see KScanOption::widget * @see getExistingGuiElement **/ KScanOption *getGuiElement(const QByteArray &name, QWidget *parent); /** * Find the @c KScanOption for an existing scanner parameter. * * @param name Name of the SANE parameter. * @return The @c KScanOption for the parameter, or @c NULL if the * parameter does not exist or it has not been created yet. **/ KScanOption *getExistingGuiElement(const QByteArray &name) const; /** * Create or retrieve a @c KScanOption for the specified scanner * parameter. * * @param name Name of the SANE parameter. * @param create If the option does not yet exist, then create it if * this is @c true (the default). If this is @c false, then do not * create the option if it does not already exist but return NULL * instead. * @return The existing or created option, or @c NULL if @p create * is @c false and the option does not currently exist. * @note This is the only means for a calling library or application * to create or obtain a @c KScanOption. * @note The returned pointer is valid until the scanner device is * closed or the @c KScanDevice object is destroyed. **/ KScanOption *getOption(const QByteArray &name, bool create = true); /** * Set the enabled/disabled state of the GUI widget for an option. * * @param name Name of the SANE parameter. * @param state @c true to enable the widget, @c false to disable it. * * @note If the option is not software settable, it will be disabled * regardless of the @p state parameter. * @see KScanOption::isSoftwareSettable **/ void guiSetEnabled(const QByteArray &name, bool state); /** * Get the maximum scan area size. This can be used, for example, to * set the size of a preview widget. * * @return The width and height of the scan area, in SANE units * as returned by the backend. * @note Currently it is assumed that these units are always * millimetres, although according to the SANE documentation it is * possible that the unit may be pixels instead. **/ QSize getMaxScanSize(); /** * Get the current scan image format and bit depth. * * @param format An integer to receive the format, this is a * @c SANE_Frame value. * @param depth An integer to receive the bit depth (the number of bits * per sample). **/ void getCurrentFormat(int *format, int *depth); /** * Access a group in the global scanner configuration file. * * @param groupName The group name * @return the group */ static KConfigGroup configGroup(const QString &groupName); /** * Get the global default value for a scanner configuration setting. * * @param item The settings template item * @return The default value **/ template static T getDefault(const KConfigSkeletonItem *item) { return (static_cast *>(item)->value()); } /** * Read a configuration setting for the current scanner * from the global scanner configuration file. * * @param item The settings template item - * @return The configuration setting, or its default value if none i + * @return The configuration setting, or its default value if none is * saved in the configuration. * * @see storeConfig **/ template T getConfig(const KConfigSkeletonItem *item) const { const KConfigGroup grp = configGroup(mScannerName); return (grp.readEntry(item->key(), getDefault(item))); } /** * Save a configuration setting for the current scanner * to the global scanner configuration file. * * @param item The settings template item * @param val Value to store * * @see getConfig **/ template void storeConfig(const KConfigSkeletonItem *item, const T &val) { if (mScannerName.isNull()) return; //kDebug() << "Storing config" << key << "in group" << mScannerName; KConfigGroup grp = configGroup(mScannerName); grp.writeEntry(item->key(), val); grp.sync(); } /** * Retrieve or prompt for a username/password to authenticate access * to the scanner. If there is a saved username/password pair for * the current scanner, these will be returned. Otherwise, * the user is prompted to enter a username/password (via a * @c KPasswordDialog) and the entries are saved in the global * scanner configuration file. * * @param retuser String to receive the user name * @param retpass String to receive the password * @return @c true if a saved username/password was available or * the user entered them, @c false if no saved username/password * was available and the user cancelled the password dialogue. * * @note This will normally be called by @c KScanGlobal::authCallback() * when a SANE operation requires authentication. **/ bool authenticate(QByteArray *retuser, QByteArray *retpass); /** * Get an error message for the last SANE operation, if it failed. * * @return the error message string **/ QString lastSaneErrorMessage() const; /** * Get an error message for the specified scan result status. * * @return the error message string **/ static QString statusMessage(KScanDevice::Status stat); /** * Find the SANE index of an option. * * @param name Name of the SANE parameter. * @return Its option index, or 0 if the option is not known by the scanner. **/ int getOptionIndex(const QByteArray &name) const; /** * Update the scanner with the value of the specified option (using * @c KScanOption::apply()), then reload and update the GUI widget * of all of the other options if necessary. Their value or state * may have changed due to the change in this option; if so, SANE * will tell us to reload them. * * @param opt The option to @c apply(). If this is @c NULL, this * function is equivalent to @c reloadAllOptions(). * * @note Even if a reload is needed, the scanner option given as the * @p opt parameter is not reloaded. This is ensures that no recursion * happens while reloading the options. * @see reloadAllOptions **/ void applyOption(KScanOption *opt); /** * Reload all of the scanner options and update their GUI widgets. * * @see applyOption **/ void reloadAllOptions(); public slots: /** * Request the scan device to stop the scan currently in progress. * The scan may continue until the current data block has been * read and processed. **/ void slotStopScanning(); signals: /** * Emitted to indicate that a scan is about to start. * Depending on the scanner, there may be a delay (for example, * while the lamp warms up) between this signal and the * @c sigAcquireStart. * * @param info Image information, if it is currently available * @see sigAcquireStart * * @note The @p info parameter may be NULL, or it may not contain * any useful format information. **/ void sigScanStart(const ImageMetaInfo *info); /** * Emitted to indicate that a scan is starting to acquire data. * The first @c sigScanProgress will be emitted with progress value 0 * immediately after this signal. * @see sigScanStart **/ void sigAcquireStart(); /** * Emitted to indicate the scanning progress. The first value sent * is always 0 and the final value is always 100. If the scan results * in multiple frames (for a 3-pass scanner) the sequence will * repeat. * * @param progress The scan progress as a percentage **/ void sigScanProgress(int progress); /** * Emitted when a new scan image has been acquired. * The receiver should take a deep copy of the image, as it will * be destroyed after this signal has been delivered. * * @param img The acquired scan image * @param info Additional information for the image * @see sigNewPreview **/ void sigNewImage(const QImage *img, const ImageMetaInfo *info); /** * Emitted when a new preview image has been acquired. * The receiver should take a deep copy of the image, as it will * be destroyed after this signal has been delivered. * * @param img The acquired preview image * @param info Additional information for the image * @see sigNewImage **/ void sigNewPreview(const QImage *img, const ImageMetaInfo *info); /** * Emitted to indicate that a scan or preview has finished. * This signal is always emitted, even if the scan failed with * an error or was cancelled, and before the @c sigNewImage or * the @c sigNewPreview. * * @param stat Status of the scan **/ void sigScanFinished(KScanDevice::Status stat); /** * Emitted when the device is about to be closed by @c closeDevice(). * This gives any callers using this device a chance to give up any * dependencies on it. While this signal is being emitted, the * scanner device is still open and valid. **/ void sigCloseDevice(); private slots: void doProcessABlock(); void slotScanFinished(KScanDevice::Status status); private: KScanDevice::Status findOptions(); void showOptions(); void loadOptionSetInternal(const KScanOptSet *optSet, bool prio); void applyAllOptions(bool prio); KScanDevice::Status createNewImage(const SANE_Parameters *p); KScanDevice::Status acquireData(bool isPreview = false); /** * Clear any saved authentication for this scanner, to ensure that the * user is prompted again next time. **/ void clearSavedAuth(); /** * Save the current option parameter set. * Only active and GUI options are saved. **/ void saveStartupConfig(); /** * Scanning progress state. **/ enum ScanningState { // only used by KScanDevice ScanIdle, ScanStarting, ScanInProgress, ScanNextFrame, ScanStopNow, ScanStopAdfFinished }; typedef QHash OptionHash; typedef QMap IndexMap; OptionHash mCreatedOptions; // option name -> KScanOption IndexMap mKnownOptions; // SANE index -> option name KScanOptSet *mSavedOptions; QByteArray mScannerName; bool mScannerInitialised; SANE_Handle mScannerHandle; KScanDevice::ScanningState mScanningState; SANE_Status mSaneStatus; SANE_Byte *mScanBuf; QImage *mScanImage; ImageMetaInfo *mImageInfo; SANE_Parameters mSaneParameters; long mBytesRead; long mBlocksRead; int mBytesUsed; int mPixelX, mPixelY; bool mScanningPreview; QSocketNotifier *mSocketNotifier; int mCurrScanResolutionX; int mCurrScanResolutionY; }; #endif // KSCANDEVICE_H diff --git a/libkookascan/kscanoption.cpp b/libkookascan/kscanoption.cpp index b4e3e0e..1d83d75 100644 --- a/libkookascan/kscanoption.cpp +++ b/libkookascan/kscanoption.cpp @@ -1,1108 +1,1108 @@ /* This file is part of the KDE Project Copyright (C) 2000 Klaas Freitag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kscanoption.h" #include #include #include #include #include #include #include #include #include extern "C" { #include #include } #include "kgammatable.h" #include "kscandevice.h" #include "kscancontrols.h" #include "kscanoptset.h" // Debugging options #undef DEBUG_MEM #undef DEBUG_GETSET #define DEBUG_APPLY #undef DEBUG_RELOAD // This defines the possible resolutions that will be shown by the combo. // Only resolutions from this list falling within the scanner's allowed range // will be included. static const int resList[] = { 50, 75, 100, 150, 200, 300, 600, 900, 1200, 1800, 2400, 4800, 9600, 0 }; KScanOption::KScanOption(const QByteArray &name, KScanDevice *scandev) { mScanDevice = scandev; if (!initOption(name)) { //qDebug() << "initOption for" << name << "failed!"; return; } if (!mIsReadable) return; // no value to read if (mBuffer.isNull()) return; // no buffer for value // read initial value from the scanner SANE_Status sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_GET_VALUE, mBuffer.data(), NULL); if (sanestat==SANE_STATUS_GOOD) mBufferClean = false; } static bool shouldBePriorityOption(const QByteArray &name) { return (name=="source"); } bool KScanOption::initOption(const QByteArray &name) { mDesc = NULL; mControl = NULL; mIsGroup = false; mIsReadable = true; mIsPriority = shouldBePriorityOption(name); mWidgetType = KScanOption::Invalid; if (name.isEmpty()) return (false); mName = name; // Look up the option (which must already exist in the map) by name. // // The default-constructed index is 0 for an invalid option, this is OK // because although a SANE option with that index exists it is never // requested by name. mIndex = mScanDevice->getOptionIndex(mName); if (mIndex<=0) { //qDebug() << "no option descriptor for" << mName; return (false); } mDesc = sane_get_option_descriptor(mScanDevice->scannerHandle(), mIndex); if (mDesc==NULL) return (false); mBuffer.resize(0); mBufferClean = true; mApplied = false; if (mDesc->type==SANE_TYPE_GROUP) mIsGroup = true; if (mIsGroup || mDesc->type==SANE_TYPE_BUTTON) mIsReadable = false; if (!(mDesc->cap & SANE_CAP_SOFT_DETECT)) mIsReadable = false; mGammaTable = NULL; // for recording gamma values mWidgetType = resolveWidgetType(); // work out the type of widget allocForDesc(); // allocate initial buffer return (true); } KScanOption::~KScanOption() { #ifdef DEBUG_MEM if (!mBuffer.isNull()) //qDebug() << "Freeing" << mBuffer.size() << "bytes for" << mName; #endif // TODO: need to delete mControl here? } void KScanOption::slotWidgetChange(const QString &t) { set(t.toUtf8()); emit guiChange(this); } void KScanOption::slotWidgetChange(int i) { set(i); emit guiChange(this); } void KScanOption::slotWidgetChange() { set(1); emit guiChange(this); } void KScanOption::updateList() { KScanCombo *combo = qobject_cast(mControl); if (combo==NULL) return; QList list = getList(); combo->setList(list); } /* called on a widget change, if a widget was created. */ void KScanOption::redrawWidget() { if (!isValid() || !isReadable() || mControl==NULL || mBuffer.isNull()) return; KScanControl::ControlType type = mControl->type(); if (type==KScanControl::Number) // numeric control { int i = 0; if (get(&i)) mControl->setValue(i); } else if (type==KScanControl::Text) // text control { mControl->setText(get()); } } /* Get and update the current setting from the scanner */ void KScanOption::reload() { if (mControl!=NULL) { if (isGroup()) { mControl->setEnabled(true); // always enabled return; // no more to do } if (!isActive()) { #ifdef DEBUG_RELOAD qDebug() << "not active" << mName; #endif mControl->setEnabled(false); } else if (!isSoftwareSettable()) { #ifdef DEBUG_RELOAD qDebug() << "not settable" << mName; #endif mControl->setEnabled(false); } else mControl->setEnabled(true); } if (!isReadable()) { #ifdef DEBUG_RELOAD qDebug() << "not readable" << mName; #endif return; } if (mBuffer.isNull()) // first get mem if needed { qDebug() << "need to allocate now"; allocForDesc(); // allocate the buffer now } if (!isActive()) return; if (mDesc->size>mBuffer.size()) { //qDebug() << "buffer too small for" << mName << "type" << mDesc->type //<< "size" << mBuffer.size() << "need" << mDesc->size; allocForDesc(); // grow the buffer } // Starting with SANE 1.0.20, the 'test' device needs this dummy call of // sane_get_option_descriptor() before any use of sane_control_option(), // otherwise the latter fails with a SANE_STATUS_INVAL. From the master // repository at http://anonscm.debian.org/gitweb/?p=sane/sane-backends.git: // // author m. allan noah // Thu, 26 Jun 2008 13:14:23 +0000 (13:14 +0000) // commit 8733651c4b07ac6ccbcee0d39eccca0c08057729 // test backend checks for options that have not been loaded before being controlled // // I'm hoping that this is not in general an expensive operation - it certainly // is not so for the 'test' device and a sample of others - so it should not be // necessary to conditionalise it for that device only. const SANE_Option_Descriptor *desc = sane_get_option_descriptor(mScanDevice->scannerHandle(), mIndex); if (desc==NULL) return; // should never happen SANE_Status sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_GET_VALUE, mBuffer.data(), NULL); if (sanestat!=SANE_STATUS_GOOD) { qWarning() << "Can't get value for" << mName << "status" << sane_strstatus(sanestat); return; } updateList(); // if range changed, update GUI #ifdef DEBUG_RELOAD qDebug() << "reloaded" << mName; #endif mBufferClean = false; } bool KScanOption::apply() { int sane_result = 0; bool reload = false; #ifdef DEBUG_APPLY QString debug = QString("option '%1'").arg(mName.constData()); #endif // DEBUG_APPLY SANE_Status sanestat = SANE_STATUS_GOOD; // See comment in reload() above const SANE_Option_Descriptor *desc = sane_get_option_descriptor(mScanDevice->scannerHandle(), mIndex); if (desc==NULL) return (false); // should never happen if (mName==SANE_NAME_PREVIEW || mName==SANE_NAME_SCAN_MODE) { sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_SET_AUTO, 0, &sane_result); /* No return here, please! Carsten, does it still work than for you? */ } if (!isInitialised() || mBuffer.isNull()) { #ifdef DEBUG_APPLY debug += " nobuffer"; #endif // DEBUG_APPLY if (!isAutoSettable()) goto ret; #ifdef DEBUG_APPLY debug += " auto"; #endif // DEBUG_APPLY sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_SET_AUTO, 0, &sane_result); } else { if (!isActive()) { #ifdef DEBUG_APPLY debug += " notactive"; #endif // DEBUG_APPLY goto ret; } else if (!isSoftwareSettable()) { #ifdef DEBUG_APPLY debug += " notsettable"; #endif // DEBUG_APPLY goto ret; } else { sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_SET_VALUE, mBuffer.data(), &sane_result); } } if (sanestat!=SANE_STATUS_GOOD) { //qDebug() << "apply" << mName << "failed, SANE status" << sane_strstatus(sanestat); return (false); } #ifdef DEBUG_APPLY debug += QString(" -> '%1'").arg(get().constData()); #endif // DEBUG_APPLY if (sane_result & SANE_INFO_RELOAD_OPTIONS) { #ifdef DEBUG_APPLY debug += " reload"; #endif // DEBUG_APPLY reload = true; } #ifdef DEBUG_APPLY if (sane_result & SANE_INFO_INEXACT) debug += " inexact"; #endif // DEBUG_APPLY mApplied = true; ret: #ifdef DEBUG_APPLY qDebug() << qPrintable(debug); // no quotes, please #endif // DEBUG_APPLY return (reload); } // The name of the option is checked here to detect options which are // a resolution or a gamma table, and therefore are to be treated // specially. This should hopefully be more reliable then the earlier // heuristics. KScanOption::WidgetType KScanOption::resolveWidgetType() const { if (!isValid()) return (KScanOption::Invalid); KScanOption::WidgetType ret; switch (mDesc->type) { case SANE_TYPE_BOOL: ret = KScanOption::Bool; break; case SANE_TYPE_INT: case SANE_TYPE_FIXED: - if (QString::compare(mDesc->name, SANE_NAME_SCAN_RESOLUTION)==0 || - QString::compare(mDesc->name, SANE_NAME_SCAN_X_RESOLUTION)==0 || - QString::compare(mDesc->name, SANE_NAME_SCAN_Y_RESOLUTION)==0) + if (qstrcmp(mDesc->name, SANE_NAME_SCAN_RESOLUTION)==0 || + qstrcmp(mDesc->name, SANE_NAME_SCAN_X_RESOLUTION)==0 || + qstrcmp(mDesc->name, SANE_NAME_SCAN_Y_RESOLUTION)==0) { ret = KScanOption::Resolution; if (mDesc->unit!=SANE_UNIT_DPI) qDebug() << "expected" << mName << "unit" << mDesc->unit << "to be DPI"; } - else if (QString::compare(mDesc->name, SANE_NAME_GAMMA_VECTOR)==0 || - QString::compare(mDesc->name, SANE_NAME_GAMMA_VECTOR_R)==0 || - QString::compare(mDesc->name, SANE_NAME_GAMMA_VECTOR_G)==0 || - QString::compare(mDesc->name, SANE_NAME_GAMMA_VECTOR_B)==0) + else if (qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR)==0 || + qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR_R)==0 || + qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR_G)==0 || + qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR_B)==0) { ret = KScanOption::GammaTable; if (mDesc->size!=sizeof(SANE_Byte)) qDebug() << "expected" << mName << "size" << mDesc->size << "to be BYTE"; } else if (mDesc->constraint_type==SANE_CONSTRAINT_RANGE) ret = KScanOption::Range; else if (mDesc->constraint_type==SANE_CONSTRAINT_WORD_LIST) ret = KScanOption::StringList; else if (mDesc->constraint_type==SANE_CONSTRAINT_NONE) ret = KScanOption::SingleValue; else ret = KScanOption::Invalid; break; case SANE_TYPE_STRING: - if (QString::compare(mDesc->name, SANE_NAME_FILE)==0) ret = KScanOption::File; + if (qstrcmp(mDesc->name, SANE_NAME_FILE)==0) ret = KScanOption::File; else if (mDesc->constraint_type==SANE_CONSTRAINT_STRING_LIST) ret = KScanOption::StringList; else ret = KScanOption::String; break; case SANE_TYPE_BUTTON: ret = KScanOption::Button; break; case SANE_TYPE_GROUP: ret = KScanOption::Group; break; default: qDebug() << "unsupported SANE type" << mDesc->type; ret = KScanOption::Invalid; break; } #ifdef DEBUG_GETSET qDebug() << "for SANE type" << mDesc->type << "returning" << ret; #endif return (ret); } bool KScanOption::set(int val) { if (!isValid() || mBuffer.isNull()) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "to" << val; #endif int word_size; QVector qa; SANE_Word sw; switch (mDesc->type) { case SANE_TYPE_BUTTON: // Activate a button case SANE_TYPE_BOOL: // Assign a Boolean value sw = (val ? SANE_TRUE : SANE_FALSE); mBuffer = QByteArray(((const char *) &sw),sizeof(SANE_Word)); break; case SANE_TYPE_INT: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = static_cast(val); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; case SANE_TYPE_FIXED: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = SANE_FIX(static_cast(val)); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBufferClean = false; return (true); } bool KScanOption::set(double val) { if (!isValid() || mBuffer.isNull()) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "to" << val; #endif int word_size; QVector qa; SANE_Word sw; switch (mDesc->type) { case SANE_TYPE_BOOL: // Assign a Boolean value sw = (val>0 ? SANE_TRUE : SANE_FALSE); mBuffer = QByteArray(((const char *) &sw),sizeof(SANE_Word)); break; case SANE_TYPE_INT: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = static_cast(val); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; case SANE_TYPE_FIXED: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = SANE_FIX(val); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBufferClean = false; return (true); } bool KScanOption::set(const int *val, int size) { if (!isValid() || mBuffer.isNull()) return (false); if (val==NULL) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "of size" << size; #endif int offset = 0; int word_size = mDesc->size/sizeof(SANE_Word); QVector qa(1+word_size); /* add 1 in case offset is needed */ #if 0 if( mDesc->constraint_type == SANE_CONSTRAINT_WORD_LIST ) { /* That means that the first entry must contain the size */ //qDebug() << "Size" << size << "word_size" << word_size << "mDescr-size"<< mDesc->size; qa[0] = (SANE_Word) 1+size; //qDebug() << "set length field to" << qa[0]; offset = 1; } #endif switch (mDesc->type) { case SANE_TYPE_INT: for (int i = 0; isize; if (offset) copybyte += sizeof(SANE_Word); //kdDebug(29000) << "Copying " << copybyte << " byte to options buffer" << endl; mBuffer = QByteArray(((const char *) qa.data()),copybyte); mBufferClean = false; return (true); } bool KScanOption::set(const QByteArray &buf) { if (!isValid() || mBuffer.isNull()) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "to" << buf; #endif int val; int origSize; // Check whether the string value looks like a gamma table specification. // If it is, then convert it to a gamma table and set that. KGammaTable gt; if (gt.setFromString(buf)) { #ifdef DEBUG_GETSET qDebug() << "is a gamma table"; #endif return (set(>)); } /* On String-type the buffer gets malloced in Constructor */ switch (mDesc->type) { case SANE_TYPE_STRING: origSize = mBuffer.size(); mBuffer = QByteArray(buf.data(),(buf.length()+1)); mBuffer.resize(origSize); // restore original size break; case SANE_TYPE_INT: case SANE_TYPE_FIXED: bool ok; val = buf.toInt(&ok); if (ok) set(&val,1); else { //qDebug() << "Conversion of string value" << buf << "failed!"; return (false); } break; case SANE_TYPE_BOOL: val = (buf=="true") ? 1 : 0; set(val); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBufferClean = false; return (true); } // The parameter here must be 'const'. // Otherwise, a call of set() with a 'const KGammaTable *' argument appears // to be silently resolved to a call of set(int) without warning. bool KScanOption::set(const KGammaTable *gt) { if (!isValid() || mBuffer.isNull()) return (false); // Remember the set values if (mGammaTable!=NULL) delete mGammaTable; mGammaTable = new KGammaTable(*gt); int size = mDesc->size/sizeof(SANE_Word); // size of scanner gamma table #ifdef DEBUG_GETSET qDebug() << "Setting gamma table for" << mName << "size" << size << "to" << gt->toString(); #endif const int *run = mGammaTable->getTable(size); // get table of that size QVector qa(size); // converted to SANE values switch (mDesc->type) { case SANE_TYPE_INT: for (int i = 0; i(run[i]); break; case SANE_TYPE_FIXED: for (int i = 0; i(run[i])); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBuffer = QByteArray(((const char *) (qa.constData())), mDesc->size); mBufferClean = false; return (true); } bool KScanOption::get(int *val) const { if (!isValid() || mBuffer.isNull()) return (false); SANE_Word sane_word; double d; switch (mDesc->type) { case SANE_TYPE_BOOL: /* Buffer has a SANE_Word */ sane_word = *((SANE_Word *) mBuffer.data()); *val = (sane_word==SANE_TRUE ? 1 : 0); break; case SANE_TYPE_INT: /* reading just the first is OK */ sane_word = *((SANE_Word *) mBuffer.data()); *val = sane_word; break; case SANE_TYPE_FIXED: /* reading just the first is OK */ d = SANE_UNFIX(*((SANE_Word *) mBuffer.data())); *val = static_cast(d); break; default: //qDebug() << "Can't get" << mName << "as this type"; return (false); } #ifdef DEBUG_GETSET qDebug() << "Returning" << mName << "as" << *val; #endif return (true); } QByteArray KScanOption::get() const { if (!isValid() || mBuffer.isNull()) return (""); QByteArray retstr; SANE_Word sane_word; /* Handle gamma-table correctly */ if (mWidgetType==KScanOption::GammaTable) { if (mGammaTable!=NULL) retstr = mGammaTable->toString().toLocal8Bit(); } else { switch (mDesc->type) { case SANE_TYPE_BOOL: sane_word = *((SANE_Word *) mBuffer.data()); retstr = (sane_word==SANE_TRUE ? "true" : "false"); break; case SANE_TYPE_STRING: retstr = (const char *) mBuffer.data(); break; case SANE_TYPE_INT: sane_word = *((SANE_Word *) mBuffer.data()); retstr.setNum(sane_word); break; case SANE_TYPE_FIXED: sane_word = (SANE_Word) SANE_UNFIX(*((SANE_Word *) mBuffer.data())); retstr.setNum(sane_word); break; default: //qDebug() << "Can't get" << mName << "as this type"; retstr = "?"; break; } } #ifdef DEBUG_GETSET qDebug() << "Returning" << mName << "as" << retstr; #endif return (retstr); } bool KScanOption::get(KGammaTable *gt) const { if (mGammaTable==NULL) return (false); // has not been set gt->setAll(mGammaTable->getGamma(), mGammaTable->getBrightness(), mGammaTable->getContrast()); #ifdef DEBUG_GETSET qDebug() << "Returning" << mName << "as" << gt->toString(); #endif return (true); } QList KScanOption::getList() const { const char **sstring = NULL; QList strList; if (mDesc==NULL) return (strList); if (mDesc->constraint_type==SANE_CONSTRAINT_STRING_LIST) { sstring = (const char **)mDesc->constraint.string_list; while (*sstring!=NULL) { strList.append(*sstring); sstring++; } } else if (mDesc->constraint_type==SANE_CONSTRAINT_WORD_LIST) { const SANE_Int *sint = mDesc->constraint.word_list; int amount_vals = *sint; sint++; QString s; for (int i = 0; itype==SANE_TYPE_FIXED) s.sprintf("%f",SANE_UNFIX(*sint)); else s.sprintf("%d",*sint); sint++; strList.append(s.toLocal8Bit()); } } else if (mDesc->constraint_type==SANE_CONSTRAINT_RANGE && mWidgetType==KScanOption::Resolution) { double min,max; int imin,imax; getRange( &min, &max); imin = static_cast(min); imax = static_cast(max); for (const int *ip = resList; *ip!=0; ++ip) { if (*ipimax) continue; strList.append(QString::number(*ip).toLocal8Bit()); } } return (strList); } bool KScanOption::getRange(double *minp, double *maxp, double *quantp) const { if (mDesc==NULL) return (false); double min = 0.0; double max = 0.0; double quant = -1; if (mDesc->constraint_type==SANE_CONSTRAINT_RANGE) { const SANE_Range *r = mDesc->constraint.range; if (mDesc->type==SANE_TYPE_FIXED) { min = SANE_UNFIX(r->min); max = SANE_UNFIX(r->max); quant = SANE_UNFIX(r->quant); } else { min = r->min; max = r->max; quant = r->quant; } } else if (mDesc->constraint_type==SANE_CONSTRAINT_WORD_LIST) { // Originally done in KScanOption::getRangeFromList() const SANE_Int *wl = mDesc->constraint.word_list; const int num = wl[0]; double value; for (int i = 1; i<=num; ++i) { if (mDesc->type==SANE_TYPE_FIXED) value = SANE_UNFIX(wl[i]); else value = wl[i]; if (i==1 || valuemax) max = value; } if (num>=2) quant = (max-min)/(num-1); // synthesise from total range } else { //qDebug() << "Not a range type" << mDesc->name; return (false); } *minp = min; *maxp = max; if (quantp!=NULL) *quantp = quant; return (true); } KScanControl *KScanOption::createWidget(QWidget *parent) { if (!isValid()) { //qDebug() << "Option is not valid!"; return (NULL); } delete mControl; mControl = NULL; // dispose of the old control if (mDesc!=NULL) mText = i18n(mDesc->title); //qDebug() << "type" << mWidgetType << "text" << mText; KScanControl *w = NULL; switch (mWidgetType) { case KScanOption::Bool: w = createToggleButton(parent, mText); // toggle button break; case KScanOption::SingleValue: w = createNumberEntry(parent, mText); // numeric entry break; case KScanOption::Range: w = createSlider(parent, mText); // slider and spinbox break; case KScanOption::Resolution: w = createComboBox(parent, mText); // special resolution combo break; case KScanOption::GammaTable: //qDebug() << "GammaTable not implemented here"; break; // no widget for this case KScanOption::StringList: w = createComboBox(parent, mText); // string list combo break; case KScanOption::String: w = createStringEntry(parent, mText); // free text entry break; case KScanOption::File: w = createFileField(parent, mText); // file name requester break; case KScanOption::Group: w = createGroupSeparator(parent, mText); // group separator break; case KScanOption::Button: w = createActionButton(parent, mText); // button to do action break; default: //qDebug() << "unknown control type " << mWidgetType; break; } if (w!=NULL) { mControl = w; updateList(); // set list for combo box switch (w->type()) { case KScanControl::Number: // numeric control connect(w, SIGNAL(settingChanged(int)), SLOT(slotWidgetChange(int))); break; case KScanControl::Text: // text control connect(w, SIGNAL(settingChanged(const QString &)), SLOT(slotWidgetChange(const QString &))); break; case KScanControl::Button: // push button connect(w, SIGNAL(returnPressed()), SLOT(slotWidgetChange())); break; case KScanControl::Group: // group separator break; // nothing to do here } if (mDesc!=NULL) // set tool tip { if (qstrlen(mDesc->desc)>0) // if there is a SANE description { QString tt = i18n(mDesc->desc); // KDE tooltips do not normally end with a full stop, unless // they are multi-sentence. But the SANE descriptions often do, // so trim it off for consistency. Is this a good thing to do // in non-western languages? if (tt.endsWith('.') && tt.count(". ")==0) tt.chop(1); // Force the format to be rich text so that it will be word wrapped // at a sensible width, see documentation for QToolTip. w->setToolTip(""+tt); } } // No accelerators for advanced options, so as not to soak up // too many of the available accelerators for controls that are // rarely going to be used. See also getLabel(). if (!isCommonOption()) KAcceleratorManager::setNoAccel(w); } reload(); // check if active, enabled etc. if (w!=NULL) redrawWidget(); return (w); } inline KScanControl *KScanOption::createToggleButton(QWidget *parent, const QString &text) { return (new KScanCheckbox(parent, text)); } inline KScanControl *KScanOption::createComboBox(QWidget *parent, const QString &text) { return (new KScanCombo(parent, text)); } inline KScanControl *KScanOption::createStringEntry(QWidget *parent, const QString &text) { return (new KScanStringEntry(parent, text)); } inline KScanControl *KScanOption::createNumberEntry(QWidget *parent, const QString &text) { return (new KScanNumberEntry(parent, text)); } inline KScanControl *KScanOption::createSlider(QWidget *parent, const QString &text) { double min, max; getRange(&min, &max); return (new KScanSlider(parent, text, min, max, true)); } inline KScanControl *KScanOption::createFileField(QWidget *parent, const QString &text) { return (new KScanFileRequester(parent, text)); } inline KScanControl *KScanOption::createGroupSeparator(QWidget *parent, const QString &text) { return (new KScanGroup(parent, text)); } inline KScanControl *KScanOption::createActionButton(QWidget *parent, const QString &text) { return (new KScanPushButton(parent, text)); } QLabel *KScanOption::getLabel(QWidget *parent, bool alwaysBuddy) const { if (mControl==NULL) return (NULL); KSqueezedTextLabel *l = new KSqueezedTextLabel(mControl->label(), parent); if (isCommonOption() || alwaysBuddy) l->setBuddy(mControl->focusProxy()); return (l); } QLabel *KScanOption::getUnit(QWidget *parent) const { if (mControl==NULL) return (NULL); QString s; switch (mDesc->unit) { case SANE_UNIT_NONE: break; case SANE_UNIT_PIXEL: s = i18n("pixels"); break; case SANE_UNIT_BIT: s = i18n("bits"); break; case SANE_UNIT_MM: s = i18n("mm"); break; case SANE_UNIT_DPI: s = i18n("dpi"); break; case SANE_UNIT_PERCENT: s = i18n("%"); break; case SANE_UNIT_MICROSECOND: s = i18n("\302\265sec"); break; default: break; } if (s.isEmpty()) return (NULL); // no unit label QLabel *l = new QLabel(s, parent); return (l); } void KScanOption::allocForDesc() { if (mDesc==NULL) return; switch (mDesc->type) { case SANE_TYPE_INT: case SANE_TYPE_FIXED: case SANE_TYPE_STRING: allocBuffer(mDesc->size); break; case SANE_TYPE_BOOL: allocBuffer(sizeof(SANE_Word)); break; default: if (mDesc->size>0) allocBuffer(mDesc->size); break; } } void KScanOption::allocBuffer(long size) { if (size<1) return; #ifdef DEBUG_MEM //qDebug() << "Allocating" << size << "bytes for" << name; #endif mBuffer.resize(size); // set buffer size if (mBuffer.isNull()) // check allocation worked??? { qWarning() << "Fatal: Allocating" << size << "bytes for" << mName << "failed!"; return; } mBuffer.fill(0); // clear allocated buffer } diff --git a/libkookascan/scanparams.cpp b/libkookascan/scanparams.cpp index 7e19773..3d5ce75 100644 --- a/libkookascan/scanparams.cpp +++ b/libkookascan/scanparams.cpp @@ -1,1136 +1,1136 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "scanparams.h" #include "scanparams_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } #include "scanglobal.h" //#include "scansourcedialog.h" //#include "massscandialog.h" #include "gammadialog.h" #include "kgammatable.h" #include "kscancontrols.h" #include "scansizeselector.h" #include "kscanoption.h" #include "kscanoptset.h" #include "scanicons.h" #include "dialogbase.h" // Debugging options #undef DEBUG_ADF // SANE testing options #ifndef SANE_NAME_TEST_PICTURE #define SANE_NAME_TEST_PICTURE "test-picture" #endif #ifndef SANE_NAME_THREE_PASS #define SANE_NAME_THREE_PASS "three-pass" #endif #ifndef SANE_NAME_HAND_SCANNER #define SANE_NAME_HAND_SCANNER "hand-scanner" #endif #ifndef SANE_NAME_GRAYIFY #define SANE_NAME_GRAYIFY "grayify" #endif ScanParamsPage::ScanParamsPage(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); mLayout = new QGridLayout(this); mLayout->setSpacing(2*DialogBase::verticalSpacing()); mLayout->setColumnStretch(2, 1); mLayout->setColumnMinimumWidth(1, 2*DialogBase::horizontalSpacing()); mNextRow = 0; mPendingGroup = NULL; } ScanParamsPage::~ScanParamsPage() { } void ScanParamsPage::checkPendingGroup() { if (mPendingGroup != NULL) { // separator to add first? QWidget *w = mPendingGroup; mPendingGroup = NULL; // avoid recursion! addRow(w); } } void ScanParamsPage::addRow(QWidget *wid) { if (wid == NULL) { return; // must have one } checkPendingGroup(); // add separator if needed mLayout->addWidget(wid, mNextRow, 0, 1, -1); ++mNextRow; } void ScanParamsPage::addRow(QLabel *lab, QWidget *wid, QLabel *unit, Qt::Alignment align) { if (wid == NULL) { return; // must have one } wid->setMaximumWidth(MAX_CONTROL_WIDTH); checkPendingGroup(); // add separator if needed if (lab != NULL) { lab->setMaximumWidth(MAX_LABEL_WIDTH); lab->setMinimumWidth(MIN_LABEL_WIDTH); mLayout->addWidget(lab, mNextRow, 0, Qt::AlignLeft | align); } if (unit != NULL) { mLayout->addWidget(wid, mNextRow, 2, align); mLayout->addWidget(unit, mNextRow, 3, Qt::AlignLeft | align); } else { mLayout->addWidget(wid, mNextRow, 2, 1, 2, align); } ++mNextRow; } bool ScanParamsPage::lastRow() { addGroup(NULL); // hide last if present mLayout->addWidget(new QLabel(QString::null, this), mNextRow, 0, 1, -1, Qt::AlignTop); mLayout->setRowStretch(mNextRow, 9); return (mNextRow > 0); } void ScanParamsPage::addGroup(QWidget *wid) { if (mPendingGroup != NULL) { mPendingGroup->hide(); // dont't need this after all } mPendingGroup = wid; } ScanParams::ScanParams(QWidget *parent) : QWidget(parent) { setObjectName("ScanParams"); mSaneDevice = NULL; mVirtualFile = NULL; mGammaEditButt = NULL; mResolutionBind = NULL; mProgressDialog = NULL; mSourceSelect = NULL; mLed = NULL; mProblemMessage = NULL; mNoScannerMessage = NULL; } ScanParams::~ScanParams() { //qDebug(); delete mProgressDialog; } bool ScanParams::connectDevice(KScanDevice *newScanDevice, bool galleryMode) { QGridLayout *lay = new QGridLayout(this); lay->setMargin(0); lay->setColumnStretch(0, 9); if (newScanDevice == NULL) { // no scanner device //qDebug() << "No scan device, gallery=" << galleryMode; mSaneDevice = NULL; createNoScannerMsg(galleryMode); return (true); } mSaneDevice = newScanDevice; mScanMode = ScanParams::NormalMode; #if 0 // TOTO: port/update adf = ADF_OFF; #endif QLabel *lab = new QLabel(xi18nc("@info", "Scanner Settings"), this); lay->addWidget(lab, 0, 0, Qt::AlignLeft); mLed = new KLed(this); mLed->setState(KLed::Off); mLed->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); lay->addWidget(mLed, 0, 1, Qt::AlignRight); lab = new QLabel(mSaneDevice->scannerDescription(), this); lay->addWidget(lab, 1, 0, 1, 2, Qt::AlignLeft); /* load the startup scanoptions */ /* Now create Widgets for the important scan settings */ QWidget *sv = createScannerParams(); lay->addWidget(sv, 3, 0, 1, 2); lay->setRowStretch(3, 9); // Load the startup options // TODO: check whether the saved scanner options apply to the current scanner? // They may be for a completely different one... // Or update KScanDevice and here to save/load the startup options // on a per-scanner basis. //qDebug() << "looking for startup options"; KScanOptSet startupOptions(KScanOptSet::startupSetName()); if (startupOptions.loadConfig(mSaneDevice->scannerBackendName())) { //qDebug() << "loading startup options"; mSaneDevice->loadOptionSet(&startupOptions); } //else qDebug() << "Could not load startup options"; // Reload all options, to take account of inactive ones mSaneDevice->reloadAllOptions(); // Send the current settings to the previewer initStartupArea(startupOptions.isEmpty()); // signal newCustomScanSize() slotNewScanMode(); // signal scanModeChanged() slotNewResolution(NULL); // signal scanResolutionChanged /* Create the Scan Buttons */ QPushButton *pb = new QPushButton(QIcon::fromTheme("preview"), i18n("Pre&view"), this); pb->setToolTip(i18n("Start a preview scan and show the preview image")); pb->setMinimumWidth(100); connect(pb, &QPushButton::clicked, this, &ScanParams::slotAcquirePreview); lay->addWidget(pb, 5, 0, Qt::AlignLeft); pb = new QPushButton(QIcon::fromTheme("scan"), i18n("Star&t Scan"), this); pb->setToolTip(i18n("Start a scan and save the scanned image")); pb->setMinimumWidth(100); connect(pb, &QPushButton::clicked, this, &ScanParams::slotStartScan); lay->addWidget(pb, 5, 1, Qt::AlignRight); /* Initialise the progress dialog */ mProgressDialog = new QProgressDialog(QString::null, i18n("Stop"), 0, 100, NULL); mProgressDialog->setModal(true); mProgressDialog->setAutoClose(true); mProgressDialog->setAutoReset(true); mProgressDialog->setWindowTitle(i18n("Scanning")); mProgressDialog->setMinimumDuration(100); // The next is necessary with Qt5, as otherwise the progress dialogue // appears to show itself after the default 'minimumDuration' (= 4 seconds), // even despite the previous and no 'value' being set. mProgressDialog->reset(); setScanDestination(QString::null); // reset destination display connect(mProgressDialog, &QProgressDialog::canceled, mSaneDevice, &KScanDevice::slotStopScanning); connect(mSaneDevice, &KScanDevice::sigScanProgress, this, &ScanParams::slotScanProgress); return (true); } KLed *ScanParams::operationLED() const { return (mLed); } ScanParamsPage *ScanParams::createTab(QTabWidget *tw, const QString &title, const char *name) { QScrollArea *scroll = new QScrollArea(this); scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scroll->setWidgetResizable(true); // stretch to width scroll->setFrameStyle(QFrame::NoFrame); ScanParamsPage *frame = new ScanParamsPage(this, name); scroll->setWidget(frame); tw->addTab(scroll, title); return (frame); } QWidget *ScanParams::createScannerParams() { QTabWidget *tw = new QTabWidget(this); tw->setTabsClosable(false); tw->setTabPosition(QTabWidget::North); ScanParamsPage *basicFrame = createTab(tw, i18n("&Basic"), "BasicFrame"); ScanParamsPage *otherFrame = createTab(tw, i18n("Other"), "OtherFrame"); ScanParamsPage *advancedFrame = createTab(tw, i18n("Advanced"), "AdvancedFrame"); KScanOption *so; QLabel *l; QWidget *w; QLabel *u; ScanParamsPage *frame; // Initial "Basic" options frame = basicFrame; // Virtual/debug image file mVirtualFile = mSaneDevice->getGuiElement(SANE_NAME_FILE, frame); if (mVirtualFile != NULL) { connect(mVirtualFile, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = mVirtualFile->getLabel(frame, true); w = mVirtualFile->widget(); frame->addRow(l, w); // Selection for either virtual scanner or SANE debug QWidget *vbg = new QWidget(frame); QVBoxLayout *vbl = new QVBoxLayout(vbg); vbl->setMargin(0); QRadioButton *rb1 = new QRadioButton(i18n("SANE Debug (from PNM image)"), vbg); 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 sane-pnm(5) for more information.")); vbl->addWidget(rb1); QRadioButton *rb2 = new QRadioButton(i18n("Virtual Scanner (any image format)"), vbg); 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.")); vbl->addWidget(rb2); if (mScanMode == ScanParams::NormalMode) mScanMode = ScanParams::SaneDebugMode; rb1->setChecked(mScanMode == ScanParams::SaneDebugMode); rb2->setChecked(mScanMode == ScanParams::VirtualScannerMode); // needed for new 'buttonClicked' signal: QButtonGroup *vbgGroup = new QButtonGroup(vbg); vbgGroup->addButton(rb1, 0); vbgGroup->addButton(rb2, 1); connect(vbgGroup, static_cast(&QButtonGroup::buttonClicked), this, &ScanParams::slotVirtScanModeSelect); l = new QLabel(i18n("Reading mode:"), frame); frame->addRow(l, vbg, NULL, Qt::AlignTop); // Separator line after these. Using a KScanGroup with a null text, // so that it looks the same as any real group separators following. frame->addGroup(new KScanGroup(frame, QString::null)); } // Mode setting so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_MODE, frame); if (so != NULL) { KScanCombo *cb = (KScanCombo *) so->widget(); // The option display strings are translated via the 'sane-backends' message // catalogue, see KScanCombo::setList(). But KScanCombo::setIcon() works on // the untranslated strings, which are really SANE values. So the strings // here need to cover the full set of possible SANE values for all backends // (extra ones which a particular SANE backend doesn't support don't matter). // // So don't translate these strings or wrap them in any sort of i18n(). // // The combo has already been populated with the current list of SANE values // at the KScanOption initialisation (by KScanOption::updateList() calling // KScanOption::getList() which reads the list values from SANE). But // this may not work properly if the list of scan modes changes at runtime! cb->setIcon(ScanIcons::self()->icon(ScanIcons::BlackWhite), "Lineart"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::BlackWhite), "Binary"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Greyscale), "Gray"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Greyscale), "Grayscale"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Colour), "Color"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Halftone), "Halftone"); connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode); l = so->getLabel(frame, true); frame->addRow(l, cb); } // Resolution setting. Try "X-Resolution" setting first, this is the // option we want if the resolutions are split up. If there is no such // option then try just "Resolution", this may not be the same as // "X-Resolution" even though this was the case in SANE<=1.0.19. so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_X_RESOLUTION, frame); if (so == NULL) { so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_RESOLUTION, frame); } if (so != NULL) { connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); // Connection that passes the resolution to the previewer connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution); l = so->getLabel(frame, true); w = so->widget(); u = so->getUnit(frame); frame->addRow(l, w, u); // Same X/Y resolution option (if present) mResolutionBind = mSaneDevice->getGuiElement(SANE_NAME_RESOLUTION_BIND, frame); if (mResolutionBind != NULL) { connect(mResolutionBind, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = so->getLabel(frame, true); w = so->widget(); frame->addRow(l, w); } // Now the "Y-Resolution" setting, if there is a separate one so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_Y_RESOLUTION, frame); if (so!=NULL) { // Connection that passes the resolution to the previewer connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution); l = so->getLabel(frame, true); w = so->widget(); u = so->getUnit(frame); frame->addRow(l, w, u); } } else { //qDebug() << "Serious: No resolution option available!"; } // Scan size setting mAreaSelect = new ScanSizeSelector(frame, mSaneDevice->getMaxScanSize()); connect(mAreaSelect, &ScanSizeSelector::sizeSelected, this, &ScanParams::slotScanSizeSelected); l = new QLabel("Scan &area:", frame); // make sure it gets an accel l->setBuddy(mAreaSelect->focusProxy()); frame->addRow(l, mAreaSelect, NULL, Qt::AlignTop); // Insert another beautification line frame->addGroup(new KScanGroup(frame, QString::null)); // Source selection mSourceSelect = mSaneDevice->getGuiElement(SANE_NAME_SCAN_SOURCE, frame); if (mSourceSelect != NULL) { connect(mSourceSelect, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = mSourceSelect->getLabel(frame, true); w = mSourceSelect->widget(); frame->addRow(l, w); // TODO: enable the "Advanced" dialogue, because that // contains other ADF options. They are not implemented at the moment // but they may be some day... //QPushButton *pb = new QPushButton( i18n("Source && ADF Options..."), frame); //connect(pb, SIGNAL(clicked()), SLOT(slotSourceSelect())); //lay->addWidget(pb,row,2,1,-1,Qt::AlignRight); //++row; } // SANE testing options, for the "test" device so = mSaneDevice->getGuiElement(SANE_NAME_TEST_PICTURE, frame); if (so != NULL) { connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = so->getLabel(frame); w = so->widget(); frame->addRow(l, w); } // Now all of the other options which have not been accounted for yet. // Split them up into "Other" and "Advanced". const QList opts = mSaneDevice->getAllOptions(); for (QList::const_iterator it = opts.constBegin(); it != opts.constEnd(); ++it) { const QByteArray opt = (*it); if (opt == SANE_NAME_SCAN_TL_X || // ignore these (scan area) opt == SANE_NAME_SCAN_TL_Y || opt == SANE_NAME_SCAN_BR_X || opt == SANE_NAME_SCAN_BR_Y) { continue; } so = mSaneDevice->getExistingGuiElement(opt); // see if already created if (so != NULL) { continue; // if so ignore, don't duplicate } so = mSaneDevice->getGuiElement(opt, frame); if (so != NULL) { //qDebug() << "creating" << (so->isCommonOption() ? "OTHER" : "ADVANCED") << "option" << opt; connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); if (so->isCommonOption()) { frame = otherFrame; } else { frame = advancedFrame; } w = so->widget(); if (so->isGroup()) { frame->addGroup(w); } else { l = so->getLabel(frame); u = so->getUnit(frame); frame->addRow(l, w, u); } // Some special things to do for particular options if (opt == SANE_NAME_BIT_DEPTH) { connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode); } else if (opt == SANE_NAME_CUSTOM_GAMMA) { // Enabling/disabling the edit button is handled by // slotOptionChanged() calling setEditCustomGammaTableState() //connect(so, SIGNAL(guiChange(KScanOption*)), SLOT(slotOptionNotify(KScanOption*))); mGammaEditButt = new QPushButton(i18n("Edit Gamma Table..."), this); mGammaEditButt->setIcon(QIcon::fromTheme("document-edit")); connect(mGammaEditButt, &QPushButton::clicked, this, &ScanParams::slotEditCustGamma); setEditCustomGammaTableState(); frame->addRow(NULL, mGammaEditButt, NULL, Qt::AlignRight); } } } basicFrame->lastRow(); // final stretch row if (!otherFrame->lastRow()) { tw->setTabEnabled(1, false); } if (!advancedFrame->lastRow()) { tw->setTabEnabled(2, false); } return (tw); // top-level (tab) widget } void ScanParams::initStartupArea(bool dontRestore) { // TODO: restore area a user preference #ifdef RESTORE_AREA if (dontRestore) // no saved options available #endif { applyRect(QRect()); // set maximum scan area return; } // set scan area from saved KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X); KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y); KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X); KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y); QRect rect; int val1, val2; tl_x->get(&val1); rect.setLeft(val1); // pass area to previewer br_x->get(&val2); rect.setWidth(val2 - val1); tl_y->get(&val1); rect.setTop(val1); br_y->get(&val2); rect.setHeight(val2 - val1); emit newCustomScanSize(rect); mAreaSelect->selectSize(rect); // set selector to match } void ScanParams::createNoScannerMsg(bool galleryMode) { QWidget *lab; if (galleryMode) { lab = messageScannerNotSelected(); } else { lab = messageScannerProblem(); } QGridLayout *lay = dynamic_cast(layout()); if (lay != NULL) { lay->addWidget(lab, 0, 0, Qt::AlignTop); } } QWidget *ScanParams::messageScannerNotSelected() { if (mNoScannerMessage == NULL) { mNoScannerMessage = new QLabel( xi18nc("@info", "No scanner selected" "" "Select a scanner device to perform scanning.")); mNoScannerMessage->setWordWrap(true); } return (mNoScannerMessage); } QWidget *ScanParams::messageScannerProblem() { if (mProblemMessage == NULL) { mProblemMessage = new QLabel( xi18nc("@info", "Problem: No scanner found, or unable to access it" "" "There was a problem using the SANE (Scanner Access Now Easy) library to access " "the scanner device. There may be a problem with your SANE installation, or it " "may not be configured to support your scanner." "" "Check that SANE is correctly installed and configured on your system, and " "also that the scanner device name and settings are correct." "" "See the SANE project home page " "(www.sane-project.org) " "for more information on SANE installation and setup.")); mProblemMessage->setWordWrap(true); mProblemMessage->setOpenExternalLinks(true); } return (mProblemMessage); } void ScanParams::slotSourceSelect() { #if 0 // TODO: port/update AdfBehaviour adf = ADF_OFF; if (mSourceSelect == NULL) { return; // no source selection GUI } if (!mSourceSelect->isValid()) { return; // no option on scanner } const QByteArray &currSource = mSourceSelect->get(); //qDebug() << "Current source is" << currSource; QList sources = mSourceSelect->getList(); #ifdef DEBUG_ADF if (!sources.contains("Automatic Document Feeder")) { sources.append("Automatic Document Feeder"); } #endif // TODO: the 'sources' list has exactly the same options as the // scan source combo (apart from the debugging hack above), so // what's the point of repeating it in this dialogue? ScanSourceDialog d(this, sources, adf); d.slotSetSource(currSource); if (d.exec() != QDialog::Accepted) { return; } QString sel_source = d.getText(); adf = d.getAdfBehave(); //qDebug() << "new source" << sel_source << "ADF" << adf; /* set the selected Document source, the behavior is stored in a membervar */ mSourceSelect->set(sel_source.toLatin1()); // TODO: FIX in ScanSourceDialog, then here mSourceSelect->apply(); mSourceSelect->reload(); mSourceSelect->redrawWidget(); #endif } /* Slot which is called if the user switches in the gui between * the SANE-Debug-Mode and the qt image reading */ void ScanParams::slotVirtScanModeSelect(int but) { if (but == 0) mScanMode = ScanParams::SaneDebugMode; // SANE Debug else mScanMode = ScanParams::VirtualScannerMode; // Virtual Scanner const bool enable = (mScanMode == ScanParams::SaneDebugMode); mSaneDevice->guiSetEnabled(SANE_NAME_HAND_SCANNER, enable); mSaneDevice->guiSetEnabled(SANE_NAME_THREE_PASS, enable); mSaneDevice->guiSetEnabled(SANE_NAME_GRAYIFY, enable); mSaneDevice->guiSetEnabled(SANE_NAME_CONTRAST, enable); mSaneDevice->guiSetEnabled(SANE_NAME_BRIGHTNESS, enable); mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_RESOLUTION, enable); mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_X_RESOLUTION, enable); mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_Y_RESOLUTION, enable); mAreaSelect->setEnabled(enable); } KScanDevice::Status ScanParams::prepareScan(QString *vfp) { //qDebug() << "scan mode=" << mScanMode; setScanDestination(QString::null); // reset progress display // Check compatibility of scan settings int format; int depth; mSaneDevice->getCurrentFormat(&format, &depth); if (depth == 1 && format != SANE_FRAME_GRAY) { // 1-bit scan depth in colour? KMessageBox::sorry(this, i18n("1-bit depth scan cannot be done in color")); return (KScanDevice::ParamError); } else if (depth == 16) { KMessageBox::sorry(this, i18n("16-bit depth scans are not supported")); return (KScanDevice::ParamError); } QString virtfile; if (mScanMode == ScanParams::SaneDebugMode || mScanMode == ScanParams::VirtualScannerMode) { if (mVirtualFile != NULL) { - virtfile = mVirtualFile->get(); + virtfile = QFile::decodeName(mVirtualFile->get()); } if (virtfile.isEmpty()) { KMessageBox::sorry(this, i18n("A file must be entered for testing or virtual scanning")); return (KScanDevice::ParamError); } QFileInfo fi(virtfile); if (!fi.exists()) { KMessageBox::sorry(this, xi18nc("@info", "The testing or virtual file %1was not found or is not readable.", virtfile)); return (KScanDevice::ParamError); } if (mScanMode == ScanParams::SaneDebugMode) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(virtfile); if (!(mime.inherits("image/x-portable-bitmap") || mime.inherits("image/x-portable-greymap") || mime.inherits("image/x-portable-pixmap"))) { KMessageBox::sorry(this, xi18nc("@info", "SANE Debug can only read PNM files." "The specified file is type %1.", mime.name())); return (KScanDevice::ParamError); } } } if (vfp != NULL) { *vfp = virtfile; } return (KScanDevice::Ok); } void ScanParams::setScanDestination(const QString &dest) { //qDebug() << "scan destination is" << dest; if (dest.isEmpty()) { mProgressDialog->setLabelText(i18n("Scan in progress")); } else { mProgressDialog->setLabelText(xi18nc("@info", "Scan in progress%1", dest)); } } void ScanParams::slotScanProgress(int value) { mProgressDialog->setValue(value); } /* Slot called to start acquiring a preview */ void ScanParams::slotAcquirePreview() { // TODO: should be able to preview in Virtual Scanner mode, it just means // that the preview image will be the same size as the final image (which // doesn't matter). if (mScanMode == ScanParams::VirtualScannerMode) { KMessageBox::sorry(this, i18n("Cannot preview in Virtual Scanner mode")); return; } QString virtfile; KScanDevice::Status stat = prepareScan(&virtfile); if (stat != KScanDevice::Ok) { return; } //qDebug() << "scan mode=" << mScanMode << "virtfile" << virtfile; KScanOption *greyPreview = mSaneDevice->getExistingGuiElement(SANE_NAME_GRAY_PREVIEW); int gp = 0; if (greyPreview != NULL) { greyPreview->get(&gp); } setMaximalScanSize(); // always preview at maximal size mAreaSelect->selectCustomSize(QRect()); // reset selector to reflect that stat = mSaneDevice->acquirePreview(gp); if (stat != KScanDevice::Ok) { //qDebug() << "Error, preview status " << stat; } } /* Slot called to start scanning */ void ScanParams::slotStartScan() { QString virtfile; KScanDevice::Status stat = prepareScan(&virtfile); if (stat != KScanDevice::Ok) { return; } //qDebug() << "scan mode=" << mScanMode << "virtfile" << virtfile; if (mScanMode != ScanParams::VirtualScannerMode) { // acquire via SANE #if 0 // TODO: port/update if (adf == ADF_OFF) { #endif //qDebug() << "Start to acquire image"; stat = mSaneDevice->acquireScan(); #if 0 } else { //qDebug() << "ADF Scan not yet implemented :-/"; // stat = performADFScan(); } #endif } else { // acquire via Qt-IMGIO //qDebug() << "Acquiring from virtual file"; stat = mSaneDevice->acquireScan(virtfile); } if (stat != KScanDevice::Ok) { //qDebug() << "Error, scan status " << stat; } } bool ScanParams::getGammaTableFrom(const QByteArray &opt, KGammaTable *gt) { KScanOption *so = mSaneDevice->getOption(opt, false); if (so == NULL) { return (false); } if (!so->get(gt)) { return (false); } //qDebug() << "got from" << so->getName() << "=" << gt->toString(); return (true); } bool ScanParams::setGammaTableTo(const QByteArray &opt, const KGammaTable *gt) { KScanOption *so = mSaneDevice->getOption(opt, false); if (so == NULL) { return (false); } //qDebug() << "set" << so->getName() << "=" << gt->toString(); so->set(gt); return (so->apply()); } void ScanParams::slotEditCustGamma() { KGammaTable gt; // start with default values // Get the current gamma table from either the combined gamma table // option, or any one of the colour channel gamma tables. if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR, >)) { if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_R, >)) { if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_G, >)) { if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_B, >)) { // Should not happen... but if it does, no problem // the dialogue will just use the default values // for an empty gamma table. //qDebug() << "no existing/active gamma option!"; } } } } //qDebug() << "initial gamma table" << gt.toString(); // TODO; Maybe need to have a separate GUI widget for each gamma table, a // little preview of the gamma curve (a GammaWidget) with an edit button. // Will avoid the special case for the SANE_NAME_CUSTOM_GAMMA button followed // by the edit button, and will allow separate editing of the R/G/B gamma // tables if the scanner has them. GammaDialog gdiag(>, this); connect(&gdiag, &GammaDialog::gammaToApply, this, &ScanParams::slotApplyGamma); gdiag.exec(); } void ScanParams::slotApplyGamma(const KGammaTable *gt) { if (gt == NULL) { return; } bool reload = false; KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA); if (so != NULL) { // do we have a gamma switch? int cg = 0; if (so->get(&cg) && !cg) { // yes, see if already on // if not, set it on now //qDebug() << "Setting gamma switch on"; so->set(true); reload = so->apply(); } } //qDebug() << "Applying gamma table" << gt->toString(); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR, gt); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_R, gt); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_G, gt); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_B, gt); if (reload) { mSaneDevice->reloadAllOptions(); // reload is needed } } // The user has changed an option. Apply that; as a result of doing so, // it may be necessary to reload every other scanner option apart from // this one. void ScanParams::slotOptionChanged(KScanOption *so) { if (so == NULL || mSaneDevice == NULL) { return; } mSaneDevice->applyOption(so); // Update the gamma edit button state, if the option exists setEditCustomGammaTableState(); } // Enable editing of the gamma tables if any one of the gamma tables // exists and is currently active. void ScanParams::setEditCustomGammaTableState() { if (mSaneDevice == NULL) { return; } if (mGammaEditButt == NULL) { return; } bool butState = false; KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA, false); if (so != NULL) { butState = so->isActive(); } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR, false); if (so != NULL) { butState = so->isActive(); } } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_R, false); if (so != NULL) { butState = so->isActive(); } } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_G, false); if (so != NULL) { butState = so->isActive(); } } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_B, false); if (so != NULL) { butState = so->isActive(); } } //qDebug() << "State of edit custom gamma button=" << butState; mGammaEditButt->setEnabled(butState); } // This assumes that the SANE unit for the scan area is millimetres. // All scanners out there appear to do this. void ScanParams::applyRect(const QRect &rect) { //qDebug() << "rect=" << rect; KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X); KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y); KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X); KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y); double min1, max1; double min2, max2; if (!rect.isValid()) { // set full scan area tl_x->getRange(&min1, &max1); tl_x->set(min1); br_x->getRange(&min1, &max1); br_x->set(max1); tl_y->getRange(&min2, &max2); tl_y->set(min2); br_y->getRange(&min2, &max2); br_y->set(max2); //qDebug() << "setting full area" << min1 << min2 << "-" << max1 << max2; } else { double tlx = rect.left(); double tly = rect.top(); double brx = rect.right(); double bry = rect.bottom(); tl_x->getRange(&min1, &max1); if (tlx < min1) { brx += (min1 - tlx); tlx = min1; } tl_x->set(tlx); br_x->set(brx); tl_y->getRange(&min2, &max2); if (tly < min2) { bry += (min2 - tly); tly = min2; } tl_y->set(tly); br_y->set(bry); //qDebug() << "setting area" << tlx << tly << "-" << brx << bry; } tl_x->apply(); tl_y->apply(); br_x->apply(); br_y->apply(); } // The previewer is telling us that the user has drawn or auto-selected a // new preview area (specified in millimetres). void ScanParams::slotNewPreviewRect(const QRect &rect) { //qDebug() << "rect=" << rect; applyRect(rect); mAreaSelect->selectSize(rect); } // A new preset scan size or orientation chosen by the user void ScanParams::slotScanSizeSelected(const QRect &rect) { //qDebug() << "rect=" << rect << "full=" << !rect.isValid(); applyRect(rect); emit newCustomScanSize(rect); } /* * sets the scan area to the default, which is the whole area. */ void ScanParams::setMaximalScanSize() { //qDebug() << "Setting to default"; slotScanSizeSelected(QRect()); } void ScanParams::slotNewResolution(KScanOption *opt) { KScanOption *opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_X_RESOLUTION); if (opt_x == NULL) { opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_RESOLUTION); } KScanOption *opt_y = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_Y_RESOLUTION); int x_res = 0; // get the X resolution if (opt_x != NULL && opt_x->isValid()) { opt_x->get(&x_res); } int y_res = 0; // get the Y resolution if (opt_y != NULL && opt_y->isValid()) { opt_y->get(&y_res); } //qDebug() << "X/Y resolution" << x_res << y_res; if (y_res == 0) { y_res = x_res; // use X res if Y unavailable } if (x_res == 0) { x_res = y_res; // unlikely, but orthogonal } if (x_res == 0 && y_res == 0) { //qDebug() << "resolution not available!"; } else { emit scanResolutionChanged(x_res, y_res); } } void ScanParams::slotNewScanMode() { int format = SANE_FRAME_RGB; int depth = 8; mSaneDevice->getCurrentFormat(&format, &depth); int strips = (format == SANE_FRAME_GRAY ? 1 : 3); qDebug() << "format" << format << "depth" << depth << "-> strips " << strips; if (strips == 1 && depth == 1) { // bitmap scan emit scanModeChanged(0); // 8 pixels per byte } else { // bytes per pixel emit scanModeChanged(strips * (depth == 16 ? 2 : 1)); } } KScanDevice::Status ScanParams::performADFScan(void) { KScanDevice::Status stat = KScanDevice::Ok; bool scan_on = true; #if 0 // TODO: port/update MassScanDialog *msd = new MassScanDialog(this); msd->show(); #endif /* The scan source should be set to ADF by the SourceSelect-Dialog */ while (scan_on) { scan_on = false; } return (stat); }