kpMainWindow_File.cpp 46.6 KB
Newer Older
1
/*
2
   Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
3
   Copyright (c) 2007 John Layt <john@layt.net>
4
   Copyright (c) 2007,2011,2015 Martin Koller <kollix@aon.at>
5
   All rights reserved.
Clarence Dang's avatar
Clarence Dang committed
6

7
8
9
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
Clarence Dang's avatar
Clarence Dang committed
10

11
12
13
14
15
   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
Clarence Dang's avatar
Clarence Dang committed
16

17
18
19
20
21
22
   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
   IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
24
   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
26
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
*/

29

30
31
#include "kpMainWindow.h"
#include "kpMainWindowPrivate.h"
Clarence Dang's avatar
Clarence Dang committed
32

33
34
#include <QAction>
#include <QDataStream>
35
#include <QDesktopWidget>
Christoph Feck's avatar
Christoph Feck committed
36
37
#include <QDialog>
#include <QDialogButtonBox>
38
#include <QFileDialog>
39
40
41
#include <QPainter>
#include <QPixmap>
#include <QSize>
42
43
#include <QPrinter>
#include <QPrintDialog>
44
#include <QScreen>
45
#include <QApplication>
46
47
48
49
#include <QTimer>
#include <QLabel>
#include <QCheckBox>
#include <QVBoxLayout>
50
51
52
#include <QImageReader>
#include <QImageWriter>
#include <QMimeDatabase>
Martin Koller's avatar
Martin Koller committed
53
#include <QPrintPreviewDialog>
54

55
#include <kactioncollection.h>
56
#include <KSharedConfig>
57
#include <kconfiggroup.h>
Martin Koller's avatar
Martin Koller committed
58
#include <KPluralHandlingSpinBox>
59
#include <kmessagebox.h>
60
#include <krecentfilesaction.h>
André Wöbbeking's avatar
André Wöbbeking committed
61
#include <kstandardshortcut.h>
Aaron J. Seigo's avatar
Aaron J. Seigo committed
62
#include <kstandardaction.h>
63
#include <ktoolinvocation.h>
64
#include <KLocalizedString>
65

66
#include "kpLogCategories.h"
67
68
69
70
71
72
73
74
75
76
#include "commands/kpCommandHistory.h"
#include "kpDefs.h"
#include "document/kpDocument.h"
#include "commands/imagelib/kpDocumentMetaInfoCommand.h"
#include "dialogs/imagelib/kpDocumentMetaInfoDialog.h"
#include "widgets/kpDocumentSaveOptionsWidget.h"
#include "pixmapfx/kpPixmapFX.h"
#include "widgets/kpPrintDialogPage.h"
#include "views/kpView.h"
#include "views/manager/kpViewManager.h"
77

78
79
80
81
#if HAVE_KSANE
#include "../scan/sanedialog.h"
#endif // HAVE_KSANE

82
83
84
// private
void kpMainWindow::setupFileMenuActions ()
{
85
#if DEBUG_KP_MAIN_WINDOW
86
    qCDebug(kpLogMainWindow) << "kpMainWindow::setupFileMenuActions()";
87
#endif
88
89
    KActionCollection *ac = actionCollection ();

90
91
    d->actionNew = KStandardAction::openNew (this, SLOT (slotNew()), ac);
    d->actionOpen = KStandardAction::open (this, SLOT (slotOpen()), ac);
92

93
94
    d->actionOpenRecent = KStandardAction::openRecent(this, &kpMainWindow::slotOpenRecent, ac);
    connect(d->actionOpenRecent, &KRecentFilesAction::recentListCleared, this, &kpMainWindow::slotRecentListCleared);
95
    d->actionOpenRecent->loadEntries (KSharedConfig::openConfig ()->group (kpSettingsGroupRecentFiles));
96
#if DEBUG_KP_MAIN_WINDOW
97
    qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
98
#endif
99

100
101
    d->actionSave = KStandardAction::save (this, SLOT (slotSave()), ac);
    d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs()), ac);
102

Laurent Montel's avatar
Laurent Montel committed
103
    d->actionExport = ac->addAction(QStringLiteral("file_export"));
104
    d->actionExport->setText (i18n ("E&xport..."));
