skanlite.cpp 27.3 KB
Newer Older
Kåre Särs's avatar
Kåre Särs committed
1
/* ============================================================
2
*
3
4
5
* SPDX-FileCopyrightText: 2007-2012 Kåre Särs <kare.sars@iki .fi>
* SPDX-FileCopyrightText: 2009 Arseniy Lartsev <receive-spam at yandex dot ru>
* SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
6
*
7
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
8
9
*
* ============================================================ */
Kåre Särs's avatar
Kåre Särs committed
10

11
#include "skanlite.h"
12

13
#include "SaveLocation.h"
Alexander Volkov's avatar
Alexander Volkov committed
14
#include "showimagedialog.h"
15
#include "SkanliteImageSaver.h"
16

17
#include <QApplication>
18
#include <QScrollArea>
19
#include <QStringList>
20
21
#include <QFileDialog>
#include <QUrl>
22
#include <QDialogButtonBox>
23
#include <QComboBox>
24
#include <QMessageBox>
Gregor Mi's avatar
Gregor Mi committed
25
26
27
28
#include <QTemporaryFile>
#include <QImageWriter>
#include <QMimeType>
#include <QMimeDatabase>
Alexander Volkov's avatar
Alexander Volkov committed
29
#include <QCloseEvent>
30
#include <QProgressBar>
31

32
#include <KAboutData>
33
#include <KAboutApplicationDialog>
Gregor Mi's avatar
Gregor Mi committed
34
#include <KLocalizedString>
35
#include <KMessageBox>
Gregor Mi's avatar
Gregor Mi committed
36
#include <KIO/StatJob>
37
38
#include <KIO/Job>
#include <KJobWidgets>
39
#include <kio/global.h>
40
41
#include <KSharedConfig>
#include <KConfigGroup>
42
#include <KHelpClient>
43
#include <kio_version.h>
44

Pino Toscano's avatar
Pino Toscano committed
45
46
#include <skanlite_debug.h>

47
48
#include <errno.h>

Laurent Montel's avatar
Laurent Montel committed
49
Skanlite::Skanlite(const QString &device, QWidget *parent)
50
    : QDialog(parent)
Alexander Trufanov's avatar
Alexander Trufanov committed
51
    , m_dbusInterface(this)
Laurent Montel's avatar
Laurent Montel committed
52
{
53
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
Laurent Montel's avatar
Laurent Montel committed
54
55

    QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this);
56
    dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Close);
57
    // was "User2:
Laurent Montel's avatar
Laurent Montel committed
58
    QPushButton *btnAbout = dlgButtonBoxBottom->addButton(i18n("About"), QDialogButtonBox::ButtonRole::ActionRole);
59
    // was "User1":
Laurent Montel's avatar
Laurent Montel committed
60
    QPushButton *btnSettings = dlgButtonBoxBottom->addButton(i18n("Settings"), QDialogButtonBox::ButtonRole::ActionRole);
61
    btnSettings->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
62

63
64
    m_firstImage = true;

65
    m_ksanew = new KSaneIface::KSaneWidget(this);
66
    connect(m_ksanew, &KSaneWidget::scannedImageReady, this, &Skanlite::imageReady);
Laurent Montel's avatar
Laurent Montel committed
67
68
    connect(m_ksanew, &KSaneWidget::userMessage, this, &Skanlite::alertUser);
    connect(m_ksanew, &KSaneWidget::buttonPressed, this, &Skanlite::buttonPressed);
Kåre Särs's avatar
Kåre Särs committed
69
    connect(m_ksanew, &KSaneWidget::scanDone, this, [this](){
70
71
72
73
        if (!m_pendingApplyScanOpts.isEmpty()) {
            applyScannerOptions(m_pendingApplyScanOpts);
        }
    });
74

75
76
77
78
79
80
81
82
83
    m_saveProgressBar = new QProgressBar(this);
    m_saveProgressBar->setVisible(false);
    m_saveProgressBar->setFormat(i18n("Saving: %v kB"));
    m_saveProgressBar->setTextVisible(true);

    m_saveUpdateTimer.setInterval(200);
    m_saveUpdateTimer.setSingleShot(false);
    connect(&m_saveUpdateTimer, &QTimer::timeout, this, &Skanlite::updateSaveProgress);

84
    mainLayout->addWidget(m_ksanew);
