File indexing completed on 2025-03-16 13:13:03
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 }