David Faure's avatar
David Faure committed
105
    d->actionExport->setIcon(QIcon::fromTheme(QStringLiteral("document-export")));
106
    connect (d->actionExport, &QAction::triggered, this, &kpMainWindow::slotExport);
107

Laurent Montel's avatar
Laurent Montel committed
108
    d->actionScan = ac->addAction(QStringLiteral("file_scan"));
109
    d->actionScan->setText(i18n ("Scan..."));
David Faure's avatar
David Faure committed
110
    d->actionScan->setIcon(QIcon::fromTheme("scanner"));
111
#if HAVE_KSANE
112
    connect (d->actionScan, &QAction::triggered, this, &kpMainWindow::slotScan);
113
114
115
#else
    d->actionScan->setEnabled(false);
#endif // HAVE_KSANE
116

Laurent Montel's avatar
Laurent Montel committed
117
    d->actionScreenshot = ac->addAction(QStringLiteral("file_screenshot"));
118
    d->actionScreenshot->setText(i18n("Acquire Screenshot"));
119
    connect (d->actionScreenshot, &QAction::triggered, this, &kpMainWindow::slotScreenshot);
120

Laurent Montel's avatar
Laurent Montel committed
121
    d->actionProperties = ac->addAction (QStringLiteral("file_properties"));
122
    d->actionProperties->setText (i18n ("Properties"));
David Faure's avatar
David Faure committed
123
    d->actionProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
124
    connect (d->actionProperties, &QAction::triggered, this, &kpMainWindow::slotProperties);
125

126
    //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert()), ac);
Laurent Montel's avatar
Laurent Montel committed
127
    d->actionReload = ac->addAction (QStringLiteral("file_revert"));
128
    d->actionReload->setText (i18n ("Reloa&d"));
David Faure's avatar
David Faure committed
129
    d->actionReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
130
    connect (d->actionReload, &QAction::triggered, this, &kpMainWindow::slotReload);
131
    ac->setDefaultShortcuts (d->actionReload, KStandardShortcut::reload ());
132
    slotEnableReload ();
133

134
135
    d->actionPrint = KStandardAction::print (this, SLOT (slotPrint()), ac);
    d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview()), ac);
136

137
    d->actionMail = KStandardAction::mail (this, SLOT (slotMail()), ac);
138

139
140
    d->actionClose = KStandardAction::close (this, SLOT (slotClose()), ac);
    d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit()), ac);
141

142
    d->scanDialog = nullptr;
143

Clarence Dang's avatar
Clarence Dang committed
144
    enableFileMenuDocumentActions (false);
145
}
Clarence Dang's avatar
   
Clarence Dang committed
146

147
148
//---------------------------------------------------------------------

Clarence Dang's avatar
Clarence Dang committed
149
150
151
// private
void kpMainWindow::enableFileMenuDocumentActions (bool enable)
{
152
153
    // d->actionNew
    // d->actionOpen
154

155
    // d->actionOpenRecent
156

157
158
    d->actionSave->setEnabled (enable);
    d->actionSaveAs->setEnabled (enable);
159

160
    d->actionExport->setEnabled (enable);
161

162
    // d->actionScan
163

164
    d->actionProperties->setEnabled (enable);
165

166
    // d->actionReload
167

168
169
    d->actionPrint->setEnabled (enable);
    d->actionPrintPreview->setEnabled (enable);
170

171
    d->actionMail->setEnabled (enable);
172

173
174
    d->actionClose->setEnabled (enable);
    // d->actionQuit->setEnabled (enable);
Clarence Dang's avatar
Clarence Dang committed
175
}
176

177
178
//---------------------------------------------------------------------