85
    mainLayout->addWidget(m_saveProgressBar);
86
    mainLayout->addWidget(dlgButtonBoxBottom);
87

88
89
    m_ksanew->initGetDeviceList();

90
    // read the size here...
91
    KConfigGroup window(KSharedConfig::openConfig(), "Window");
Laurent Montel's avatar
Laurent Montel committed
92
    QSize rect = window.readEntry("Geometry", QSize(740, 400));
93
94
    resize(rect);

95
96
97
98
99
100
    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);
101

102
    //
103
    // Create the settings dialog
104
    //
105
106
    {
        m_settingsDialog = new QDialog(this);
Laurent Montel's avatar
Laurent Montel committed
107

108
109
110
111
        QVBoxLayout *mainLayout = new QVBoxLayout(m_settingsDialog);

        QWidget *settingsWidget = new QWidget(m_settingsDialog);
        m_settingsUi.setupUi(settingsWidget);
112
        m_settingsUi.revertOptions->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
113
114
115
        m_saveLocation = new SaveLocation(this);

        // add the supported image types
Gregor Mi's avatar
Gregor Mi committed
116
117
        const QList<QByteArray> tmpList = QImageWriter::supportedMimeTypes();
        m_filterList.clear();
118
        for (const auto &ba : tmpList) {
Pino Toscano's avatar
Pino Toscano committed
119
120
121
            if (ba.isEmpty()) {
                continue;
            }
Gregor Mi's avatar
Gregor Mi committed
122
123
124
            m_filterList.append(QString::fromLatin1(ba));
        }

Pino Toscano's avatar
Pino Toscano committed
125
        qCDebug(SKANLITE_LOG) << m_filterList;
Laurent Montel's avatar
Laurent Montel committed
126

127
        // Put first class citizens at first place
128
129
130
131
132
133
134
135
        m_filterList.removeAll(QStringLiteral("image/jpeg"));
        m_filterList.removeAll(QStringLiteral("image/tiff"));
        m_filterList.removeAll(QStringLiteral("image/png"));
        m_filterList.insert(0, QStringLiteral("image/png"));
        m_filterList.insert(1, QStringLiteral("image/jpeg"));
        m_filterList.insert(2, QStringLiteral("image/tiff"));

        m_filter16BitList << QStringLiteral("image/png");
136
        m_filter16BitList << QStringLiteral("image/tiff");
137

138
        // fill m_filterList (...)
139
140
141
142
143
        {
            QStringList namedMimeTypes;
            for (const QString &mimeStr : qAsConst(m_filterList)) {
                QMimeType mimeType = QMimeDatabase().mimeTypeForName(mimeStr);
                namedMimeTypes.append(mimeType.name());
Gregor Mi's avatar
Gregor Mi committed
144

145
146
                m_settingsUi.imgFormat->addItem(mimeType.preferredSuffix(), mimeType.name());
                m_saveLocation->addImageFormat(mimeType.preferredSuffix(), mimeType.name());
147
            }
148
            m_filterList << std::move(namedMimeTypes);
149
        }
Gregor Mi's avatar
Gregor Mi committed
150

151
        mainLayout->addWidget(settingsWidget);
Laurent Montel's avatar
Laurent Montel committed
152
153

        QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this);
154
155
156
        dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
        connect(dlgButtonBoxBottom, &QDialogButtonBox::accepted, m_settingsDialog, &QDialog::accept);
        connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, m_settingsDialog, &QDialog::reject);
Laurent Montel's avatar
Laurent Montel committed
157

158
159
160
161
162
163
164
165
        mainLayout->addWidget(dlgButtonBoxBottom);

        m_settingsDialog->setWindowTitle(i18n("Skanlite Settings"));

        connect(m_settingsUi.revertOptions, &QPushButton::clicked, this, &Skanlite::defaultScannerOptions);
        readSettings();

        // default directory for the save dialog
Kåre Särs's avatar
Kåre Särs committed
166
167
        m_saveLocation->setFolderUrl(m_settingsUi.saveDirRequester->url());
        m_saveLocation->setImagePrefix(m_settingsUi.imgPrefix->text());
168
        m_saveLocation->setImageFormatIndex(m_settingsUi.imgFormat->currentIndex());
169
    }
170

171
    // open the scan device
