diff --git a/hotkeys_and_scripts/endless_scan.sh b/hotkeys_and_scripts/endless_scan.sh new file mode 100755 index 0000000..febc7a7 --- /dev/null +++ b/hotkeys_and_scripts/endless_scan.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Skanlite D-Bus example script +# this script sends 'scan' command to Skanlite via D-Bus +# then listens for scanDone signal and repeats in infinite loop + +interface=org.kde.skanlite +sender=org.kde.skanlite +member=scanDone +command=scan + + +dbus-send --session --dest=$interface --type=method_call / $interface.$command + +dbus-monitor --profile "type='signal',sender='$sender',interface='$interface',member='$member'" --monitor | +while read -r line; do +dbus-send --session --dest=$interface --type=method_call / $interface.$command +done diff --git a/hotkeys_and_scripts/hw_btn_scan.sh b/hotkeys_and_scripts/hw_btn_scan.sh new file mode 100755 index 0000000..e077683 --- /dev/null +++ b/hotkeys_and_scripts/hw_btn_scan.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Skanlite D-Bus example script + +# this script listens for D-Bus buttonPressed signal from Skanlite +# checks which button is pressed (default: 'Button 0') and its pressed state +# then sends Skanlite 'scan' command via D-Bus + +# WARNING: buttonPressed signal might work unstable as it's driver dependant +# WARNING: for example my HP 4370 stop sending hw btn events just after first scan + +interface=org.kde.skanlite +sender=org.kde.skanlite +member=buttonPressed +command=scan + +# listen for buttonPressed D-Bus events, +# each time we enter the loop, we just got an event +# so handle the event, e.g. by checking button name and its state. + +dbus-monitor --profile "type='signal',sender='$sender',interface='$interface',member='$member'" --monitor | +while read -r line; do + +# uncomment the line below to output to console all incoming buttonPressed data +# this may be useful to detect proper button names + +# printf "Received line: $line\n" + + if [[ $line == *"button 0"* ]]; then + read -r line; + if [[ $line == *"Scanner button 0"* ]]; then + read -r line; + if [[ $line == *"boolean true"* ]]; then + # printf "\nButton pressed. Performing scan.\n" + dbus-send --session --dest=$interface --type=method_call / $interface.$command + fi + fi + fi +done diff --git a/hotkeys_and_scripts/skanlite.khotkeys b/hotkeys_and_scripts/skanlite.khotkeys new file mode 100644 index 0000000..3119de2 --- /dev/null +++ b/hotkeys_and_scripts/skanlite.khotkeys @@ -0,0 +1,193 @@ +[Data] +DataCount=1 + +[Data_1] +Comment=Shortcuts for Skanlite +DataCount=6 +Enabled=true +Name=Skanlite +SystemGroup=0 +Type=ACTION_DATA_GROUP + +[Data_1Conditions] +Comment= +ConditionsCount=0 + +[Data_1_1] +Comment=Global hotkey: Alt+S to start scanning. Skanlite must be up and running. +Enabled=true +Name=Scan +Type=SIMPLE_ACTION_DATA + +[Data_1_1Actions] +ActionsCount=1 + +[Data_1_1Actions0] +Arguments= +Call=scan +RemoteApp=org.kde.skanlite +RemoteObj=/ +Type=DBUS + +[Data_1_1Conditions] +Comment= +ConditionsCount=0 + +[Data_1_1Triggers] +Comment=Simple_action +TriggersCount=1 + +[Data_1_1Triggers0] +Key=Alt+S +Type=SHORTCUT +Uuid={3c04e4ea-1f6e-430e-82bb-0e4adb79a6a3} + +[Data_1_2] +Comment=Global hotkey: Alt+C to cancel scanning while it's performing. Do nothing otherwise. +Enabled=true +Name=Cancel Scan +Type=SIMPLE_ACTION_DATA + +[Data_1_2Actions] +ActionsCount=1 + +[Data_1_2Actions0] +Arguments= +Call=scanCancel +RemoteApp=org.kde.skanlite +RemoteObj=/ +Type=DBUS + +[Data_1_2Conditions] +Comment= +ConditionsCount=0 + +[Data_1_2Triggers] +Comment=Simple_action +TriggersCount=1 + +[Data_1_2Triggers0] +Key=Alt+C +Type=SHORTCUT +Uuid={aaed7bc1-a7bc-4abc-b319-9b95abb5216b} + +[Data_1_3] +Comment=Global hotkey: press Ctrl+Alt+1 to save current Skanlite scanner settings as Profile 1. You can switch to this profile later with Alt+1. +Enabled=true +Name=Save Profile 1 +Type=SIMPLE_ACTION_DATA + +[Data_1_3Actions] +ActionsCount=1 + +[Data_1_3Actions0] +Arguments=1 +Call=saveCurrentScannerOptionsToProfile +RemoteApp=org.kde.skanlite +RemoteObj=/ +Type=DBUS + +[Data_1_3Conditions] +Comment= +ConditionsCount=0 + +[Data_1_3Triggers] +Comment=Simple_action +TriggersCount=1 + +[Data_1_3Triggers0] +Key=Alt+! +Type=SHORTCUT +Uuid={5d4c37a6-d8b3-40b0-aa6a-92f325302538} + +[Data_1_4] +Comment=Global hotkey: press Alt+1 to restore scanner settings from Profile 1 in Skanlite. If profile doesn't exists - restore default settings. +Enabled=true +Name=Load Profile 1 +Type=SIMPLE_ACTION_DATA + +[Data_1_4Actions] +ActionsCount=1 + +[Data_1_4Actions0] +Arguments=1 +Call=switchToProfile +RemoteApp=org.kde.skanlite +RemoteObj=/ +Type=DBUS + +[Data_1_4Conditions] +Comment= +ConditionsCount=0 + +[Data_1_4Triggers] +Comment=Simple_action +TriggersCount=1 + +[Data_1_4Triggers0] +Key=Alt+1 +Type=SHORTCUT +Uuid={81787487-f07d-4111-b1cf-9da1406075ca} + +[Data_1_5] +Comment=Global hotkey: press Ctrl+Alt+2 to save current Skanlite scanner settings as Profile 2. You can switch to this profile later with Alt+2. +Enabled=true +Name=Save Profile 2 +Type=SIMPLE_ACTION_DATA + +[Data_1_5Actions] +ActionsCount=1 + +[Data_1_5Actions0] +Arguments=2 +Call=saveCurrentScannerOptionsToProfile +RemoteApp=org.kde.skanlite +RemoteObj=/ +Type=DBUS + +[Data_1_5Conditions] +Comment= +ConditionsCount=0 + +[Data_1_5Triggers] +Comment=Simple_action +TriggersCount=1 + +[Data_1_5Triggers0] +Key=Alt+@ +Type=SHORTCUT +Uuid={e74b442f-47db-4314-a0e0-a56e1b753804} + +[Data_1_6] +Comment=Global hotkey: press Alt+2 to restore scanner settings from Profile 2 in Skanlite. If profile doesn't exists - restore default settings. +Enabled=true +Name=Load Profile 2 +Type=SIMPLE_ACTION_DATA + +[Data_1_6Actions] +ActionsCount=1 + +[Data_1_6Actions0] +Arguments=2 +Call=switchToProfile +RemoteApp=org.kde.skanlite +RemoteObj=/ +Type=DBUS + +[Data_1_6Conditions] +Comment= +ConditionsCount=0 + +[Data_1_6Triggers] +Comment=Simple_action +TriggersCount=1 + +[Data_1_6Triggers0] +Key=Alt+2 +Type=SHORTCUT +Uuid={9f894206-fbcf-48f1-9fb3-df9cec37bacb} + +[Main] +AllowMerge=true +ImportId=skanlite +Version=2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 241f0c1..89f8cb3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,23 +1,23 @@ -set(skanlite_SRCS main.cpp skanlite.cpp ImageViewer.cpp KSaneImageSaver.cpp SaveLocation.cpp) +set(skanlite_SRCS main.cpp skanlite.cpp ImageViewer.cpp KSaneImageSaver.cpp SaveLocation.cpp DBusInterface.cpp) ki18n_wrap_ui(skanlite_SRCS settings.ui SaveLocation.ui) #kde4_add_app_icon(skanlite_SRCS "${KDE4_INSTALL_DIR}/share/icons/oxygen/*/devices/scanner.png") add_executable(skanlite ${skanlite_SRCS}) target_link_libraries(skanlite PUBLIC Qt5::Core PRIVATE KF5::CoreAddons KF5::Sane KF5::I18n KF5::XmlGui KF5::KIOWidgets ${PNG_LIBRARY} ) install(TARGETS skanlite ${INSTALL_TARGETS_DEFAULT_ARGS}) install(PROGRAMS org.kde.skanlite.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install( FILES org.kde.skanlite.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) diff --git a/src/DBusInterface.cpp b/src/DBusInterface.cpp new file mode 100644 index 0000000..3dec5ca --- /dev/null +++ b/src/DBusInterface.cpp @@ -0,0 +1,62 @@ +/* ============================================================ +* +* Copyright (C) 2017 by Alexander Trufanov +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License or (at your option) version 3 or any later version +* accepted by the membership of KDE e.V. (or its successor approved +* by the membership of KDE e.V.), which shall act as a proxy +* defined in Section 14 of version 3 of the license. +* +* 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. If not, see +* +* ============================================================ */ + +#include "DBusInterface.h" + +bool DBusInterface::setupDBusInterface() +{ + QDBusConnection session = QDBusConnection::sessionBus(); + + if (!session.isConnected()) { + qDebug() << ("ERROR: Cannot connect to the D-Bus session bus. Continuing..."); + return false; + } + + if(!session.registerObject(QLatin1String("/"), this, QDBusConnection::ExportScriptableContents)) { + qDebug() << ("ERROR: Cannot register D-Bus object. Continuing..."); + return false; + } + + if(!session.registerService(QLatin1String("org.kde.skanlite"))) { + qDebug() << ("ERROR: Cannot register D-Bus service. Continuing..."); + return false; + } + + return true; +} + + +// QDbusViewer (qttools5-dev-tools) is a very convinient tool to test D-Bus messaging (QTBUG-7341) +// But it can't pass string list parameters. It passes them as one string in following format: +// "{"KSane::InvertColors=false", "blue-gamma-table=0:0:100", "br-x=220", "br-y=300", "button 0=false", "button 1=false", "button 2=false", "button 3=false", "depth=8 битов", "green-gamma-table=0:0:100", "mode=Color", "opt_chipid=4", "opt_chipname=RTS8822L-02A", "opt_dbgimages=false", "opt_emulategray=false", "opt_model=HP4370", "opt_negative=false", "opt_nogamma=false", "opt_nowarmup=true", "opt_nowshading=true", "opt_realdepth=false", "opt_scancount=12381", "red-gamma-table=0:0:100", "resolution=50 DPI", "source=Flatbed", "tl-x=0", "tl-y=0"}" +// We check for this format and convert string back to string list if detected. +const QStringList DBusInterface::ensureStringList(const QStringList &list) +{ + if (list.length() == 1) { + QString s = list[0].trimmed(); + if (s.left(2) == QLatin1String("{\"") && s.right(2) == QLatin1String("\"}")) { + return s.remove(QLatin1String("{\"")).remove(QLatin1String("\"}")).split(QLatin1String("\", \"")); + } + } + + return list; +} diff --git a/src/DBusInterface.h b/src/DBusInterface.h new file mode 100644 index 0000000..d247e6e --- /dev/null +++ b/src/DBusInterface.h @@ -0,0 +1,150 @@ +/* ============================================================ + * + * Copyright (C) 2017 by Alexander Trufanov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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. If not, see + * + * ============================================================ */ + +#ifndef DBusInterface_h +#define DBusInterface_h + +#include +#include +#include +#include + +static const bool defaultSelectionFiltering = true; +static const QLatin1String defaultProfile("1"); + +class DBusInterface : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.skanlite") + QStringList m_msgBuffer; + +public: + + DBusInterface(QObject* parent = NULL) : QObject(parent) {} + + bool setupDBusInterface(); + + const QStringList& reply() { return m_msgBuffer; } + void setReply(const QStringList &reply) { m_msgBuffer = reply; } + +private: + // helper method for QDbusViewer compatibility. The details are in .cpp + const QStringList ensureStringList(const QStringList &list); + +Q_SIGNALS: + // used to communicate with Skanlite class + void requestedScan(); + void requestedScanCancel(); + void requestedGetScannerOptions(); + void requestedSetScannerOptions(const QStringList &options, bool ignoreSelection); + void requestedDefaultScannerOptions(); + void requestedDeviceName(); + void requestedSaveScannerOptionsToProfile(const QStringList &options, const QString &profile, bool ignoreSelection); + void requestedSwitchToProfile(const QString &profile, bool ignoreSelection); + void requestedGetSelection(); + void requestedSetSelection(const QStringList &options); + +public Q_SLOTS: + + // called via D-Bus + + // Perform scan operation if is in idle + Q_SCRIPTABLE void scan() { emit requestedScan(); } + + // Cancel any ongoing operation + Q_SCRIPTABLE void scanCancel() { emit requestedScanCancel(); } + + // Return device name, like "Hewlett-Packard:Scanjet 4370" + Q_SCRIPTABLE QString getDeviceName() + { + emit requestedDeviceName(); + return reply().join(QLatin1String()); + } + + // Return current scanner options as returned by KSaneWidget::getOptVals() + Q_SCRIPTABLE QStringList getScannerOptions() + { + emit requestedGetScannerOptions(); + return reply(); + } + + // Replaces current scanner options with argument value. Ignores page selection area info if ignoreSelection is true (by default) + Q_SCRIPTABLE void setScannerOptions(const QStringList &options, bool ignoreSelection = defaultSelectionFiltering) + { + emit requestedSetScannerOptions(ensureStringList(options), ignoreSelection); + } + + // Return current scanner options + Q_SCRIPTABLE QStringList getDefaultScannerOptions() + { + emit requestedDefaultScannerOptions(); + return reply(); + } + + // Save options to KConfigGroup named ""Options For %Current_Device% - Profile %profile%"" + // Thus it's device specific. Default profile is "1". Could be any string value. + // Ignores page selection area info if ignoreSelection is true (by default) + Q_SCRIPTABLE void saveScannerOptionsToProfile(const QStringList &options, const QString &profile = defaultProfile, bool ignoreSelection = defaultSelectionFiltering) + { + emit requestedSaveScannerOptionsToProfile(ensureStringList(options), profile, ignoreSelection); + } + + // Gets current options via KSaneWidget::getOptVals() and saves them into provile + // Acts as a combination of saveScannerOptionsToProfile and requestedSaveScannerOptionsToProfile + // Made for easy bind both to hotkey in KHotkeys + Q_SCRIPTABLE void saveCurrentScannerOptionsToProfile(const QString &profile = defaultProfile, bool ignoreSelection = defaultSelectionFiltering) + { + emit requestedGetScannerOptions(); // put result in m_msgBuffer + emit requestedSaveScannerOptionsToProfile(reply(), profile, ignoreSelection); + } + + // Loads options from specified profile and applies them. Ignores page selection area info if ignoreSelection is true (by default) + Q_SCRIPTABLE void switchToProfile(const QString &profile = defaultProfile, bool ignoreSelection = defaultSelectionFiltering) + { + emit requestedSwitchToProfile(profile, ignoreSelection); + } + + // Returns current selection area in form "{"tl-x=0", "tl-y=0", "br-x=220", "br-y=248"}" + Q_SCRIPTABLE QStringList getSelection() + { + emit requestedGetSelection(); + return reply(); + } + + // Changes current selection area. Argument must be a list of strings in form "{"tl-x=0", "tl-y=0", "br-x=220", "br-y=248"}" + Q_SCRIPTABLE void setSelection(const QStringList &options) + { + emit requestedSetSelection(ensureStringList(options)); + } + +Q_SIGNALS: + + // Below are 4 signals which are just forwarded from KSaneWidget. + // You can take a look in KSaneWidget.h for detailed arguments description + + Q_SCRIPTABLE void scanDone(int status, const QString &strStatus); + Q_SCRIPTABLE void userMessage(int type, const QString &strStatus); + Q_SCRIPTABLE void scanProgress(int percent); + Q_SCRIPTABLE void buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed); +}; + +#endif diff --git a/src/ImageViewer.cpp b/src/ImageViewer.cpp index 2c1703d..178871f 100644 --- a/src/ImageViewer.cpp +++ b/src/ImageViewer.cpp @@ -1,137 +1,139 @@ /* ============================================================ * Date : 2008-08-26 * Description : Preview image viewer. * * Copyright (C) 2008-2012 by Kåre Särs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see * * ============================================================ */ #include "ImageViewer.h" #include #include #include #include #include #include #include struct ImageViewer::Private { QGraphicsScene *scene; QImage *img; QAction *zoomInAction; QAction *zoomOutAction; QAction *zoom100Action; QAction *zoom2FitAction; }; ImageViewer::ImageViewer(QWidget *parent) : QGraphicsView(parent), d(new Private) { //setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setMouseTracking(true); // Init the scene d->scene = new QGraphicsScene; setScene(d->scene); // create context menu d->zoomInAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), this); connect(d->zoomInAction, &QAction::triggered, this, &ImageViewer::zoomIn); d->zoomOutAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), this); connect(d->zoomOutAction, &QAction::triggered, this, &ImageViewer::zoomOut); d->zoom100Action = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Zoom to Actual size"), this); connect(d->zoom100Action, &QAction::triggered, this, &ImageViewer::zoomActualSize); d->zoom2FitAction = new QAction(QIcon::fromTheme(QLatin1String("document-preview")), i18n("Zoom to Fit"), this); connect(d->zoom2FitAction, &QAction::triggered, this, &ImageViewer::zoom2Fit); addAction(d->zoomInAction); addAction(d->zoomOutAction); addAction(d->zoom100Action); addAction(d->zoom2FitAction); setContextMenuPolicy(Qt::ActionsContextMenu); } // ------------------------------------------------------------------------ ImageViewer::~ImageViewer() { delete d; } // ------------------------------------------------------------------------ void ImageViewer::setQImage(QImage *img) { if (img == 0) { return; } d->img = img; d->scene->setSceneRect(0, 0, img->width(), img->height()); } // ------------------------------------------------------------------------ void ImageViewer::drawBackground(QPainter *painter, const QRectF &rect) { painter->fillRect(rect, QColor(0x70, 0x70, 0x70)); painter->drawImage(rect, *d->img, rect); } // ------------------------------------------------------------------------ void ImageViewer::zoomIn() { scale(1.5, 1.5); } // ------------------------------------------------------------------------ void ImageViewer::zoomOut() { scale(1.0 / 1.5, 1.0 / 1.5); } // ------------------------------------------------------------------------ void ImageViewer::zoomActualSize() { setMatrix(QMatrix()); } // ------------------------------------------------------------------------ void ImageViewer::zoom2Fit() { fitInView(d->img->rect(), Qt::KeepAspectRatio); } // ------------------------------------------------------------------------ void ImageViewer::wheelEvent(QWheelEvent *e) { if (e->modifiers() == Qt::ControlModifier) { if (e->delta() > 0) { zoomIn(); - } else { + } + else { zoomOut(); } - } else { + } + else { QGraphicsView::wheelEvent(e); } } diff --git a/src/KSaneImageSaver.cpp b/src/KSaneImageSaver.cpp index 49123a1..cf6b605 100644 --- a/src/KSaneImageSaver.cpp +++ b/src/KSaneImageSaver.cpp @@ -1,247 +1,248 @@ /* ============================================================ * Date : 2010-07-02 * Description : Image saver class for libksane image data. * * Copyright (C) 2010-2012 by Kåre Särs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see * * ============================================================ */ #include "KSaneImageSaver.h" #include #include #include #include struct KSaneImageSaver::Private { enum ImageType { ImageTypePNG, ImageTypeTIFF }; bool m_savedOk; QMutex m_runMutex; KSaneImageSaver *q; QString m_name; QByteArray m_data; int m_width; int m_height; int m_format; int m_dpi; ImageType m_type; bool savePng(); bool saveTiff(); }; // ------------------------------------------------------------------------ KSaneImageSaver::KSaneImageSaver(QObject *parent) : QThread(parent), d(new Private) { d->q = this; } // ------------------------------------------------------------------------ KSaneImageSaver::~KSaneImageSaver() { delete d; } bool KSaneImageSaver::savePng(const QString &name, const QByteArray &data, int width, int height, int format, int dpi) { if (!d->m_runMutex.tryLock()) { return false; } d->m_name = name; d->m_data = data; d->m_width = width; d->m_height = height; d->m_format = format; d->m_dpi = dpi; d->m_type = Private::ImageTypePNG; start(); return true; } bool KSaneImageSaver::savePngSync(const QString &name, const QByteArray &data, int width, int height, int format, int dpi) { if (!savePng(name, data, width, height, format, dpi)) { qDebug() << "fail"; return false; } wait(); return d->m_savedOk; } bool KSaneImageSaver::saveTiff(const QString &name, const QByteArray &data, int width, int height, int format) { if (!d->m_runMutex.tryLock()) { return false; } d->m_name = name; d->m_data = data; d->m_width = width; d->m_height = height; d->m_format = format; d->m_type = Private::ImageTypeTIFF; qDebug() << "saving Tiff images is not yet supported"; d->m_runMutex.unlock(); return false; } bool KSaneImageSaver::saveTiffSync(const QString &name, const QByteArray &data, int width, int height, int format) { if (!saveTiff(name, data, width, height, format)) { return false; } wait(); return d->m_savedOk; } void KSaneImageSaver::run() { if (d->m_type == Private::ImageTypeTIFF) { d->m_savedOk = d->saveTiff(); emit imageSaved(d->m_savedOk); - } else { + } + else { d->m_savedOk = d->savePng(); emit imageSaved(d->m_savedOk); } d->m_runMutex.unlock(); } bool KSaneImageSaver::Private::saveTiff() { return false; } bool KSaneImageSaver::Private::savePng() { FILE *file; png_structp png_ptr; png_infop info_ptr; png_color_8 sig_bit; png_bytep row_ptr; int bytesPerPixel; // open the file file = fopen(qPrintable(m_name), "wb"); if (!file) { return false; } // create the png struct png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose(file); return false; } // create the image information srtuct info_ptr = png_create_info_struct(png_ptr); if (!png_ptr) { fclose(file); png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return false; } // initialize IO png_init_io(png_ptr, file); // set the image attributes switch ((KSaneIface::KSaneWidget::ImageFormat)m_format) { case KSaneIface::KSaneWidget::FormatGrayScale16: png_set_IHDR(png_ptr, info_ptr, m_width, m_height, 16, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); sig_bit.gray = 16; bytesPerPixel = 2; break; case KSaneIface::KSaneWidget::FormatRGB_16_C: png_set_IHDR(png_ptr, info_ptr, m_width, m_height, 16, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); sig_bit.red = 16; sig_bit.green = 16; sig_bit.blue = 16; bytesPerPixel = 6; break; default: fclose(file); png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return false; } png_set_sBIT(png_ptr, info_ptr, &sig_bit); png_uint_32 dpm = m_dpi * (1000.0 / 25.4); png_set_pHYs(png_ptr, info_ptr, dpm, dpm, 1); /* Optionally write comments into the image */ // text_ptr[0].key = "Title"; // text_ptr[0].text = "Mona Lisa"; // text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; // text_ptr[1].key = "Author"; // text_ptr[1].text = "Leonardo DaVinci"; // text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE; // text_ptr[2].key = "Description"; // text_ptr[2].text = ""; // text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt; // png_set_text(png_ptr, info_ptr, text_ptr, 2); /* Write the file header information. */ png_write_info(png_ptr, info_ptr); //png_set_shift(png_ptr, &sig_bit); //png_set_packing(png_ptr); png_set_compression_level(png_ptr, 9); // This is not nice :( swap bytes in the 16 bit color.... char tmp; // Make sure we have a buffer size that is divisible by 2 int dataSize = m_data.size(); if ((dataSize % 2) > 0) { dataSize--; } for (int i = 0; i < dataSize; i += 2) { tmp = m_data[i]; m_data[i] = m_data[i + 1]; m_data[i + 1] = tmp; } row_ptr = (png_bytep)m_data.data(); for (int i = 0; i < m_height; i++) { png_write_rows(png_ptr, &row_ptr, 1); row_ptr += m_width * bytesPerPixel; } png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); fclose(file); return true; } diff --git a/src/skanlite.cpp b/src/skanlite.cpp index 1817425..54d8158 100644 --- a/src/skanlite.cpp +++ b/src/skanlite.cpp @@ -1,634 +1,799 @@ /* ============================================================ * * Copyright (C) 2007-2012 by Kåre Särs * Copyright (C) 2009 by Arseniy Lartsev * Copyright (C) 2014 by Gregor Mitsch: port to KDE5 frameworks * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see * * ============================================================ */ #include "skanlite.h" #include "KSaneImageSaver.h" #include "SaveLocation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Skanlite::Skanlite(const QString &device, QWidget *parent) : QDialog(parent) , m_aboutData(nullptr) + , m_dbusInterface(this) { QVBoxLayout *mainLayout = new QVBoxLayout(this); QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this); dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Close); // was "User2: QPushButton *btnAbout = dlgButtonBoxBottom->addButton(i18n("About"), QDialogButtonBox::ButtonRole::ActionRole); // was "User1": QPushButton *btnSettings = dlgButtonBoxBottom->addButton(i18n("Settings"), QDialogButtonBox::ButtonRole::ActionRole); btnSettings->setIcon(QIcon::fromTheme(QLatin1String("configure"))); m_firstImage = true; m_ksanew = new KSaneIface::KSaneWidget(this); connect(m_ksanew, &KSaneWidget::imageReady, this, &Skanlite::imageReady); connect(m_ksanew, &KSaneWidget::availableDevices, this, &Skanlite::availableDevices); connect(m_ksanew, &KSaneWidget::userMessage, this, &Skanlite::alertUser); connect(m_ksanew, &KSaneWidget::buttonPressed, this, &Skanlite::buttonPressed); mainLayout->addWidget(m_ksanew); mainLayout->addWidget(dlgButtonBoxBottom); m_ksanew->initGetDeviceList(); // read the size here... KConfigGroup window(KSharedConfig::openConfig(), "Window"); QSize rect = window.readEntry("Geometry", QSize(740, 400)); resize(rect); connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, this, &QDialog::close); connect(this, &QDialog::finished, this, &Skanlite::saveWindowSize); connect(this, &QDialog::finished, this, &Skanlite::saveScannerOptions); connect(btnSettings, &QPushButton::clicked, this, &Skanlite::showSettingsDialog); connect(btnAbout, &QPushButton::clicked, this, &Skanlite::showAboutDialog); connect(dlgButtonBoxBottom, &QDialogButtonBox::helpRequested, this, &Skanlite::showHelp); // // Create the settings dialog // { m_settingsDialog = new QDialog(this); QVBoxLayout *mainLayout = new QVBoxLayout(m_settingsDialog); QWidget *settingsWidget = new QWidget(m_settingsDialog); m_settingsUi.setupUi(settingsWidget); m_settingsUi.revertOptions->setIcon(QIcon::fromTheme(QLatin1String("edit-undo"))); m_saveLocation = new SaveLocation(this); // add the supported image types const QList tmpList = QImageWriter::supportedMimeTypes(); m_filterList.clear(); foreach (auto ba, tmpList) { if (ba.isEmpty()) { continue; } m_filterList.append(QString::fromLatin1(ba)); } qDebug() << m_filterList; // Put first class citizens at first place m_filterList.removeAll(QLatin1String("image/jpeg")); m_filterList.removeAll(QLatin1String("image/tiff")); m_filterList.removeAll(QLatin1String("image/png")); m_filterList.insert(0, QLatin1String("image/png")); m_filterList.insert(1, QLatin1String("image/jpeg")); m_filterList.insert(2, QLatin1String("image/tiff")); m_filter16BitList << QLatin1String("image/png"); //m_filter16BitList << QLatin1String("image/tiff"); // fill m_filterList (...) and m_typeList (list of file suffixes) foreach (QString mimeStr, m_filterList) { QMimeType mimeType = QMimeDatabase().mimeTypeForName(mimeStr); m_filterList.append(mimeType.name()); QStringList fileSuffixes = mimeType.suffixes(); if (fileSuffixes.size() > 0) { m_typeList << fileSuffixes.first(); } } m_settingsUi.imgFormat->addItems(m_typeList); m_saveLocation->u_imgFormat->addItems(m_typeList); mainLayout->addWidget(settingsWidget); QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this); dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Close); connect(dlgButtonBoxBottom, &QDialogButtonBox::accepted, m_settingsDialog, &QDialog::accept); connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, m_settingsDialog, &QDialog::reject); mainLayout->addWidget(dlgButtonBoxBottom); m_settingsDialog->setWindowTitle(i18n("Skanlite Settings")); connect(m_settingsUi.getDirButton, &QPushButton::clicked, this, &Skanlite::getDir); connect(m_settingsUi.revertOptions, &QPushButton::clicked, this, &Skanlite::defaultScannerOptions); readSettings(); // default directory for the save dialog m_saveLocation->u_urlRequester->setUrl(QUrl::fromUserInput(m_settingsUi.saveDirLEdit->text())); m_saveLocation->u_imgPrefix->setText(m_settingsUi.imgPrefix->text()); m_saveLocation->u_imgFormat->setCurrentText(m_settingsUi.imgFormat->currentText()); } // open the scan device if (m_ksanew->openDevice(device) == false) { QString dev = m_ksanew->selectDevice(0); if (dev.isEmpty()) { // either no scanner was found or then cancel was pressed. exit(0); } if (m_ksanew->openDevice(dev) == false) { // could not open a scanner KMessageBox::sorry(0, i18n("Opening the selected scanner failed.")); exit(1); } else { setWindowTitle(i18nc("@title:window %1 = scanner maker, %2 = scanner model", "%1 %2 - Skanlite", m_ksanew->make(), m_ksanew->model())); m_deviceName = QString::fromLatin1("%1:%2").arg(m_ksanew->make()).arg(m_ksanew->model()); } } else { setWindowTitle(i18nc("@title:window %1 = scanner device", "%1 - Skanlite", device)); m_deviceName = device; } // prepare the Show Image Dialog { m_showImgDialog = new QDialog(this); QVBoxLayout *mainLayout = new QVBoxLayout(m_showImgDialog); QDialogButtonBox *imgButtonBox = new QDialogButtonBox(m_showImgDialog); // "Close" (now Discard) and "User1"=Save imgButtonBox->setStandardButtons(QDialogButtonBox::Discard | QDialogButtonBox::Save); mainLayout->addWidget(&m_imageViewer); mainLayout->addWidget(imgButtonBox); m_showImgDialogSaveButton = imgButtonBox->button(QDialogButtonBox::Save); m_showImgDialogSaveButton->setDefault(true); // still needed? m_showImgDialog->resize(640, 480); connect(imgButtonBox, &QDialogButtonBox::accepted, this, &Skanlite::saveImage); //connect(imgButtonBox, &QDialogButtonBox::accepted, m_showImgDialog, &QDialog::accept); connect(imgButtonBox->button(QDialogButtonBox::Discard), &QPushButton::clicked, m_ksanew, &KSaneWidget::scanCancel); connect(imgButtonBox->button(QDialogButtonBox::Discard), &QPushButton::clicked, m_showImgDialog, &QDialog::reject); } // save the default sane options for later use m_ksanew->getOptVals(m_defaultScanOpts); // load saved options loadScannerOptions(); m_ksanew->initGetDeviceList(); m_firstImage = true; + + if (m_dbusInterface.setupDBusInterface()) { + // D-Bus related slots + connect(&m_dbusInterface, &DBusInterface::requestedScan, m_ksanew, &KSaneWidget::scanFinal); + connect(&m_dbusInterface, &DBusInterface::requestedScanCancel, m_ksanew, &KSaneWidget::scanCancel); + connect(&m_dbusInterface, &DBusInterface::requestedSetScannerOptions, this, &Skanlite::setScannerOptions); + connect(&m_dbusInterface, &DBusInterface::requestedSetSelection, this, &Skanlite::setSelection); + + // D-Bus related slots below must be Qt::DirectConnection to simplify return value forwarding via DBusInterface + connect(&m_dbusInterface, &DBusInterface::requestedGetScannerOptions, this, &Skanlite::getScannerOptions, Qt::DirectConnection); + connect(&m_dbusInterface, &DBusInterface::requestedDefaultScannerOptions, this, &Skanlite::getDefaultScannerOptions, Qt::DirectConnection); + connect(&m_dbusInterface, &DBusInterface::requestedDeviceName, this, &Skanlite::getDeviceName, Qt::DirectConnection); + connect(&m_dbusInterface, &DBusInterface::requestedSaveScannerOptionsToProfile, this, &Skanlite::saveScannerOptionsToProfile, Qt::DirectConnection); + connect(&m_dbusInterface, &DBusInterface::requestedSwitchToProfile, this, &Skanlite::switchToProfile, Qt::DirectConnection); + connect(&m_dbusInterface, &DBusInterface::requestedGetSelection, this, &Skanlite::getSelection, Qt::DirectConnection); + + // D-Bus related signals + connect(m_ksanew, &KSaneWidget::scanDone, &m_dbusInterface, &DBusInterface::scanDone); + connect(m_ksanew, &KSaneWidget::userMessage, &m_dbusInterface, &DBusInterface::userMessage); + connect(m_ksanew, &KSaneWidget::scanProgress, &m_dbusInterface, &DBusInterface::scanProgress); + connect(m_ksanew, &KSaneWidget::buttonPressed, &m_dbusInterface, &DBusInterface::buttonPressed); + } + else { + // keep working without dbus + } } void Skanlite::showHelp() { KHelpClient::invokeHelp(QLatin1String("index"), QLatin1String("skanlite")); } void Skanlite::setAboutData(KAboutData *aboutData) { m_aboutData = aboutData; } void Skanlite::closeEvent(QCloseEvent *event) { saveWindowSize(); saveScannerOptions(); event->accept(); } void Skanlite::saveWindowSize() { KConfigGroup window(KSharedConfig::openConfig(), "Window"); window.writeEntry("Geometry", size()); window.sync(); } // Pops up message box similar to what perror() would print //************************************************************ static void perrorMessageBox(const QString &text) { if (errno != 0) { KMessageBox::sorry(0, i18n("%1: %2", text, QString::fromLocal8Bit(strerror(errno)))); - } else { + } + else { KMessageBox::sorry(0, text); } } void Skanlite::readSettings(void) { // enable the widgets to allow modifying m_settingsUi.setQuality->setChecked(true); m_settingsUi.setPreviewDPI->setChecked(true); // read the saved parameters KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving"); m_settingsUi.saveModeCB->setCurrentIndex(saving.readEntry("SaveMode", (int)SaveModeManual)); if (m_settingsUi.saveModeCB->currentIndex() != SaveModeAskFirst) { m_firstImage = false; } m_settingsUi.saveDirLEdit->setText(saving.readEntry("Location", QDir::homePath())); m_settingsUi.imgPrefix->setText(saving.readEntry("NamePrefix", i18nc("prefix for auto naming", "Image-"))); m_settingsUi.imgFormat->setCurrentText(saving.readEntry("ImgFormat", "png")); m_settingsUi.imgQuality->setValue(saving.readEntry("ImgQuality", 90)); m_settingsUi.setQuality->setChecked(saving.readEntry("SetQuality", false)); m_settingsUi.showB4Save->setChecked(saving.readEntry("ShowBeforeSave", true)); KConfigGroup general(KSharedConfig::openConfig(), "General"); //m_settingsUi.previewDPI->setCurrentItem(general.readEntry("PreviewDPI", "100"), true); // FIXME KF5 is the 'true' parameter still needed? m_settingsUi.previewDPI->setCurrentText(general.readEntry("PreviewDPI", "100")); m_settingsUi.setPreviewDPI->setChecked(general.readEntry("SetPreviewDPI", false)); if (m_settingsUi.setPreviewDPI->isChecked()) { m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat()); - } else { + } + else { m_ksanew->setPreviewResolution(0.0); } m_settingsUi.u_disableSelections->setChecked(general.readEntry("DisableAutoSelection", false)); m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked()); } void Skanlite::showSettingsDialog(void) { readSettings(); // show the dialog if (m_settingsDialog->exec()) { // save the settings KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving"); saving.writeEntry("SaveMode", m_settingsUi.saveModeCB->currentIndex()); saving.writeEntry("Location", m_settingsUi.saveDirLEdit->text()); saving.writeEntry("NamePrefix", m_settingsUi.imgPrefix->text()); saving.writeEntry("ImgFormat", m_settingsUi.imgFormat->currentText()); saving.writeEntry("SetQuality", m_settingsUi.setQuality->isChecked()); saving.writeEntry("ImgQuality", m_settingsUi.imgQuality->value()); saving.writeEntry("ShowBeforeSave", m_settingsUi.showB4Save->isChecked()); saving.sync(); KConfigGroup general(KSharedConfig::openConfig(), "General"); general.writeEntry("PreviewDPI", m_settingsUi.previewDPI->currentText()); general.writeEntry("SetPreviewDPI", m_settingsUi.setPreviewDPI->isChecked()); general.writeEntry("DisableAutoSelection", m_settingsUi.u_disableSelections->isChecked()); general.sync(); // the previewDPI has to be set here if (m_settingsUi.setPreviewDPI->isChecked()) { m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat()); - } else { + } + else { // 0.0 means default value. m_ksanew->setPreviewResolution(0.0); } m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked()); // pressing OK in the settings dialog means use those settings. m_saveLocation->u_urlRequester->setUrl(QUrl::fromUserInput(m_settingsUi.saveDirLEdit->text())); m_saveLocation->u_imgPrefix->setText(m_settingsUi.imgPrefix->text()); m_saveLocation->u_imgFormat->setCurrentText(m_settingsUi.imgFormat->currentText()); m_firstImage = true; - } else { + } + else { //Forget Changes readSettings(); } } void Skanlite::imageReady(QByteArray &data, int w, int h, int bpl, int f) { // save the image data m_data = data; m_width = w; m_height = h; m_bytesPerLine = bpl; m_format = f; if (m_settingsUi.showB4Save->isChecked() == true) { /* copy the image data into m_img and show it*/ m_img = m_ksanew->toQImageSilent(data, w, h, bpl, (KSaneIface::KSaneWidget::ImageFormat)f); m_imageViewer.setQImage(&m_img); m_imageViewer.zoom2Fit(); m_showImgDialogSaveButton->setFocus(); m_showImgDialog->exec(); // save has been done as a result of save or then we got cancel - } else { + } + else { m_img = QImage(); // clear the image to ensure we save the correct one. saveImage(); } } void Skanlite::saveImage() { // ask the first time if we are in "ask on first" mode if ((m_settingsUi.saveModeCB->currentIndex() == SaveModeAskFirst) && m_firstImage) { if (m_saveLocation->exec() != QFileDialog::Accepted) { m_ksanew->scanCancel(); // In case we are cancelling a document feeder scan return; } m_firstImage = false; } QString dir = QDir::cleanPath(m_saveLocation->u_urlRequester->url().url()).append(QLatin1Char('/')); //make sure whole value is processed as path to directory QString prefix = m_saveLocation->u_imgPrefix->text(); QString imgFormat = m_saveLocation->u_imgFormat->currentText().toLower(); int fileNumber = m_saveLocation->u_numStartFrom->value(); QStringList filterList = m_filterList; if ((m_format == KSaneIface::KSaneWidget::FormatRGB_16_C) || (m_format == KSaneIface::KSaneWidget::FormatGrayScale16)) { filterList = m_filter16BitList; if (imgFormat != QLatin1String("png")) { imgFormat = QLatin1String("png"); KMessageBox::information(this, i18n("The image will be saved in the PNG format, as Skanlite only supports saving 16 bit color images in the PNG format.")); } } //qDebug() << dir << prefix << imgFormat; // find next available file name for name suggestion QUrl fileUrl; QString fname; for (int i = fileNumber; i <= m_saveLocation->u_numStartFrom->maximum(); ++i) { fname = QString::fromLatin1("%1%2.%3") .arg(prefix) .arg(i, 4, 10, QLatin1Char('0')) .arg(imgFormat); fileUrl = QUrl::fromUserInput(QStringLiteral("%1%2").arg(dir, fname)); //qDebug() << fileUrl; if (fileUrl.isLocalFile()) { if (!QFileInfo(fileUrl.toLocalFile()).exists()) { break; } } else { KIO::StatJob *statJob = KIO::stat(fileUrl, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (!statJob->exec()) { break; } } } if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) { // prepare the save dialog QFileDialog saveDialog(this, i18n("New Image File Name")); saveDialog.setAcceptMode(QFileDialog::AcceptSave); saveDialog.setFileMode(QFileDialog::AnyFile); // ask for a filename if requested. saveDialog.setDirectoryUrl(fileUrl.adjusted(QUrl::RemoveFilename)); saveDialog.selectUrl(fileUrl); // NOTE it is probably a bug that both setDirectoryUrl and selectUrl have // to be set to get remote urls to work QStringList actualFilterList = filterList; QString currentMimeFilter = QLatin1String("image/") + imgFormat; saveDialog.setMimeTypeFilters(actualFilterList); saveDialog.selectMimeTypeFilter(currentMimeFilter); //qDebug() << fileUrl.url() << fileUrl.toLocalFile() << currentMimeFilter; do { if (saveDialog.exec() != QFileDialog::Accepted) { return; } fileUrl = saveDialog.selectedUrls()[0]; bool exists; if (fileUrl.isLocalFile()) { exists = QFileInfo(fileUrl.toLocalFile()).exists(); } else { KIO::StatJob *statJob = KIO::stat(fileUrl, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); exists = statJob->exec(); } if (exists) { if (KMessageBox::warningContinueCancel(this, i18n("Do you want to overwrite \"%1\"?", fileUrl.fileName()), QString(), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QLatin1String("editorWindowSaveOverwrite") - ) == KMessageBox::Continue) - { + ) == KMessageBox::Continue) { break; } } else { break; } } while (true); } m_firstImage = false; // Get the quality int quality = -1; if (m_settingsUi.setQuality->isChecked()) { quality = m_settingsUi.imgQuality->value(); } //qDebug() << "suffix" << QFileInfo(fileUrl.fileName()).suffix(); QString localName; QString suffix = QFileInfo(fileUrl.fileName()).suffix(); const char *fileFormat = nullptr; if (suffix.isEmpty()) { fileFormat = "png"; } if (!fileUrl.isLocalFile()) { QTemporaryFile tmp; tmp.open(); if (suffix.isEmpty()) { localName = tmp.fileName(); } else { localName = QStringLiteral("%1.%2").arg(tmp.fileName(), suffix); } tmp.close(); // we just want the filename } else { localName = fileUrl.toLocalFile(); } // Save if ((m_format == KSaneIface::KSaneWidget::FormatRGB_16_C) || - (m_format == KSaneIface::KSaneWidget::FormatGrayScale16)) - { + (m_format == KSaneIface::KSaneWidget::FormatGrayScale16)) { KSaneImageSaver saver; if (saver.savePngSync(localName, m_data, m_width, m_height, m_format, m_ksanew->currentDPI())) { m_showImgDialog->close(); // closing the window if it is closed should not be a problem. } else { perrorMessageBox(i18n("Failed to save image")); return; } } else { // create the image if needed. if (m_img.width() < 1) { m_img = m_ksanew->toQImage(m_data, m_width, m_height, m_bytesPerLine, (KSaneIface::KSaneWidget::ImageFormat)m_format); } if (m_img.save(localName, fileFormat, quality)) { m_showImgDialog->close(); // calling close() on a closed window does nothing. } else { perrorMessageBox(i18n("Failed to save image")); return; } } if (!fileUrl.isLocalFile()) { QFile tmpFile(localName); tmpFile.open(QIODevice::ReadOnly); auto uploadJob = KIO::storedPut(&tmpFile, fileUrl, -1); KJobWidgets::setWindow(uploadJob, QApplication::activeWindow()); bool ok = uploadJob->exec(); tmpFile.close(); tmpFile.remove(); if (!ok) { KMessageBox::sorry(0, i18n("Failed to upload image")); } } // Save the file base name without number QString baseName = QFileInfo(fileUrl.fileName()).completeBaseName(); while ((!baseName.isEmpty()) && (baseName[baseName.size() - 1].isNumber())) { baseName.remove(baseName.size() - 1, 1); } m_saveLocation->u_imgPrefix->setText(baseName); // Save the number QString fileNumStr = QFileInfo(fileUrl.fileName()).completeBaseName(); fileNumStr.remove(baseName); fileNumber = fileNumStr.toInt(); if (fileNumber) { m_saveLocation->u_numStartFrom->setValue(fileNumber + 1); } if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) { // Save last used dir, prefix and suffix. m_saveLocation->u_urlRequester->setUrl(KIO::upUrl(fileUrl)); m_saveLocation->u_imgFormat->setCurrentText(QFileInfo(fileUrl.fileName()).suffix()); } } void Skanlite::getDir(void) { QString dir = QFileDialog::getExistingDirectory(m_settingsDialog, QString(), m_settingsUi.saveDirLEdit->text()); if (!dir.isEmpty()) { m_settingsUi.saveDirLEdit->setText(dir); } } void Skanlite::showAboutDialog(void) { KAboutApplicationDialog(*m_aboutData).exec(); } +void writeScannerOptions(const QString &groupName, const QMap &opts) +{ + KConfigGroup options(KSharedConfig::openConfig(), groupName); + QMap::const_iterator it = opts.constBegin(); + while (it != opts.constEnd()) { + options.writeEntry(it.key(), it.value()); + ++it; + } + options.sync(); +} + +void readScannerOptions(const QString &groupName, QMap &opts) +{ + KConfigGroup scannerOptions(KSharedConfig::openConfig(), groupName); + opts = scannerOptions.entryMap(); +} + void Skanlite::saveScannerOptions() { KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving"); saving.writeEntry("NumberStartsFrom", m_saveLocation->u_numStartFrom->value()); if (!m_ksanew) { return; } KConfigGroup options(KSharedConfig::openConfig(), QString::fromLatin1("Options For %1").arg(m_deviceName)); QMap opts; m_ksanew->getOptVals(opts); - QMap::const_iterator it = opts.constBegin(); - while (it != opts.constEnd()) { - options.writeEntry(it.key(), it.value()); - ++it; - } - options.sync(); + writeScannerOptions(QString::fromLatin1("Options For %1").arg(m_deviceName), opts); } void Skanlite::defaultScannerOptions() { if (!m_ksanew) { return; } m_ksanew->setOptVals(m_defaultScanOpts); } void Skanlite::loadScannerOptions() { KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving"); m_saveLocation->u_numStartFrom->setValue(saving.readEntry("NumberStartsFrom", 1)); if (!m_ksanew) { return; } - KConfigGroup scannerOptions(KSharedConfig::openConfig(), QString::fromLatin1("Options For %1").arg(m_deviceName)); - m_ksanew->setOptVals(scannerOptions.entryMap()); + QMap opts; + readScannerOptions(QString::fromLatin1("Options For %1").arg(m_deviceName), opts); + m_ksanew->setOptVals(opts); } void Skanlite::availableDevices(const QList &deviceList) { for (int i = 0; i < deviceList.size(); ++i) { qDebug() << deviceList.at(i).name; } } void Skanlite::alertUser(int type, const QString &strStatus) { switch (type) { case KSaneWidget::ErrorGeneral: KMessageBox::sorry(0, strStatus, QLatin1String("Skanlite Test")); break; default: KMessageBox::information(0, strStatus, QLatin1String("Skanlite Test")); } } void Skanlite::buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed) { qDebug() << "Button" << optionName << optionLabel << ((pressed) ? "pressed" : "released"); } + +// D-Bus interface related helper functions + +QStringList serializeScannerOptions(const QMap &opts) +{ + QStringList sl; + QMap::const_iterator it = opts.constBegin(); + while (it != opts.constEnd()) { + sl.append(it.key() + QLatin1String("=") + it.value()); + ++it; + } + return sl; +} + +void deserializeScannerOptions(const QStringList &settings, QMap &opts) +{ + foreach (QString s, settings) { + int i = s.lastIndexOf(QLatin1Char('=')); + opts[s.left(i)] = s.right(s.length()-i-1); + } +} + +static const QStringList selectionSettings = { QLatin1String("tl-x"), QLatin1String("tl-y"), + QLatin1String("br-x"), QLatin1String("br-y") }; + +void filterSelectionSettings(QMap &opts) +{ + foreach (QString s, selectionSettings) { + opts.remove(s); + } +} + +bool containsSelectionSettings(const QMap &opts) +{ + foreach (QString s, selectionSettings) { + if (opts.contains(s)) { + return true; + } + } + return false; +} + +void Skanlite::processSelectionOptions(QMap &opts, bool ignoreSelection) +{ + if (ignoreSelection) { + filterSelectionSettings(opts); + } + else { + if (containsSelectionSettings(opts)) { // make sure we really have selection to apply + m_ksanew->setSelection(QPointF(0,0), QPointF(1,1)); // bcs settings have no effect if nothing was selected beforehand (Bug 377009) + } + } +} + +// D-Bus interface related slots + +void Skanlite::getScannerOptions() +{ + QMap opts; + m_ksanew->getOptVals(opts); + m_dbusInterface.setReply(serializeScannerOptions(opts)); +} + +void Skanlite::setScannerOptions(const QStringList &options, bool ignoreSelection) +{ + QMap opts; + deserializeScannerOptions(options, opts); + processSelectionOptions(opts, ignoreSelection); + m_ksanew->setOptVals(opts); +} + + +void Skanlite::getDefaultScannerOptions() +{ + m_dbusInterface.setReply(serializeScannerOptions(m_defaultScanOpts)); +} + +static const QLatin1String defaultProfileGroup("Options For %1 - Profile %2"); // 1 - device, 2 - arg + +void Skanlite::saveScannerOptionsToProfile(const QStringList &options, const QString &profile, bool ignoreSelection) +{ + QMap opts; + deserializeScannerOptions(options, opts); + processSelectionOptions(opts, ignoreSelection); + writeScannerOptions(QString(defaultProfileGroup).arg(m_deviceName).arg(profile), opts); +} + +void Skanlite::switchToProfile(const QString &profile, bool ignoreSelection) +{ + QMap opts; + readScannerOptions(QString(defaultProfileGroup).arg(m_deviceName).arg(profile), opts); + + if (opts.empty()) { + opts = m_defaultScanOpts; + } + + processSelectionOptions(opts, ignoreSelection); + m_ksanew->setOptVals(opts); +} + +void Skanlite::getDeviceName() +{ + m_dbusInterface.setReply(QStringList(m_deviceName)); +} + +void Skanlite::getSelection() +{ + QMap opts; + m_ksanew->getOptVals(opts); + + QStringList reply; + foreach ( QString key, selectionSettings ) { + if (opts.contains(key)) { + reply.append(key + QLatin1String("=") + opts[key]); + } + } + m_dbusInterface.setReply(reply); +} + +void Skanlite::setSelection(const QStringList &options) +{ // here options contains selection related subset of options + setScannerOptions(options, false); +} diff --git a/src/skanlite.h b/src/skanlite.h index a6d6c0f..97a7487 100644 --- a/src/skanlite.h +++ b/src/skanlite.h @@ -1,106 +1,120 @@ /* ============================================================ * * Copyright (C) 2007-2012 by Kåre Särs * Copyright (C) 2014 by Gregor Mitsch: port to KDE5 frameworks * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see * * ============================================================ */ #ifndef Skanlite_h #define Skanlite_h #include #include #include #include "ui_settings.h" #include "ImageViewer.h" +#include "DBusInterface.h" class SaveLocation; class KAboutData; using namespace KSaneIface; class Skanlite : public QDialog { Q_OBJECT public: explicit Skanlite(const QString &device, QWidget *parent); void setAboutData(KAboutData *aboutData); private: // Order of items in save mode combo-box enum SaveMode { SaveModeManual = 0, SaveModeAskFirst = 1, }; void readSettings(); void doSaveImage(bool askFilename = true); void loadScannerOptions(); + void processSelectionOptions(QMap &opts, bool ignoreSelection); + private Q_SLOTS: void showSettingsDialog(); void getDir(); void imageReady(QByteArray &, int, int, int, int); void saveImage(); void showAboutDialog(); void saveWindowSize(); void saveScannerOptions(); void defaultScannerOptions(); void availableDevices(const QList &deviceList); void alertUser(int type, const QString &strStatus); void buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed); void showHelp(); + // slots to communicate with D-Bus interface + void getScannerOptions(); + void setScannerOptions(const QStringList &options, bool ignoreSelection); + void getDefaultScannerOptions(); + void saveScannerOptionsToProfile(const QStringList &options, const QString &profile, bool ignoreSelection); + void switchToProfile(const QString &profile, bool ignoreSelection); + void getDeviceName(); + void getSelection(); + void setSelection(const QStringList &options); + protected: void closeEvent(QCloseEvent *event); private: KAboutData *m_aboutData; KSaneWidget *m_ksanew = nullptr; Ui::SkanliteSettings m_settingsUi; QDialog *m_settingsDialog = nullptr; QDialog *m_showImgDialog = nullptr; // having this variable here is not so nice; ShowImgageDialog should be separate class QPushButton *m_showImgDialogSaveButton = nullptr; SaveLocation *m_saveLocation = nullptr; QString m_deviceName; QMap m_defaultScanOpts; QImage m_img; QByteArray m_data; int m_width; int m_height; int m_bytesPerLine; int m_format; ImageViewer m_imageViewer; + DBusInterface m_dbusInterface; QStringList m_filterList; QStringList m_filter16BitList; QStringList m_typeList; bool m_firstImage; }; #endif