Clarence Dang's avatar
Clarence Dang committed
179
// private
Christoph Feck's avatar
Christoph Feck committed
180
void kpMainWindow::addRecentURL (const QUrl &url_)
181
{
182
    // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls
183
184
185
186
187
188
189
190
191
    //       map.
    //
    //       So afterwards, the URL ref, our method is given, points to an
    //       element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)).
    //       Accessing it would result in a crash.
    //
    //       To avoid the crash, make a copy of it before calling
    //       loadEntries() and use this copy, instead of the to-be-dangling
    //       ref.
192
    const QUrl url = url_; // DO NOT MAKE IT A REFERENCE, THE CALL BELOW TO loadEntries DESTROYS url_
193

194
#if DEBUG_KP_MAIN_WINDOW
195
    qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")";
196
#endif
Clarence Dang's avatar
Clarence Dang committed
197
198
199
200
    if (url.isEmpty ())
        return;


201
    KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
Clarence Dang's avatar
Clarence Dang committed
202
203

    // KConfig::readEntry() does not actually reread from disk, hence doesn't
Thorsten Roeder's avatar
Thorsten Roeder committed
204
    // realize what other processes have done e.g. Settings / Show Path
Clarence Dang's avatar
Clarence Dang committed
205
206
    cfg->reparseConfiguration ();

207
#if DEBUG_KP_MAIN_WINDOW
208
    qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
209
#endif
Clarence Dang's avatar
Clarence Dang committed
210
211
    // HACK: Something might have changed interprocess.
    // If we could PROPAGATE: interprocess, then this wouldn't be required.
212
    d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles));
213
#if DEBUG_KP_MAIN_WINDOW
214
    qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items ();
215
#endif
Clarence Dang's avatar
Clarence Dang committed
216

217
    d->actionOpenRecent->addUrl (url);
Clarence Dang's avatar
Clarence Dang committed
218

219
    d->actionOpenRecent->saveEntries (cfg->group (kpSettingsGroupRecentFiles));
Clarence Dang's avatar
Clarence Dang committed
220
221
    cfg->sync ();

222
#if DEBUG_KP_MAIN_WINDOW
223
    qCDebug(kpLogMainWindow) << "\tnew recent URLs=" << d->actionOpenRecent->items ();
224
#endif
Clarence Dang's avatar
Clarence Dang committed
225
226
227


    // TODO: PROPAGATE: interprocess
228
229
230
    // TODO: Is this loop safe since a KMainWindow later along in the list,
    //       could be closed as the code in the body almost certainly re-enters
    //       the event loop?  Problem for KDE 3 as well, I think.
231
    for (auto *kmw : KMainWindow::memberList ())
Clarence Dang's avatar
Clarence Dang committed
232
    {
233
        Q_ASSERT (dynamic_cast <kpMainWindow *> (kmw));
234
        auto *mw = dynamic_cast <kpMainWindow *> (kmw);
235

236
    #if DEBUG_KP_MAIN_WINDOW
237
        qCDebug(kpLogMainWindow) << "\t\tmw=" << mw;
238
    #endif
Clarence Dang's avatar
Clarence Dang committed
239

240
        if (mw != this)
Clarence Dang's avatar
Clarence Dang committed
241
        {
242
243
244
245
246
247
            // WARNING: Do not use KRecentFilesAction::setItems()
            //          - it does not work since only its superclass,
            //          KSelectAction, implements setItems() and can't
            //          update KRecentFilesAction's URL list.

            // Avoid URL memory leak in KRecentFilesAction::loadEntries().
248
            mw->d->actionOpenRecent->clear ();
Clarence Dang's avatar
Clarence Dang committed
249

250
            mw->d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles));
251
        #if DEBUG_KP_MAIN_WINDOW
252
            qCDebug(kpLogMainWindow) << "\t\t\tcheck recent URLs="
253
                        << mw->d->actionOpenRecent->items ();
254
        #endif
Clarence Dang's avatar
Clarence Dang committed
255
256
257
258
        }
    }
}

259
260
//---------------------------------------------------------------------

Clarence Dang's avatar
Clarence Dang committed
261

Clarence Dang's avatar
Clarence Dang committed
262
// private slot
263
// TODO: Disable action if
264
//       (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty())
265
//       as it does nothing if this is true.
Clarence Dang's avatar
   
