File indexing completed on 2024-04-21 15:23:40

0001 /********************************************************************************
0002  * Copyright (C) 2011-2015 by Stephen Allewell                                  *
0003  * steve.allewell@gmail.com                                                     *
0004  *                                                                              *
0005  * This program is free software; you can redistribute it and/or modify         *
0006  * it under the terms of the GNU General Public License as published by         *
0007  * the Free Software Foundation; either version 2 of the License, or            *
0008  * (at your option) any later version.                                          *
0009  ********************************************************************************/
0010 
0011 
0012 /**
0013  * @file
0014  * Implement the MainWindow class. This supplies the main user interface which comprises of a tabbed widget
0015  * containing a symbol editor and a library of symbols loaded from a symbol file.  A standard menu bar, tool
0016  * bar and status bar are provided to access the various functions and provide any suitable feedback or status
0017  * messages.
0018  */
0019 
0020 
0021 /**
0022  * @page main_window Main Window
0023  * The symbol editor main window comprises a tabbed widget containing the symbol editor and a list of the
0024  * current symbols in the loaded file. A standard menu bar, tool bar and status bar are provided to access
0025  * the various tools and functions and provide any necessary status messages or user feedback.
0026  *
0027  * @image html ui-main-editor.png "The user interface showing the editor tab"
0028  *
0029  * @section file_menu File Menu
0030  *
0031  * @subsection file_new New
0032  * Start a new symbol definition. The editor is cleared ready to define the new symbol. If there is an existing
0033  * symbol being edited that has not been saved, the user is prompted to save it or allow it to be overwritten.
0034  * Alternatively the user can cancel the creation of the new symbol leaving the current one intact.
0035  *
0036  * @subsection file_open Open
0037  * Open an existing symbol library. The current symbol and symbol library are cleared and the user is prompted to
0038  * select a file to be opened. If the current symbol of symbol file have not been saved the user is asked to
0039  * save them or allow them to be overwritten. Alternatively the user can cancel opening a file leaving the current
0040  * ones intact.
0041  *
0042  * @subsection file_open_recent Open Recent
0043  * Previously opened files are added to the recent files menu and can be opened by selecting the file specified.
0044  * The same rules relating to Open and the current symbol and library apply here.
0045  *
0046  * @subsection file_save Save
0047  * Save the current library to a file. If this is a new library the user will be prompted to enter a file name.
0048  *
0049  * @subsection file_save_as Save As
0050  * Save the current library using a different name. The user will be prompted to enter a file name.
0051  *
0052  * @subsection file_save_symbol Save Symbol
0053  * Save the current symbol being edited to the current library. This does not save the symbol library to disk, this
0054  * is done by the Save command.
0055  *
0056  * @subsection file_save_symbol_as_new Save Symbol as New
0057  * Save the symbol as if it was a new one. This would apply if the symbol was one from the library being edited. The
0058  * editor maintains a link between it and the library and would normally update it when saved. This will reset the
0059  * index and add the symbol to the library as a new one. The symbol being edited is now detached from the library
0060  * and subsequent saves will continue to add new symbols to the library. This can be used to create new symbols based
0061  * on an existing library symbol.
0062  *
0063  * @subsection file_import_library Import Library
0064  * Import an existing symbol library and append the symbols in it to the current library.
0065  *
0066  * @subsection file_close Close
0067  * Close the current library. The editor and the library are cleared leaving an empty library ready for new symbols
0068  * to be added. If the current symbol and library need to be saved the user is prompted to do so.
0069  *
0070  * @subsection file_quit Quit
0071  * Quit the application. If the current symbol and library need to be saved the user is prompted to do so.
0072  *
0073  * @section edit_menu Edit Menu
0074  *
0075  * @subsection edit_undo Undo
0076  * Actions that modify the symbol currently being edited or the current symbol library can be undone reverting to
0077  * the previous state. The editor and the library have independent undo stacks and changes to one do not affect the
0078  * other. The undo command affects the currently selected tab. The undo command in the menu shows a description of
0079  * the action that will be undone. For the undo command on the toolbar, the tooltip will show the desciption.
0080  *
0081  * @subsection edit_redo Redo
0082  * Actions that are undone can be redone. As with undo, the editor and the symbol library are independent of each
0083  * other and the redo command affects the currently selected tab. The redo command in the menu and the toolbar tooltip
0084  * shows a description of the action that will be redone.
0085  *
0086  * @subsection file_edit_toolbar File and Edit Toolbar
0087  * The file and edit menu toolbar allow quick access to these common functions.
0088  * @image html ui-main-toolbar.png
0089  * - @ref file_new
0090  * - @ref file_open
0091  * - @ref file_save
0092  * - @ref edit_undo
0093  * - @ref edit_redo
0094  * - @ref file_save_symbol
0095  *
0096  * @section rendering_menu Rendering Menu
0097  * The defined path can be rendered with various settings. Filled or unfilled, for which the fill method can be defined.
0098  * The path end cap can be defined as flat, square and round. The line join type can be defined as bevel, miter and round.
0099  * The line width can be increased or decreased.
0100  *
0101  * For full details of the rendering options, see the @ref path_rendering.
0102  *
0103  * @subsection rendering_toolbar Rendering Toolbar
0104  * The rendering toolbar allows quick access to these common functions.
0105  * @image html ui-rendering-toolbar.png
0106  * - @ref fill_mode
0107  * - @ref fill_rule
0108  * - @ref line_cap
0109  * - @ref line_join
0110  * - @ref line_width
0111  *
0112  * @section tools_menu Tools Menu
0113  * A number of tools are available to aid the design of the symbols. The symbols are composed of a series of sub paths
0114  * and each sub path is composed of a move to the start position (this defaults to 0,0 for new symbols) followed by
0115  * lines and curves. The curves are cubic splines having a start, an end and two control points defining the curve.
0116  * There are convenience tools to create rectangles and ellipses, but these will be broken down into lines and curves.
0117  *
0118  * All points created for the elements are moveable by dragging them to their new position. All points can be snapped
0119  * to the grid intersections if the snap option is turned on, otherwise they can be positioned anywhere.
0120  *
0121  * The symbol can be rotated clockwise and counter clockwise and also flipped vertically and horizontally. This allows
0122  * multiple symbols to be easily created based on the same design. Remember to use the save symbol as new option for this.
0123  *
0124  * For full details of the tools see the @ref editor_tools.
0125  *
0126  * @subsection tools_toolbar Tools Toolbar
0127  * The tools toolbar allows quick access to these common functions.
0128  * @image html ui-tools-toolbar.png
0129  * - @ref move_to
0130  * - @ref line_to
0131  * - @ref cubic_to
0132  * - @ref rectangle
0133  * - @ref ellipse
0134  * - @ref character
0135  * - @ref rotate_left
0136  * - @ref rotate_right
0137  * - @ref flip_horizontal
0138  * - @ref flip_vertical
0139  * - @ref scale_preferred
0140  * - @ref snap_grid
0141  * - @ref guide_lines
0142  */
0143 
0144 
0145 #include "MainWindow.h"
0146 
0147 #include <QAction>
0148 #include <QFileDialog>
0149 #include <QIcon>
0150 #include <QVBoxLayout>
0151 #include <QListWidgetItem>
0152 #include <QMenu>
0153 #include <QStatusBar>
0154 #include <QTabWidget>
0155 #include <QTemporaryFile>
0156 
0157 #include <KActionCollection>
0158 #include <KConfigDialog>
0159 #include <KConfigGroup>
0160 #include <KIO/FileCopyJob>
0161 #include <KIO/StatJob>
0162 #include <KLocalizedString>
0163 #include <KMessageBox>
0164 #include <KRecentFilesAction>
0165 
0166 #include "ConfigurationDialogs.h"
0167 #include "Editor.h"
0168 #include "Exceptions.h"
0169 #include "SymbolListWidget.h"
0170 #include "SymbolLibrary.h"
0171 
0172 #include "ui_EditorConfigPage.h"
0173 
0174 #include "SymbolEditor.h"
0175 
0176 
0177 /**
0178  * Construct the MainWindow.
0179  * Create an instance of a symbol file.
0180  * Create the tab widget, editor, list widget and the symbol file. The tab widget is then set as
0181  * the central widget and will contain the editor and list widgets. The Editor is added to a
0182  * layout to allow it to be centralized in the main window.
0183  * Set up the actions, add the two undo stacks to the undo group and connect any signal slots required.
0184  * Set up the GUI from the applications rc file.
0185  * The editor page is selected in the tab widget which should also initialise the undo redo buttons.
0186  * The moveTo tool action is triggered to enable the moveTo tool as the initial one.
0187  * Other actions are initialised from the current Editor symbol.
0188  */
0189 MainWindow::MainWindow()
0190     :   m_tabWidget(new QTabWidget(this)),
0191         m_editor(new Editor),
0192         m_listWidget(new SymbolListWidget(m_tabWidget)),
0193         m_symbolLibrary(new SymbolLibrary(m_listWidget)),
0194         m_item(0),
0195         m_menu(0)
0196 {
0197     m_listWidget->loadFromLibrary(m_symbolLibrary);
0198     m_url = QUrl(i18n("Untitled"));
0199 
0200     setObjectName("MainWindow#");
0201 
0202     KActionCollection *actions = actionCollection();
0203 
0204     m_listWidget->setIconSize(48);
0205     m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
0206 
0207     m_tabWidget->addTab(m_editor, i18nc("The editor tab title", "Editor"));
0208     m_tabWidget->addTab(m_listWidget, i18nc("The library tab title", "Library"));
0209 
0210     setCentralWidget(m_tabWidget);
0211 
0212     setupActions();
0213 
0214     m_undoGroup.addStack(m_editor->undoStack());
0215     m_undoGroup.addStack(m_symbolLibrary->undoStack());
0216     connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentChanged(int)));
0217     connect(m_editor, SIGNAL(message(QString)), statusBar(), SLOT(showMessage(QString)));
0218     connect(m_editor, SIGNAL(minLineWidth(bool)), actions->action("decreaseLineWidth"), SLOT(setDisabled(bool)));
0219     connect(m_editor, SIGNAL(maxLineWidth(bool)), actions->action("increaseLineWidth"), SLOT(setDisabled(bool)));
0220     connect(&m_undoGroup, SIGNAL(canUndoChanged(bool)), actions->action("edit_undo"), SLOT(setEnabled(bool)));
0221     connect(&m_undoGroup, SIGNAL(canRedoChanged(bool)), actions->action("edit_redo"), SLOT(setEnabled(bool)));
0222     connect(&m_undoGroup, SIGNAL(undoTextChanged(QString)), this, SLOT(undoTextChanged(QString)));
0223     connect(&m_undoGroup, SIGNAL(redoTextChanged(QString)), this, SLOT(redoTextChanged(QString)));
0224     connect(&m_undoGroup, SIGNAL(cleanChanged(bool)), this, SLOT(cleanChanged(bool)));
0225     connect(m_editor->undoStack(), SIGNAL(cleanChanged(bool)), actions->action("saveSymbol"), SLOT(setDisabled(bool)));
0226     connect(m_editor->undoStack(), SIGNAL(cleanChanged(bool)), actions->action("saveSymbolAsNew"), SLOT(setDisabled(bool)));
0227     connect(m_symbolLibrary->undoStack(), SIGNAL(cleanChanged(bool)), actions->action("file_save"), SLOT(setDisabled(bool)));
0228     connect(m_listWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(itemSelected(QListWidgetItem*)));
0229     connect(m_listWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(listWidgetContextMenuRequested(QPoint)));
0230 
0231     setupGUI(KXmlGuiWindow::Default, "SymbolEditorui.rc");
0232 
0233     actions->action("moveTo")->trigger();                   // select draw tool
0234     actions->action("enableSnap")->setChecked(true);        // enable snap
0235     actions->action("enableGuides")->setChecked(true);      // enable creation of guides
0236     actions->action("file_save")->setEnabled(false);        // nothing to save yet
0237     actions->action("saveSymbol")->setEnabled(false);       // nothing to save yet
0238     actions->action("saveSymbolAsNew")->setEnabled(false);  // nothing to save yet
0239     setActionsFromSymbol(m_editor->symbol().second);        // set the actions that depend on the current empty symbol, i.e. the defaults
0240 
0241     currentChanged(m_tabWidget->currentIndex());              // this should be the editor
0242 }
0243 
0244 
0245 /**
0246  * Descructor for the MainWindow
0247  * Delete the SymbolLibrary object. The other widgets that are created in the constructor are children of the
0248  * MainWindow and will be destroyed when this is.
0249  */
0250 MainWindow::~MainWindow()
0251 {
0252     delete m_symbolLibrary;
0253 }
0254 
0255 
0256 /**
0257  * Test if it is ok to close this window.
0258  * Check if the current symbol being edited has been changed and if the library has been changed.
0259  *
0260  * @return true if it is ok to close, false otherwise
0261  */
0262 bool MainWindow::queryClose()
0263 {
0264     return (editorClean() && libraryClean());
0265 }
0266 
0267 
0268 /**
0269  * Check if it ok to close the currently edited symbol.
0270  *
0271  * @return true if is ok to close the symbol, false otherwise
0272  */
0273 bool MainWindow::editorClean()
0274 {
0275     bool clean = m_editor->undoStack()->isClean();
0276 
0277     if (!clean) {
0278         int messageBoxResult = KMessageBox::warningYesNoCancel(this, i18n("Save changes to the symbol?\nSelecting No discards changes."));
0279 
0280         switch (messageBoxResult) {
0281         case KMessageBox::Yes:
0282             saveSymbol();
0283             save();
0284             clean = true;
0285             break;
0286 
0287         case KMessageBox::No:
0288             clean = true;
0289             break;
0290 
0291         case KMessageBox::Cancel:
0292             clean = false;
0293             break;
0294         }
0295     }
0296 
0297     return clean;
0298 }
0299 
0300 
0301 /**
0302  * Check if it is ok to close the library.
0303  *
0304  * @return true if it is ok to close the library, false otherwise
0305  */
0306 bool MainWindow::libraryClean()
0307 {
0308     bool clean = m_symbolLibrary->undoStack()->isClean();
0309 
0310     if (!clean) {
0311         int messageBoxResult = KMessageBox::warningYesNoCancel(this, i18n("Save changes to the library?\nSelecting No discards changes."));
0312 
0313         switch (messageBoxResult) {
0314         case KMessageBox::Yes:
0315             save();
0316             clean = true;
0317             break;
0318 
0319         case KMessageBox::No:
0320             clean = true;
0321             break;
0322 
0323         case KMessageBox::Cancel:
0324             clean = false;
0325             break;
0326         }
0327     }
0328 
0329     return clean;
0330 }
0331 
0332 
0333 /**
0334  * Test if it is ok to close the application.
0335  *
0336  * @return return true since queryClose has already been called
0337  */
0338 bool MainWindow::queryExit()
0339 {
0340     return true;
0341 }
0342 
0343 
0344 /**
0345  * Open a file.
0346  * Use the QFileDialog::getOpenFileUrl to get a QUrl to open which is then passed to filOpen(const QUrl &).
0347  */
0348 void MainWindow::fileOpen()
0349 {
0350     QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open file"), QUrl::fromLocalFile(QDir::homePath()), i18n("Cross Stitch Symbols (*.sym)"));
0351 
0352     if (!url.isEmpty()) {
0353         fileOpen(url);
0354     }
0355 }
0356 
0357 
0358 /**
0359  * If a valid url is supplied, try and download the file (in case it comes from a remote source) and
0360  * then try and open it read only. Once opened create a QDataStream and try and read the contents.
0361  * This is protected in a try-catch block to intercept any exceptions that may be thrown by the loading
0362  * routines. If there were any errors, the symbol library will be cleared and a suitable error message
0363  * will be displayed.
0364  * The url of the file is set in the symbol file object only if there were no errors. This will avoid
0365  * writing to a corrupt file or to a file that isn't a symbol file. The url is added to the recent file
0366  * list.
0367  */
0368 void MainWindow::fileOpen(const QUrl &url)
0369 {
0370     if (!editorClean() || !libraryClean()) {
0371         return;
0372     }
0373 
0374     m_symbolLibrary->clear();
0375     m_editor->clear();
0376 
0377     if (url.isValid()) {
0378         QTemporaryFile tmpFile;
0379 
0380         if (tmpFile.open()) {
0381             KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName()), -1, KIO::Overwrite);
0382 
0383             if (job->exec()) {
0384                 QDataStream stream(&tmpFile);
0385 
0386                 try {
0387                     stream >> *m_symbolLibrary;
0388                     m_url = url;
0389                     KRecentFilesAction *action = static_cast<KRecentFilesAction *>(actionCollection()->action("file_open_recent"));
0390                     action->addUrl(url);
0391                     action->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "RecentFiles"));
0392                     m_tabWidget->setCurrentIndex(1);
0393                 } catch (const InvalidFile &e) {
0394                     KMessageBox::sorry(0, i18n("This doesn't appear to be a valid symbol file"));
0395                 } catch (const InvalidFileVersion &e) {
0396                     KMessageBox::sorry(0, i18n("Version %1 of the library file is not supported in this version of SymbolEditor", e.version));
0397                 } catch (const InvalidSymbolVersion &e) {
0398                     KMessageBox::sorry(0, i18n("Version %1 of the symbol is not supported in this version of SymbolEditor", e.version));
0399                 } catch (const FailedReadLibrary &e) {
0400                     KMessageBox::sorry(0, i18n("Failed to read the library\n%1", e.statusMessage()));
0401                     m_symbolLibrary->clear();
0402                 }
0403             } else {
0404                 KMessageBox::sorry(0, job->errorString());
0405             }
0406         } else {
0407             KMessageBox::sorry(0, tmpFile.errorString());
0408         }
0409     } else {
0410         KMessageBox::sorry(0, i18n("The url %1 is invalid", url.fileName()));
0411     }
0412 }
0413 
0414 
0415 /**
0416  * Save the library using its url, if this is Untitled than call saveAs to get a valid url.
0417  * Open the file and write to the stream. This is protected in a try-catch block to intercept
0418  * any exceptions thrown by the writing routines. If there were any exceptions thrown or the
0419  * file could not be opened a suitable error message is shown.
0420  */
0421 void MainWindow::save()
0422 {
0423     if (m_url == QUrl(i18n("Untitled"))) {
0424         saveAs();
0425     } else {
0426         QFile file(m_url.path());
0427 
0428         if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0429             QDataStream stream(&file);
0430             stream.setVersion(QDataStream::Qt_4_0);
0431 
0432             try {
0433                 stream << *m_symbolLibrary;
0434             } catch (const FailedWriteLibrary &e) {
0435                 KMessageBox::sorry(0, i18n("Failed to write the library\n%1", e.statusMessage()));
0436             }
0437 
0438             file.close();
0439             m_symbolLibrary->undoStack()->setClean();
0440         } else {
0441             KMessageBox::sorry(0, i18n("Failed to open the file %1\n%2", m_url.fileName(), file.errorString()));
0442         }
0443     }
0444 }
0445 
0446 
0447 /**
0448  * Save the library using a different url.
0449  * This is also called from save when the assigned url is Untitled.
0450  * The new url is added to the recent files list.
0451  */
0452 void MainWindow::saveAs()
0453 {
0454     QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::homePath()), i18n("Cross Stitch Symbols (*.sym)"));
0455 
0456     if (url.isValid()) {
0457         KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0);
0458 
0459         if (statJob->exec()) {
0460             if (KMessageBox::warningYesNo(this, i18n("This file already exists\nDo you want to overwrite it?")) == KMessageBox::No) {
0461                 return;
0462             }
0463         }
0464 
0465         m_url = url;
0466         save();
0467         KRecentFilesAction *action = static_cast<KRecentFilesAction *>(actionCollection()->action("file_open_recent"));
0468         action->addUrl(url);
0469         action->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "RecentFiles"));
0470     }
0471 }
0472 
0473 
0474 /**
0475  * Initialise a new symbol.
0476  * Check if the current symbol has been saved or can be overwritten and then call clear
0477  * on the editor which initializes the editor with an empty symbol.
0478  * The actions are reset to those relevant to the new empty symbol.
0479  * The moveTo action is triggered as this is the most likely to be used next.
0480  */
0481 void MainWindow::newSymbol()
0482 {
0483     if (editorClean()) {
0484         m_editor->clear();
0485         setActionsFromSymbol(m_editor->symbol().second);
0486         actionCollection()->action("moveTo")->trigger();   // Select move tool
0487     }
0488 }
0489 
0490 
0491 /**
0492  * Save the current symbol.
0493  * Store the symbol currently in the editor into the symbol library object. If it is a
0494  * new symbol the index will be 0, a new index will then be created. Otherwise the index
0495  * will be the one from the library and storing it will overwrite the existing symbol.
0496  */
0497 void MainWindow::saveSymbol()
0498 {
0499     QPair<qint16, Symbol> pair = m_editor->symbol();
0500     m_symbolLibrary->undoStack()->push(new UpdateSymbolCommand(m_symbolLibrary, pair.first, pair.second));
0501     m_editor->undoStack()->setClean();
0502 }
0503 
0504 
0505 /**
0506  * Save the current symbol using a new index.
0507  * Store the symbol currently in the editor into the symbol library object.  Ignore the
0508  * editors index, resetting it to 0 and a new index will then be created.
0509  * Reassign this symbol back to the editor with a 0 index effectively creating a new symbol in
0510  * the editor.
0511  */
0512 void MainWindow::saveSymbolAsNew()
0513 {
0514     QPair<qint16, Symbol> pair = m_editor->symbol();
0515     pair.first = 0;
0516     m_symbolLibrary->undoStack()->push(new UpdateSymbolCommand(m_symbolLibrary, pair.first, pair.second));
0517     m_editor->setSymbol(pair);
0518 }
0519 
0520 
0521 /**
0522  * Import a library of symbols into the current library.
0523  * Get a url for the library file and try and download it in case it comes from a remote source.
0524  * Open the file and read the contents into a new SymbolLibrary object. The read is protected by
0525  * a try-catch block to intercept any exceptions thrown by the read routines. If there were no
0526  * errors an ImportLibraryCommand is created and pushed onto the symbol library undo stack.
0527  * This will copy all the symbols from the imported library into the current library.
0528  * Any errors will display a suitable error message.
0529  */
0530 void MainWindow::importLibrary()
0531 {
0532     QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Import library"), QUrl::fromLocalFile(QDir::homePath()), i18n("Cross Stitch Symbols (*.sym)"));
0533 
0534     if (url.isEmpty()) {
0535         return;
0536     }
0537 
0538     if (url.isValid()) {
0539         QTemporaryFile tmpFile;
0540 
0541         if (tmpFile.open()) {
0542             KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName()), -1, KIO::Overwrite);
0543 
0544             if (job->exec()) {
0545                 SymbolLibrary *lib = new SymbolLibrary;
0546                 QDataStream stream(&tmpFile);
0547 
0548                 try {
0549                     stream >> *lib;
0550                     m_symbolLibrary->undoStack()->push(new ImportLibraryCommand(m_symbolLibrary, lib));
0551                 } catch (const InvalidFile &e) {
0552                     KMessageBox::sorry(0, i18n("This doesn't appear to be a valid symbol file"));
0553                 } catch (const InvalidFileVersion &e) {
0554                     KMessageBox::sorry(0, i18n("Version %1 of the library file is not supported in this version of SymbolEditor", e.version));
0555                 }
0556             } else {
0557                 KMessageBox::sorry(0, job->errorString());
0558             }
0559         } else {
0560             KMessageBox::sorry(0, tmpFile.errorString());
0561         }
0562     } else {
0563         KMessageBox::sorry(0, i18n("The url %1 is invalid", url.fileName()));
0564     }
0565 }
0566 
0567 
0568 /**
0569  * Close the current library.
0570  * Check if the current symbol and the symbol library need to be saved and then clear
0571  * the library and the editor.
0572  */
0573 void MainWindow::close()
0574 {
0575     if (editorClean() && libraryClean()) {
0576         m_editor->clear();
0577         m_symbolLibrary->clear();
0578         m_url = QUrl(i18n("Untitled"));
0579     }
0580 }
0581 
0582 
0583 /**
0584  * Quit the application.
0585  * Closes this MainWindow.
0586  */
0587 void MainWindow::quit()
0588 {
0589     KXmlGuiWindow::close();
0590 }
0591 
0592 
0593 /**
0594  * Undo the last operation.
0595  * Several undo stacks will be available. One for the library object and one for the editor.
0596  * Changing the tab will update the actions depending on the contents of the relevant undo stack.
0597  */
0598 void MainWindow::undo()
0599 {
0600     m_undoGroup.undo();
0601 }
0602 
0603 
0604 /**
0605  * Redo the last operation undone.
0606  * Several undo stacks will be available. One for the library object and one for the editor.
0607  * Changing the tab will update the actions depending on the contents of the relevant undo stack.
0608  */
0609 void MainWindow::redo()
0610 {
0611     m_undoGroup.redo();
0612 }
0613 
0614 
0615 /**
0616  * Update the undo action text to reflect the last operation available to undo.
0617  * Several undo stacks will be available. One for the library object and one for the editor.
0618  * Changing the tab will update the actions depending on the contents of the relevant undo stack.
0619  *
0620  * @param text the text string to describe the operation
0621  */
0622 void MainWindow::undoTextChanged(const QString &text)
0623 {
0624     actionCollection()->action("edit_undo")->setText(QString(i18n("Undo %1", text)));
0625 }
0626 
0627 
0628 /**
0629  * Update the redo action text to reflect the last operation available to redo.
0630  * Several undo stacks will be available. One for the library object and one for the editor.
0631  * Changing the tab will update the actions depending on the contents of the relevant undo stack.
0632  *
0633  * @param text the text string to describe the operation
0634  */
0635 void MainWindow::redoTextChanged(const QString &text)
0636 {
0637     actionCollection()->action("edit_redo")->setText(QString(i18n("Redo %1", text)));
0638 }
0639 
0640 
0641 /**
0642  * Update the caption based on the state of the undo stack.
0643  *
0644  * @param clean true if the symbol has not been changed, false otherwise
0645  */
0646 void MainWindow::cleanChanged(bool clean)
0647 {
0648     QString tab;
0649 
0650     if (m_tabWidget->currentIndex() == 1) {
0651         tab = QString(i18nc("The library tab title %1 is the file name", "%1 Library", m_url.fileName()));
0652     } else {
0653         tab = QString(i18nc("The editor tab title %1 is the file name", "%1 Editor", m_url.fileName()));
0654     }
0655 
0656     setCaption(tab, !clean);
0657 }
0658 
0659 
0660 /**
0661  * Change the tab selected.
0662  * This is connected to the QTabWidget::currentChanged() slot and indicates that the current tab
0663  * has changed. This allows the undo stack connections to be modified.
0664  *
0665  * @param index the index of the page
0666  */
0667 void MainWindow::currentChanged(int index)
0668 {
0669     if (index == 0) { // Editor
0670         m_undoGroup.setActiveStack(m_editor->undoStack());
0671         m_editor->updateStatusMessage();
0672     } else if (index == 1) { // QListWidget
0673         m_undoGroup.setActiveStack(m_symbolLibrary->undoStack());
0674         statusBar()->clearMessage();
0675     }
0676 }
0677 
0678 
0679 /**
0680  * Edit an existing symbol from the symbol library.
0681  * Check if the current symbol being edited has been changed. If yes, ask if it should be
0682  * saved or discarded.
0683  * Clear the contents of the editor and assign a copy of the item symbol to it to edit.
0684  * The actions are updated to reflect the settings of the symbol being edited.
0685  *
0686  * @param item a pointer to a QListWidgetItem that was double clicked.
0687  */
0688 void MainWindow::itemSelected(QListWidgetItem *item)
0689 {
0690     QPair<qint16, Symbol> pair;
0691 
0692     if (editorClean()) {
0693         m_editor->clear();
0694         pair.first = static_cast<qint16>(item->data(Qt::UserRole).toInt());
0695         pair.second = m_symbolLibrary->symbol(pair.first);
0696         m_editor->setSymbol(pair);
0697         setActionsFromSymbol(pair.second);
0698         m_tabWidget->setCurrentIndex(0);
0699     }
0700 }
0701 
0702 
0703 /**
0704  * Display a context menu for the list widget.
0705  * Options:
0706  *  Delete Symbol
0707  *
0708  * @param pos a const reference to a QPoint representing the cursor position
0709  */
0710 void MainWindow::listWidgetContextMenuRequested(const QPoint &pos)
0711 {
0712     if ((m_item = m_listWidget->itemAt(pos))) {
0713         if (!m_menu) {
0714             m_menu = new QMenu;
0715             m_menu->addAction(i18n("Delete Symbol"), this, SLOT(deleteSymbol()));
0716         }
0717 
0718         m_menu->popup(QCursor::pos());
0719     }
0720 }
0721 
0722 
0723 /**
0724  * Delete the symbol pointed to by m_item.
0725  */
0726 void MainWindow::deleteSymbol()
0727 {
0728     m_symbolLibrary->undoStack()->push(new DeleteSymbolCommand(m_symbolLibrary, static_cast<qint16>(m_item->data(Qt::UserRole).toInt())));
0729 }
0730 
0731 
0732 /**
0733  * Configure the application.
0734  * Display the configuration dialog, creating it if necessary.
0735  */
0736 void MainWindow::preferences()
0737 {
0738     if (KConfigDialog::showDialog("preferences")) {
0739         return;
0740     }
0741 
0742     KConfigDialog *dialog = new KConfigDialog(this, "preferences", Configuration::self());
0743     dialog->setFaceType(KPageDialog::List);
0744 
0745     dialog->addPage(new EditorConfigPage(0, "EditorConfigPage"), i18nc("The Editor configuration page", "Editor"), "preferences-desktop");
0746 //    dialog->setHelp("ConfigurationDialog");
0747 
0748     connect(dialog, SIGNAL(settingsChanged(QString)), m_editor, SLOT(readSettings()));
0749 
0750     dialog->show();
0751 }
0752 
0753 
0754 /**
0755  * Set up the applications actions.
0756  * Create standard actions.
0757  * Create other actions, setting the icon and data as required.
0758  * Several actions are added to groups which are set as exclusive.
0759  * All actions are added to the applications QActionCollection.
0760  */
0761 void MainWindow::setupActions()
0762 {
0763     QAction *action;
0764     QActionGroup *actionGroup;
0765 
0766     KActionCollection *actions = actionCollection();
0767 
0768     // File menu
0769     KStandardAction::open(this, SLOT(fileOpen()), actions);
0770     KStandardAction::openNew(this, SLOT(newSymbol()), actions);
0771     KStandardAction::openRecent(this, SLOT(fileOpen(QUrl)), actions)->loadEntries(KConfigGroup(KSharedConfig::openConfig(), "RecentFiles"));
0772     KStandardAction::save(this, SLOT(save()), actions);
0773     KStandardAction::saveAs(this, SLOT(saveAs()), actions);
0774 
0775     action = new QAction(this);
0776     action->setText(i18n("Import Library"));
0777     action->setWhatsThis(i18n("Imports another library appending the symbols it contains to the current library."));
0778     connect(action, SIGNAL(triggered()), this, SLOT(importLibrary()));
0779     actions->addAction("importLibrary", action);
0780 
0781     action = new QAction(this);
0782     action->setText(i18n("Save Symbol"));
0783     action->setWhatsThis(i18n("Save the symbol to the library. If this is a new symbol, subsequent saves will create additional symbols in the library. If the symbol was selected from the library to edit then saving will update that symbol in the library."));
0784     action->setIcon(QIcon::fromTheme("symboleditor-save-symbol"));
0785     connect(action, SIGNAL(triggered()), this, SLOT(saveSymbol()));
0786     actions->addAction("saveSymbol", action);
0787 
0788     action = new QAction(this);
0789     action->setText(i18n("Save Symbol as New"));
0790     action->setWhatsThis(i18n("Save the current symbol as a new one in the library. Subsequent saves will update the new symbol."));
0791     connect(action, SIGNAL(triggered()), this, SLOT(saveSymbolAsNew()));
0792     actions->addAction("saveSymbolAsNew", action);
0793 
0794     KStandardAction::close(this, SLOT(close()), actions);
0795     KStandardAction::quit(this, SLOT(quit()), actions);
0796 
0797     // Edit menu
0798     KStandardAction::undo(this, SLOT(undo()), actions);
0799     KStandardAction::redo(this, SLOT(redo()), actions);
0800 
0801     // Rendering menu
0802     action = new QAction(this);
0803     action->setText(i18n("Fill Path"));
0804     action->setWhatsThis(i18n("Enable path filling. The path defines the closed boundary of the shape and the path is filled with the selected fill method."));
0805     action->setIcon(QIcon::fromTheme("format-fill-color"));
0806     action->setCheckable(true);
0807     connect(action, SIGNAL(triggered(bool)), m_editor, SLOT(selectFilled(bool)));
0808     actions->addAction("fillPath", action);
0809 
0810     actionGroup = new QActionGroup(this);
0811     actionGroup->setExclusive(true);
0812 
0813     action = new QAction(this);
0814     action->setText(i18n("Odd Even Fill"));
0815     action->setWhatsThis(i18n("The Odd Even fill method will fill alternate areas of the symbol."));
0816     action->setData(Qt::OddEvenFill);
0817     action->setIcon(QIcon::fromTheme("symboleditor-odd-even-fill"));
0818     action->setCheckable(true);
0819     actions->addAction("oddEvenFill", action);
0820     actionGroup->addAction(action);
0821 
0822     action = new QAction(this);
0823     action->setText(i18n("Winding Fill"));
0824     action->setWhatsThis(i18n("The Winding fill method will fill the complete interior of the path."));
0825     action->setData(Qt::WindingFill);
0826     action->setIcon(QIcon::fromTheme("symboleditor-winding-fill"));
0827     action->setCheckable(true);
0828     actions->addAction("windingFill", action);
0829     actionGroup->addAction(action);
0830 
0831     connect(actionGroup, SIGNAL(triggered(QAction*)), m_editor, SLOT(selectFillRule(QAction*)));
0832 
0833     actionGroup = new QActionGroup(this);
0834     actionGroup->setExclusive(true);
0835 
0836     action = new QAction(this);
0837     action->setText(i18n("Flat Cap"));
0838     action->setWhatsThis(i18n("The Flat Cap end type provides a square end that stops at the end point of the line.\n\nThis is only applicable to non-filled paths."));
0839     action->setData(Qt::FlatCap);
0840     action->setIcon(QIcon::fromTheme("stroke-cap-butt"));
0841     action->setCheckable(true);
0842     actions->addAction("flatCap", action);
0843     actionGroup->addAction(action);
0844 
0845     action = new QAction(this);
0846     action->setText(i18n("Square Cap"));
0847     action->setWhatsThis(i18n("The Square Cap end type provides a square end that projects beyond the end point of the line by half the line width.\n\nThis is only applicable to non-filled paths."));
0848     action->setData(Qt::SquareCap);
0849     action->setIcon(QIcon::fromTheme("stroke-cap-square"));
0850     action->setCheckable(true);
0851     actions->addAction("squareCap", action);
0852     actionGroup->addAction(action);
0853 
0854     action = new QAction(this);
0855     action->setText(i18n("Round Cap"));
0856     action->setWhatsThis(i18n("The Round Cap end type provides a round end that projects beyond the end point of the line with a radius of half the line width.\n\nThis is only applicable to non-filled paths."));
0857     action->setData(Qt::RoundCap);
0858     action->setIcon(QIcon::fromTheme("stroke-cap-round"));
0859     action->setCheckable(true);
0860     actions->addAction("roundCap", action);
0861     actionGroup->addAction(action);
0862 
0863     connect(actionGroup, SIGNAL(triggered(QAction*)), m_editor, SLOT(selectCapStyle(QAction*)));
0864 
0865     actionGroup = new QActionGroup(this);
0866     actionGroup->setExclusive(true);
0867 
0868     action = new QAction(this);
0869     action->setText(i18n("Bevel Join"));
0870     action->setWhatsThis(i18n("The Bevel Join provides a beveled corner between two lines.\n\nThis is only applicable to non-filled paths."));
0871     action->setData(Qt::BevelJoin);
0872     action->setIcon(QIcon::fromTheme("stroke-join-bevel"));
0873     action->setCheckable(true);
0874     actions->addAction("bevelJoin", action);
0875     actionGroup->addAction(action);
0876 
0877     action = new QAction(this);
0878     action->setText(i18n("Miter Join"));
0879     action->setWhatsThis(i18n("The Miter Join provides a mitered corner between two lines.\n\nThis is only applicable to non-filled paths."));
0880     action->setData(Qt::MiterJoin);
0881     action->setIcon(QIcon::fromTheme("stroke-join-miter"));
0882     action->setCheckable(true);
0883     actions->addAction("miterJoin", action);
0884     actionGroup->addAction(action);
0885 
0886     action = new QAction(this);
0887     action->setText(i18n("Round Join"));
0888     action->setWhatsThis(i18n("The Round Join provides a rounded corner between two lines using a radius of half the line width.\n\nThis is only applicable to non-filled paths."));
0889     action->setData(Qt::RoundJoin);
0890     action->setIcon(QIcon::fromTheme("stroke-join-round"));
0891     action->setCheckable(true);
0892     actions->addAction("roundJoin", action);
0893     actionGroup->addAction(action);
0894 
0895     connect(actionGroup, SIGNAL(triggered(QAction*)), m_editor, SLOT(selectJoinStyle(QAction*)));
0896 
0897     action = new QAction(this);
0898     action->setText(i18n("Increase Line Width"));
0899     action->setWhatsThis(i18n("Increases the line width.\n\nThis is only applicable to non-filled paths."));
0900     action->setIcon(QIcon::fromTheme("symboleditor-increase-line-width"));
0901     connect(action, SIGNAL(triggered()), m_editor, SLOT(increaseLineWidth()));
0902     actions->addAction("increaseLineWidth", action);
0903 
0904     action = new QAction(this);
0905     action->setText(i18n("Decrease Line Width"));
0906     action->setWhatsThis(i18n("Decreases the line width.\n\nThis is only applicable to non-filled paths."));
0907     action->setIcon(QIcon::fromTheme("symboleditor-decrease-line-width"));
0908     connect(action, SIGNAL(triggered()), m_editor, SLOT(decreaseLineWidth()));
0909     actions->addAction("decreaseLineWidth", action);
0910 
0911     // Tools Menu
0912     actionGroup = new QActionGroup(this);
0913     actionGroup->setExclusive(true);
0914 
0915     action = new QAction(this);
0916     action->setText(i18n("Move To"));
0917     action->setWhatsThis(i18n("Move the current point to a new position. This implicitly closes any existing sub path, starting a new one."));
0918     action->setData(Editor::MoveTo);
0919     action->setIcon(QIcon::fromTheme("go-jump"));
0920     action->setCheckable(true);
0921     actions->addAction("moveTo", action);
0922     actionGroup->addAction(action);
0923 
0924     action = new QAction(this);
0925     action->setText(i18n("Draw To"));
0926     action->setWhatsThis(i18n("Add a straight line from the current position to a defined end point. The end point becomes the new current position."));
0927     action->setData(Editor::LineTo);
0928     action->setIcon(QIcon::fromTheme("draw-line"));
0929     action->setCheckable(true);
0930     actions->addAction("lineTo", action);
0931     actionGroup->addAction(action);
0932 
0933     action = new QAction(this);
0934     action->setText(i18n("Cubic To"));
0935     action->setWhatsThis(i18n("Add a cubic bezier curve from the current position using two control points and an end point. The end point becomes the new current position."));
0936     action->setData(Editor::CubicTo);
0937     action->setIcon(QIcon::fromTheme("draw-bezier-curves"));
0938     action->setCheckable(true);
0939     actions->addAction("cubicTo", action);
0940     actionGroup->addAction(action);
0941 
0942     action = new QAction(this);
0943     action->setText(i18n("Rectangle"));
0944     action->setWhatsThis(i18n("Add a rectangle as a separate sub path defined by two points representing the opposite corners."));
0945     action->setData(Editor::Rectangle);
0946     action->setIcon(QIcon::fromTheme("draw-rectangle"));
0947     action->setCheckable(true);
0948     actions->addAction("rectangle", action);
0949     actionGroup->addAction(action);
0950 
0951     action = new QAction(this);
0952     action->setText(i18n("Ellipse"));
0953     action->setWhatsThis(i18n("Add an ellipse as a separate sub path defined by a bounding rectangle represented by two opposite corners."));
0954     action->setData(Editor::Ellipse);
0955     action->setIcon(QIcon::fromTheme("draw-ellipse"));
0956     action->setCheckable(true);
0957     actions->addAction("ellipse", action);
0958     actionGroup->addAction(action);
0959 
0960     action = new QAction(this);
0961     action->setText(i18n("Insert Character"));
0962     action->setWhatsThis(i18n("Allows selection of a character from any font to be inserted as a closed sub path. The inserted character will overwrite any existing path, but additional sub paths may be added to the character."));
0963     action->setData(Editor::Character);
0964     action->setIcon(QIcon::fromTheme("insert-text"));
0965     action->setCheckable(true);
0966     actions->addAction("character", action);
0967     actionGroup->addAction(action);
0968 
0969     connect(actionGroup, SIGNAL(triggered(QAction*)), m_editor, SLOT(selectTool(QAction*)));
0970 
0971     action = new QAction(this);
0972     action->setText(i18n("Rotate Left"));
0973     action->setWhatsThis(i18n("Rotate all the points of a path counter-clockwise 90 degrees around the center of the editor."));
0974     action->setIcon(QIcon::fromTheme("object-rotate-left"));
0975     connect(action, SIGNAL(triggered()), m_editor, SLOT(rotateLeft()));
0976     actions->addAction("rotateLeft", action);
0977 
0978     action = new QAction(this);
0979     action->setText(i18n("Rotate Right"));
0980     action->setWhatsThis(i18n("Rotate all the points of a path clockwise 90 degrees around the center point of the editor."));
0981     action->setIcon(QIcon::fromTheme("object-rotate-right"));
0982     connect(action, SIGNAL(triggered()), m_editor, SLOT(rotateRight()));
0983     actions->addAction("rotateRight", action);
0984 
0985     action = new QAction(this);
0986     action->setText(i18n("Flip Horizontal"));
0987     action->setWhatsThis(i18n("Flip all the points of the path horizontally about the vertical center of the editor."));
0988     action->setIcon(QIcon::fromTheme("object-flip-horizontal"));
0989     connect(action, SIGNAL(triggered()), m_editor, SLOT(flipHorizontal()));
0990     actions->addAction("flipHorizontal", action);
0991 
0992     action = new QAction(this);
0993     action->setText(i18n("Flip Vertical"));
0994     action->setWhatsThis(i18n("Flip all the points of the path vertically about the horizontal center of the editor."));
0995     action->setIcon(QIcon::fromTheme("object-flip-vertical"));
0996     connect(action, SIGNAL(triggered()), m_editor, SLOT(flipVertical()));
0997     actions->addAction("flipVertical", action);
0998 
0999     action = new QAction(this);
1000     action->setText(i18n("Scale to Preferred Size"));
1001     action->setWhatsThis(i18n("Scale the current symbol so that it fits within the preferred size of a symbol."));
1002     action->setIcon(QIcon::fromTheme("symboleditor-scale-preferred"));
1003     connect(action, SIGNAL(triggered()), m_editor, SLOT(scalePreferred()));
1004     actions->addAction("scalePreferred", action);
1005 
1006     action = new QAction(this);
1007     action->setText(i18n("Enable Snap"));
1008     action->setWhatsThis(i18n("Enable snapping of points to guide intersections or to the grid."));
1009     action->setIcon(QIcon::fromTheme("snap-orthogonal"));
1010     action->setCheckable(true);
1011     connect(action, SIGNAL(toggled(bool)), m_editor, SLOT(enableSnap(bool)));
1012     actions->addAction("enableSnap", action);
1013 
1014     action = new QAction(this);
1015     action->setText(i18n("Enable Guides"));
1016     action->setWhatsThis(i18n("Enable the generation of guide intersections."));
1017     action->setIcon(QIcon::fromTheme("snap-intersection"));
1018     action->setCheckable(true);
1019     connect(action, SIGNAL(toggled(bool)), m_editor, SLOT(enableGuides(bool)));
1020     actions->addAction("enableGuides", action);
1021 
1022     // Settings Menu
1023     KStandardAction::preferences(this, SLOT(preferences()), actions);
1024 }
1025 
1026 
1027 /**
1028  * Set the actions status based on the settings in the specified Symbol.
1029  *
1030  * @param symbol a const reference to a Symbol
1031  */
1032 void MainWindow::setActionsFromSymbol(const Symbol &symbol)
1033 {
1034     action("fillPath")->setChecked(symbol.filled());
1035 
1036     switch (symbol.path().fillRule()) {
1037     case Qt::WindingFill:
1038         action("windingFill")->setChecked(true);
1039         break;
1040 
1041     case Qt::OddEvenFill:
1042         action("oddEvenFill")->setChecked(true);
1043         break;
1044     }
1045 
1046     switch (symbol.capStyle()) {
1047     case Qt::FlatCap:
1048         action("flatCap")->setChecked(true);
1049         break;
1050 
1051     case Qt::SquareCap:
1052         action("squareCap")->setChecked(true);
1053         break;
1054 
1055     case Qt::RoundCap:
1056         action("roundCap")->setChecked(true);
1057         break;
1058 
1059     case Qt::MPenCapStyle: // this is a combination of the Qt::FlatCap, Qt::SquareCap and Qt::RoundCap
1060         break;
1061     }
1062 
1063     switch (symbol.joinStyle()) {
1064     case Qt::BevelJoin:
1065         action("bevelJoin")->setChecked(true);
1066         break;
1067 
1068     case Qt::MiterJoin:
1069     case Qt::SvgMiterJoin:
1070         action("miterJoin")->setChecked(true);
1071         break;
1072 
1073     case Qt::RoundJoin:
1074         action("roundJoin")->setChecked(true);
1075         break;
1076 
1077     case Qt::MPenJoinStyle: // this is a combination of Qt::BevelJoin, Qt::MiterJoin, Qt::SvgMiterJoin and Qt::RoundJoin
1078         break;
1079     }
1080 
1081     action("increaseLineWidth")->setDisabled(symbol.lineWidth() == 1.00);
1082     action("decreaseLineWidth")->setDisabled(symbol.lineWidth() == 0.01);
1083 }