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