Clarence Dang committed
266
void kpMainWindow::slotNew ()
Clarence Dang's avatar
Clarence Dang committed
267
{
Clarence Dang's avatar
Clarence Dang committed
268
    toolEndShape ();
Clarence Dang's avatar
Clarence Dang committed
269

270
    if (d->document && !d->configOpenImagesInSameWindow)
Clarence Dang's avatar
   
Clarence Dang committed
271
    {
272
273
274
        // A document -- empty or otherwise -- is open.
        // Force open a new window.  In contrast, open() might not open
        // a new window in this case.
275
        auto *win = new kpMainWindow ();
Clarence Dang's avatar
   
Clarence Dang committed
276
277
278
279
        win->show ();
    }
    else
    {
Christoph Feck's avatar
Christoph Feck committed
280
        open (QUrl (), true/*create an empty doc*/);
Clarence Dang's avatar
   
Clarence Dang committed
281
    }
Clarence Dang's avatar
Clarence Dang committed
282
283
}

284
285
//---------------------------------------------------------------------

286
287
288

// private
QSize kpMainWindow::defaultDocSize () const
Clarence Dang's avatar
Clarence Dang committed
289
290
{
    // KConfig::readEntry() does not actually reread from disk, hence doesn't
Thorsten Roeder's avatar
Thorsten Roeder committed
291
    // realize what other processes have done e.g. Settings / Show Path
292
    KSharedConfig::openConfig ()->reparseConfiguration ();
Clarence Dang's avatar
Clarence Dang committed
293

294
    KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral);
Clarence Dang's avatar
Clarence Dang committed
295

296
    QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ());
Clarence Dang's avatar
Clarence Dang committed
297
298

    if (docSize.isEmpty ())
299
    {
Clarence Dang's avatar
Clarence Dang committed
300
        docSize = QSize (400, 300);
301
302
303
304
305
    }
    else
    {
        // Don't get too big or you'll thrash (or even lock up) the computer
        // just by opening a window
Clarence Dang's avatar
Clarence Dang committed
306
307
        docSize = QSize (qMin (2048, docSize.width ()),
                         qMin (2048, docSize.height ()));
308
    }
Clarence Dang's avatar
Clarence Dang committed
309
310
311
312

    return docSize;
}

313
314
//---------------------------------------------------------------------

315
316
317
// private
void kpMainWindow::saveDefaultDocSize (const QSize &size)
{
318
#if DEBUG_KP_MAIN_WINDOW
319
    qCDebug(kpLogMainWindow) << "\tCONFIG: saving Last Doc Size = " << size;
320
#endif
321

322
    KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral);
323

324
325
    cfg.writeEntry (kpSettingLastDocSize, size);
    cfg.sync ();
326
327
}

328
//---------------------------------------------------------------------
329

330
// private
331
bool kpMainWindow::shouldOpen ()
332
{
333
334
    if (d->configOpenImagesInSameWindow)
    {
335
    #if DEBUG_KP_MAIN_WINDOW
336
        qCDebug(kpLogMainWindow) << "\topenImagesInSameWindow";
337
    #endif
338
        // (this brings up a dialog and might save the current doc)
339
        if (!queryCloseDocument ())
340
        {
341
        #if DEBUG_KP_MAIN_WINDOW
342
            qCDebug(kpLogMainWindow) << "\t\tqueryCloseDocument() aborts open";
343
        #endif
344
345
346
347
            return false;
        }
    }

348
349
    return true;
}
350

351
352
//---------------------------------------------------------------------

353
354
355
// private
void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc)
{
356
    // Want new window?
357
    if (d->document && !d->document->isEmpty () &&
358
        !d->configOpenImagesInSameWindow)
359
    {
360
        // Send doc to new window.
361
        auto *win = new kpMainWindow (doc);
Clarence Dang's avatar
Clarence Dang committed
362
363
364
365
        win->show ();
    }
    else
    {
366
        // (sets up views, doc signals)
367
        setDocument (doc);
368
    }
369
}
370

371
//---------------------------------------------------------------------
372
373