172
    if (m_ksanew->openDevice(device) == false) {
Kåre Särs's avatar
Kåre Särs committed
173
        QString dev = m_ksanew->selectDevice(nullptr);
174
175
176
177
        if (dev.isEmpty()) {
            // either no scanner was found or then cancel was pressed.
            exit(0);
        }
178
        if (m_ksanew->openDevice(dev) == false) {
179
            // could not open a scanner
Kåre Särs's avatar
Kåre Särs committed
180
            KMessageBox::sorry(nullptr, i18n("Opening the selected scanner failed."));
181
            exit(1);
182
183
        }
        else {
184
185
            setWindowTitle(i18nc("@title:window %1 = scanner maker, %2 = scanner model", "%1 %2 - Skanlite", m_ksanew->deviceVendor(), m_ksanew->deviceModel()));
            m_deviceName = QStringLiteral("%1:%2").arg(m_ksanew->deviceVendor(), m_ksanew->deviceModel());
Kåre Särs's avatar
Kåre Särs committed
186
        }
187
188
    }
    else {
189
        setWindowTitle(i18nc("@title:window %1 = scanner device", "%1 - Skanlite", device));
190
        m_deviceName = device;
Kåre Särs's avatar
Kåre Särs committed
191
    }
192

Laurent Montel's avatar
Laurent Montel committed
193
    // prepare the Show Image Dialog
Alexander Volkov's avatar
Alexander Volkov committed
194
195
196
    m_showImgDialog = new ShowImageDialog(this);
    connect(m_showImgDialog, &ShowImageDialog::saveRequested, this, &Skanlite::saveImage);
    connect(m_showImgDialog, &ShowImageDialog::rejected, m_ksanew, &KSaneWidget::scanCancel);
197

198
199
200
201
202
203
    // save the default sane options for later use
    m_ksanew->getOptVals(m_defaultScanOpts);

    // load saved options
    loadScannerOptions();

204
    m_firstImage = true;
Alexander Stippich's avatar
Alexander Stippich committed
205
    m_ksanew->setFocus();
Alexander Trufanov's avatar
Alexander Trufanov committed
206
207
208
209

    if (m_dbusInterface.setupDBusInterface()) {
        // D-Bus related slots
        connect(&m_dbusInterface, &DBusInterface::requestedScan, m_ksanew, &KSaneWidget::scanFinal);
210
        connect(&m_dbusInterface, &DBusInterface::requestedPreview, m_ksanew, &KSaneWidget::startPreviewScan);
Alexander Trufanov's avatar
Alexander Trufanov committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        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
    }
232
233
}

234
235
void Skanlite::showHelp()
{
236
    KHelpClient::invokeHelp(QStringLiteral("index"), QStringLiteral("skanlite"));
237
238
}

239
240
void Skanlite::closeEvent(QCloseEvent *event)
{
241
    saveWindowSize();
242
    saveScannerOptions();
243
244
245
    event->accept();
}

246
247
void Skanlite::saveWindowSize()
{
248
    KConfigGroup window(KSharedConfig::openConfig(), "Window");
249
250
251
    window.writeEntry("Geometry", size());
    window.sync();
}
Gregor Mi's avatar
Gregor Mi committed
252

253
// Pops up message box similar to what perror() would print
254
//************************************************************
255
static void perrorMessageBox(const QString &text)
256
{
257
    if (errno != 0) {
Kåre Särs's avatar
Kåre Särs committed
258
        KMessageBox::sorry(nullptr, i18n("%1: %2", text, QString::fromLocal8Bit(strerror(errno))));
Alexander Trufanov's avatar
Alexander Trufanov committed
259
260
    }
    else {
Kåre Särs's avatar
Kåre Särs committed
261
        KMessageBox::sorry(nullptr, text);
262
263
264
    }
}

