1 /*
2  * Copyright (C) 2010-2015 by Stephen Allewell
3  * steve.allewell@gmail.com
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  */
10 
11 
12 #include "MainWindow.h"
13 
14 #include <QAction>
15 #include <QActionGroup>
16 #include <QClipboard>
17 #include <QDataStream>
18 #include <QDockWidget>
19 #include <QFileDialog>
20 #include <QGridLayout>
21 #include <QMenu>
22 #include <QMimeData>
23 #include <QPainter>
24 #include <QPaintEngine>
25 #include <QProgressDialog>
26 #include <QPrintDialog>
27 #include <QPrinter>
28 #include <QPrintEngine>
29 #include <QPrintPreviewDialog>
30 #include <QSaveFile>
31 #include <QScrollArea>
32 #include <QTemporaryFile>
33 #include <QUndoView>
34 #include <QUrl>
35 
36 #include <KActionCollection>
37 #include <KConfigDialog>
38 #include <KIO/FileCopyJob>
39 #include <KIO/StatJob>
40 #include <KLocalizedString>
41 #include <KMessageBox>
42 #include <KRecentFilesAction>
43 #include <KSelectAction>
44 #include <KXMLGUIFactory>
45 
46 #include "BackgroundImage.h"
47 #include "configuration.h"
48 #include "ConfigurationDialogs.h"
49 #include "Commands.h"
50 #include "Document.h"
51 #include "Editor.h"
52 #include "ExtendPatternDlg.h"
53 #include "FilePropertiesDlg.h"
54 #include "Floss.h"
55 #include "FlossScheme.h"
56 #include "ImportImageDlg.h"
57 #include "Palette.h"
58 #include "PaletteManagerDlg.h"
59 #include "PaperSizes.h"
60 #include "Preview.h"
61 #include "PrintSetupDlg.h"
62 #include "QVariantPtr.h"
63 #include "Scale.h"
64 #include "ScaledPixmapLabel.h"
65 #include "SchemeManager.h"
66 #include "SymbolLibrary.h"
67 #include "SymbolManager.h"
68 
69 
MainWindow()70 MainWindow::MainWindow()
71     :   m_printer(nullptr)
72 {
73     setupActions();
74 }
75 
76 
MainWindow(const QUrl & url)77 MainWindow::MainWindow(const QUrl &url)
78     :   m_printer(nullptr)
79 {
80     setupMainWindow();
81     setupLayout();
82     setupDockWindows();
83     setupActions();
84     setupDocument();
85     setupConnections();
86     setupActionDefaults();
87     loadSettings();
88     fileOpen(url);
89     setupActionsFromDocument();
90     setCaption(m_document->url().fileName(), !m_document->undoStack().isClean());
91     this->findChild<QDockWidget *>(QStringLiteral("ImportedImage#"))->hide();
92 }
93 
94 
MainWindow(const QString & source)95 MainWindow::MainWindow(const QString &source)
96     :   m_printer(nullptr)
97 {
98     setupMainWindow();
99     setupLayout();
100     setupDockWindows();
101     setupActions();
102     setupDocument();
103     setupConnections();
104     setupActionDefaults();
105     loadSettings();
106     convertImage(source);
107     setupActionsFromDocument();
108     setCaption(m_document->url().fileName(), !m_document->undoStack().isClean());
109     this->findChild<QDockWidget *>(QStringLiteral("ImportedImage#"))->show();
110 }
111 
112 
setupMainWindow()113 void MainWindow::setupMainWindow()
114 {
115     setObjectName(QStringLiteral("MainWindow#"));
116     setAutoSaveSettings();
117 }
118 
119 
setupLayout()120 void MainWindow::setupLayout()
121 {
122     QScrollArea *scrollArea = new QScrollArea();
123     scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
124     scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
125     m_editor = new Editor(scrollArea);
126     scrollArea->installEventFilter(m_editor);
127     scrollArea->setWidget(m_editor);
128 
129     m_horizontalScale = m_editor->horizontalScale();
130     m_verticalScale = m_editor->verticalScale();
131 
132     QGridLayout *gridLayout = new QGridLayout(this);
133     gridLayout->addWidget(m_horizontalScale, 0, 1);
134     gridLayout->addWidget(m_verticalScale, 1, 0);
135     gridLayout->addWidget(scrollArea, 1, 1);
136 
137     QWidget *layout = new QWidget();
138     layout->setLayout(gridLayout);
139 
140     setCentralWidget(layout);
141 }
142 
143 
setupDocument()144 void MainWindow::setupDocument()
145 {
146     m_document = new Document();
147 
148     m_editor->setDocument(m_document);
149     m_editor->setPreview(m_preview);
150     m_palette->setDocument(m_document);
151     m_preview->setDocument(m_document);
152     m_history->setStack(&(m_document->undoStack()));
153 
154     m_document->addView(m_editor);
155     m_document->addView(m_preview);
156     m_document->addView(m_palette);
157 }
158 
159 
setupConnections()160 void MainWindow::setupConnections()
161 {
162     KActionCollection *actions = actionCollection();
163 
164     connect(&(m_document->undoStack()), &QUndoStack::canUndoChanged, actions->action(QStringLiteral("edit_undo")), &QAction::setEnabled);
165     connect(&(m_document->undoStack()), &QUndoStack::canUndoChanged, actions->action(QStringLiteral("file_revert")), &QAction::setEnabled);
166     connect(&(m_document->undoStack()), &QUndoStack::canRedoChanged, actions->action(QStringLiteral("edit_redo")), &QAction::setEnabled);
167     connect(QApplication::clipboard(),  &QClipboard::dataChanged, this, &MainWindow::clipboardDataChanged);
168     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("edit_cut")), &QAction::setEnabled);
169     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("edit_copy")), &QAction::setEnabled);
170     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("mirrorHorizontal")), &QAction::setEnabled);
171     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("mirrorVertical")), &QAction::setEnabled);
172     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("rotate90")), &QAction::setEnabled);
173     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("rotate180")), &QAction::setEnabled);
174     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("rotate270")), &QAction::setEnabled);
175     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("patternCropToSelection")), &QAction::setEnabled);
176     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("insertColumns")), &QAction::setEnabled);
177     connect(m_editor, &Editor::selectionMade, actionCollection()->action(QStringLiteral("insertRows")), &QAction::setEnabled);
178     connect(&(m_document->undoStack()), &QUndoStack::undoTextChanged, this, &MainWindow::undoTextChanged);
179     connect(&(m_document->undoStack()), &QUndoStack::redoTextChanged, this, &MainWindow::redoTextChanged);
180     connect(&(m_document->undoStack()), &QUndoStack::cleanChanged, this, &MainWindow::documentModified);
181     connect(m_palette, &Palette::colorSelected, m_editor, static_cast<void (Editor::*)()>(&Editor::drawContents));
182     connect(m_palette, static_cast<void (Palette::*)(int, int)>(&Palette::swapColors), this, &MainWindow::paletteSwapColors);
183     connect(m_palette, static_cast<void (Palette::*)(int, int)>(&Palette::replaceColor), this, &MainWindow::paletteReplaceColor);
184     connect(m_palette, &Palette::signalStateChanged, this, static_cast<void (KXmlGuiWindow::*)(const QString &, bool)>(&KXmlGuiWindow::slotStateChanged));
185     connect(m_palette, &Palette::customContextMenuRequested, this, &MainWindow::paletteContextMenu);
186     connect(m_editor,  &Editor::changedVisibleCells, m_preview, &Preview::setVisibleCells);
187     connect(m_preview, static_cast<void (Preview::*)(QPoint)>(&Preview::clicked), m_editor, static_cast<void (Editor::*)(const QPoint &)>(&Editor::previewClicked));
188     connect(m_preview, static_cast<void (Preview::*)(QRect)>(&Preview::clicked), m_editor, static_cast<void (Editor::*)(const QRect &)>(&Editor::previewClicked));
189 }
190 
191 
setupActionDefaults()192 void MainWindow::setupActionDefaults()
193 {
194     KActionCollection *actions = actionCollection();
195 
196     actions->action(QStringLiteral("maskStitch"))->setChecked(false);
197     actions->action(QStringLiteral("maskColor"))->setChecked(false);
198     actions->action(QStringLiteral("maskBackstitch"))->setChecked(false);
199     actions->action(QStringLiteral("maskKnot"))->setChecked(false);
200 
201     actions->action(QStringLiteral("stitchFull"))->trigger();   // Select full stitch
202 
203     actions->action(QStringLiteral("toolPaint"))->trigger();    // Select paint tool
204 
205     clipboardDataChanged();
206 }
207 
208 
~MainWindow()209 MainWindow::~MainWindow()
210 {
211     delete m_printer;
212 }
213 
214 
editor()215 Editor *MainWindow::editor()
216 {
217     return m_editor;
218 }
219 
220 
preview()221 Preview *MainWindow::preview()
222 {
223     return m_preview;
224 }
225 
226 
palette()227 Palette *MainWindow::palette()
228 {
229     return m_palette;
230 }
231 
232 
queryClose()233 bool MainWindow::queryClose()
234 {
235     if (m_document->undoStack().isClean()) {
236         return true;
237     }
238 
239     while (true) {
240         int messageBoxResult = KMessageBox::warningYesNoCancel(this, i18n("Save changes to document?\nSelecting No discards changes."));
241 
242         switch (messageBoxResult) {
243         case KMessageBox::Yes :
244             fileSave();
245 
246             if (m_document->undoStack().isClean()) {
247                 return true;
248             } else {
249                 KMessageBox::error(this, i18n("Unable to save the file"));
250             }
251 
252             break;
253 
254         case KMessageBox::No :
255             return true;
256 
257         case KMessageBox::Cancel :
258             return false;
259         }
260     }
261 }
262 
263 
setupActionsFromDocument()264 void MainWindow::setupActionsFromDocument()
265 {
266     KActionCollection *actions = actionCollection();
267 
268     actions->action(QStringLiteral("file_revert"))->setEnabled(!m_document->undoStack().isClean());
269     actions->action(QStringLiteral("edit_undo"))->setEnabled(m_document->undoStack().canUndo());
270     actions->action(QStringLiteral("edit_redo"))->setEnabled(m_document->undoStack().canRedo());
271 
272     updateBackgroundImageActionLists();
273 }
274 
275 
fileNew()276 void MainWindow::fileNew()
277 {
278     MainWindow *window = new MainWindow(QUrl());
279     window->show();
280 }
281 
282 
fileOpen()283 void MainWindow::fileOpen()
284 {
285     fileOpen(QFileDialog::getOpenFileUrl(this, i18n("Open file"), QUrl::fromLocalFile(QDir::homePath()), i18n("KXStitch Patterns (*.kxs);;PC Stitch Patterns (*.pat);;All Files (*)")));
286 }
287 
288 
fileOpen(const QUrl & url)289 void MainWindow::fileOpen(const QUrl &url)
290 {
291     MainWindow *window;
292     bool docEmpty = (m_document->undoStack().isClean() && (m_document->url().toString() == i18n("Untitled")));
293 
294     if (url.isValid()) {
295         if (docEmpty) {
296             QTemporaryFile tmpFile;
297 
298             if (tmpFile.open()) {
299                 tmpFile.close();
300 
301                 KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName()), -1, KIO::Overwrite);
302 
303                 if (job->exec()) {
304                     /* In earlier versions of KDE/Qt creating a QDataStream on tmpFile allowed reading the data from the copied file.
305                      * Somewhere after KDE 5.55.0/Qt 5.9.7 this no longer possible as tmpFile size() is reported with a length of 0
306                      * whereas previously tmpFile size() was reported as the size of the copied file.
307                      * Therefore open a new QFile on the temporary file after downloading to allow reading.
308                      */
309                     QFile reader(tmpFile.fileName());
310                     if (reader.open(QIODevice::ReadOnly)) {
311                         QDataStream stream(&reader);
312 
313                         try {
314                             m_document->readKXStitch(stream);
315                             m_document->setUrl(url);
316                             KRecentFilesAction *action = static_cast<KRecentFilesAction *>(actionCollection()->action(QStringLiteral("file_open_recent")));
317                             action->addUrl(url);
318                             action->saveEntries(KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("RecentFiles")));
319                         } catch (const InvalidFile &e) {
320                             stream.device()->seek(0);
321 
322                             try {
323                                 m_document->readPCStitch(stream);
324                             } catch (const InvalidFile &e) {
325                                 KMessageBox::sorry(nullptr, i18n("The file does not appear to be a recognized cross stitch file."));
326                             }
327                         } catch (const InvalidFileVersion &e) {
328                             KMessageBox::sorry(nullptr, i18n("This version of the file is not supported.\n%1", e.version));
329                         } catch (const FailedReadFile &e) {
330                             KMessageBox::error(nullptr, i18n("Failed to read the file.\n%1.", e.status));
331                             m_document->initialiseNew();
332                         }
333 
334                         setupActionsFromDocument();
335                         m_editor->readDocumentSettings();
336                         m_preview->readDocumentSettings();
337                         m_palette->update();
338                         documentModified(true); // this is the clean value true
339 
340                         reader.close();
341                     } else {
342                         KMessageBox::error(nullptr, reader.errorString());
343                     }
344                 } else {
345                     KMessageBox::error(nullptr, job->errorString());
346                 }
347 
348                 tmpFile.close();
349             } else {
350                 KMessageBox::error(nullptr, tmpFile.errorString());
351             }
352         } else {
353             window = new MainWindow(url);
354             window->show();
355         }
356     }
357 }
358 
359 
fileSave()360 void MainWindow::fileSave()
361 {
362     QUrl url = m_document->url();
363 
364     if (url.toString() == i18n("Untitled")) {
365         fileSaveAs();
366     } else {
367         // ### Why use QUrl everywhere if this only supports local files?
368         QSaveFile file(url.toLocalFile());
369 
370         if (file.open(QIODevice::WriteOnly)) {
371             QDataStream stream(&file);
372 
373             try {
374                 m_document->write(stream);
375 
376                 if (!file.commit()) {
377                     throw FailedWriteFile(stream.status());
378                 }
379 
380                 m_document->undoStack().setClean();
381             } catch (const FailedWriteFile &e) {
382                 KMessageBox::error(nullptr, QString(i18n("Failed to save the file.\n%1", file.errorString())));
383                 file.cancelWriting();
384             }
385         } else {
386             KMessageBox::error(nullptr, QString(i18n("Failed to open the file.\n%1", file.errorString())));
387         }
388     }
389 
390 }
391 
392 
fileSaveAs()393 void MainWindow::fileSaveAs()
394 {
395     QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::homePath()), i18n("Cross Stitch Patterns (*.kxs)"));
396 
397     if (url.isValid()) {
398         KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0);
399 
400         if (statJob->exec()) {
401             if (KMessageBox::warningYesNo(this, i18n("This file already exists\nDo you want to overwrite it?")) == KMessageBox::No) {
402                 return;
403             }
404         }
405 
406         m_document->setUrl(url);
407         fileSave();
408         KRecentFilesAction *action = static_cast<KRecentFilesAction *>(actionCollection()->action(QStringLiteral("file_open_recent")));
409         action->addUrl(url);
410         action->saveEntries(KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("RecentFiles")));
411     }
412 }
413 
414 
fileRevert()415 void MainWindow::fileRevert()
416 {
417     if (!m_document->undoStack().isClean()) {
418         if (KMessageBox::warningYesNo(this, i18n("Revert changes to document?")) == KMessageBox::Yes) {
419             m_document->undoStack().setIndex(m_document->undoStack().cleanIndex());
420         }
421     }
422 }
423 
424 
filePrintSetup()425 void MainWindow::filePrintSetup()
426 {
427     if (m_printer == nullptr) {
428         m_printer = new QPrinter();
429     }
430 
431     QPointer<PrintSetupDlg> printSetupDlg = new PrintSetupDlg(this, m_document, m_printer);
432 
433     if (printSetupDlg->exec() == QDialog::Accepted) {
434         m_document->undoStack().push(new UpdatePrinterConfigurationCommand(m_document, printSetupDlg->printerConfiguration()));
435     }
436 
437     delete printSetupDlg;
438 }
439 
440 
filePrint()441 void MainWindow::filePrint()
442 {
443     if (m_printer == nullptr) {
444         filePrintSetup();
445     }
446 
447     if (!m_document->printerConfiguration().pages().isEmpty()) {
448         m_printer->setFullPage(true);
449         m_printer->setPrintRange(QPrinter::AllPages);
450         m_printer->setFromTo(1, m_document->printerConfiguration().pages().count());
451 
452         QPointer<QPrintDialog> printDialog = new QPrintDialog(m_printer, this);
453 
454         if (printDialog->exec() == QDialog::Accepted) {
455             printPages();
456         }
457 
458         delete printDialog;
459     } else {
460         KMessageBox::information(this, i18n("There is nothing to print"));
461     }
462 }
463 
464 
printPages()465 void MainWindow::printPages()
466 {
467     QList<Page *> pages = m_document->printerConfiguration().pages();
468 
469     int fromPage = 1;
470     int toPage = pages.count();
471 
472     if (m_printer->printRange() == QPrinter::PageRange) {
473         fromPage = m_printer->fromPage();
474         toPage = m_printer->toPage();
475     }
476 
477     while (toPage < pages.count()) pages.removeLast();
478     while (--fromPage) pages.removeFirst();
479 
480     int totalPages = pages.count();
481 
482     const Page *page = (m_printer->pageOrder() == QPrinter::FirstPageFirst)?pages.takeFirst():pages.takeLast();
483 
484     m_printer->setPaperSize(page->paperSize());             // DEPRECATED
485     m_printer->setOrientation(page->orientation());         // DEPRECATED
486 
487     QPainter painter;
488     painter.begin(m_printer);
489     painter.setRenderHint(QPainter::Antialiasing, true);
490 
491     for (int p = 0 ; p < totalPages ;) {
492         int paperWidth = PaperSizes::width(page->paperSize(), page->orientation());
493         int paperHeight = PaperSizes::height(page->paperSize(), page->orientation());
494 
495         painter.setWindow(0, 0, paperWidth, paperHeight);
496 
497         page->render(m_document, &painter);
498 
499         if (++p < totalPages) {
500             page = (m_printer->pageOrder() == QPrinter::FirstPageFirst)?pages.takeFirst():pages.takeLast();
501 
502             m_printer->setPaperSize(page->paperSize());     // DEPRECATED
503             m_printer->setOrientation(page->orientation()); // DEPRECATED
504 
505             m_printer->newPage();
506         }
507     }
508 
509     painter.end();
510 }
511 
512 
fileImportImage()513 void MainWindow::fileImportImage()
514 {
515     MainWindow *window;
516     bool docEmpty = ((m_document->undoStack().isClean()) && (m_document->url().toString() == i18n("Untitled")));
517     QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Import Image"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)), i18n("Images (*.bmp *.gif *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm *.svg)"));
518 
519     if (url.isValid()) {
520         QTemporaryFile tmpFile;
521 
522         if (tmpFile.open()) {
523             KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName()), -1, KIO::Overwrite);
524 
525             if (job->exec()) {
526                 if (docEmpty) {
527                     convertImage(tmpFile.fileName());
528                     this->findChild<QDockWidget *>(QStringLiteral("ImportedImage#"))->show();
529                 } else {
530                     window = new MainWindow(tmpFile.fileName());
531                     window->show();
532                 }
533             } else {
534                 KMessageBox::error(nullptr, job->errorString());
535             }
536         }
537     }
538 }
539 
540 
convertImage(const QString & source)541 void MainWindow::convertImage(const QString &source)
542 {
543     Magick::Image image(source.toStdString());
544 
545     QMap<int, QColor> documentFlosses;
546     QList<qint16> symbolIndexes = SymbolManager::library(Configuration::palette_DefaultSymbolLibrary())->indexes();
547 
548     QPointer<ImportImageDlg> importImageDlg = new ImportImageDlg(this, image);
549 
550     if (importImageDlg->exec()) {
551         Magick::Image convertedImage = importImageDlg->convertedImage();
552 
553         int imageWidth = convertedImage.columns();
554         int imageHeight = convertedImage.rows();
555         int documentWidth = imageWidth;
556         int documentHeight = imageHeight;
557 
558         bool useFractionals = importImageDlg->useFractionals();
559 
560 /*
561  * ImageMagick prior to V7 used matte (opacity) to determine if an image has transparency.
562  * 0.0 for transparent to 1.0 for opaque
563  *
564  * ImageMagick V7 now uses alpha (transparency).
565  * 1.0 for transparent to 0.0 for opaque
566  *
567  * Access to pixels has changed too, V7 can use pixelColor to access the color of a particular
568  * pixel, but although this was available in V6, it doesn't appear to produce the same result
569  * and has resulted in black images when importing.
570  */
571 #if MagickLibVersion < 0x700
572         bool hasTransparency = convertedImage.matte();
573         double transparent = 1.0;
574         const Magick::PixelPacket *pixels = convertedImage.getConstPixels(0, 0, imageWidth, imageHeight);
575 #else
576         bool hasTransparency = convertedImage.alpha();
577         double transparent = 0.0;
578 #endif
579 
580         bool ignoreColor = importImageDlg->ignoreColor();
581         Magick::Color ignoreColorValue = importImageDlg->ignoreColorValue();
582 
583         int pixelCount = imageWidth * imageHeight;
584 
585         if (useFractionals) {
586             documentWidth /= 2;
587             documentHeight /= 2;
588         }
589 
590         QString schemeName = importImageDlg->flossScheme();
591         FlossScheme *flossScheme = SchemeManager::scheme(schemeName);
592 
593         QUndoCommand *importImageCommand = new ImportImageCommand(m_document);
594         new ResizeDocumentCommand(m_document, documentWidth, documentHeight, importImageCommand);
595         new ChangeSchemeCommand(m_document, schemeName, importImageCommand);
596 
597         QProgressDialog progress(i18n("Converting to stitches"), i18n("Cancel"), 0, pixelCount, this);
598         progress.setWindowModality(Qt::WindowModal);
599 
600         for (int dy = 0 ; dy < imageHeight ; dy++) {
601             progress.setValue(dy * imageWidth);
602             QApplication::processEvents();
603 
604             if (progress.wasCanceled()) {
605                 delete importImageDlg;
606                 delete importImageCommand;
607                 return;
608             }
609 
610             for (int dx = 0 ; dx < imageWidth ; dx++) {
611 #if MagickLibVersion < 0x700
612                 Magick::ColorRGB rgb = Magick::Color(*pixels++);
613 #else
614                 Magick::ColorRGB rgb = convertedImage.pixelColor(dx, dy);
615 #endif
616 
617                 if (hasTransparency && (rgb.alpha() == transparent)) {
618                     // ignore this pixel as it is transparent
619                 } else {
620                     if (!(ignoreColor && (rgb == ignoreColorValue))) {
621                         int flossIndex;
622                         QColor color((int)(255*rgb.red()), (int)(255*rgb.green()), (int)(255*rgb.blue()));
623 
624                         for (flossIndex = 0 ; flossIndex < documentFlosses.count() ; ++flossIndex) {
625                             if (documentFlosses[flossIndex] == color) {
626                                 break;
627                             }
628                         }
629 
630                         if (flossIndex == documentFlosses.count()) { // reached the end of the list
631                             qint16 stitchSymbol = symbolIndexes.takeFirst();
632                             Qt::PenStyle backstitchSymbol(Qt::SolidLine);
633                             Floss *floss = flossScheme->find(color);
634 
635                             DocumentFloss *documentFloss = new DocumentFloss(floss->name(), stitchSymbol, backstitchSymbol, Configuration::palette_StitchStrands(), Configuration::palette_BackstitchStrands());
636                             documentFloss->setFlossColor(floss->color());
637                             new AddDocumentFlossCommand(m_document, flossIndex, documentFloss, importImageCommand);
638                             documentFlosses.insert(flossIndex, color);
639                         }
640 
641                         // at this point
642                         //   flossIndex will be the index for the found color
643                         if (useFractionals) {
644                             int zone = (dy % 2) * 2 + (dx % 2);
645                             new AddStitchCommand(m_document, QPoint(dx / 2, dy / 2), stitchMap[0][zone], flossIndex, importImageCommand);
646                         } else {
647                             new AddStitchCommand(m_document, QPoint(dx, dy), Stitch::Full, flossIndex, importImageCommand);
648                         }
649                     }
650                 }
651             }
652         }
653 
654         new SetPropertyCommand(m_document, QStringLiteral("horizontalClothCount"), importImageDlg->horizontalClothCount(), importImageCommand);
655         new SetPropertyCommand(m_document, QStringLiteral("verticalClothCount"), importImageDlg->verticalClothCount(), importImageCommand);
656         m_document->undoStack().push(importImageCommand);
657 
658         convertPreview(source, importImageDlg->croppedArea());
659     }
660 
661     delete importImageDlg;
662 }
663 
664 
convertPreview(const QString & source,const QRect & croppedArea)665 void MainWindow::convertPreview(const QString &source, const QRect &croppedArea)
666 {
667     QPixmap pixmap;
668     pixmap.load(source);
669     pixmap = pixmap.copy(croppedArea);
670     m_imageLabel->setPixmap(pixmap);
671 }
672 
673 
fileProperties()674 void MainWindow::fileProperties()
675 {
676     QPointer<FilePropertiesDlg> filePropertiesDlg = new FilePropertiesDlg(this, m_document);
677 
678     if (filePropertiesDlg->exec()) {
679         QUndoCommand *cmd = new FilePropertiesCommand(m_document);
680 
681         if ((filePropertiesDlg->documentWidth() != m_document->pattern()->stitches().width()) || (filePropertiesDlg->documentHeight() != m_document->pattern()->stitches().height())) {
682             new ResizeDocumentCommand(m_document, filePropertiesDlg->documentWidth(), filePropertiesDlg->documentHeight(), cmd);
683         }
684 
685         if (filePropertiesDlg->unitsFormat() != static_cast<Configuration::EnumDocument_UnitsFormat::type>(m_document->property(QStringLiteral("unitsFormat")).toInt())) {
686             new SetPropertyCommand(m_document, QStringLiteral("unitsFormat"), QVariant(filePropertiesDlg->unitsFormat()), cmd);
687         }
688 
689         if (filePropertiesDlg->horizontalClothCount() != m_document->property(QStringLiteral("horizontalClothCount")).toDouble()) {
690             new SetPropertyCommand(m_document, QStringLiteral("horizontalClothCount"), QVariant(filePropertiesDlg->horizontalClothCount()), cmd);
691         }
692 
693         if (filePropertiesDlg->clothCountLink() != m_document->property(QStringLiteral("clothCountLink")).toBool()) {
694             new SetPropertyCommand(m_document, QStringLiteral("clothCountLink"), QVariant(filePropertiesDlg->clothCountLink()), cmd);
695         }
696 
697         if (filePropertiesDlg->verticalClothCount() != m_document->property(QStringLiteral("verticalClothCount")).toDouble()) {
698             new SetPropertyCommand(m_document, QStringLiteral("verticalClothCount"), QVariant(filePropertiesDlg->verticalClothCount()), cmd);
699         }
700 
701         if (filePropertiesDlg->clothCountUnits() != static_cast<Configuration::EnumEditor_ClothCountUnits::type>(m_document->property(QStringLiteral("clothCountUnits")).toInt())) {
702             new SetPropertyCommand(m_document, QStringLiteral("clothCountUnits"), QVariant(filePropertiesDlg->clothCountUnits()), cmd);
703         }
704 
705         if (filePropertiesDlg->title() != m_document->property(QStringLiteral("title")).toString()) {
706             new SetPropertyCommand(m_document, QStringLiteral("title"), QVariant(filePropertiesDlg->title()), cmd);
707         }
708 
709         if (filePropertiesDlg->author() != m_document->property(QStringLiteral("author")).toString()) {
710             new SetPropertyCommand(m_document, QStringLiteral("author"), QVariant(filePropertiesDlg->author()), cmd);
711         }
712 
713         if (filePropertiesDlg->copyright() != m_document->property(QStringLiteral("copyright")).toString()) {
714             new SetPropertyCommand(m_document, QStringLiteral("copyright"), QVariant(filePropertiesDlg->copyright()), cmd);
715         }
716 
717         if (filePropertiesDlg->fabric() != m_document->property(QStringLiteral("fabric")).toString()) {
718             new SetPropertyCommand(m_document, QStringLiteral("fabric"), QVariant(filePropertiesDlg->fabric()), cmd);
719         }
720 
721         if (filePropertiesDlg->fabricColor() != m_document->property(QStringLiteral("fabricColor")).value<QColor>()) {
722             new SetPropertyCommand(m_document, QStringLiteral("fabricColor"), QVariant(filePropertiesDlg->fabricColor()), cmd);
723         }
724 
725         if (filePropertiesDlg->instructions() != m_document->property(QStringLiteral("instructions")).toString()) {
726             new SetPropertyCommand(m_document, QStringLiteral("instructions"), QVariant(filePropertiesDlg->instructions()), cmd);
727         }
728 
729         if (filePropertiesDlg->flossScheme() != m_document->pattern()->palette().schemeName()) {
730             new ChangeSchemeCommand(m_document, filePropertiesDlg->flossScheme(), cmd);
731         }
732 
733         if (cmd->childCount()) {
734             m_document->undoStack().push(cmd);
735         } else {
736             delete cmd;
737         }
738     }
739 
740     delete filePropertiesDlg;
741 }
742 
743 
fileAddBackgroundImage()744 void MainWindow::fileAddBackgroundImage()
745 {
746     QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Background Image"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)), i18n("Images (*.bmp *.gif *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm *.svg)"));
747 
748     if (!url.isEmpty()) {
749         QRect patternArea(0, 0, m_document->pattern()->stitches().width(), m_document->pattern()->stitches().height());
750         QRect selectionArea = m_editor->selectionArea();
751         QSharedPointer<BackgroundImage> backgroundImage(new BackgroundImage(url, (selectionArea.isValid() ? selectionArea : patternArea)));
752 
753         if (backgroundImage->isValid()) {
754             m_document->undoStack().push(new AddBackgroundImageCommand(m_document, backgroundImage, this));
755         }
756     }
757 }
758 
759 
fileRemoveBackgroundImage()760 void MainWindow::fileRemoveBackgroundImage()
761 {
762     QAction *action = qobject_cast<QAction *>(sender());
763     m_document->undoStack().push(new RemoveBackgroundImageCommand(m_document, action->data().value<QSharedPointer<BackgroundImage>>(), this));
764 }
765 
766 
fileClose()767 void MainWindow::fileClose()
768 {
769     if (queryClose()) {
770         m_document->initialiseNew();
771         setupActionsFromDocument();
772         m_editor->readDocumentSettings();
773         m_preview->readDocumentSettings();
774     }
775 
776     close();
777 }
778 
779 
fileQuit()780 void MainWindow::fileQuit()
781 {
782     close();
783 }
784 
785 
editUndo()786 void MainWindow::editUndo()
787 {
788     m_document->undoStack().undo();
789 }
790 
791 
editRedo()792 void MainWindow::editRedo()
793 {
794     m_document->undoStack().redo();
795 }
796 
797 
undoTextChanged(const QString & text)798 void MainWindow::undoTextChanged(const QString &text)
799 {
800     actionCollection()->action(QStringLiteral("edit_undo"))->setText(i18n("Undo %1", text));
801 }
802 
803 
redoTextChanged(const QString & text)804 void MainWindow::redoTextChanged(const QString &text)
805 {
806     actionCollection()->action(QStringLiteral("edit_redo"))->setText(i18n("Redo %1", text));
807 }
808 
809 
clipboardDataChanged()810 void MainWindow::clipboardDataChanged()
811 {
812     actionCollection()->action(QStringLiteral("edit_paste"))->setEnabled(QApplication::clipboard()->mimeData()->hasFormat(QStringLiteral("application/kxstitch")));
813 }
814 
815 
paletteManager()816 void MainWindow::paletteManager()
817 {
818     QPointer<PaletteManagerDlg> paletteManagerDlg = new PaletteManagerDlg(this, m_document);
819 
820     if (paletteManagerDlg->exec()) {
821         DocumentPalette palette = paletteManagerDlg->palette();
822 
823         if (palette != m_document->pattern()->palette()) {
824             m_document->undoStack().push(new UpdateDocumentPaletteCommand(m_document, palette));
825         }
826     }
827 
828     delete paletteManagerDlg;
829 }
830 
831 
paletteShowSymbols(bool show)832 void MainWindow::paletteShowSymbols(bool show)
833 {
834     m_palette->showSymbols(show);
835 }
836 
837 
paletteClearUnused()838 void MainWindow::paletteClearUnused()
839 {
840     QMap<int, FlossUsage> flossUsage = m_document->pattern()->stitches().flossUsage();
841     QMapIterator<int, DocumentFloss *> flosses(m_document->pattern()->palette().flosses());
842     ClearUnusedFlossesCommand *clearUnusedFlossesCommand = new ClearUnusedFlossesCommand(m_document);
843 
844     while (flosses.hasNext()) {
845         flosses.next();
846 
847         if (flossUsage[flosses.key()].totalStitches() == 0) {
848             new RemoveDocumentFlossCommand(m_document, flosses.key(), flosses.value(), clearUnusedFlossesCommand);
849         }
850     }
851 
852     if (clearUnusedFlossesCommand->childCount()) {
853         m_document->undoStack().push(clearUnusedFlossesCommand);
854     } else {
855         delete clearUnusedFlossesCommand;
856     }
857 }
858 
859 
paletteCalibrateScheme()860 void MainWindow::paletteCalibrateScheme()
861 {
862 }
863 
864 
paletteSwapColors(int originalIndex,int replacementIndex)865 void MainWindow::paletteSwapColors(int originalIndex, int replacementIndex)
866 {
867     if (originalIndex != replacementIndex) {
868         m_document->undoStack().push(new PaletteSwapColorCommand(m_document, originalIndex, replacementIndex));
869     }
870 }
871 
872 
paletteReplaceColor(int originalIndex,int replacementIndex)873 void MainWindow::paletteReplaceColor(int originalIndex, int replacementIndex)
874 {
875     if (originalIndex != replacementIndex) {
876         m_document->undoStack().push(new PaletteReplaceColorCommand(m_document, originalIndex, replacementIndex));
877     }
878 }
879 
880 
viewFitBackgroundImage()881 void MainWindow::viewFitBackgroundImage()
882 {
883     QAction *action = qobject_cast<QAction *>(sender());
884     m_document->undoStack().push(new FitBackgroundImageCommand(m_document, action->data().value<QSharedPointer<BackgroundImage>>(), m_editor->selectionArea()));
885 }
886 
887 
paletteContextMenu(const QPoint & pos)888 void MainWindow::paletteContextMenu(const QPoint &pos)
889 {
890     static_cast<QMenu *>(guiFactory()->container(QStringLiteral("PalettePopup"), this))->popup(qobject_cast<QWidget *>(sender())->mapToGlobal(pos));
891 }
892 
893 
viewShowBackgroundImage()894 void MainWindow::viewShowBackgroundImage()
895 {
896     QAction *action = qobject_cast<QAction *>(sender());
897     m_document->undoStack().push(new ShowBackgroundImageCommand(m_document, action->data().value<QSharedPointer<BackgroundImage>>(), action->isChecked()));
898 }
899 
900 
patternExtend()901 void MainWindow::patternExtend()
902 {
903     QPointer<ExtendPatternDlg> extendPatternDlg = new ExtendPatternDlg(this);
904 
905     if (extendPatternDlg->exec()) {
906         int top = extendPatternDlg->top();
907         int left = extendPatternDlg->left();
908         int right = extendPatternDlg->right();
909         int bottom = extendPatternDlg->bottom();
910 
911         if (top || left || right || bottom) {
912             m_document->undoStack().push(new ExtendPatternCommand(m_document, top, left, right, bottom));
913         }
914     }
915 
916     delete extendPatternDlg;
917 }
918 
919 
patternCentre()920 void MainWindow::patternCentre()
921 {
922     m_document->undoStack().push(new CentrePatternCommand(m_document));
923 }
924 
925 
patternCrop()926 void MainWindow::patternCrop()
927 {
928     m_document->undoStack().push(new CropToPatternCommand(m_document));
929 }
930 
931 
patternCropToSelection()932 void MainWindow::patternCropToSelection()
933 {
934     m_document->undoStack().push(new CropToSelectionCommand(m_document, m_editor->selectionArea()));
935 }
936 
937 
insertColumns()938 void MainWindow::insertColumns()
939 {
940     m_document->undoStack().push(new InsertColumnsCommand(m_document, m_editor->selectionArea()));
941 }
942 
943 
insertRows()944 void MainWindow::insertRows()
945 {
946     m_document->undoStack().push(new InsertRowsCommand(m_document, m_editor->selectionArea()));
947 }
948 
949 
preferences()950 void MainWindow::preferences()
951 {
952     if (KConfigDialog::showDialog(QStringLiteral("preferences"))) {
953         return;
954     }
955 
956     KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("preferences"), Configuration::self());
957     dialog->setFaceType(KPageDialog::List);
958 
959     dialog->addPage(new EditorConfigPage(0, QStringLiteral("EditorConfigPage")), i18nc("The Editor config page", "Editor"), QStringLiteral("preferences-desktop"));
960     dialog->addPage(new PatternConfigPage(0, QStringLiteral("PatternConfigPage")), i18n("Pattern"), QStringLiteral("ksnapshot"));
961     PaletteConfigPage *paletteConfigPage = new PaletteConfigPage(0, QStringLiteral("PaletteConfigPage"));
962     dialog->addPage(paletteConfigPage, i18n("Palette"), QStringLiteral("preferences-desktop-color"));
963     dialog->addPage(new ImportConfigPage(0, QStringLiteral("ImportConfigPage")), i18n("Import"), QStringLiteral("insert-image"));
964     dialog->addPage(new LibraryConfigPage(0, QStringLiteral("LibraryConfigPage")), i18n("Library"), QStringLiteral("accessories-dictionary"));
965     dialog->addPage(new PrinterConfigPage(0, QStringLiteral("PrinterConfigPage")), i18n("Printer Configuration"), QStringLiteral("preferences-desktop-printer"));
966 
967     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::settingsChanged);
968 
969     dialog->show();
970 }
971 
972 
settingsChanged()973 void MainWindow::settingsChanged()
974 {
975     QList<QUndoCommand *> documentChanges;
976     ConfigurationCommand *configurationCommand = new ConfigurationCommand(this);
977 
978     if (m_document->property(QStringLiteral("cellHorizontalGrouping")) != Configuration::editor_CellHorizontalGrouping()) {
979         documentChanges.append(new SetPropertyCommand(m_document, QStringLiteral("cellHorizontalGrouping"), Configuration::editor_CellHorizontalGrouping(), configurationCommand));
980     }
981 
982     if (m_document->property(QStringLiteral("cellVerticalGrouping")) != Configuration::editor_CellVerticalGrouping()) {
983         documentChanges.append(new SetPropertyCommand(m_document, QStringLiteral("cellVerticalGrouping"), Configuration::editor_CellVerticalGrouping(), configurationCommand));
984     }
985 
986     if (m_document->property(QStringLiteral("thickLineColor")) != Configuration::editor_ThickLineColor()) {
987         documentChanges.append(new SetPropertyCommand(m_document, QStringLiteral("thickLineColor"), Configuration::editor_ThickLineColor(), configurationCommand));
988     }
989 
990     if (m_document->property(QStringLiteral("thinLineColor")) != Configuration::editor_ThinLineColor()) {
991         documentChanges.append(new SetPropertyCommand(m_document, QStringLiteral("thinLineColor"), Configuration::editor_ThinLineColor(), configurationCommand));
992     }
993 
994     if (documentChanges.count()) {
995         m_document->undoStack().push(configurationCommand);
996     } else {
997         delete configurationCommand;
998     }
999 
1000     loadSettings();
1001 }
1002 
1003 
loadSettings()1004 void MainWindow::loadSettings()
1005 {
1006     m_horizontalScale->setMinimumSize(0, Configuration::editor_HorizontalScaleHeight());
1007     m_verticalScale->setMinimumSize(Configuration::editor_VerticalScaleWidth(), 0);
1008     m_horizontalScale->setCellGrouping(Configuration::editor_CellHorizontalGrouping());
1009     m_verticalScale->setCellGrouping(Configuration::editor_CellVerticalGrouping());
1010 
1011     m_editor->loadSettings();
1012     m_preview->loadSettings();
1013     m_palette->loadSettings();
1014 
1015     KActionCollection *actions = actionCollection();
1016 
1017     actions->action(QStringLiteral("makesCopies"))->setChecked(Configuration::tool_MakesCopies());
1018 
1019     actions->action(QStringLiteral("colorHighlight"))->setChecked(Configuration::renderer_ColorHilight());
1020 
1021     actions->action(QStringLiteral("renderStitches"))->setChecked(Configuration::renderer_RenderStitches());
1022     actions->action(QStringLiteral("renderBackstitches"))->setChecked(Configuration::renderer_RenderBackstitches());
1023     actions->action(QStringLiteral("renderFrenchKnots"))->setChecked(Configuration::renderer_RenderFrenchKnots());
1024     actions->action(QStringLiteral("renderGrid"))->setChecked(Configuration::renderer_RenderGrid());
1025     actions->action(QStringLiteral("renderBackgroundImages"))->setChecked(Configuration::renderer_RenderBackgroundImages());
1026 
1027     switch (Configuration::editor_FormatScalesAs()) {
1028     case Configuration::EnumEditor_FormatScalesAs::Stitches:
1029         actions->action(QStringLiteral("formatScalesAsStitches"))->trigger();
1030         break;
1031 
1032     case Configuration::EnumEditor_FormatScalesAs::Inches:
1033         actions->action(QStringLiteral("formatScalesAsInches"))->trigger();
1034         break;
1035 
1036     case Configuration::EnumEditor_FormatScalesAs::Centimeters:
1037         actions->action(QStringLiteral("formatScalesAsCentimeters"))->trigger();
1038         break;
1039 
1040     default:
1041         break;
1042     }
1043 
1044     switch (Configuration::renderer_RenderStitchesAs()) {
1045     case Configuration::EnumRenderer_RenderStitchesAs::Stitches:
1046         actions->action(QStringLiteral("renderStitchesAsRegularStitches"))->trigger();
1047         break;
1048 
1049     case Configuration::EnumRenderer_RenderStitchesAs::BlackWhiteSymbols:
1050         actions->action(QStringLiteral("renderStitchesAsBlackWhiteSymbols"))->trigger();
1051         break;
1052 
1053     case Configuration::EnumRenderer_RenderStitchesAs::ColorSymbols:
1054         actions->action(QStringLiteral("renderStitchesAsColorSymbols"))->trigger();
1055         break;
1056 
1057     case Configuration::EnumRenderer_RenderStitchesAs::ColorBlocks:
1058         actions->action(QStringLiteral("renderStitchesAsColorBlocks"))->trigger();
1059         break;
1060 
1061     case Configuration::EnumRenderer_RenderStitchesAs::ColorBlocksSymbols:
1062         actions->action(QStringLiteral("renderStitchesAsColorBlocksSymbols"))->trigger();
1063         break;
1064 
1065     default:
1066         break;
1067     }
1068 
1069     switch (Configuration::renderer_RenderBackstitchesAs()) {
1070     case Configuration::EnumRenderer_RenderBackstitchesAs::ColorLines:
1071         actions->action(QStringLiteral("renderBackstitchesAsColorLines"))->trigger();
1072         break;
1073 
1074     case Configuration::EnumRenderer_RenderBackstitchesAs::BlackWhiteSymbols:
1075         actions->action(QStringLiteral("renderBackstitchesAsBlackWhiteSymbols"))->trigger();
1076         break;
1077 
1078     default:
1079         break;
1080     }
1081 
1082     switch (Configuration::renderer_RenderKnotsAs()) {
1083     case Configuration::EnumRenderer_RenderKnotsAs::ColorBlocks:
1084         actions->action(QStringLiteral("renderKnotsAsColorBlocks"))->trigger();
1085         break;
1086 
1087     case Configuration::EnumRenderer_RenderKnotsAs::ColorBlocksSymbols:
1088         actions->action(QStringLiteral("renderKnotsAsColorBlocksSymbols"))->trigger();
1089         break;
1090 
1091     case Configuration::EnumRenderer_RenderKnotsAs::ColorSymbols:
1092         actions->action(QStringLiteral("renderKnotsAsColorSymbols"))->trigger();
1093         break;
1094 
1095     case Configuration::EnumRenderer_RenderKnotsAs::BlackWhiteSymbols:
1096         actions->action(QStringLiteral("renderKnotsAsBlackWhiteSymbols"))->trigger();
1097         break;
1098 
1099     default:
1100         break;
1101     }
1102 
1103     actions->action(QStringLiteral("paletteShowSymbols"))->setChecked(Configuration::palette_ShowSymbols());
1104 }
1105 
1106 
documentModified(bool clean)1107 void MainWindow::documentModified(bool clean)
1108 {
1109     setCaption(m_document->url().fileName(), !clean);
1110 }
1111 
1112 
setupActions()1113 void MainWindow::setupActions()
1114 {
1115     QAction *action;
1116     QActionGroup *actionGroup;
1117 
1118     KActionCollection *actions = actionCollection();
1119 
1120     // File menu
1121     KStandardAction::openNew(this, &MainWindow::fileNew, actions);
1122     KStandardAction::open(this, static_cast<void (MainWindow::*)()>(&MainWindow::fileOpen), actions);
1123     KStandardAction::openRecent(this, static_cast<void (MainWindow::*)(const QUrl &)>(&MainWindow::fileOpen), actions)->loadEntries(KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("RecentFiles")));
1124     KStandardAction::save(this, &MainWindow::fileSave, actions);
1125     KStandardAction::saveAs(this, &MainWindow::fileSaveAs, actions);
1126     KStandardAction::revert(this, &MainWindow::fileRevert, actions);
1127 
1128     action = new QAction(this);
1129     action->setText(i18n("Print Setup..."));
1130     connect(action, &QAction::triggered, this, &MainWindow::filePrintSetup);
1131     actions->addAction(QStringLiteral("filePrintSetup"), action);
1132 
1133     KStandardAction::print(this, &MainWindow::filePrint, actions);
1134 
1135     action = new QAction(this);
1136     action->setText(i18n("Import Image"));
1137     connect(action, &QAction::triggered, this, &MainWindow::fileImportImage);
1138     actions->addAction(QStringLiteral("fileImportImage"), action);
1139 
1140     action = new QAction(this);
1141     action->setText(i18n("File Properties"));
1142     connect(action, &QAction::triggered, this, &MainWindow::fileProperties);
1143     actions->addAction(QStringLiteral("fileProperties"), action);
1144 
1145     action = new QAction(this);
1146     action->setText(i18n("Add Background Image..."));
1147     connect(action, &QAction::triggered, this, &MainWindow::fileAddBackgroundImage);
1148     actions->addAction(QStringLiteral("fileAddBackgroundImage"), action);
1149 
1150     KStandardAction::close(this, &MainWindow::fileClose, actions);
1151     KStandardAction::quit(this, &MainWindow::fileQuit, actions);
1152 
1153 
1154     // Edit menu
1155     KStandardAction::undo(this, &MainWindow::editUndo, actions);
1156     KStandardAction::redo(this, &MainWindow::editRedo, actions);
1157     KStandardAction::cut(m_editor, &Editor::editCut, actions);
1158     actions->action(QStringLiteral("edit_cut"))->setEnabled(false);
1159     KStandardAction::copy(m_editor, &Editor::editCopy, actions);
1160     actions->action(QStringLiteral("edit_copy"))->setEnabled(false);
1161     KStandardAction::paste(m_editor, &Editor::editPaste, actions);
1162 
1163     action = new QAction(this);
1164     action->setText(i18n("Mirror/Rotate makes copies"));
1165     action->setCheckable(true);
1166     connect(action, &QAction::triggered, m_editor, &Editor::setMakesCopies);
1167     actions->addAction(QStringLiteral("makesCopies"), action);
1168 
1169     action = new QAction(this);
1170     action->setText(i18n("Horizontally"));
1171     action->setData(Qt::Horizontal);
1172     connect(action, &QAction::triggered, m_editor, &Editor::mirrorSelection);
1173     action->setEnabled(false);
1174     actions->addAction(QStringLiteral("mirrorHorizontal"), action);
1175 
1176     action = new QAction(this);
1177     action->setText(i18n("Vertically"));
1178     action->setData(Qt::Vertical);
1179     connect(action, &QAction::triggered, m_editor, &Editor::mirrorSelection);
1180     action->setEnabled(false);
1181     actions->addAction(QStringLiteral("mirrorVertical"), action);
1182 
1183     action = new QAction(this);
1184     action->setText(i18n("90 Degrees"));
1185     action->setData(StitchData::Rotate90);
1186     connect(action, &QAction::triggered, m_editor, &Editor::rotateSelection);
1187     action->setEnabled(false);
1188     actions->addAction(QStringLiteral("rotate90"), action);
1189 
1190     action = new QAction(this);
1191     action->setText(i18n("180 Degrees"));
1192     action->setData(StitchData::Rotate180);
1193     connect(action, &QAction::triggered, m_editor, &Editor::rotateSelection);
1194     action->setEnabled(false);
1195     actions->addAction(QStringLiteral("rotate180"), action);
1196 
1197     action = new QAction(this);
1198     action->setText(i18n("270 Degrees"));
1199     action->setData(StitchData::Rotate270);
1200     connect(action, &QAction::triggered, m_editor, &Editor::rotateSelection);
1201     action->setEnabled(false);
1202     actions->addAction(QStringLiteral("rotate270"), action);
1203 
1204     // Selection mask sub menu
1205     action = new QAction(this);
1206     action->setText(i18n("Stitch Mask"));
1207     action->setCheckable(true);
1208     connect(action, &QAction::triggered, m_editor, &Editor::setMaskStitch);
1209     actions->addAction(QStringLiteral("maskStitch"), action);
1210 
1211     action = new QAction(this);
1212     action->setText(i18n("Color Mask"));
1213     action->setCheckable(true);
1214     connect(action, &QAction::triggered, m_editor, &Editor::setMaskColor);
1215     actions->addAction(QStringLiteral("maskColor"), action);
1216 
1217     action = new QAction(this);
1218     action->setText(i18n("Exclude Backstitches"));
1219     action->setCheckable(true);
1220     connect(action, &QAction::triggered, m_editor, &Editor::setMaskBackstitch);
1221     actions->addAction(QStringLiteral("maskBackstitch"), action);
1222 
1223     action = new QAction(this);
1224     action->setText(i18n("Exclude Knots"));
1225     action->setCheckable(true);
1226     connect(action, &QAction::triggered, m_editor, &Editor::setMaskKnot);
1227     actions->addAction(QStringLiteral("maskKnot"), action);
1228 
1229 
1230     // View menu
1231     KStandardAction::zoomIn(m_editor, &Editor::zoomIn, actions);
1232     KStandardAction::zoomOut(m_editor, &Editor::zoomOut, actions);
1233     KStandardAction::actualSize(m_editor, &Editor::actualSize, actions);
1234     action = KStandardAction::fitToPage(m_editor, &Editor::fitToPage, actions);
1235     action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best")));
1236     action = KStandardAction::fitToWidth(m_editor, &Editor::fitToWidth, actions);
1237     action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-width")));
1238     action = KStandardAction::fitToHeight(m_editor, &Editor::fitToHeight, actions);
1239     action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-height")));
1240 
1241     // Entries for Show/Hide Preview and Palette dock windows are added dynamically
1242     // Entries for Show/Hide and Remove background images are added dynamically
1243 
1244 
1245     // Stitches Menu
1246     actionGroup = new QActionGroup(this);
1247     actionGroup->setExclusive(true);
1248 
1249     action = new QAction(this);
1250     action->setText(i18n("Quarter Stitch"));
1251     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-quarter-stitch")));
1252     action->setCheckable(true);
1253     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::StitchQuarter); });
1254     actions->addAction(QStringLiteral("stitchQuarter"), action);
1255     actionGroup->addAction(action);
1256 
1257     action = new QAction(this);
1258     action->setText(i18n("Half Stitch"));
1259     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-half-stitch")));
1260     action->setCheckable(true);
1261     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::StitchHalf); });
1262     actions->addAction(QStringLiteral("stitchHalf"), action);
1263     actionGroup->addAction(action);
1264 
1265     action = new QAction(this);
1266     action->setText(i18n("3 Quarter Stitch"));
1267     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-3quarter-stitch")));
1268     action->setCheckable(true);
1269     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::Stitch3Quarter); });
1270     actions->addAction(QStringLiteral("stitch3Quarter"), action);
1271     actionGroup->addAction(action);
1272 
1273     action = new QAction(this);
1274     action->setText(i18n("Full Stitch"));
1275     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-full-stitch")));
1276     action->setCheckable(true);
1277     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::StitchFull); });
1278     actions->addAction(QStringLiteral("stitchFull"), action);
1279     actionGroup->addAction(action);
1280 
1281     action = new QAction(this);
1282     action->setText(i18n("Small Half Stitch"));
1283     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-small-half-stitch")));
1284     action->setCheckable(true);
1285     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::StitchSmallHalf); });
1286     actions->addAction(QStringLiteral("stitchSmallHalf"), action);
1287     actionGroup->addAction(action);
1288 
1289     action = new QAction(this);
1290     action->setText(i18n("Small Full Stitch"));
1291     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-small-full-stitch")));
1292     action->setCheckable(true);
1293     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::StitchSmallFull); });
1294     actions->addAction(QStringLiteral("stitchSmallFull"), action);
1295     actionGroup->addAction(action);
1296 
1297     action = new QAction(this);
1298     action->setText(i18n("French Knot"));
1299     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-frenchknot")));
1300     action->setCheckable(true);
1301     connect(action, &QAction::triggered, m_editor, [=]() { m_editor->selectStitch(Editor::StitchFrenchKnot); });
1302     actions->addAction(QStringLiteral("stitchFrenchKnot"), action);
1303     actionGroup->addAction(action);
1304 
1305 
1306     // Tools Menu
1307     actionGroup = new QActionGroup(this);
1308     actionGroup->setExclusive(true);
1309 
1310     action = new QAction(this);
1311     action->setText(i18n("Paint"));
1312     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-brush")));
1313     action->setCheckable(true);
1314     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolPaint);});
1315     actions->addAction(QStringLiteral("toolPaint"), action);
1316     actionGroup->addAction(action);
1317 
1318     action = new QAction(this);
1319     action->setText(i18n("Draw"));
1320     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-freehand")));
1321     action->setCheckable(true);
1322     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolDraw);});
1323     actions->addAction(QStringLiteral("toolDraw"), action);
1324     actionGroup->addAction(action);
1325 
1326     action = new QAction(this);
1327     action->setText(i18n("Erase"));
1328     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-eraser")));
1329     action->setCheckable(true);
1330     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolErase);});
1331     actions->addAction(QStringLiteral("toolErase"), action);
1332     actionGroup->addAction(action);
1333 
1334     action = new QAction(this);
1335     action->setText(i18n("Draw Rectangle"));
1336     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-rectangle")));
1337     action->setCheckable(true);
1338     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolRectangle);});
1339     actions->addAction(QStringLiteral("toolRectangle"), action);
1340     actionGroup->addAction(action);
1341 
1342     action = new QAction(this);
1343     action->setText(i18n("Fill Rectangle"));
1344     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-draw-rectangle-filled")));
1345     action->setCheckable(true);
1346     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolFillRectangle);});
1347     actions->addAction(QStringLiteral("toolFillRectangle"), action);
1348     actionGroup->addAction(action);
1349 
1350     action = new QAction(this);
1351     action->setText(i18n("Draw Ellipse"));
1352     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-ellipse")));
1353     action->setCheckable(true);
1354     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolEllipse);});
1355     actions->addAction(QStringLiteral("toolEllipse"), action);
1356     actionGroup->addAction(action);
1357 
1358     action = new QAction(this);
1359     action->setText(i18n("Fill Ellipse"));
1360     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-draw-ellipse-filled")));
1361     action->setCheckable(true);
1362     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolFillEllipse);});
1363     actions->addAction(QStringLiteral("toolFillEllipse"), action);
1364     actionGroup->addAction(action);
1365 
1366     action = new QAction(this);
1367     action->setText(i18n("Fill Polygon"));
1368     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-polyline")));
1369     action->setCheckable(true);
1370     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolFillPolygon);});
1371     actions->addAction(QStringLiteral("toolFillPolygon"), action);
1372     actionGroup->addAction(action);
1373 
1374     action = new QAction(this);
1375     action->setText(i18n("Text"));
1376     action->setIcon(QIcon::fromTheme(QStringLiteral("draw-text")));
1377     action->setCheckable(true);
1378     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolText);});
1379     actions->addAction(QStringLiteral("toolText"), action);
1380     actionGroup->addAction(action);
1381 
1382     action = new QAction(this);
1383     action->setText(i18n("Alphabet"));
1384     action->setIcon(QIcon::fromTheme(QStringLiteral("text-field")));
1385     action->setCheckable(true);
1386     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolAlphabet);});
1387     actions->addAction(QStringLiteral("toolAlphabet"), action);
1388     actionGroup->addAction(action);
1389 
1390     action = new QAction(this);
1391     action->setText(i18nc("Select an area of the pattern", "Select"));
1392     action->setIcon(QIcon::fromTheme(QStringLiteral("select-rectangular")));
1393     action->setCheckable(true);
1394     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolSelect);});
1395     actions->addAction(QStringLiteral("toolSelectRectangle"), action);
1396     actionGroup->addAction(action);
1397 
1398     action = new QAction(this);
1399     action->setText(i18n("Backstitch"));
1400     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-backstitch")));
1401     action->setCheckable(true);
1402     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolBackstitch);});
1403     actions->addAction(QStringLiteral("toolBackstitch"), action);
1404     actionGroup->addAction(action);
1405 
1406     action = new QAction(this);
1407     action->setText(i18n("Color Picker"));
1408     action->setIcon(QIcon::fromTheme(QStringLiteral("color-picker")));
1409     action->setCheckable(true);
1410     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->selectTool(Editor::ToolColorPicker);});
1411     actions->addAction(QStringLiteral("toolColorPicker"), action);
1412     actionGroup->addAction(action);
1413 
1414 
1415     // Palette Menu
1416     action = new QAction(this);
1417     action->setText(i18n("Palette Manager..."));
1418     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-color-add")));
1419     connect(action, &QAction::triggered, this, &MainWindow::paletteManager);
1420     actions->addAction(QStringLiteral("paletteManager"), action);
1421 
1422     action = new QAction(this);
1423     action->setText(i18n("Show Symbols"));
1424     action->setCheckable(true);
1425     connect(action, &QAction::toggled, this, &MainWindow::paletteShowSymbols);
1426     actions->addAction(QStringLiteral("paletteShowSymbols"), action);
1427 
1428     action = new QAction(this);
1429     action->setText(i18n("Clear Unused"));
1430     connect(action, &QAction::triggered, this, &MainWindow::paletteClearUnused);
1431     actions->addAction(QStringLiteral("paletteClearUnused"), action);
1432 
1433     action = new QAction(this);
1434     action->setText(i18n("Calibrate Scheme..."));
1435     connect(action, &QAction::triggered, this, &MainWindow::paletteCalibrateScheme);
1436     actions->addAction(QStringLiteral("paletteCalibrateScheme"), action);
1437 
1438     action = new QAction(this);
1439     action->setText(i18n("Swap Colors"));
1440     connect(action, &QAction::triggered, m_palette, static_cast<void (Palette::*)()>(&Palette::swapColors));
1441     actions->addAction(QStringLiteral("paletteSwapColors"), action);
1442 
1443     action = new QAction(this);
1444     action->setText(i18n("Replace Colors"));
1445     connect(action, &QAction::triggered, m_palette, static_cast<void (Palette::*)()>(&Palette::replaceColor));
1446     actions->addAction(QStringLiteral("paletteReplaceColor"), action);
1447 
1448 
1449     // Pattern Menu
1450     action = new QAction(this);
1451     action->setText(i18n("Extend Pattern..."));
1452     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-extend-pattern")));
1453     connect(action, &QAction::triggered, this, &MainWindow::patternExtend);
1454     actions->addAction(QStringLiteral("patternExtend"), action);
1455 
1456     action = new QAction(this);
1457     action->setText(i18n("Center Pattern"));
1458     action->setIcon(QIcon::fromTheme(QStringLiteral("kxstitch-center-pattern")));
1459     connect(action, &QAction::triggered, this, &MainWindow::patternCentre);
1460     actions->addAction(QStringLiteral("patternCentre"), action);
1461 
1462     action = new QAction(this);
1463     action->setText(i18n("Crop Canvas to Pattern"));
1464     connect(action, &QAction::triggered, this, &MainWindow::patternCrop);
1465     actions->addAction(QStringLiteral("patternCrop"), action);
1466 
1467     action = new QAction(this);
1468     action->setText(i18n("Crop Canvas to Selection"));
1469     action->setIcon(QIcon::fromTheme(QStringLiteral("transform-crop")));
1470     connect(action, &QAction::triggered, this, &MainWindow::patternCropToSelection);
1471     action->setEnabled(false);
1472     actions->addAction(QStringLiteral("patternCropToSelection"), action);
1473 
1474     action = new QAction(this);
1475     action->setText(i18n("Insert Rows"));
1476     connect(action, &QAction::triggered, this, &MainWindow::insertRows);
1477     action->setEnabled(false);
1478     actions->addAction(QStringLiteral("insertRows"), action);
1479 
1480     action = new QAction(this);
1481     action->setText(i18n("Insert Columns"));
1482     connect(action, &QAction::triggered, this, &MainWindow::insertColumns);
1483     action->setEnabled(false);
1484     actions->addAction(QStringLiteral("insertColumns"), action);
1485 
1486 
1487     // Library Menu
1488     action = new QAction(this);
1489     action->setText(i18n("Library Manager..."));
1490     connect(action, &QAction::triggered, m_editor, &Editor::libraryManager);
1491     actions->addAction(QStringLiteral("libraryManager"), action);
1492 
1493     // Settings Menu
1494     KStandardAction::preferences(this, &MainWindow::preferences, actions);
1495     // formatScalesAs
1496     actionGroup = new QActionGroup(this);
1497     actionGroup->setExclusive(true);
1498 
1499     action = new QAction(this);
1500     action->setText(i18n("Stitches"));
1501     action->setCheckable(true);
1502     connect(action, &QAction::triggered, m_editor, &Editor::formatScalesAsStitches);
1503     actions->addAction(QStringLiteral("formatScalesAsStitches"), action);
1504     actionGroup->addAction(action);
1505 
1506     action = new QAction(this);
1507     action->setText(i18n("Centimeters"));
1508     action->setCheckable(true);
1509     connect(action, &QAction::triggered, m_editor, &Editor::formatScalesAsCentimeters);
1510     actions->addAction(QStringLiteral("formatScalesAsCentimeters"), action);
1511     actionGroup->addAction(action);
1512 
1513     action = new QAction(this);
1514     action->setText(i18n("Inches"));
1515     action->setCheckable(true);
1516     connect(action, &QAction::triggered, m_editor, &Editor::formatScalesAsInches);
1517     actions->addAction(QStringLiteral("formatScalesAsInches"), action);
1518     actionGroup->addAction(action);
1519 
1520     // ShowStitchesAs
1521     actionGroup = new QActionGroup(this);
1522     actionGroup->setExclusive(true);
1523 
1524     action = new QAction(this);
1525     action->setText(i18n("Regular Stitches"));
1526     action->setCheckable(true);
1527     action->setChecked(true);
1528     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderStitchesAs(Configuration::EnumRenderer_RenderStitchesAs::Stitches);});
1529     actions->addAction(QStringLiteral("renderStitchesAsRegularStitches"), action);
1530     actionGroup->addAction(action);
1531 
1532     action = new QAction(this);
1533     action->setText(i18n("Black & White Symbols"));
1534     action->setCheckable(true);
1535     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderStitchesAs(Configuration::EnumRenderer_RenderStitchesAs::BlackWhiteSymbols);});
1536     actions->addAction(QStringLiteral("renderStitchesAsBlackWhiteSymbols"), action);
1537     actionGroup->addAction(action);
1538 
1539     action = new QAction(this);
1540     action->setText(i18n("Color Symbols"));
1541     action->setCheckable(true);
1542     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderStitchesAs(Configuration::EnumRenderer_RenderStitchesAs::ColorSymbols);});
1543     actions->addAction(QStringLiteral("renderStitchesAsColorSymbols"), action);
1544     actionGroup->addAction(action);
1545 
1546     action = new QAction(this);
1547     action->setText(i18n("Color Blocks"));
1548     action->setCheckable(true);
1549     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderStitchesAs(Configuration::EnumRenderer_RenderStitchesAs::ColorBlocks);});
1550     actions->addAction(QStringLiteral("renderStitchesAsColorBlocks"), action);
1551     actionGroup->addAction(action);
1552 
1553     action = new QAction(this);
1554     action->setText(i18n("Color Blocks & Symbols"));
1555     action->setCheckable(true);
1556     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderStitchesAs(Configuration::EnumRenderer_RenderStitchesAs::ColorBlocksSymbols);});
1557     actions->addAction(QStringLiteral("renderStitchesAsColorBlocksSymbols"), action);
1558     actionGroup->addAction(action);
1559 
1560     // ShowBackstitchesAs
1561     actionGroup = new QActionGroup(this);
1562     actionGroup->setExclusive(true);
1563 
1564     action = new QAction(this);
1565     action->setText(i18n("Color Lines"));
1566     action->setCheckable(true);
1567     action->setChecked(true);
1568     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderBackstitchesAs(Configuration::EnumRenderer_RenderBackstitchesAs::ColorLines);});
1569     actions->addAction(QStringLiteral("renderBackstitchesAsColorLines"), action);
1570     actionGroup->addAction(action);
1571 
1572     action = new QAction(this);
1573     action->setText(i18n("Black & White Symbols"));
1574     action->setCheckable(true);
1575     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderBackstitchesAs(Configuration::EnumRenderer_RenderBackstitchesAs::BlackWhiteSymbols);});
1576     actions->addAction(QStringLiteral("renderBackstitchesAsBlackWhiteSymbols"), action);
1577     actionGroup->addAction(action);
1578 
1579     // ShowKnotsAs
1580     actionGroup = new QActionGroup(this);
1581     actionGroup->setExclusive(true);
1582 
1583     action = new QAction(this);
1584     action->setText(i18n("Color Blocks"));
1585     action->setCheckable(true);
1586     action->setChecked(true);
1587     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderKnotsAs(Configuration::EnumRenderer_RenderKnotsAs::ColorBlocks);});
1588     actions->addAction(QStringLiteral("renderKnotsAsColorBlocks"), action);
1589     actionGroup->addAction(action);
1590 
1591     action = new QAction(this);
1592     action->setText(i18n("Color Blocks & Symbols"));
1593     action->setCheckable(true);
1594     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderKnotsAs(Configuration::EnumRenderer_RenderKnotsAs::ColorBlocksSymbols);});
1595     actions->addAction(QStringLiteral("renderKnotsAsColorBlocksSymbols"), action);
1596     actionGroup->addAction(action);
1597 
1598     action = new QAction(this);
1599     action->setText(i18n("Color Symbols"));
1600     action->setCheckable(true);
1601     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderKnotsAs(Configuration::EnumRenderer_RenderKnotsAs::ColorSymbols);});
1602     actions->addAction(QStringLiteral("renderKnotsAsColorSymbols"), action);
1603     actionGroup->addAction(action);
1604 
1605     action = new QAction(this);
1606     action->setText(i18n("Black & White Symbols"));
1607     action->setCheckable(true);
1608     connect(action, &QAction::triggered, m_editor, [=]() {m_editor->renderKnotsAs(Configuration::EnumRenderer_RenderKnotsAs::BlackWhiteSymbols);});
1609     actions->addAction(QStringLiteral("renderKnotsAsBlackWhiteSymbols"), action);
1610     actionGroup->addAction(action);
1611 
1612 
1613     action = new QAction(this);
1614     action->setText(i18n("Color Highlight"));
1615     action->setCheckable(true);
1616     connect(action, &QAction::toggled, m_editor, &Editor::colorHighlight);
1617     actions->addAction(QStringLiteral("colorHighlight"), action);
1618 
1619     action = new QAction(this);
1620     action->setText(i18n("Show Stitches"));
1621     action->setCheckable(true);
1622     connect(action, &QAction::toggled, m_editor, static_cast<void (Editor::*)(bool)>(&Editor::renderStitches));
1623     actions->addAction(QStringLiteral("renderStitches"), action);
1624 
1625     action = new QAction(this);
1626     action->setText(i18n("Show Backstitches"));
1627     action->setCheckable(true);
1628     connect(action, &QAction::toggled, m_editor, static_cast<void (Editor::*)(bool)>(&Editor::renderBackstitches));
1629     actions->addAction(QStringLiteral("renderBackstitches"), action);
1630 
1631     action = new QAction(this);
1632     action->setText(i18n("Show French Knots"));
1633     action->setCheckable(true);
1634     connect(action, &QAction::toggled, m_editor, static_cast<void (Editor::*)(bool)>(&Editor::renderFrenchKnots));
1635     actions->addAction(QStringLiteral("renderFrenchKnots"), action);
1636 
1637     action = new QAction(this);
1638     action->setText(i18n("Show Grid"));
1639     action->setCheckable(true);
1640     connect(action, &QAction::toggled, m_editor, static_cast<void (Editor::*)(bool)>(&Editor::renderGrid));
1641     actions->addAction(QStringLiteral("renderGrid"), action);
1642 
1643     action = new QAction(this);
1644     action->setText(i18n("Show Background Images"));
1645     action->setCheckable(true);
1646     connect(action, &QAction::toggled, m_editor, static_cast<void (Editor::*)(bool)>(&Editor::renderBackgroundImages));
1647     actions->addAction(QStringLiteral("renderBackgroundImages"), action);
1648 
1649     m_horizontalScale->addAction(actions->action(QStringLiteral("formatScalesAsStitches")));
1650     m_horizontalScale->addAction(actions->action(QStringLiteral("formatScalesAsCentimeters")));
1651     m_horizontalScale->addAction(actions->action(QStringLiteral("formatScalesAsInches")));
1652 
1653     m_verticalScale->addAction(actions->action(QStringLiteral("formatScalesAsStitches")));
1654     m_verticalScale->addAction(actions->action(QStringLiteral("formatScalesAsCentimeters")));
1655     m_verticalScale->addAction(actions->action(QStringLiteral("formatScalesAsInches")));
1656 
1657     setupGUI(KXmlGuiWindow::Default, QStringLiteral("kxstitchui.rc"));
1658 }
1659 
1660 
updateBackgroundImageActionLists()1661 void MainWindow::updateBackgroundImageActionLists()
1662 {
1663     auto backgroundImages = m_document->backgroundImages().backgroundImages();
1664 
1665     unplugActionList(QStringLiteral("removeBackgroundImageActions"));
1666     unplugActionList(QStringLiteral("fitBackgroundImageActions"));
1667     unplugActionList(QStringLiteral("showBackgroundImageActions"));
1668 
1669     QList<QAction *> removeBackgroundImageActions;
1670     QList<QAction *> fitBackgroundImageActions;
1671     QList<QAction *> showBackgroundImageActions;
1672 
1673     while (backgroundImages.hasNext()) {
1674         QSharedPointer<BackgroundImage> backgroundImage = backgroundImages.next();
1675 
1676         QAction *action = new QAction(backgroundImage->url().fileName(), this);
1677         action->setData(QVariant::fromValue(backgroundImage));
1678         action->setIcon(backgroundImage->icon());
1679         connect(action, &QAction::triggered, this, &MainWindow::fileRemoveBackgroundImage);
1680         removeBackgroundImageActions.append(action);
1681 
1682         action = new QAction(backgroundImage->url().fileName(), this);
1683         action->setData(QVariant::fromValue(backgroundImage));
1684         action->setIcon(backgroundImage->icon());
1685         connect(action, &QAction::triggered, this, &MainWindow::viewFitBackgroundImage);
1686         fitBackgroundImageActions.append(action);
1687 
1688         action = new QAction(backgroundImage->url().fileName(), this);
1689         action->setData(QVariant::fromValue(backgroundImage));
1690         action->setIcon(backgroundImage->icon());
1691         action->setCheckable(true);
1692         action->setChecked(backgroundImage->isVisible());
1693         connect(action, &QAction::triggered, this, &MainWindow::viewShowBackgroundImage);
1694         showBackgroundImageActions.append(action);
1695     }
1696 
1697     plugActionList(QStringLiteral("removeBackgroundImageActions"), removeBackgroundImageActions);
1698     plugActionList(QStringLiteral("fitBackgroundImageActions"), fitBackgroundImageActions);
1699     plugActionList(QStringLiteral("showBackgroundImageActions"), showBackgroundImageActions);
1700 }
1701 
1702 
setupDockWindows()1703 void MainWindow::setupDockWindows()
1704 {
1705     QDockWidget *dock = new QDockWidget(i18n("Preview"), this);
1706     dock->setObjectName(QStringLiteral("PreviewDock#"));
1707     dock->setAllowedAreas(Qt::AllDockWidgetAreas);
1708     QScrollArea *scrollArea = new QScrollArea();
1709     m_preview = new Preview(scrollArea);
1710     scrollArea->setWidget(m_preview);
1711     scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
1712     scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
1713     scrollArea->setMinimumSize(std::min(300, m_preview->width()), std::min(400, m_preview->height()));
1714     dock->setWidget(scrollArea);
1715     addDockWidget(Qt::LeftDockWidgetArea, dock);
1716     actionCollection()->addAction(QStringLiteral("showPreviewDockWidget"), dock->toggleViewAction());
1717 
1718     dock = new QDockWidget(i18n("Palette"), this);
1719     dock->setObjectName(QStringLiteral("PaletteDock#"));
1720     dock->setAllowedAreas(Qt::AllDockWidgetAreas);
1721     m_palette = new Palette(this);
1722     m_palette->setContextMenuPolicy(Qt::CustomContextMenu);
1723     dock->setWidget(m_palette);
1724     addDockWidget(Qt::LeftDockWidgetArea, dock);
1725     actionCollection()->addAction(QStringLiteral("showPaletteDockWidget"), dock->toggleViewAction());
1726 
1727     dock = new QDockWidget(i18n("History"), this);
1728     dock->setObjectName(QStringLiteral("HistoryDock#"));
1729     dock->setAllowedAreas(Qt::AllDockWidgetAreas);
1730     m_history = new QUndoView(this);
1731     dock->setWidget(m_history);
1732     addDockWidget(Qt::LeftDockWidgetArea, dock);
1733     actionCollection()->addAction(QStringLiteral("showHistoryDockWidget"), dock->toggleViewAction());
1734 
1735     dock = new QDockWidget(i18n("Imported Image"), this);
1736     dock->setObjectName(QStringLiteral("ImportedImage#"));
1737     dock->setAllowedAreas(Qt::AllDockWidgetAreas);
1738     m_imageLabel = new ScaledPixmapLabel(this);
1739     m_imageLabel->setScaledContents(false);
1740     dock->setWidget(m_imageLabel);
1741     addDockWidget(Qt::LeftDockWidgetArea, dock);
1742     actionCollection()->addAction(QStringLiteral("showImportedDockWidget"), dock->toggleViewAction());
1743 }
1744