// private
Christoph Feck's avatar
Christoph Feck committed
374
kpDocument *kpMainWindow::openInternal (const QUrl &url,
375
376
377
378
379
380
        const QSize &fallbackDocSize,
        bool newDocSameNameIfNotExist)
{
    // If using OpenImagesInSameWindow mode, ask whether to close the
    // current document.
    if (!shouldOpen ())
381
        return nullptr;
382
383

    // Create/open doc.
384
385
386
    auto *newDoc = new kpDocument (fallbackDocSize.width (),
                                   fallbackDocSize.height (), documentEnvironment ());

387
388
    if (!newDoc->open (url, newDocSameNameIfNotExist))
    {
389
    #if DEBUG_KP_MAIN_WINDOW
390
        qCDebug(kpLogMainWindow) << "\topen failed";
391
    #endif
392
        delete newDoc;
393
        return nullptr;
394
395
    }

396
#if DEBUG_KP_MAIN_WINDOW
397
    qCDebug(kpLogMainWindow) << "\topen OK";
398
#endif
399
400
401
402
    // Send document to current or new window.
    setDocumentChoosingWindow (newDoc);

    return newDoc;
403
404
}

405
406
//---------------------------------------------------------------------

407
// private
Christoph Feck's avatar
Christoph Feck committed
408
bool kpMainWindow::open (const QUrl &url, bool newDocSameNameIfNotExist)
409
{
410
#if DEBUG_KP_MAIN_WINDOW
411
    qCDebug(kpLogMainWindow) << "kpMainWindow::open(" << url
412
              << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist
413
              << ")";
414
#endif
415
416
417
418
419
420

    kpDocument *newDoc = openInternal (url,
                                       defaultDocSize (),
                                       newDocSameNameIfNotExist);
    if (newDoc)
    {
421
        if (newDoc->isFromExistingURL ())
422
423
424
            addRecentURL (url);
        return true;
    }
425
426

    return false;
427
428
}

429
//---------------------------------------------------------------------
430

431
// private
Christoph Feck's avatar
Christoph Feck committed
432
QList<QUrl> kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs)
433
{
434
435
436
  QMimeDatabase db;
  QStringList filterList;
  QString filter;
437
  for (const auto &type : QImageReader::supportedMimeTypes())
438
  {
439
    if ( !filter.isEmpty() ) {
440
      filter += QLatin1Char(' ');
441
    }
442

443
444
445
446
    QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type)));
    if ( mime.isValid() )
    {
      QString glob = mime.globPatterns().join(QLatin1Char(' '));
Clarence Dang's avatar
Clarence Dang committed
447

448
      filter += glob;
Clarence Dang's avatar
Clarence Dang committed
449

Martin Koller's avatar
Martin Koller committed
450
451
452
453
      // I want to show the mime comment AND the file glob pattern,
      // but to avoid that the "All Supported Files" entry shows ALL glob patterns,
      // I must add the pattern here a second time so that QFileDialog::HideNameFilterDetails
      // can hide the first pattern and I still see the second one
Laurent Montel's avatar
Laurent Montel committed
454
      filterList << mime.comment() + QStringLiteral(" (%1)(%2)").arg(glob).arg(glob);
455
456
457
458
459
460
461
462
463
464
    }
  }

  filterList.prepend(i18n("All Supported Files (%1)", filter));

  QFileDialog fd(this);
  fd.setNameFilters(filterList);
  fd.setOption(QFileDialog::HideNameFilterDetails);
  fd.setWindowTitle(caption);

465
  if ( allowMultipleURLs ) {
466
    fd.setFileMode(QFileDialog::ExistingFiles);
467
  }
468

469
  if ( fd.exec() ) {
470
    return fd.selectedUrls();
471
472
  }

473
  return {};
474
}
Clarence Dang's avatar
   
Clarence Dang committed
475

476
477
//---------------------------------------------------------------------

478
479
480
// private slot
void kpMainWindow::slotOpen ()
{
Clarence Dang's avatar
Clarence Dang committed
481
    toolEndShape ();
482

Christoph Feck's avatar
Christoph Feck committed
483
    const QList<QUrl> urls = askForOpenURLs(i18nc("@title:window", "Open Image"));
484

485
    for (const auto & url : urls)
486
    {
487
        open (url);
Clarence Dang's avatar
   
Clarence Dang committed
488
    }
489
490
}

491
492
//---------------------------------------------------------------------