265
void Skanlite::readSettings(void)
266
267
{
    // enable the widgets to allow modifying
268
269
    m_settingsUi.setQuality->setChecked(true);
    m_settingsUi.setPreviewDPI->setChecked(true);
270

271
    // read the saved parameters
272
    KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
273
    m_settingsUi.saveModeCB->setCurrentIndex(saving.readEntry("SaveMode", (int)SaveModeManual));
Laurent Montel's avatar
Laurent Montel committed
274
275
276
    if (m_settingsUi.saveModeCB->currentIndex() != SaveModeAskFirst) {
        m_firstImage = false;
    }
Kåre Särs's avatar
Kåre Särs committed
277
    m_settingsUi.saveDirRequester->setUrl(saving.readEntry("Location", QUrl(QDir::homePath())));
278
    m_settingsUi.imgPrefix->setText(saving.readEntry("NamePrefix", i18nc("prefix for auto naming", "Image-")));
279
280
281
282
283
284
    QString format = saving.readEntry("ImgFormat", "image/png");
    int index = m_settingsUi.imgFormat->findData(format);
    if (index >= 0) {
        m_settingsUi.imgFormat->setCurrentIndex(index);
    }

285
286
287
    m_settingsUi.imgQuality->setValue(saving.readEntry("ImgQuality", 90));
    m_settingsUi.setQuality->setChecked(saving.readEntry("SetQuality", false));
    m_settingsUi.showB4Save->setChecked(saving.readEntry("ShowBeforeSave", true));
Kåre Särs's avatar
Kåre Särs committed
288

289
    KConfigGroup general(KSharedConfig::openConfig(), "General");
Laurent Montel's avatar
Laurent Montel committed
290

Gregor Mi's avatar
Gregor Mi committed
291
292
    //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"));
Laurent Montel's avatar
Laurent Montel committed
293

294
295
296
    m_settingsUi.setPreviewDPI->setChecked(general.readEntry("SetPreviewDPI", false));
    if (m_settingsUi.setPreviewDPI->isChecked()) {
        m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat());
Alexander Trufanov's avatar
Alexander Trufanov committed
297
298
    }
    else {
299
300
        m_ksanew->setPreviewResolution(0.0);
    }
301
302
    m_settingsUi.u_disableSelections->setChecked(general.readEntry("DisableAutoSelection", false));
    m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked());
303
304
}

305
void Skanlite::showSettingsDialog(void)
306
307
308
309
{
    readSettings();

    // show the dialog
310
    if (m_settingsDialog->exec()) {
311
        // save the settings
312
        KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
313
        saving.writeEntry("SaveMode", m_settingsUi.saveModeCB->currentIndex());
Kåre Särs's avatar
Kåre Särs committed
314
        saving.writeEntry("Location", m_settingsUi.saveDirRequester->url());
315
        saving.writeEntry("NamePrefix", m_settingsUi.imgPrefix->text());
316
        saving.writeEntry("ImgFormat", m_settingsUi.imgFormat->currentData().toString());
317
318
319
320
        saving.writeEntry("SetQuality", m_settingsUi.setQuality->isChecked());
        saving.writeEntry("ImgQuality", m_settingsUi.imgQuality->value());
        saving.writeEntry("ShowBeforeSave", m_settingsUi.showB4Save->isChecked());
        saving.sync();
321

322
        KConfigGroup general(KSharedConfig::openConfig(), "General");
323
324
        general.writeEntry("PreviewDPI", m_settingsUi.previewDPI->currentText());
        general.writeEntry("SetPreviewDPI", m_settingsUi.setPreviewDPI->isChecked());
325
        general.writeEntry("DisableAutoSelection", m_settingsUi.u_disableSelections->isChecked());
326
        general.sync();
327
328
329
330

        // the previewDPI has to be set here
        if (m_settingsUi.setPreviewDPI->isChecked()) {
            m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat());
Alexander Trufanov's avatar
Alexander Trufanov committed
331
332
        }
        else {
333
            // 0.0 means default value.
334
335
            m_ksanew->setPreviewResolution(0.0);
        }
336
        m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked());
337

338
        // pressing OK in the settings dialog means use those settings.
Kåre Särs's avatar
Kåre Särs committed
339
340
        m_saveLocation->setFolderUrl(m_settingsUi.saveDirRequester->url());
        m_saveLocation->setImagePrefix(m_settingsUi.imgPrefix->text());
341
        m_saveLocation->setImageFormatIndex(m_settingsUi.imgFormat->currentIndex());
342
343

        m_firstImage = true;
Alexander Trufanov's avatar
Alexander Trufanov committed
344
345
    }
    else {
346
347
348
349
350
        //Forget Changes
        readSettings();
    }
}