493
// private slot
Christoph Feck's avatar
Christoph Feck committed
494
void kpMainWindow::slotOpenRecent (const QUrl &url)
495
{
496
#if DEBUG_KP_MAIN_WINDOW
497
498
    qCDebug(kpLogMainWindow) << "kpMainWindow::slotOpenRecent(" << url << ")";
    qCDebug(kpLogMainWindow) << "\titems=" << d->actionOpenRecent->items ();
499
#endif
Clarence Dang's avatar
Clarence Dang committed
500

Clarence Dang's avatar
Clarence Dang committed
501
    toolEndShape ();
502

Clarence Dang's avatar
   
Clarence Dang committed
503
    open (url);
504
505
506
507
508
509
510
511
512
513
514

    // If the open is successful, addRecentURL() would have bubbled up the
    // URL in the File / Open Recent action.  As a side effect, the URL is
    // deselected.
    //
    // If the open fails, we should deselect the URL:
    //
    // 1. for consistency
    //
    // 2. because it has not been opened.
    //
515
    d->actionOpenRecent->setCurrentItem (-1);
516
517
}

518
//---------------------------------------------------------------------
519

520
521
522
523
524
525
526
void kpMainWindow::slotRecentListCleared()
{
  d->actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group(kpSettingsGroupRecentFiles));
}

//---------------------------------------------------------------------

527
#if HAVE_KSANE
528
529
530
// private slot
void kpMainWindow::slotScan ()
{
531
#if DEBUG_KP_MAIN_WINDOW
532
    qCDebug(kpLogMainWindow) << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog;
533
#endif
534

Clarence Dang's avatar
Clarence Dang committed
535
    toolEndShape ();
536

537
    if (!d->scanDialog)
538
    {
539
540
        // Create scan dialog
        d->scanDialog = new SaneDialog(this);
541
542

        // No scanning support (kdegraphics/libkscan) installed?
543
        if (!d->scanDialog)
544
545
        {
            KMessageBox::sorry (this,
546
547
                                i18n("Failed to open scanning dialog."),
                                i18nc("@title:window", "Scanning Failed"));
548
549
550
            return;
        }

551
    #if DEBUG_KP_MAIN_WINDOW
552
        qCDebug(kpLogMainWindow) << "\tcreated scanDialog=" << d->scanDialog;
553
    #endif
554
        connect (d->scanDialog, &SaneDialog::finalImage, this, &kpMainWindow::slotScanned);
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
    }


    // If using OpenImagesInSameWindow mode, ask whether to close the
    // current document.
    //
    // Do this after scan support is detected.  Because if it's not, what
    // would be the point of closing the document?
    //
    // Ideally, we would do this after the user presses "Final Scan" in
    // the scan dialog and before the scan begins (if the user wants to
    // cancel the scan operation, it would be annoying to offer this choice
    // only after the slow scan is completed) but the KScanDialog API does
    // not allow this.  So we settle for doing this before any
    // scan dialogs are shown.  We don't do this between KScanDialog::setup()
    // and KScanDialog::exec() as it could be confusing alternating between
    // scanning and KolourPaint dialogs.
572
    if (!shouldOpen ()) {
573
        return;
574
    }
575
576


577
#if DEBUG_KP_MAIN_WINDOW
578
    qCDebug(kpLogMainWindow) << "\tcalling setup";
579
#endif
580
581
582
    // Bring up dialog to select scan device.
    // If there is no scanner, we find that this does not bring up a dialog
    // but still returns true.
583
    if (d->scanDialog->setup ())
584
    {
585
    #if DEBUG_KP_MAIN_WINDOW
586
        qCDebug(kpLogMainWindow) << "\t\tOK - showing dialog";
587
    #endif
588
589
590
591
592
593
594
        // Called only if scanner configured/available.
        //
        // In reality, this seems to be called even if you press "Cancel" in
        // the KScanDialog::setup() dialog!
        //
        // We use exec() to make sure it's modal.  show() seems to work too
        // but better safe than sorry.
595
        d->scanDialog->exec ();
596
597
598
599
    }
    else
    {
        // Have never seen this code path execute even if "Cancel" is pressed.
600
    #if DEBUG_KP_MAIN_WINDOW
601
        qCDebug(kpLogMainWindow) << "\t\tFAIL";
602
    #endif
603
604
605
    }
}

606
607
//---------------------------------------------------------------------