351
void Skanlite::imageReady(const QImage &image)
352
{
353
    // save the image data
354
    m_img = image;
355
    if (m_settingsUi.showB4Save->isChecked() == true) {
356
        // show the image in the preview
Alexander Volkov's avatar
Alexander Volkov committed
357
358
        m_showImgDialog->setQImage(&m_img);
        m_showImgDialog->zoom2Fit();
359
        m_showImgDialog->exec();
360
        // save has been done as a result of save or then we got cancel
Alexander Trufanov's avatar
Alexander Trufanov committed
361
362
    }
    else {
363
        saveImage();
364
365
366
    }
}

367
bool urlExists(const QUrl& url)
368
{
369
    if (url.isLocalFile()) {
Kåre Särs's avatar
Kåre Särs committed
370
        if (!QFileInfo::exists(url.toLocalFile())) {
371
372
373
374
            return false;
        }
    }
    else {
375
        KIO::StatJob *statJob = KIO::statDetails(url, KIO::StatJob::DestinationSide, KIO::StatNoDetails);
376
377
378
        KJobWidgets::setWindow(statJob, QApplication::activeWindow());
        if (!statJob->exec()) {
            return false;
379
380
381
382
383
        }
    }
    return true;
}

384
void Skanlite::saveImage()
385
{
Kåre Särs's avatar
Kåre Särs committed
386
    QUrl dirUrl = m_saveLocation->folderUrl();
387
388
389
390
391
392
393
394
395
396
397
398
399
    bool dirExists = urlExists(dirUrl);

    // Ask the first time if we are in "ask on first" mode
    if (m_settingsUi.saveModeCB->currentIndex() == SaveModeAskFirst) {
        while (m_firstImage || !dirExists) {
            m_saveLocation->setOpenRequesterOnShow(!dirExists);
            if (m_saveLocation->exec() != QFileDialog::Accepted) {
                m_ksanew->scanCancel(); // In case we are cancelling a document feeder scan
                return;
            }
            dirUrl = m_saveLocation->folderUrl();
            dirExists = urlExists(dirUrl); // check that we actually got an existing folder
            m_firstImage = false;
Laurent Montel's avatar
Laurent Montel committed
400
        }
401
402
403
404
405
    }
    else if (!dirExists) {
        // The save-folder from settings does not exist! Use the users home directory.
        dirUrl = QUrl::fromUserInput(QDir::homePath() + QLatin1Char('/'));
        m_saveLocation->setFolderUrl(dirUrl);
406
    }
407

Kåre Särs's avatar
Kåre Särs committed
408
    QString prefix = m_saveLocation->imagePrefix();
409
    QString imageMimetype = m_saveLocation->imageMimetype();
Kåre Särs's avatar
Kåre Särs committed
410
    int fileNumber = m_saveLocation->startNumber();
411
    QStringList filterList;
412
413
414

    if ((m_img.format() == QImage::Format_Grayscale16) ||
        (m_img.format() == QImage::Format_RGBX64))
Kåre Särs's avatar
Kåre Särs committed
415
    {
416
        filterList = m_filter16BitList;
417
418
        if (imageMimetype != QLatin1String("image/png") && imageMimetype != QLatin1String("image/tiff")) {
            imageMimetype = QStringLiteral("image/png");
419
            KMessageBox::information(this, i18n("The image will be saved in the PNG format, as the selected image type does not support saving 16 bit color images."));
420
        }
421
422
    } else {
        filterList = m_filterList;
423
424
    }

Kåre Särs's avatar
Kåre Särs committed
425
    // find next available file name for name suggestion
426
    QUrl fileUrl;
427
    QString fname;
Kåre Särs's avatar
Kåre Särs committed
428
    for (int i = fileNumber; i <= m_saveLocation->startNumberMax(); ++i) {
429
        fname = QStringLiteral("%1%2.%3")
Laurent Montel's avatar
Laurent Montel committed
430
431
                .arg(prefix)
                .arg(i, 4, 10, QLatin1Char('0'))
432
                .arg(m_saveLocation->imageSuffix());
433

Kåre Särs's avatar
Kåre Särs committed
434
435
436
        fileUrl = dirUrl;
        fileUrl.setPath(fileUrl.path() + fname);
        fileUrl = fileUrl.adjusted(QUrl::NormalizePathSegments);
437
438
        if (!urlExists(fileUrl)) {
            break;
Kåre Särs's avatar
Kåre Särs committed
439
440
        }
    }
441

442
    if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) {
443
        // prepare the save dialog
444
        QFileDialog saveDialog(this, i18n("New Image File Name"));
445
446
        saveDialog.setAcceptMode(QFileDialog::AcceptSave);
        saveDialog.setFileMode(QFileDialog::AnyFile);
Laurent Montel's avatar
Laurent Montel committed
447

448
        // ask for a filename if requested.
449
450
451
452
        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
Laurent Montel's avatar
Laurent Montel committed
453

454
455
        saveDialog.setMimeTypeFilters(filterList);
        saveDialog.selectMimeTypeFilter(imageMimetype);
Gregor Mi's avatar
Gregor Mi committed
456

457
458
459
        if (saveDialog.exec() != QFileDialog::Accepted) {
            return;
        }
Laurent Montel's avatar
Laurent Montel committed
460

Kåre Särs's avatar
Kåre Särs committed
461
        fileUrl = saveDialog.selectedUrls().at(0);
462
    }