608
609
610
// private slot
void kpMainWindow::slotScanned (const QImage &image, int)
{
611
#if DEBUG_KP_MAIN_WINDOW
612
    qCDebug(kpLogMainWindow) << "kpMainWindow::slotScanned() image.rect=" << image.rect ();
613
#endif
614

615
#if DEBUG_KP_MAIN_WINDOW
616
    qCDebug(kpLogMainWindow) << "\thiding dialog";
617
#endif
618
619
620
621
622
623
624
625
    // (KScanDialog does not close itself after a scan is made)
    //
    // Close the dialog, first thing:
    //
    // 1. This means that any dialogs we bring up won't be nested on top.
    //
    // 2. We don't want to return from this method but forget to close
    //    the dialog.  So do it before anything else.
626
    d->scanDialog->hide ();
627
628
629

    // (just in case there's some drawing between slotScan() exiting and
    //  us being called)
Clarence Dang's avatar
Clarence Dang committed
630
    toolEndShape ();
631
632
633
634
635
636
637
638


    // TODO: Maybe this code should be moved into kpdocument.cpp -
    //       since it resembles the responsibilities of kpDocument::open().

    kpDocumentSaveOptions saveOptions;
    kpDocumentMetaInfo metaInfo;

639
    kpDocument::getDataFromImage(image, saveOptions, metaInfo);
640
641

    // Create document from image and meta info.
642
    auto *doc = new kpDocument (image.width (), image.height (), documentEnvironment ());
643
    doc->setImage (image);
644
645
646
647
648
649
    doc->setSaveOptions (saveOptions);
    doc->setMetaInfo (metaInfo);

    // Send document to current or new window.
    setDocumentChoosingWindow (doc);
}
650
#endif // HAVE_KSANE
651

652
//---------------------------------------------------------------------
653

654
655
void kpMainWindow::slotScreenshot()
{
656
  toolEndShape();
657

658
659
  auto *dialog = new QDialog(this);
  auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
Christoph Feck's avatar
Christoph Feck committed
660
                                                   QDialogButtonBox::Cancel, dialog);
661
662
  connect (buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
  connect (buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
663

664
665
  auto *label = new QLabel(i18n("Snapshot Delay"));
  auto *seconds = new KPluralHandlingSpinBox;
666
667
668
669
  seconds->setRange(0, 99);
  seconds->setSuffix(ki18np(" second", " seconds"));
  seconds->setSpecialValueText(i18n("No delay"));

670
  auto *hideWindow = new QCheckBox(i18n("Hide Main Window"));
671
672
  hideWindow->setChecked(true);

673
  auto *vbox = new QVBoxLayout(dialog);
674
675
676
  vbox->addWidget(label);
  vbox->addWidget(seconds);
  vbox->addWidget(hideWindow);
Christoph Feck's avatar
Christoph Feck committed
677
  vbox->addWidget(buttons);
678

Christoph Feck's avatar
Christoph Feck committed
679
  if ( dialog->exec() == QDialog::Rejected )
680
681
682
683
684
  {
    delete dialog;
    return;
  }

685
  if ( hideWindow->isChecked() ) {
686
    hide();
687
  }
688
689

  // at least 1 seconds to make sure the window is hidden and the hide effect already stopped
Laurent Montel's avatar
Laurent Montel committed
690
  QTimer::singleShot((seconds->value() + 1) * 1000, this, &kpMainWindow::slotMakeScreenshot);
691
692
693
694
695
696
697
698
699

  delete dialog;
}

//---------------------------------------------------------------------

void kpMainWindow::slotMakeScreenshot()
{
  QCoreApplication::processEvents();
Martin Koller's avatar
Martin Koller committed
700
  QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId());
701

702
  auto *doc = new kpDocument(pixmap.width(), pixmap.height(), documentEnvironment());
703
704
705
706
707
708
  doc->setImage(pixmap.toImage());

  // Send document to current or new window.
  setDocumentChoosingWindow(doc);

  show();  // in case we hid the mainwindow, show it again
709
710
711
712
}

//---------------------------------------------------------------------

713
714
715
// private slot
void kpMainWindow::slotProperties ()
{
Clarence Dang's avatar
Clarence Dang committed
716
    toolEndShape ();
717
718
719
720
721
722
723
724

    kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this);

    if (dialog.exec () && !dialog.isNoOp ())
    {
        commandHistory ()->addCommand (
            new kpDocumentMetaInfoCommand (
                i18n ("Document Properties"),
725
                dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/,
726
727
                commandEnvironment ()));
    }
728
729
}

730
//---------------------------------------------------------------------
731
732
733
734

// private slot
bool kpMainWindow::save (bool localOnly)
{
735
    if (d->document->url ().isEmpty () ||
736
737
        !QImageWriter::supportedMimeTypes()
            .contains(d->document->saveOptions ()->mimeType().toLatin1()) ||
738
        // SYNC: kpDocument::getPixmapFromFile() can't determine quality
739
        //       from file so it has been set initially to an invalid value.
740
741
742
        (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () &&
            d->document->saveOptions ()->qualityIsInvalid ()) ||
        (localOnly && !d->document->url ().isLocalFile ()))
743
744
745
    {
        return saveAs (localOnly);
    }
746

747
    if (d->document->save (!d->document->savedAtLeastOnceBefore ()/*lossy prompt*/))
748
    {
749
750
        addRecentURL (d->document->url ());
        return true;
751
    }
752
753

    return false;
754
755
}

756
757
//---------------------------------------------------------------------

758
759
760
// private slot
bool kpMainWindow::slotSave ()
{
Clarence Dang's avatar
Clarence Dang committed
761
    toolEndShape ();
762
763
764
765

    return save ();
}

766
767
//---------------------------------------------------------------------

768
// private
Christoph Feck's avatar
Christoph Feck committed
769
QUrl kpMainWindow::askForSaveURL (const QString &caption,
770
                                  const QString &startURL,
771
                                  const kpImage &imageToBeSaved,
772
773
774
                                  const kpDocumentSaveOptions &startSaveOptions,
                                  const kpDocumentMetaInfo &docMetaInfo,
                                  const QString &forcedSaveOptionsGroup,
775
                                  bool localOnly,
776
777
778
                                  kpDocumentSaveOptions *chosenSaveOptions,
                                  bool isSavingForFirstTime,
                                  bool *allowLossyPrompt)
779
{
780
#if DEBUG_KP_MAIN_WINDOW
781
    qCDebug(kpLogMainWindow) << "kpMainWindow::askForURL() startURL=" << startURL;
782
783
784
785
786
787
    startSaveOptions.printDebug ("\tstartSaveOptions");
#endif

    bool reparsedConfiguration = false;

    // KConfig::readEntry() does not actually reread from disk, hence doesn't
Thorsten Roeder's avatar
Thorsten Roeder committed
788
    // realize what other processes have done e.g. Settings / Show Path
789
    // so reparseConfiguration() must be called
Clarence Dang's avatar
Clarence Dang committed
790
791
792
#define SETUP_READ_CFG()                                                             \
    if (!reparsedConfiguration)                                                      \
    {                                                                                \
793
        KSharedConfig::openConfig ()->reparseConfiguration ();                       \
Clarence Dang's avatar
Clarence Dang committed
794
795
796
        reparsedConfiguration = true;                                                \
    }                                                                                \
                                                                                     \
797
    KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup);
798

799

800
    if (chosenSaveOptions) {
801
        *chosenSaveOptions = kpDocumentSaveOptions ();
802
    }
803

804
    if (allowLossyPrompt) {
805
        *allowLossyPrompt = true;  // play it safe for now
806
    }
807

808
809

    kpDocumentSaveOptions fdSaveOptions = startSaveOptions;
810

811
    QStringList mimeTypes;
812
    for (const auto &type : QImageWriter::supportedMimeTypes()) {
813
      mimeTypes << QString::fromLatin1(type);
814
    }
815
816
817
#if DEBUG_KP_MAIN_WINDOW
    QStringList sortedMimeTypes = mimeTypes;
    sortedMimeTypes.sort ();
818
    qCDebug(kpLogMainWindow) << "\tmimeTypes=" << mimeTypes
819
820
               << "\tsortedMimeTypes=" << sortedMimeTypes;
#endif