463

464
    m_firstImage = false;
465

Kåre Särs's avatar
Kåre Särs committed
466
467
    // Get the quality
    int quality = -1;
468
469
    if (m_settingsUi.setQuality->isChecked()) {
        quality = m_settingsUi.imgQuality->value();
Kåre Särs's avatar
Kåre Särs committed
470
    }
471

472
    QString localName;
473
    QString suffix = QFileInfo(fileUrl.fileName()).suffix();
474
    QString fileFormat;
475
    if (suffix.isEmpty()) {
476
        fileFormat = QStringLiteral("png");
477
478
    }

479
    if (!fileUrl.isLocalFile()) {
480
        QTemporaryFile tmp;
481
        tmp.open();
482
483
484
485
486
487
        if (suffix.isEmpty()) {
            localName = tmp.fileName();
        }
        else {
            localName = QStringLiteral("%1.%2").arg(tmp.fileName(), suffix);
        }
488
489
        tmp.close(); // we just want the filename
    }
490
491
492
    else {
        localName = fileUrl.toLocalFile();
    }
493

494
495
    SkanliteImageSaver *imageSaver = new SkanliteImageSaver(this);
    connect(imageSaver, &SkanliteImageSaver::imageSaved, this, &Skanlite::imageSaved);
496

Alexander Stippich's avatar
Alexander Stippich committed
497
    imageSaver->saveQImage(fileUrl, localName, m_img, fileFormat, quality);
498

499
    m_showImgDialog->blockSignals(true);
500
    m_showImgDialog->close(); // calling close() on a closed window does nothing.
501
502
    // NOTE we need to block the signals since close() will emit rejected()
    m_showImgDialog->blockSignals(false);
503
504
505
506
507
508
509
510

    // Disable parts of the interface and indicate that we are saving the image
    m_currentSaveUrl = fileUrl;
    m_ksanew->setDisabled(true);
    m_saveProgressBar->setMaximum(0);
    m_saveProgressBar->setValue(0);
    m_saveProgressBar->setVisible(true);
    m_saveUpdateTimer.start();
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530

    // 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->setImagePrefix(baseName);

    // Save the number
    if (fileNumber) {
        m_saveLocation->setStartNumber(fileNumber + 1);
    }

    if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) {
        // Save last used dir, prefix and suffix.
        m_saveLocation->setFolderUrl(KIO::upUrl(fileUrl));
        m_saveLocation->setImageFormat(QFileInfo(fileUrl.fileName()).suffix());
    }


531
532
533
534
535
536
537
538
}

void Skanlite::updateSaveProgress()
{
    QFileInfo saveInfo(m_currentSaveUrl.toLocalFile());
    quint64 size = saveInfo.size()/1024;
    m_saveProgressBar->setMaximum(size);
    m_saveProgressBar->setValue(size);
539
540
541
542
543
544
545
}

void Skanlite::imageSaved(const QUrl &fileUrl, const QString &localName, bool success)
{
    if (!success) {
        perrorMessageBox(i18n("Failed to save image"));
        return;
546
    }
547

548
    if (!fileUrl.isLocalFile()) {
549
550
551
552
553
554
555
556
        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) {
557
            KMessageBox::sorry(nullptr, i18n("Failed to upload image"));
558
        }
Alexander Trufanov's avatar
Alexander Trufanov committed
559
        else {
560
            Q_EMIT m_dbusInterface.imageSaved(fileUrl.toString());
Alexander Trufanov's avatar
Alexander Trufanov committed
561
562
563
        }
    }
    else {
564
        Q_EMIT m_dbusInterface.imageSaved(localName);
565
    }
566
    m_ksanew->setDisabled(false);
Alexander Stippich's avatar
Alexander Stippich committed
567
    m_ksanew->setFocus();
568
569
    m_saveUpdateTimer.stop();
    m_saveProgressBar->setVisible(false);
570

571
    SkanliteImageSaver *imageSaver = qobject_cast<SkanliteImageSaver *>(sender());
572
573
    if (imageSaver) {
        imageSaver->deleteLater();
574
575
    }
}
576

577
void Skanlite::showAboutDialog(void)
578
{
579
    KAboutApplicationDialog(KAboutData::applicationData()).exec();
580
581
}

Alexander Trufanov's avatar
Alexander Trufanov committed
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
void writeScannerOptions(const QString &groupName, const QMap <QString, QString> &opts)
{
    KConfigGroup options(KSharedConfig::openConfig(), groupName);
    QMap<QString, QString>::const_iterator it = opts.constBegin();
    while (it != opts.constEnd()) {
        options.writeEntry(it.key(), it.value());
        ++it;
    }
    options.sync();
}

void readScannerOptions(const QString &groupName, QMap <QString, QString> &opts)
{
    KConfigGroup scannerOptions(KSharedConfig::openConfig(), groupName);
    opts = scannerOptions.entryMap();
}

599
600
void Skanlite::saveScannerOptions()
{
601
    KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
Kåre Särs's avatar
Kåre Särs committed
602
    saving.writeEntry("NumberStartsFrom", m_saveLocation->startNumber());
603

Laurent Montel's avatar
Laurent Montel committed
604
605
606
    if (!m_ksanew) {
        return;
    }
607

608
    KConfigGroup options(KSharedConfig::openConfig(), QStringLiteral("Options For %1").arg(m_deviceName));
609
610
    QMap <QString, QString> opts;
    m_ksanew->getOptVals(opts);
611
    writeScannerOptions(QStringLiteral("Options For %1").arg(m_deviceName), opts);
612
613
614
615
}

void Skanlite::defaultScannerOptions()
{
Laurent Montel's avatar
Laurent Montel committed
616
617
618
    if (!m_ksanew) {
        return;
    }
619

620
621
622
623
624
625
626
627
628
629
    applyScannerOptions(m_defaultScanOpts);
}

void Skanlite::applyScannerOptions(const QMap <QString, QString> &opts)
{
    if (m_ksanew->setOptVals(opts) == -1) {
        m_pendingApplyScanOpts = opts;
    } else {
        m_pendingApplyScanOpts.clear();
    }
630
631
632
633
}

void Skanlite::loadScannerOptions()
{
634
    KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
Kåre Särs's avatar
Kåre Särs committed
635
    m_saveLocation->setStartNumber(saving.readEntry("NumberStartsFrom", 1));
636

Laurent Montel's avatar
Laurent Montel committed
637
638
639
    if (!m_ksanew) {
        return;
    }
640

Alexander Trufanov's avatar
Alexander Trufanov committed
641
    QMap <QString, QString> opts;
642
    readScannerOptions(QStringLiteral("Options For %1").arg(m_deviceName), opts);
643
    applyScannerOptions(opts);
644
}
645

646
647
648
void Skanlite::alertUser(int type, const QString &strStatus)
{
    switch (type) {
Laurent Montel's avatar
Laurent Montel committed
649
    case KSaneWidget::ErrorGeneral:
650
        KMessageBox::sorry(nullptr, strStatus, QStringLiteral("Skanlite Test"));
Laurent Montel's avatar
Laurent Montel committed
651
652
        break;
    default:
653
        KMessageBox::information(nullptr, strStatus, QStringLiteral("Skanlite Test"));
654
655
656
657
658
    }
}

void Skanlite::buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed)
{
Pino Toscano's avatar
Pino Toscano committed
659
    qCDebug(SKANLITE_LOG) << "Button" << optionName << optionLabel << ((pressed) ? "pressed" : "released");
660
}
Alexander Trufanov's avatar
Alexander Trufanov committed
661
662
663
664
665
666
667
668

// D-Bus interface related helper functions

QStringList serializeScannerOptions(const QMap<QString, QString> &opts)
{
    QStringList sl;
    QMap<QString, QString>::const_iterator it = opts.constBegin();
    while (it != opts.constEnd()) {
Laurent Montel's avatar
Laurent Montel committed
669
        sl.append(it.key() + QLatin1Char('=') + it.value());
Alexander Trufanov's avatar
Alexander Trufanov committed
670
671
672
673
674
675
676
        ++it;
    }
    return sl;
}

void deserializeScannerOptions(const QStringList &settings, QMap<QString, QString> &opts)
{
677
    for (const QString &s : settings) {
Alexander Trufanov's avatar
Alexander Trufanov committed
678
679
680
681
682
        int i = s.lastIndexOf(QLatin1Char('='));
        opts[s.left(i)] = s.right(s.length()-i-1);
    }
}

Kåre Särs's avatar
Kåre Särs committed
683
684
static const auto selectionSettings = { QLatin1String("tl-x"), QLatin1String("tl-y"),
                                        QLatin1String("br-x"), QLatin1String("br-y") };
Alexander Trufanov's avatar
Alexander Trufanov committed
685
686
687

void filterSelectionSettings(QMap<QString, QString> &opts)
{
688
    for (const auto &s : selectionSettings) {
Alexander Trufanov's avatar
Alexander Trufanov committed
689
690
691
692
693
694
        opts.remove(s);
    }
}

bool containsSelectionSettings(const QMap<QString, QString> &opts)
{
695
    for (const auto &s : selectionSettings) {
Alexander Trufanov's avatar
Alexander Trufanov committed
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
        if (opts.contains(s)) {
            return true;
        }
    }
    return false;
}

void Skanlite::processSelectionOptions(QMap<QString, QString> &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 <QString, QString> opts;
    m_ksanew->getOptVals(opts);
    m_dbusInterface.setReply(serializeScannerOptions(opts));
}

void Skanlite::setScannerOptions(const QStringList &options, bool ignoreSelection)
{
    QMap <QString, QString> opts;
    deserializeScannerOptions(options, opts);
    processSelectionOptions(opts, ignoreSelection);
729
    applyScannerOptions(opts);
Alexander Trufanov's avatar
Alexander Trufanov committed
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
}


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 <QString, QString> opts;
    deserializeScannerOptions(options, opts);
    processSelectionOptions(opts, ignoreSelection);
Kåre Särs's avatar
Kåre Särs committed
745
    writeScannerOptions(QString(defaultProfileGroup).arg(m_deviceName, profile), opts);
Alexander Trufanov's avatar
Alexander Trufanov committed
746
747
748
749
750
}

void Skanlite::switchToProfile(const QString &profile, bool ignoreSelection)
{
    QMap <QString, QString> opts;
Kåre Särs's avatar
Kåre Särs committed
751
    readScannerOptions(QString(defaultProfileGroup).arg(m_deviceName, profile), opts);
Alexander Trufanov's avatar
Alexander Trufanov committed
752
753
754
755
756
757

    if (opts.empty()) {
        opts = m_defaultScanOpts;
    }

    processSelectionOptions(opts, ignoreSelection);
758
    applyScannerOptions(opts);
Alexander Trufanov's avatar
Alexander Trufanov committed
759
760
761
762
763
764
765
766
767
768
769
770
771
}

void Skanlite::getDeviceName()
{
    m_dbusInterface.setReply(QStringList(m_deviceName));
}

void Skanlite::getSelection()
{
    QMap <QString, QString> opts;
    m_ksanew->getOptVals(opts);

    QStringList reply;
772
    for (const auto &key : selectionSettings ) {
Alexander Trufanov's avatar
Alexander Trufanov committed
773
        if (opts.contains(key)) {
Laurent Montel's avatar
Laurent Montel committed
774
            reply.append(key + QLatin1Char('=') + opts[key]);
Alexander Trufanov's avatar
Alexander Trufanov committed
775
776
777
778
779
780
781
782
783
        }
    }
    m_dbusInterface.setReply(reply);
}

void Skanlite::setSelection(const QStringList &options)
{ // here options contains selection related subset of options
    setScannerOptions(options, false);
}