File indexing completed on 2024-04-28 05:08:25
0001 /*************************************************************************** 0002 Copyright (C) 2001-2020 Robby Stephenson <robby@periapsis.org> 0003 Copyright (C) 2011 Pedro Miguel Carvalho <kde@pmc.com.pt> 0004 ***************************************************************************/ 0005 0006 /*************************************************************************** 0007 * * 0008 * This program is free software; you can redistribute it and/or * 0009 * modify it under the terms of the GNU General Public License as * 0010 * published by the Free Software Foundation; either version 2 of * 0011 * the License or (at your option) version 3 or any later version * 0012 * accepted by the membership of KDE e.V. (or its successor approved * 0013 * by the membership of KDE e.V.), which shall act as a proxy * 0014 * defined in Section 14 of version 3 of the license. * 0015 * * 0016 * This program is distributed in the hope that it will be useful, * 0017 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0019 * GNU General Public License for more details. * 0020 * * 0021 * You should have received a copy of the GNU General Public License * 0022 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0023 * * 0024 ***************************************************************************/ 0025 0026 #include "mainwindow.h" 0027 #include "tellico_kernel.h" 0028 #include "document.h" 0029 #include "detailedlistview.h" 0030 #include "entryeditdialog.h" 0031 #include "groupview.h" 0032 #include "viewstack.h" 0033 #include "collection.h" 0034 #include "collectionfactory.h" 0035 #include "entry.h" 0036 #include "configdialog.h" 0037 #include "filter.h" 0038 #include "filterdialog.h" 0039 #include "collectionfieldsdialog.h" 0040 #include "controller.h" 0041 #include "importdialog.h" 0042 #include "exportdialog.h" 0043 #include "core/filehandler.h" // needed so static mainWindow variable can be set 0044 #include "core/logger.h" 0045 #include "printhandler.h" 0046 #include "entryview.h" 0047 #include "entryiconview.h" 0048 #include "images/imagefactory.h" // needed so tmp files can get cleaned 0049 #include "collections/collectioninitializer.h" 0050 #include "collections/bibtexcollection.h" // needed for bibtex string macro dialog 0051 #include "utils/bibtexhandler.h" // needed for bibtex options 0052 #include "utils/datafileregistry.h" 0053 #include "fetchdialog.h" 0054 #include "reportdialog.h" 0055 #include "bibtexkeydialog.h" 0056 #include "core/tellico_strings.h" 0057 #include "filterview.h" 0058 #include "loanview.h" 0059 #include "fetch/fetchmanager.h" 0060 #include "fetch/fetcherinitializer.h" 0061 #include "cite/actionmanager.h" 0062 #include "config/tellico_config.h" 0063 #include "core/netaccess.h" 0064 #include "dbusinterface.h" 0065 #include "models/models.h" 0066 #include "models/entryiconmodel.h" 0067 #include "models/entryselectionmodel.h" 0068 #include "newstuff/manager.h" 0069 #include "gui/drophandler.h" 0070 #include "gui/stringmapdialog.h" 0071 #include "gui/lineedit.h" 0072 #include "gui/statusbar.h" 0073 #include "gui/tabwidget.h" 0074 #include "gui/dockwidget.h" 0075 #include "gui/collectiontemplatedialog.h" 0076 #include "utils/cursorsaver.h" 0077 #include "utils/guiproxy.h" 0078 #include "utils/tellico_utils.h" 0079 #include "tellico_debug.h" 0080 0081 #include <KComboBox> 0082 #include <KToolBar> 0083 #include <KLocalizedString> 0084 #include <KConfig> 0085 #include <KStandardAction> 0086 #include <KWindowConfig> 0087 #include <KMessageBox> 0088 #include <KTipDialog> 0089 #include <KRecentDocument> 0090 #include <KRecentDirs> 0091 #include <KEditToolBar> 0092 #include <KShortcutsDialog> 0093 #include <KRecentFilesAction> 0094 #include <KToggleAction> 0095 #include <KActionCollection> 0096 #include <KActionMenu> 0097 #include <KFileWidget> 0098 #include <KDualAction> 0099 #include <KXMLGUIFactory> 0100 #include <KAboutData> 0101 #include <kwidgetsaddons_version.h> 0102 0103 #include <QApplication> 0104 #include <QUndoStack> 0105 #include <QAction> 0106 #include <QSignalMapper> 0107 #include <QTimer> 0108 #include <QMetaObject> // needed for copy, cut, paste slots 0109 #include <QMimeDatabase> 0110 #include <QMimeType> 0111 #include <QMenuBar> 0112 #include <QFileDialog> 0113 #include <QMetaMethod> 0114 #include <QVBoxLayout> 0115 #include <QTextEdit> 0116 0117 namespace { 0118 static const int MAX_IMAGES_WARN_PERFORMANCE = 200; 0119 0120 QIcon mimeIcon(const char* s) { 0121 QMimeDatabase db; 0122 QMimeType ptr = db.mimeTypeForName(QLatin1String(s)); 0123 if(!ptr.isValid()) { 0124 myDebug() << "*** no icon for" << s; 0125 } 0126 return ptr.isValid() ? QIcon::fromTheme(ptr.iconName()) : QIcon(); 0127 } 0128 0129 QIcon mimeIcon(const char* s1, const char* s2) { 0130 QMimeDatabase db; 0131 QMimeType ptr = db.mimeTypeForName(QLatin1String(s1)); 0132 if(!ptr.isValid()) { 0133 ptr = db.mimeTypeForName(QLatin1String(s2)); 0134 if(!ptr.isValid()) { 0135 myDebug() << "*** no icon for" << s1 << "or" << s2; 0136 } 0137 } 0138 return ptr.isValid() ? QIcon::fromTheme(ptr.iconName()) : QIcon(); 0139 } 0140 0141 } 0142 0143 using namespace Tellico; 0144 using Tellico::MainWindow; 0145 0146 MainWindow::MainWindow(QWidget* parent_/*=0*/) : KXmlGuiWindow(parent_), 0147 m_updateAll(nullptr), 0148 m_statusBar(nullptr), 0149 m_editDialog(nullptr), 0150 m_groupView(nullptr), 0151 m_filterView(nullptr), 0152 m_loanView(nullptr), 0153 m_configDlg(nullptr), 0154 m_filterDlg(nullptr), 0155 m_collFieldsDlg(nullptr), 0156 m_stringMacroDlg(nullptr), 0157 m_bibtexKeyDlg(nullptr), 0158 m_fetchDlg(nullptr), 0159 m_reportDlg(nullptr), 0160 m_queuedFilters(0), 0161 m_initialized(false), 0162 m_newDocument(true), 0163 m_dontQueueFilter(false), 0164 m_savingImageLocationChange(false) { 0165 0166 Controller::init(this); // the only time this is ever called! 0167 // has to be after controller init 0168 Kernel::init(this); // the only time this is ever called! 0169 GUI::Proxy::setMainWidget(this); 0170 0171 setWindowIcon(QIcon::fromTheme(QStringLiteral("tellico"), 0172 QIcon(QLatin1String(":/icons/tellico")))); 0173 0174 // initialize the status bar and progress bar 0175 initStatusBar(); 0176 0177 // initialize all the collection types 0178 // which must be done before the document is created 0179 CollectionInitializer initCollections; 0180 // register all the fetcher types 0181 Fetch::FetcherInitializer initFetchers; 0182 0183 // create a document, which also creates an empty book collection 0184 // must be done before the different widgets are created 0185 initDocument(); 0186 0187 // set up all the actions, some connect to the document, so this must be after initDocument() 0188 initActions(); 0189 0190 // create the different widgets in the view, some widgets connect to actions, so must be after initActions() 0191 initView(); 0192 0193 // The edit dialog is not created until after the main window is initialized, so it can be a child. 0194 // So don't make any connections, don't read options for it until initFileOpen 0195 readOptions(); 0196 0197 setAcceptDrops(true); 0198 DropHandler* drophandler = new DropHandler(this); 0199 installEventFilter(drophandler); 0200 0201 new ApplicationInterface(this); 0202 new CollectionInterface(this); 0203 0204 MARK_LINE; 0205 QTimer::singleShot(0, this, &MainWindow::slotInit); 0206 } 0207 0208 MainWindow::~MainWindow() { 0209 qDeleteAll(m_fetchActions); 0210 m_fetchActions.clear(); 0211 // when closing the mainwindow, immediately after running Tellico, often there was a long pause 0212 // before the application eventually quit, something related to polling on eventfd, I don't 0213 // know what. So when closing the window, make sure to immediately quit the application 0214 QTimer::singleShot(0, qApp, &QCoreApplication::quit); 0215 } 0216 0217 void MainWindow::slotInit() { 0218 // if the edit dialog exists, we know we've already called this function 0219 if(m_editDialog) { 0220 return; 0221 } 0222 MARK; 0223 0224 m_editDialog = new EntryEditDialog(this); 0225 Controller::self()->addObserver(m_editDialog); 0226 0227 m_toggleEntryEditor->setChecked(Config::showEditWidget()); 0228 slotToggleEntryEditor(); 0229 m_lockLayout->setActive(Config::lockLayout()); 0230 0231 initConnections(); 0232 connect(ImageFactory::self(), &ImageFactory::imageLocationMismatch, 0233 this, &MainWindow::slotImageLocationMismatch); 0234 // Init DBUS for new stuff manager 0235 NewStuff::Manager::self(); 0236 } 0237 0238 void MainWindow::initStatusBar() { 0239 MARK; 0240 m_statusBar = new Tellico::StatusBar(this); 0241 setStatusBar(m_statusBar); 0242 } 0243 0244 void MainWindow::initActions() { 0245 MARK; 0246 /************************************************* 0247 * File->New menu 0248 *************************************************/ 0249 QSignalMapper* collectionMapper = new QSignalMapper(this); 0250 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0251 void (QSignalMapper::* mappedInt)(int) = &QSignalMapper::mapped; 0252 connect(collectionMapper, mappedInt, this, &MainWindow::slotFileNew); 0253 #else 0254 connect(collectionMapper, &QSignalMapper::mappedInt, this, &MainWindow::slotFileNew); 0255 #endif 0256 0257 m_newCollectionMenu = new KActionMenu(i18n("New Collection"), this); 0258 m_newCollectionMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0259 m_newCollectionMenu->setToolTip(i18n("Create a new collection")); 0260 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,77,0) 0261 m_newCollectionMenu->setPopupMode(QToolButton::InstantPopup); 0262 #else 0263 m_newCollectionMenu->setDelayed(false); 0264 #endif 0265 actionCollection()->addAction(QStringLiteral("file_new_collection"), m_newCollectionMenu); 0266 0267 QAction* action; 0268 0269 void (QSignalMapper::* mapVoid)() = &QSignalMapper::map; 0270 #define COLL_ACTION(TYPE, NAME, TEXT, TIP, ICON) \ 0271 action = actionCollection()->addAction(QStringLiteral(NAME), collectionMapper, mapVoid); \ 0272 action->setText(TEXT); \ 0273 action->setToolTip(TIP); \ 0274 action->setIcon(QIcon(QStringLiteral(":/icons/" ICON))); \ 0275 m_newCollectionMenu->addAction(action); \ 0276 collectionMapper->setMapping(action, Data::Collection::TYPE); 0277 0278 COLL_ACTION(Book, "new_book_collection", i18n("New &Book Collection"), 0279 i18n("Create a new book collection"), "book"); 0280 0281 COLL_ACTION(Bibtex, "new_bibtex_collection", i18n("New B&ibliography"), 0282 i18n("Create a new bibtex bibliography"), "bibtex"); 0283 0284 COLL_ACTION(ComicBook, "new_comic_book_collection", i18n("New &Comic Book Collection"), 0285 i18n("Create a new comic book collection"), "comic"); 0286 0287 COLL_ACTION(Video, "new_video_collection", i18n("New &Video Collection"), 0288 i18n("Create a new video collection"), "video"); 0289 0290 COLL_ACTION(Album, "new_music_collection", i18n("New &Music Collection"), 0291 i18n("Create a new music collection"), "album"); 0292 0293 COLL_ACTION(Coin, "new_coin_collection", i18n("New C&oin Collection"), 0294 i18n("Create a new coin collection"), "coin"); 0295 0296 COLL_ACTION(Stamp, "new_stamp_collection", i18n("New &Stamp Collection"), 0297 i18n("Create a new stamp collection"), "stamp"); 0298 0299 COLL_ACTION(Card, "new_card_collection", i18n("New C&ard Collection"), 0300 i18n("Create a new trading card collection"), "card"); 0301 0302 COLL_ACTION(Wine, "new_wine_collection", i18n("New &Wine Collection"), 0303 i18n("Create a new wine collection"), "wine"); 0304 0305 COLL_ACTION(Game, "new_game_collection", i18n("New Video &Game Collection"), 0306 i18n("Create a new video game collection"), "game"); 0307 0308 COLL_ACTION(BoardGame, "new_boardgame_collection", i18n("New Boa&rd Game Collection"), 0309 i18n("Create a new board game collection"), "boardgame"); 0310 0311 COLL_ACTION(File, "new_file_catalog", i18n("New &File Catalog"), 0312 i18n("Create a new file catalog"), "file"); 0313 0314 action = actionCollection()->addAction(QStringLiteral("new_custom_collection"), collectionMapper, mapVoid); 0315 action->setText(i18n("New C&ustom Collection")); 0316 action->setToolTip(i18n("Create a new custom collection")); 0317 action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0318 m_newCollectionMenu->addAction(action); 0319 collectionMapper->setMapping(action, Data::Collection::Base); 0320 0321 #undef COLL_ACTION 0322 0323 /************************************************* 0324 * File menu 0325 *************************************************/ 0326 action = KStandardAction::open(this, SLOT(slotFileOpen()), actionCollection()); 0327 action->setToolTip(i18n("Open an existing document")); 0328 m_fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(const QUrl&)), actionCollection()); 0329 m_fileOpenRecent->setToolTip(i18n("Open a recently used file")); 0330 m_fileSave = KStandardAction::save(this, SLOT(slotFileSave()), actionCollection()); 0331 m_fileSave->setToolTip(i18n("Save the document")); 0332 action = KStandardAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection()); 0333 action->setToolTip(i18n("Save the document as a different file...")); 0334 0335 action = actionCollection()->addAction(QStringLiteral("file_save_template"), 0336 this, SLOT(slotFileSaveAsTemplate())); 0337 action->setText(i18n("Save As Template...")); 0338 action->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as-template"))); 0339 action->setToolTip(i18n("Save as a collection template")); 0340 0341 action = KStandardAction::print(this, SLOT(slotFilePrint()), actionCollection()); 0342 action->setToolTip(i18n("Print the contents of the collection...")); 0343 #ifdef USE_KHTML 0344 { 0345 KHTMLPart w; 0346 // KHTMLPart printing was broken in KDE until KHTML 5.16 0347 const QString version = w.componentData().version(); 0348 const uint major = version.section(QLatin1Char('.'), 0, 0).toUInt(); 0349 const uint minor = version.section(QLatin1Char('.'), 1, 1).toUInt(); 0350 if(major == 5 && minor < 16) { 0351 myWarning() << "Printing is broken for KDE Frameworks < 5.16. Please upgrade"; 0352 action->setEnabled(false); 0353 } 0354 } 0355 #else 0356 // print preview is only available with QWebEngine 0357 action = KStandardAction::printPreview(this, SLOT(slotFilePrintPreview()), actionCollection()); 0358 action->setToolTip(i18n("Preview the contents of the collection...")); 0359 #endif 0360 0361 action = KStandardAction::quit(this, SLOT(slotFileQuit()), actionCollection()); 0362 action->setToolTip(i18n("Quit the application")); 0363 0364 /**************** Import Menu ***************************/ 0365 0366 QSignalMapper* importMapper = new QSignalMapper(this); 0367 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0368 connect(importMapper, mappedInt, this, &MainWindow::slotFileImport); 0369 #else 0370 connect(importMapper, &QSignalMapper::mappedInt, this, &MainWindow::slotFileImport); 0371 #endif 0372 0373 KActionMenu* importMenu = new KActionMenu(i18n("&Import"), this); 0374 importMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); 0375 importMenu->setToolTip(i18n("Import the collection data from other formats")); 0376 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,77,0) 0377 importMenu->setPopupMode(QToolButton::InstantPopup); 0378 #else 0379 importMenu->setDelayed(false); 0380 #endif 0381 actionCollection()->addAction(QStringLiteral("file_import"), importMenu); 0382 0383 #define IMPORT_ACTION(TYPE, NAME, TEXT, TIP, ICON) \ 0384 action = actionCollection()->addAction(QStringLiteral(NAME), importMapper, mapVoid); \ 0385 action->setText(TEXT); \ 0386 action->setToolTip(TIP); \ 0387 action->setIcon(ICON); \ 0388 importMenu->addAction(action); \ 0389 importMapper->setMapping(action, TYPE); 0390 0391 IMPORT_ACTION(Import::TellicoXML, "file_import_tellico", i18n("Import Tellico Data..."), 0392 i18n("Import another Tellico data file"), 0393 QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QLatin1String(":/icons/tellico")))); 0394 0395 IMPORT_ACTION(Import::CSV, "file_import_csv", i18n("Import CSV Data..."), 0396 i18n("Import a CSV file"), mimeIcon("text/csv", "text/x-csv")); 0397 0398 IMPORT_ACTION(Import::MODS, "file_import_mods", i18n("Import MODS Data..."), 0399 i18n("Import a MODS data file"), mimeIcon("text/xml")); 0400 0401 IMPORT_ACTION(Import::Alexandria, "file_import_alexandria", i18n("Import Alexandria Data..."), 0402 i18n("Import data from the Alexandria book collection manager"), 0403 QIcon::fromTheme(QStringLiteral("alexandria"), QIcon(QLatin1String(":/icons/alexandria")))); 0404 0405 IMPORT_ACTION(Import::Delicious, "file_import_delicious", i18n("Import Delicious Library Data..."), 0406 i18n("Import data from Delicious Library"), 0407 QIcon::fromTheme(QStringLiteral("deliciouslibrary"), QIcon(QLatin1String(":/icons/deliciouslibrary")))); 0408 0409 IMPORT_ACTION(Import::Collectorz, "file_import_collectorz", i18n("Import Collectorz Data..."), 0410 i18n("Import data from Collectorz"), 0411 QIcon::fromTheme(QStringLiteral("collectorz"), QIcon(QLatin1String(":/icons/collectorz")))); 0412 0413 IMPORT_ACTION(Import::DataCrow, "file_import_datacrow", i18n("Import Data Crow Data..."), 0414 i18n("Import data from Data Crow"), 0415 QIcon::fromTheme(QStringLiteral("datacrow"), QIcon(QLatin1String(":/icons/datacrow")))); 0416 0417 IMPORT_ACTION(Import::Referencer, "file_import_referencer", i18n("Import Referencer Data..."), 0418 i18n("Import data from Referencer"), 0419 QIcon::fromTheme(QStringLiteral("referencer"), QIcon(QLatin1String(":/icons/referencer")))); 0420 0421 IMPORT_ACTION(Import::Bibtex, "file_import_bibtex", i18n("Import Bibtex Data..."), 0422 i18n("Import a bibtex bibliography file"), mimeIcon("text/x-bibtex")); 0423 #ifndef ENABLE_BTPARSE 0424 action->setEnabled(false); 0425 #endif 0426 0427 IMPORT_ACTION(Import::Bibtexml, "file_import_bibtexml", i18n("Import Bibtexml Data..."), 0428 i18n("Import a Bibtexml bibliography file"), mimeIcon("text/xml")); 0429 0430 IMPORT_ACTION(Import::RIS, "file_import_ris", i18n("Import RIS Data..."), 0431 i18n("Import an RIS reference file"), QIcon::fromTheme(QStringLiteral(":/icons/cite"))); 0432 0433 IMPORT_ACTION(Import::MARC, "file_import_marc", i18n("Import MARC Data..."), 0434 i18n("Import MARC data"), QIcon::fromTheme(QStringLiteral(":/icons/cite"))); 0435 // disable this import action if the necessary executable is not available 0436 QTimer::singleShot(1000, this, [action]() { 0437 const QString ymd = QStandardPaths::findExecutable(QStringLiteral("yaz-marcdump")); 0438 action->setEnabled(!ymd.isEmpty()); 0439 }); 0440 0441 IMPORT_ACTION(Import::Goodreads, "file_import_goodreads", i18n("Import Goodreads Collection..."), 0442 i18n("Import a collection from Goodreads.com"), QIcon::fromTheme(QStringLiteral(":/icons/goodreads"))); 0443 0444 IMPORT_ACTION(Import::LibraryThing, "file_import_librarything", i18n("Import LibraryThing Collection..."), 0445 i18n("Import a collection from LibraryThing.com"), QIcon::fromTheme(QStringLiteral(":/icons/librarything"))); 0446 0447 IMPORT_ACTION(Import::PDF, "file_import_pdf", i18n("Import PDF File..."), 0448 i18n("Import a PDF file"), mimeIcon("application/pdf")); 0449 0450 IMPORT_ACTION(Import::AudioFile, "file_import_audiofile", i18n("Import Audio File Metadata..."), 0451 i18n("Import meta-data from audio files"), mimeIcon("audio/mp3", "audio/x-mp3")); 0452 #ifndef HAVE_TAGLIB 0453 action->setEnabled(false); 0454 #endif 0455 0456 IMPORT_ACTION(Import::FreeDB, "file_import_freedb", i18n("Import Audio CD Data..."), 0457 i18n("Import audio CD information"), mimeIcon("media/audiocd", "application/x-cda")); 0458 #if !defined (HAVE_OLD_KCDDB) && !defined (HAVE_KCDDB) 0459 action->setEnabled(false); 0460 #endif 0461 0462 IMPORT_ACTION(Import::Discogs, "file_import_discogs", i18n("Import Discogs Collection..."), 0463 i18n("Import a collection from Discogs.com"), QIcon::fromTheme(QStringLiteral(":/icons/discogs"))); 0464 0465 IMPORT_ACTION(Import::GCstar, "file_import_gcstar", i18n("Import GCstar Data..."), 0466 i18n("Import a GCstar data file"), 0467 QIcon::fromTheme(QStringLiteral("gcstar"), QIcon(QLatin1String(":/icons/gcstar")))); 0468 0469 IMPORT_ACTION(Import::Griffith, "file_import_griffith", i18n("Import Griffith Data..."), 0470 i18n("Import a Griffith database"), 0471 QIcon::fromTheme(QStringLiteral("griffith"), QIcon(QLatin1String(":/icons/griffith")))); 0472 0473 IMPORT_ACTION(Import::AMC, "file_import_amc", i18n("Import Ant Movie Catalog Data..."), 0474 i18n("Import an Ant Movie Catalog data file"), 0475 QIcon::fromTheme(QStringLiteral("amc"), QIcon(QLatin1String(":/icons/amc")))); 0476 0477 IMPORT_ACTION(Import::BoardGameGeek, "file_import_boardgamegeek", i18n("Import BoardGameGeek Collection..."), 0478 i18n("Import a collection from BoardGameGeek.com"), QIcon(QLatin1String(":/icons/boardgamegeek"))); 0479 0480 IMPORT_ACTION(Import::VinoXML, "file_import_vinoxml", i18n("Import VinoXML..."), 0481 i18n("Import VinoXML data"), QIcon(QLatin1String(":/icons/vinoxml"))); 0482 0483 IMPORT_ACTION(Import::FileListing, "file_import_filelisting", i18n("Import File Listing..."), 0484 i18n("Import information about files in a folder"), mimeIcon("inode/directory")); 0485 0486 IMPORT_ACTION(Import::XSLT, "file_import_xslt", i18n("Import XSL Transform..."), 0487 i18n("Import using an XSL Transform"), mimeIcon("application/xslt+xml", "text/x-xslt")); 0488 0489 #undef IMPORT_ACTION 0490 0491 /**************** Export Menu ***************************/ 0492 0493 QSignalMapper* exportMapper = new QSignalMapper(this); 0494 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0495 connect(exportMapper, mappedInt, this, &MainWindow::slotFileExport); 0496 #else 0497 connect(exportMapper, &QSignalMapper::mappedInt, this, &MainWindow::slotFileExport); 0498 #endif 0499 0500 KActionMenu* exportMenu = new KActionMenu(i18n("&Export"), this); 0501 exportMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); 0502 exportMenu->setToolTip(i18n("Export the collection data to other formats")); 0503 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,77,0) 0504 exportMenu->setPopupMode(QToolButton::InstantPopup); 0505 #else 0506 exportMenu->setDelayed(false); 0507 #endif 0508 actionCollection()->addAction(QStringLiteral("file_export"), exportMenu); 0509 0510 #define EXPORT_ACTION(TYPE, NAME, TEXT, TIP, ICON) \ 0511 action = actionCollection()->addAction(QStringLiteral(NAME), exportMapper, mapVoid); \ 0512 action->setText(TEXT); \ 0513 action->setToolTip(TIP); \ 0514 action->setIcon(ICON); \ 0515 exportMenu->addAction(action); \ 0516 exportMapper->setMapping(action, TYPE); 0517 0518 EXPORT_ACTION(Export::TellicoXML, "file_export_xml", i18n("Export to XML..."), 0519 i18n("Export to a Tellico XML file"), 0520 QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QStringLiteral(":/icons/tellico")))); 0521 0522 EXPORT_ACTION(Export::TellicoZip, "file_export_zip", i18n("Export to Zip..."), 0523 i18n("Export to a Tellico Zip file"), 0524 QIcon::fromTheme(QStringLiteral("tellico"), QIcon(QStringLiteral(":/icons/tellico")))); 0525 0526 EXPORT_ACTION(Export::HTML, "file_export_html", i18n("Export to HTML..."), 0527 i18n("Export to an HTML file"), mimeIcon("text/html")); 0528 0529 EXPORT_ACTION(Export::CSV, "file_export_csv", i18n("Export to CSV..."), 0530 i18n("Export to a comma-separated values file"), mimeIcon("text/csv", "text/x-csv")); 0531 0532 EXPORT_ACTION(Export::Alexandria, "file_export_alexandria", i18n("Export to Alexandria..."), 0533 i18n("Export to an Alexandria library"), 0534 QIcon::fromTheme(QStringLiteral("alexandria"), QIcon(QStringLiteral(":/icons/alexandria")))); 0535 0536 EXPORT_ACTION(Export::Bibtex, "file_export_bibtex", i18n("Export to Bibtex..."), 0537 i18n("Export to a bibtex file"), mimeIcon("text/x-bibtex")); 0538 0539 EXPORT_ACTION(Export::Bibtexml, "file_export_bibtexml", i18n("Export to Bibtexml..."), 0540 i18n("Export to a Bibtexml file"), mimeIcon("text/xml")); 0541 0542 EXPORT_ACTION(Export::ONIX, "file_export_onix", i18n("Export to ONIX..."), 0543 i18n("Export to an ONIX file"), mimeIcon("text/xml")); 0544 0545 EXPORT_ACTION(Export::GCstar, "file_export_gcstar", i18n("Export to GCstar..."), 0546 i18n("Export to a GCstar data file"), 0547 QIcon::fromTheme(QStringLiteral("gcstar"), QIcon(QStringLiteral(":/icons/gcstar")))); 0548 0549 EXPORT_ACTION(Export::XSLT, "file_export_xslt", i18n("Export XSL Transform..."), 0550 i18n("Export using an XSL Transform"), mimeIcon("application/xslt+xml", "text/x-xslt")); 0551 0552 #undef EXPORT_ACTION 0553 0554 /************************************************* 0555 * Edit menu 0556 *************************************************/ 0557 KStandardAction::undo(Kernel::self()->commandHistory(), SLOT(undo()), actionCollection()); 0558 KStandardAction::redo(Kernel::self()->commandHistory(), SLOT(undo()), actionCollection()); 0559 0560 action = KStandardAction::cut(this, SLOT(slotEditCut()), actionCollection()); 0561 action->setToolTip(i18n("Cut the selected text and puts it in the clipboard")); 0562 action = KStandardAction::copy(this, SLOT(slotEditCopy()), actionCollection()); 0563 action->setToolTip(i18n("Copy the selected text to the clipboard")); 0564 action = KStandardAction::paste(this, SLOT(slotEditPaste()), actionCollection()); 0565 action->setToolTip(i18n("Paste the clipboard contents")); 0566 action = KStandardAction::selectAll(this, SLOT(slotEditSelectAll()), actionCollection()); 0567 action->setToolTip(i18n("Select all the entries in the collection")); 0568 action = KStandardAction::deselect(this, SLOT(slotEditDeselect()), actionCollection()); 0569 action->setToolTip(i18n("Deselect all the entries in the collection")); 0570 0571 action = actionCollection()->addAction(QStringLiteral("filter_dialog"), this, SLOT(slotShowFilterDialog())); 0572 action->setText(i18n("Advanced &Filter...")); 0573 action->setIconText(i18n("Filter")); 0574 action->setIcon(QIcon::fromTheme(QStringLiteral("view-filter"))); 0575 actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_J); 0576 action->setToolTip(i18n("Filter the collection")); 0577 0578 /************************************************* 0579 * Collection menu 0580 *************************************************/ 0581 m_newEntry = actionCollection()->addAction(QStringLiteral("coll_new_entry"), 0582 this, SLOT(slotNewEntry())); 0583 m_newEntry->setText(i18n("&New Entry...")); 0584 m_newEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0585 m_newEntry->setIconText(i18n("New Entry")); 0586 actionCollection()->setDefaultShortcut(m_newEntry, Qt::CTRL + Qt::Key_N); 0587 m_newEntry->setToolTip(i18n("Create a new entry")); 0588 0589 KActionMenu* addEntryMenu = new KActionMenu(i18n("Add Entry"), this); 0590 addEntryMenu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 0591 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,77,0) 0592 addEntryMenu->setPopupMode(QToolButton::InstantPopup); 0593 #else 0594 addEntryMenu->setDelayed(false); 0595 #endif 0596 actionCollection()->addAction(QStringLiteral("coll_add_entry"), addEntryMenu); 0597 0598 action = actionCollection()->addAction(QStringLiteral("edit_search_internet"), this, SLOT(slotShowFetchDialog())); 0599 action->setText(i18n("Internet Search...")); 0600 action->setIconText(i18n("Internet Search")); 0601 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-wizard"))); 0602 actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_I); 0603 action->setToolTip(i18n("Search the internet...")); 0604 0605 addEntryMenu->addAction(m_newEntry); 0606 addEntryMenu->addAction(actionCollection()->action(QStringLiteral("edit_search_internet"))); 0607 0608 m_editEntry = actionCollection()->addAction(QStringLiteral("coll_edit_entry"), 0609 this, SLOT(slotShowEntryEditor())); 0610 m_editEntry->setText(i18n("&Edit Entry...")); 0611 m_editEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); 0612 actionCollection()->setDefaultShortcut(m_editEntry, Qt::CTRL + Qt::Key_E); 0613 m_editEntry->setToolTip(i18n("Edit the selected entries")); 0614 0615 m_copyEntry = actionCollection()->addAction(QStringLiteral("coll_copy_entry"), 0616 Controller::self(), SLOT(slotCopySelectedEntries())); 0617 m_copyEntry->setText(i18n("D&uplicate Entry")); 0618 m_copyEntry->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0619 actionCollection()->setDefaultShortcut(m_copyEntry, Qt::CTRL + Qt::Key_Y); 0620 m_copyEntry->setToolTip(i18n("Copy the selected entries")); 0621 0622 m_deleteEntry = actionCollection()->addAction(QStringLiteral("coll_delete_entry"), 0623 Controller::self(), SLOT(slotDeleteSelectedEntries())); 0624 m_deleteEntry->setText(i18n("&Delete Entry")); 0625 m_deleteEntry->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0626 actionCollection()->setDefaultShortcut(m_deleteEntry, Qt::CTRL + Qt::Key_D); 0627 m_deleteEntry->setToolTip(i18n("Delete the selected entries")); 0628 0629 m_mergeEntry = actionCollection()->addAction(QStringLiteral("coll_merge_entry"), 0630 Controller::self(), SLOT(slotMergeSelectedEntries())); 0631 m_mergeEntry->setText(i18n("&Merge Entries")); 0632 m_mergeEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); 0633 // CTRL+G is ambiguous, pick another 0634 // actionCollection()->setDefaultShortcut(m_mergeEntry, Qt::CTRL + Qt::Key_G); 0635 m_mergeEntry->setToolTip(i18n("Merge the selected entries")); 0636 m_mergeEntry->setEnabled(false); // gets enabled when more than 1 entry is selected 0637 0638 m_checkOutEntry = actionCollection()->addAction(QStringLiteral("coll_checkout"), Controller::self(), SLOT(slotCheckOut())); 0639 m_checkOutEntry->setText(i18n("Check-&out...")); 0640 m_checkOutEntry->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up-double"))); 0641 m_checkOutEntry->setToolTip(i18n("Check-out the selected items")); 0642 0643 m_checkInEntry = actionCollection()->addAction(QStringLiteral("coll_checkin"), Controller::self(), SLOT(slotCheckIn())); 0644 m_checkInEntry->setText(i18n("Check-&in")); 0645 m_checkInEntry->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down-double"))); 0646 m_checkInEntry->setToolTip(i18n("Check-in the selected items")); 0647 0648 action = actionCollection()->addAction(QStringLiteral("coll_rename_collection"), this, SLOT(slotRenameCollection())); 0649 action->setText(i18n("&Rename Collection...")); 0650 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); 0651 actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_R); 0652 action->setToolTip(i18n("Rename the collection")); 0653 0654 action = actionCollection()->addAction(QStringLiteral("coll_fields"), this, SLOT(slotShowCollectionFieldsDialog())); 0655 action->setText(i18n("Collection &Fields...")); 0656 action->setIconText(i18n("Fields")); 0657 action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); 0658 actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_U); 0659 action->setToolTip(i18n("Modify the collection fields")); 0660 0661 action = actionCollection()->addAction(QStringLiteral("coll_reports"), this, SLOT(slotShowReportDialog())); 0662 action->setText(i18n("&Generate Reports...")); 0663 action->setIconText(i18n("Reports")); 0664 action->setIcon(QIcon::fromTheme(QStringLiteral("text-rdf"))); 0665 action->setToolTip(i18n("Generate collection reports")); 0666 0667 action = actionCollection()->addAction(QStringLiteral("coll_convert_bibliography"), this, SLOT(slotConvertToBibliography())); 0668 action->setText(i18n("Convert to &Bibliography")); 0669 action->setIcon(QIcon(QLatin1String(":/icons/bibtex"))); 0670 action->setToolTip(i18n("Convert a book collection to a bibliography")); 0671 0672 action = actionCollection()->addAction(QStringLiteral("coll_string_macros"), this, SLOT(slotShowStringMacroDialog())); 0673 action->setText(i18n("String &Macros...")); 0674 action->setIcon(QIcon::fromTheme(QStringLiteral("view-list-text"))); 0675 action->setToolTip(i18n("Edit the bibtex string macros")); 0676 0677 action = actionCollection()->addAction(QStringLiteral("coll_key_manager"), this, SLOT(slotShowBibtexKeyDialog())); 0678 action->setText(i18n("Check for Duplicate Keys...")); 0679 action->setIcon(mimeIcon("text/x-bibtex")); 0680 action->setToolTip(i18n("Check for duplicate citation keys")); 0681 0682 QSignalMapper* citeMapper = new QSignalMapper(this); 0683 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0684 connect(citeMapper, mappedInt, this, &MainWindow::slotCiteEntry); 0685 #else 0686 connect(citeMapper, &QSignalMapper::mappedInt, this, &MainWindow::slotCiteEntry); 0687 #endif 0688 0689 action = actionCollection()->addAction(QStringLiteral("cite_clipboard"), citeMapper, mapVoid); 0690 action->setText(i18n("Copy Bibtex to Cli&pboard")); 0691 action->setToolTip(i18n("Copy bibtex citations to the clipboard")); 0692 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); 0693 citeMapper->setMapping(action, Cite::CiteClipboard); 0694 0695 action = actionCollection()->addAction(QStringLiteral("cite_lyxpipe"), citeMapper, mapVoid); 0696 action->setText(i18n("Cite Entry in &LyX")); 0697 action->setToolTip(i18n("Cite the selected entries in LyX")); 0698 action->setIcon(QIcon::fromTheme(QStringLiteral("lyx"), QIcon(QLatin1String(":/icons/lyx")))); 0699 citeMapper->setMapping(action, Cite::CiteLyxpipe); 0700 0701 m_updateMapper = new QSignalMapper(this); 0702 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0703 void (QSignalMapper::* mappedString)(const QString&) = &QSignalMapper::mapped; 0704 connect(m_updateMapper, mappedString, 0705 Controller::self(), &Controller::slotUpdateSelectedEntries); 0706 #else 0707 connect(m_updateMapper, &QSignalMapper::mappedString, 0708 Controller::self(), &Controller::slotUpdateSelectedEntries); 0709 #endif 0710 0711 m_updateEntryMenu = new KActionMenu(i18n("&Update Entry"), this); 0712 m_updateEntryMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); 0713 m_updateEntryMenu->setIconText(i18nc("Update Entry", "Update")); 0714 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,77,0) 0715 m_updateEntryMenu->setPopupMode(QToolButton::InstantPopup); 0716 #else 0717 m_updateEntryMenu->setDelayed(false); 0718 #endif 0719 actionCollection()->addAction(QStringLiteral("coll_update_entry"), m_updateEntryMenu); 0720 0721 m_updateAll = actionCollection()->addAction(QStringLiteral("update_entry_all"), m_updateMapper, mapVoid); 0722 m_updateAll->setText(i18n("All Sources")); 0723 m_updateAll->setToolTip(i18n("Update entry data from all available sources")); 0724 m_updateMapper->setMapping(m_updateAll, QStringLiteral("_all")); 0725 0726 /************************************************* 0727 * Settings menu 0728 *************************************************/ 0729 setStandardToolBarMenuEnabled(true); 0730 createStandardStatusBarAction(); 0731 0732 m_lockLayout = new KDualAction(this); 0733 connect(m_lockLayout, &KDualAction::activeChanged, this, &MainWindow::slotToggleLayoutLock); 0734 m_lockLayout->setActiveText(i18n("Unlock Layout")); 0735 m_lockLayout->setActiveToolTip(i18n("Unlock the window's layout")); 0736 m_lockLayout->setActiveIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); 0737 m_lockLayout->setInactiveText(i18n("Lock Layout")); 0738 m_lockLayout->setInactiveToolTip(i18n("Lock the window's layout")); 0739 m_lockLayout->setInactiveIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); 0740 actionCollection()->addAction(QStringLiteral("lock_layout"), m_lockLayout); 0741 0742 action = actionCollection()->addAction(QStringLiteral("reset_layout"), this, SLOT(slotResetLayout())); 0743 action->setText(i18n("Reset Layout")); 0744 action->setToolTip(i18n("Reset the window's layout")); 0745 action->setIcon(QIcon::fromTheme(QStringLiteral("resetview"))); 0746 0747 m_toggleEntryEditor = new KToggleAction(i18n("Entry &Editor"), this); 0748 connect(m_toggleEntryEditor, &QAction::triggered, this, &MainWindow::slotToggleEntryEditor); 0749 m_toggleEntryEditor->setToolTip(i18n("Enable/disable the editor")); 0750 actionCollection()->addAction(QStringLiteral("toggle_edit_widget"), m_toggleEntryEditor); 0751 0752 KStandardAction::preferences(this, SLOT(slotShowConfigDialog()), actionCollection()); 0753 0754 /************************************************* 0755 * Help menu 0756 *************************************************/ 0757 action = actionCollection()->addAction(QStringLiteral("show_log"), this, SLOT(showLog())); 0758 action->setText(i18n("Show Log")); 0759 action->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); 0760 if(Logger::self()->logFile().isEmpty()) { 0761 action->setEnabled(false); 0762 } 0763 0764 0765 /************************************************* 0766 * Short cuts 0767 *************************************************/ 0768 KStandardAction::fullScreen(this, SLOT(slotToggleFullScreen()), this, actionCollection()); 0769 KStandardAction::showMenubar(this, SLOT(slotToggleMenuBarVisibility()), actionCollection()); 0770 0771 /************************************************* 0772 * Collection Toolbar 0773 *************************************************/ 0774 action = actionCollection()->addAction(QStringLiteral("change_entry_grouping_accel"), this, SLOT(slotGroupLabelActivated())); 0775 action->setText(i18n("Change Grouping")); 0776 actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_G); 0777 0778 m_entryGrouping = new KSelectAction(i18n("&Group Selection"), this); 0779 m_entryGrouping->setToolTip(i18n("Change the grouping of the collection")); 0780 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,78,0) 0781 void (KSelectAction::* triggeredInt)(int) = &KSelectAction::indexTriggered; 0782 #else 0783 void (KSelectAction::* triggeredInt)(int) = &KSelectAction::triggered; 0784 #endif 0785 connect(m_entryGrouping, triggeredInt, this, &MainWindow::slotChangeGrouping); 0786 actionCollection()->addAction(QStringLiteral("change_entry_grouping"), m_entryGrouping); 0787 0788 action = actionCollection()->addAction(QStringLiteral("quick_filter_accel"), this, SLOT(slotFilterLabelActivated())); 0789 action->setText(i18n("Filter")); 0790 actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_F); 0791 0792 m_quickFilter = new GUI::LineEdit(this); 0793 m_quickFilter->setPlaceholderText(i18n("Filter here...")); // same text as kdepim and amarok 0794 m_quickFilter->setClearButtonEnabled(true); 0795 // same as Dolphin text edit 0796 m_quickFilter->setMinimumWidth(150); 0797 m_quickFilter->setMaximumWidth(300); 0798 // want to update every time the filter text changes 0799 connect(m_quickFilter, &QLineEdit::textChanged, 0800 this, &MainWindow::slotQueueFilter); 0801 connect(m_quickFilter, &KLineEdit::clearButtonClicked, 0802 this, &MainWindow::slotClearFilter); 0803 m_quickFilter->installEventFilter(this); // intercept keyEvents 0804 0805 QWidgetAction* widgetAction = new QWidgetAction(this); 0806 widgetAction->setDefaultWidget(m_quickFilter); 0807 widgetAction->setText(i18n("Filter")); 0808 widgetAction->setToolTip(i18n("Filter the collection")); 0809 widgetAction->setProperty("isShortcutConfigurable", false); 0810 actionCollection()->addAction(QStringLiteral("quick_filter"), widgetAction); 0811 0812 // final GUI setup is in initView() 0813 } 0814 0815 #undef mimeIcon 0816 0817 void MainWindow::initDocument() { 0818 MARK; 0819 Data::Document* doc = Data::Document::self(); 0820 Kernel::self()->resetHistory(); 0821 0822 KConfigGroup config(KSharedConfig::openConfig(), "General Options"); 0823 doc->setLoadAllImages(config.readEntry("Load All Images", false)); 0824 0825 // allow status messages from the document 0826 connect(doc, &Data::Document::signalStatusMsg, 0827 this, &MainWindow::slotStatusMsg); 0828 0829 // do stuff that changes when the doc is modified 0830 connect(doc, &Data::Document::signalModified, 0831 this, &MainWindow::slotEnableModifiedActions); 0832 0833 connect(doc, &Data::Document::signalCollectionAdded, 0834 Controller::self(), &Controller::slotCollectionAdded); 0835 connect(doc, &Data::Document::signalCollectionDeleted, 0836 Controller::self(), &Controller::slotCollectionDeleted); 0837 connect(doc, &Data::Document::signalCollectionModified, 0838 Controller::self(), &Controller::slotCollectionModified); 0839 0840 connect(Kernel::self()->commandHistory(), &QUndoStack::cleanChanged, 0841 doc, &Data::Document::slotSetClean); 0842 } 0843 0844 void MainWindow::initView() { 0845 MARK; 0846 // initialize the image factory before the entry models are created 0847 ImageFactory::init(); 0848 0849 m_entryView = new EntryView(this); 0850 connect(m_entryView, &EntryView::signalTellicoAction, 0851 this, &MainWindow::slotURLAction); 0852 0853 // trick to make sure the group views always extend along the entire left or right side 0854 // using QMainWindow::setCorner does not seem to work 0855 // https://wiki.qt.io/Technical_FAQ#Is_it_possible_for_either_the_left_or_right_dock_areas_to_have_full_height_of_their_side_rather_than_having_the_bottom_take_the_full_width.3F 0856 m_dummyWindow = new QMainWindow(this); 0857 #ifdef USE_KHTML 0858 m_entryView->view()->setWhatsThis(i18n("<qt>The <i>Entry View</i> shows a formatted view of the entry's contents.</qt>")); 0859 m_dummyWindow->setCentralWidget(m_entryView->view()); 0860 #else 0861 m_entryView->setWhatsThis(i18n("<qt>The <i>Entry View</i> shows a formatted view of the entry's contents.</qt>")); 0862 m_dummyWindow->setCentralWidget(m_entryView); 0863 #endif 0864 m_dummyWindow->setWindowFlags(Qt::Widget); 0865 setCentralWidget(m_dummyWindow); 0866 0867 m_collectionViewDock = new GUI::DockWidget(i18n("Collection View"), m_dummyWindow); 0868 m_collectionViewDock->setObjectName(QStringLiteral("collection_dock")); 0869 0870 m_viewStack = new ViewStack(this); 0871 0872 m_detailedView = m_viewStack->listView(); 0873 Controller::self()->addObserver(m_detailedView); 0874 m_detailedView->setWhatsThis(i18n("<qt>The <i>Column View</i> shows the value of multiple fields " 0875 "for each entry.</qt>")); 0876 connect(Data::Document::self(), &Data::Document::signalCollectionImagesLoaded, 0877 m_detailedView, &DetailedListView::slotRefreshImages); 0878 0879 m_iconView = m_viewStack->iconView(); 0880 EntryIconModel* iconModel = new EntryIconModel(m_iconView); 0881 iconModel->setSourceModel(m_detailedView->model()); 0882 m_iconView->setModel(iconModel); 0883 Controller::self()->addObserver(m_iconView); 0884 m_iconView->setWhatsThis(i18n("<qt>The <i>Icon View</i> shows each entry in the collection or group using " 0885 "an icon, which may be an image in the entry.</qt>")); 0886 0887 m_collectionViewDock->setWidget(m_viewStack); 0888 m_dummyWindow->addDockWidget(Qt::TopDockWidgetArea, m_collectionViewDock); 0889 actionCollection()->addAction(QStringLiteral("toggle_column_widget"), m_collectionViewDock->toggleViewAction()); 0890 0891 m_groupViewDock = new GUI::DockWidget(i18n("Group View"), this); 0892 m_groupViewDock->setObjectName(QStringLiteral("group_dock")); 0893 m_groupViewDock->setAllowedAreas(Qt::DockWidgetAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea)); 0894 0895 m_viewTabs = new GUI::TabWidget(this); 0896 m_viewTabs->setTabBarHidden(true); 0897 m_viewTabs->setDocumentMode(true); 0898 m_groupView = new GroupView(m_viewTabs); 0899 Controller::self()->addObserver(m_groupView); 0900 m_viewTabs->addTab(m_groupView, QIcon::fromTheme(QStringLiteral("folder")), i18n("Groups")); 0901 m_groupView->setWhatsThis(i18n("<qt>The <i>Group View</i> sorts the entries into groupings " 0902 "based on a selected field.</qt>")); 0903 m_groupViewDock->setWidget(m_viewTabs); 0904 addDockWidget(Qt::LeftDockWidgetArea, m_groupViewDock); 0905 actionCollection()->addAction(QStringLiteral("toggle_group_widget"), m_groupViewDock->toggleViewAction()); 0906 0907 EntrySelectionModel* proxySelect = new EntrySelectionModel(m_iconView->model(), 0908 m_detailedView->selectionModel(), 0909 this); 0910 m_iconView->setSelectionModel(proxySelect); 0911 0912 // setting up GUI now rather than in initActions 0913 // initial parameter is default window size 0914 setupGUI(QSize(1280,800), Keys | ToolBar); 0915 createGUI(); 0916 } 0917 0918 void MainWindow::initConnections() { 0919 // have to toggle the menu item if the dialog gets closed 0920 connect(m_editDialog, &QDialog::finished, 0921 this, &MainWindow::slotEditDialogFinished); 0922 0923 EntrySelectionModel* proxySelect = static_cast<EntrySelectionModel*>(m_iconView->selectionModel()); 0924 connect(proxySelect, &EntrySelectionModel::entriesSelected, 0925 Controller::self(), &Controller::slotUpdateSelection); 0926 connect(proxySelect, &EntrySelectionModel::entriesSelected, 0927 m_editDialog, &EntryEditDialog::setContents); 0928 connect(proxySelect, &EntrySelectionModel::entriesSelected, 0929 m_entryView, &EntryView::showEntries); 0930 0931 // let the group view call filters, too 0932 connect(m_groupView, &GroupView::signalUpdateFilter, 0933 this, &MainWindow::slotUpdateFilter); 0934 // use the EntrySelectionModel as a proxy so when entries get selected in the group view 0935 // the edit dialog and entry view are updated 0936 proxySelect->addSelectionProxy(m_groupView->selectionModel()); 0937 } 0938 0939 void MainWindow::initFileOpen(bool nofile_) { 0940 MARK; 0941 slotInit(); 0942 // check to see if most recent file should be opened 0943 bool happyStart = false; 0944 if(!nofile_ && Config::reopenLastFile()) { 0945 // Config::lastOpenFile() is the full URL, protocol included 0946 QUrl lastFile(Config::lastOpenFile()); // empty string is actually ok, it gets handled 0947 if(!lastFile.isEmpty() && lastFile.isValid()) { 0948 myLog() << "Opening previous file:" << lastFile.toDisplayString(QUrl::PreferLocalFile); 0949 slotFileOpen(lastFile); 0950 happyStart = true; 0951 } 0952 } 0953 if(!happyStart) { 0954 myLog() << "Creating default book collection"; 0955 // the document is created with an initial book collection, continue with that 0956 Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); 0957 0958 m_fileSave->setEnabled(false); 0959 slotEnableOpenedActions(); 0960 slotEnableModifiedActions(false); 0961 0962 slotEntryCount(); 0963 // tell the entry views and models that there are no images to load 0964 m_detailedView->slotRefreshImages(); 0965 } 0966 0967 // show welcome text, even when opening an existing collection 0968 const int type = Kernel::self()->collectionType(); 0969 QString welcomeFile = DataFileRegistry::self()->locate(QStringLiteral("welcome.html")); 0970 QString text = FileHandler::readTextFile(QUrl::fromLocalFile(welcomeFile)); 0971 text.replace(QLatin1String("$FGCOLOR$"), Config::templateTextColor(type).name()); 0972 text.replace(QLatin1String("$BGCOLOR$"), Config::templateBaseColor(type).name()); 0973 text.replace(QLatin1String("$COLOR1$"), Config::templateHighlightedTextColor(type).name()); 0974 text.replace(QLatin1String("$COLOR2$"), Config::templateHighlightedBaseColor(type).name()); 0975 text.replace(QLatin1String("$LINKCOLOR$"), Config::templateLinkColor(type).name()); 0976 text.replace(QLatin1String("$IMGDIR$"), QUrl::fromLocalFile(ImageFactory::imageDir()).url()); 0977 text.replace(QLatin1String("$BANNER$"), 0978 i18n("Welcome to the Tellico Collection Manager")); 0979 text.replace(QLatin1String("$WELCOMETEXT$"), 0980 i18n("<h3>Tellico is a tool for managing collections of books, " 0981 "videos, music, and whatever else you want to catalog.</h3>" 0982 "<h3>New entries can be added to your collection by " 0983 "<a href=\"tc:///coll_new_entry\">entering data manually</a> or by " 0984 "<a href=\"tc:///edit_search_internet\">downloading data</a> from " 0985 "various Internet sources.</h3>")); 0986 m_entryView->showText(text); 0987 0988 m_initialized = true; 0989 } 0990 0991 // These are general options. 0992 // The options that can be changed in the "Configuration..." dialog 0993 // are taken care of by the ConfigDialog object. 0994 void MainWindow::saveOptions() { 0995 KConfigGroup config(KSharedConfig::openConfig(), "Main Window Options"); 0996 saveMainWindowSettings(config); 0997 config.writeEntry(QStringLiteral("Central Dock State"), m_dummyWindow->saveState()); 0998 0999 Config::setShowEditWidget(m_toggleEntryEditor->isChecked()); 1000 // check any single dock widget, they all get locked together 1001 Config::setLockLayout(m_groupViewDock->isLocked()); 1002 1003 KConfigGroup filesConfig(KSharedConfig::openConfig(), "Recent Files"); 1004 m_fileOpenRecent->saveEntries(filesConfig); 1005 if(!isNewDocument()) { 1006 Config::setLastOpenFile(Data::Document::self()->URL().url()); 1007 } 1008 1009 Config::setViewWidget(m_viewStack->currentWidget()); 1010 1011 // historical reasons 1012 // sorting by count was faked by sorting by phantom second column 1013 const int sortColumn = m_groupView->sortRole() == RowCountRole ? 1 : 0; 1014 Config::setGroupViewSortColumn(sortColumn); // ok to use SortColumn key, save semantics 1015 Config::setGroupViewSortAscending(m_groupView->sortOrder() == Qt::AscendingOrder); 1016 1017 if(m_loanView) { 1018 const int sortColumn = m_loanView->sortRole() == RowCountRole ? 1 : 0; 1019 Config::setLoanViewSortAscending(sortColumn); // ok to use SortColumn key, save semantics 1020 Config::setLoanViewSortAscending(m_loanView->sortOrder() == Qt::AscendingOrder); 1021 } 1022 1023 if(m_filterView) { 1024 const int sortColumn = m_filterView->sortRole() == RowCountRole ? 1 : 0; 1025 Config::setFilterViewSortAscending(sortColumn); // ok to use SortColumn key, save semantics 1026 Config::setFilterViewSortAscending(m_filterView->sortOrder() == Qt::AscendingOrder); 1027 } 1028 1029 // this is used in the EntryEditDialog constructor, too 1030 KConfigGroup editDialogConfig(KSharedConfig::openConfig(), "Edit Dialog Options"); 1031 KWindowConfig::saveWindowSize(m_editDialog->windowHandle(), editDialogConfig); 1032 1033 saveCollectionOptions(Data::Document::self()->collection()); 1034 Config::self()->save(); 1035 } 1036 1037 void MainWindow::readCollectionOptions(Tellico::Data::CollPtr coll_) { 1038 if(!coll_) { 1039 myDebug() << "Bad, no collection in MainWindow::readCollectionOptions()"; 1040 return; 1041 } 1042 const QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(coll_)); 1043 KConfigGroup group(KSharedConfig::openConfig(), configGroup); 1044 1045 QString defaultGroup = coll_->defaultGroupField(); 1046 QString entryGroup, groupSortField; 1047 if(coll_->type() != Data::Collection::Base) { 1048 entryGroup = group.readEntry("Group By", defaultGroup); 1049 groupSortField = group.readEntry("GroupEntrySortField", QString()); 1050 } else { 1051 QUrl url = Kernel::self()->URL(); 1052 for(int i = 0; i < Config::maxCustomURLSettings(); ++i) { 1053 QUrl u(group.readEntry(QStringLiteral("URL_%1").arg(i))); 1054 if(url == u) { 1055 entryGroup = group.readEntry(QStringLiteral("Group By_%1").arg(i), defaultGroup); 1056 groupSortField = group.readEntry(QStringLiteral("GroupEntrySortField_%1").arg(i), QString()); 1057 break; 1058 } 1059 } 1060 // fall back to old setting 1061 if(entryGroup.isEmpty()) { 1062 entryGroup = group.readEntry("Group By", defaultGroup); 1063 } 1064 } 1065 if(entryGroup.isEmpty() || 1066 (!coll_->entryGroups().contains(entryGroup) && entryGroup != Data::Collection::s_peopleGroupName)) { 1067 entryGroup = defaultGroup; 1068 } 1069 m_groupView->setGroupField(entryGroup); 1070 1071 if(!groupSortField.isEmpty()) { 1072 m_groupView->setEntrySortField(groupSortField); 1073 } 1074 1075 QString entryXSLTFile; 1076 if(coll_->type() == Data::Collection::Base && 1077 Data::Document::self()->URL().fileName() != i18n(Tellico::untitledFilename)) { 1078 // use a nested config group for template specific to custom collections 1079 // using the filename alone as a keyEvents 1080 KConfigGroup subGroup(&group, Data::Document::self()->URL().fileName()); 1081 entryXSLTFile = subGroup.readEntry(QStringLiteral("Template Name")); 1082 } 1083 if(entryXSLTFile.isEmpty()) { 1084 // lookup by collection type 1085 entryXSLTFile = Config::templateName(coll_->type()); 1086 } 1087 if(entryXSLTFile.isEmpty()) { 1088 entryXSLTFile = QStringLiteral("Fancy"); // should never happen, but just in case 1089 } 1090 m_entryView->setXSLTFile(entryXSLTFile + QLatin1String(".xsl")); 1091 1092 // make sure the right combo element is selected 1093 slotUpdateCollectionToolBar(coll_); 1094 } 1095 1096 void MainWindow::saveCollectionOptions(Tellico::Data::CollPtr coll_) { 1097 // don't save initial collection options, or empty collections 1098 if(!coll_ || coll_->entryCount() == 0 || isNewDocument()) { 1099 return; 1100 } 1101 1102 int configIndex = -1; 1103 QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(coll_)); 1104 KConfigGroup config(KSharedConfig::openConfig(), configGroup); 1105 QString groupName; 1106 const QString groupEntrySort = m_groupView->entrySortField(); 1107 if(m_entryGrouping->currentItem() > -1 && 1108 static_cast<int>(coll_->entryGroups().count()) > m_entryGrouping->currentItem()) { 1109 if(m_entryGrouping->currentText() == (QLatin1Char('<') + i18n("People") + QLatin1Char('>'))) { 1110 groupName = Data::Collection::s_peopleGroupName; 1111 } else { 1112 groupName = Data::Document::self()->collection()->fieldNameByTitle(m_entryGrouping->currentText()); 1113 } 1114 if(coll_->type() != Data::Collection::Base) { 1115 config.writeEntry("Group By", groupName); 1116 if(!groupEntrySort.isEmpty()) { 1117 config.writeEntry("GroupEntrySortField", groupEntrySort); 1118 } 1119 } 1120 } 1121 1122 if(coll_->type() == Data::Collection::Base) { 1123 // all of this is to have custom settings on a per file basis 1124 QUrl url = Kernel::self()->URL(); 1125 QList<QUrl> urls = QList<QUrl>() << url; 1126 QStringList groupBys = QStringList() << groupName; 1127 QStringList groupSorts = QStringList() << groupEntrySort; 1128 for(int i = 0; i < Config::maxCustomURLSettings(); ++i) { 1129 QUrl u = config.readEntry(QStringLiteral("URL_%1").arg(i), QUrl()); 1130 if(!u.isEmpty() && url != u) { 1131 urls.append(u); 1132 QString g = config.readEntry(QStringLiteral("Group By_%1").arg(i), QString()); 1133 groupBys.append(g); 1134 QString gs = config.readEntry(QStringLiteral("GroupEntrySortField_%1").arg(i), QString()); 1135 groupSorts.append(gs); 1136 } else if(!u.isEmpty()) { 1137 configIndex = i; 1138 } 1139 } 1140 int limit = qMin(urls.count(), Config::maxCustomURLSettings()); 1141 for(int i = 0; i < limit; ++i) { 1142 config.writeEntry(QStringLiteral("URL_%1").arg(i), urls[i].url()); 1143 config.writeEntry(QStringLiteral("Group By_%1").arg(i), groupBys[i]); 1144 config.writeEntry(QStringLiteral("GroupEntrySortField_%1").arg(i), groupSorts[i]); 1145 } 1146 } 1147 m_detailedView->saveConfig(coll_, configIndex); 1148 } 1149 1150 void MainWindow::readOptions() { 1151 KConfigGroup mainWindowConfig(KSharedConfig::openConfig(), "Main Window Options"); 1152 applyMainWindowSettings(mainWindowConfig); 1153 m_dummyWindow->restoreState(mainWindowConfig.readEntry(QStringLiteral("Central Dock State"), QByteArray())); 1154 1155 m_viewStack->setCurrentWidget(Config::viewWidget()); 1156 m_iconView->setMaxAllowedIconWidth(Config::maxIconSize()); 1157 1158 connect(toolBar(QStringLiteral("collectionToolBar")), &QToolBar::iconSizeChanged, this, &MainWindow::slotUpdateToolbarIcons); 1159 1160 // initialize the recent file list 1161 KConfigGroup filesConfig(KSharedConfig::openConfig(), "Recent Files"); 1162 m_fileOpenRecent->loadEntries(filesConfig); 1163 1164 // sort by count if column = 1 1165 int sortRole = Config::groupViewSortColumn() == 0 ? static_cast<int>(Qt::DisplayRole) : static_cast<int>(RowCountRole); 1166 Qt::SortOrder sortOrder = Config::groupViewSortAscending() ? Qt::AscendingOrder : Qt::DescendingOrder; 1167 m_groupView->setSorting(sortOrder, sortRole); 1168 1169 BibtexHandler::s_quoteStyle = Config::useBraces() ? BibtexHandler::BRACES : BibtexHandler::QUOTES; 1170 1171 // Don't read any options for the edit dialog here, since it's not yet initialized. 1172 // Put them in init() 1173 } 1174 1175 bool MainWindow::querySaveModified() { 1176 bool completed = true; 1177 1178 if(Data::Document::self()->isModified()) { 1179 QString str = i18n("The current file has been modified.\n" 1180 "Do you want to save it?"); 1181 #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0) 1182 auto want_save = KMessageBox::warningYesNoCancel(this, str, i18n("Unsaved Changes"), 1183 KStandardGuiItem::save(), KStandardGuiItem::discard()); 1184 switch(want_save) { 1185 case KMessageBox::Yes: 1186 completed = fileSave(); 1187 break; 1188 1189 case KMessageBox::No: 1190 Data::Document::self()->setModified(false); 1191 completed = true; 1192 break; 1193 1194 case KMessageBox::Cancel: 1195 default: 1196 completed = false; 1197 break; 1198 } 1199 #else 1200 auto want_save = KMessageBox::warningTwoActionsCancel(this, str, i18n("Unsaved Changes"), 1201 KStandardGuiItem::save(), KStandardGuiItem::discard()); 1202 switch(want_save) { 1203 case KMessageBox::ButtonCode::PrimaryAction: 1204 completed = fileSave(); 1205 break; 1206 1207 case KMessageBox::ButtonCode::SecondaryAction: 1208 Data::Document::self()->setModified(false); 1209 completed = true; 1210 break; 1211 1212 case KMessageBox::ButtonCode::Cancel: 1213 default: 1214 completed = false; 1215 break; 1216 } 1217 #endif 1218 } 1219 1220 return completed; 1221 } 1222 1223 bool MainWindow::queryClose() { 1224 // in case we're still loading the images, cancel that 1225 Data::Document::self()->cancelImageWriting(); 1226 const bool willClose = m_editDialog->queryModified() && querySaveModified(); 1227 if(willClose) { 1228 ImageFactory::clean(true); 1229 saveOptions(); 1230 } 1231 return willClose; 1232 } 1233 1234 void MainWindow::slotFileNew(int type_) { 1235 slotStatusMsg(i18n("Creating new collection...")); 1236 1237 // close the fields dialog 1238 slotHideCollectionFieldsDialog(); 1239 1240 if(m_editDialog->queryModified() && querySaveModified()) { 1241 // remove filter and loan tabs, they'll get re-added if needed 1242 if(m_filterView) { 1243 m_viewTabs->removeTab(m_viewTabs->indexOf(m_filterView)); 1244 Controller::self()->removeObserver(m_filterView); 1245 delete m_filterView; 1246 m_filterView = nullptr; 1247 } 1248 if(m_loanView) { 1249 m_viewTabs->removeTab(m_viewTabs->indexOf(m_loanView)); 1250 Controller::self()->removeObserver(m_loanView); 1251 delete m_loanView; 1252 m_loanView = nullptr; 1253 } 1254 m_viewTabs->setTabBarHidden(true); 1255 Data::Document::self()->newDocument(type_); 1256 myLog() << "Creating new collection, type" << CollectionFactory::typeName(type_); 1257 Kernel::self()->resetHistory(); 1258 m_fileOpenRecent->setCurrentItem(-1); 1259 slotEnableOpenedActions(); 1260 slotEnableModifiedActions(false); 1261 m_newDocument = true; 1262 ImageFactory::clean(false); 1263 } 1264 1265 StatusBar::self()->clearStatus(); 1266 } 1267 1268 void MainWindow::slotFileNewByTemplate(const QString& collectionTemplate_) { 1269 slotStatusMsg(i18n("Creating new collection...")); 1270 1271 // close the fields dialog 1272 slotHideCollectionFieldsDialog(); 1273 1274 if(m_editDialog->queryModified() && querySaveModified()) { 1275 openURL(QUrl::fromLocalFile(collectionTemplate_)); 1276 myLog() << "Creating new collection from template:" << collectionTemplate_; 1277 Data::Document::self()->setURL(QUrl::fromLocalFile(i18n(Tellico::untitledFilename))); 1278 Kernel::self()->resetHistory(); 1279 m_fileOpenRecent->setCurrentItem(-1); 1280 slotEnableOpenedActions(); 1281 slotEnableModifiedActions(false); 1282 m_newDocument = true; 1283 ImageFactory::clean(false); 1284 } 1285 1286 StatusBar::self()->clearStatus(); 1287 } 1288 1289 void MainWindow::slotFileOpen() { 1290 slotStatusMsg(i18n("Opening file...")); 1291 1292 if(m_editDialog->queryModified() && querySaveModified()) { 1293 QString filter = i18n("Tellico Files") + QLatin1String(" (*.tc *.bc)"); 1294 filter += QLatin1String(";;"); 1295 filter += i18n("XML Files") + QLatin1String(" (*.xml)"); 1296 filter += QLatin1String(";;"); 1297 filter += i18n("All Files") + QLatin1String(" (*)"); 1298 // keyword 'open' 1299 QString fileClass; 1300 const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///open")), fileClass); 1301 QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open File"), startUrl, filter); 1302 if(!url.isEmpty() && url.isValid()) { 1303 myLog() << "Opening collection file:" << url.toDisplayString(QUrl::PreferLocalFile); 1304 slotFileOpen(url); 1305 if(url.isLocalFile()) { 1306 KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); 1307 } 1308 } 1309 } 1310 StatusBar::self()->clearStatus(); 1311 } 1312 1313 void MainWindow::slotFileOpen(const QUrl& url_) { 1314 slotStatusMsg(i18n("Opening file...")); 1315 1316 // close the fields dialog 1317 slotHideCollectionFieldsDialog(); 1318 1319 // there seems to be a race condition at start between slotInit() and initFileOpen() 1320 // which means the edit dialog might not have been created yet 1321 if((!m_editDialog || m_editDialog->queryModified()) && querySaveModified()) { 1322 if(openURL(url_)) { 1323 m_fileOpenRecent->addUrl(url_); 1324 m_fileOpenRecent->setCurrentItem(-1); 1325 } 1326 } 1327 1328 StatusBar::self()->clearStatus(); 1329 } 1330 1331 void MainWindow::slotFileOpenRecent(const QUrl& url_) { 1332 slotStatusMsg(i18n("Opening file...")); 1333 1334 // close the fields dialog 1335 slotHideCollectionFieldsDialog(); 1336 1337 if(m_editDialog->queryModified() && querySaveModified()) { 1338 if(!openURL(url_)) { 1339 m_fileOpenRecent->removeUrl(url_); 1340 m_fileOpenRecent->setCurrentItem(-1); 1341 } 1342 } else { 1343 // the QAction shouldn't be checked now 1344 m_fileOpenRecent->setCurrentItem(-1); 1345 } 1346 1347 StatusBar::self()->clearStatus(); 1348 } 1349 1350 void MainWindow::openFile(const QString& file_) { 1351 QUrl url(file_); 1352 if(!url.isEmpty() && url.isValid()) { 1353 slotFileOpen(url); 1354 } 1355 } 1356 1357 bool MainWindow::openURL(const QUrl& url_) { 1358 MARK; 1359 // try to open document 1360 GUI::CursorSaver cs(Qt::WaitCursor); 1361 1362 bool success = Data::Document::self()->openDocument(url_); 1363 1364 if(success) { 1365 Kernel::self()->resetHistory(); 1366 m_quickFilter->clear(); 1367 slotEnableOpenedActions(); 1368 m_newDocument = false; 1369 slotEnableModifiedActions(Data::Document::self()->isModified()); // doc might add some stuff 1370 } else if(!m_initialized) { 1371 // special case on startup when openURL() is called with a command line argument 1372 // and that URL can't be opened. The window still needs to be initialized 1373 // the doc object is created with an initial book collection, continue with that 1374 Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); 1375 1376 m_fileSave->setEnabled(false); 1377 slotEnableOpenedActions(); 1378 slotEnableModifiedActions(false); 1379 1380 slotEntryCount(); 1381 } 1382 // slotFileOpen(URL) gets called when opening files on the command line 1383 // so go ahead and make sure m_initialized is set. 1384 m_initialized = true; 1385 1386 // remove filter and loan tabs, they'll get re-added if needed 1387 if(m_filterView && m_filterView->isEmpty()) { 1388 m_viewTabs->removeTab(m_viewTabs->indexOf(m_filterView)); 1389 Controller::self()->removeObserver(m_filterView); 1390 delete m_filterView; 1391 m_filterView = nullptr; 1392 } 1393 if(m_loanView && m_loanView->isEmpty()) { 1394 m_viewTabs->removeTab(m_viewTabs->indexOf(m_loanView)); 1395 Controller::self()->removeObserver(m_loanView); 1396 delete m_loanView; 1397 m_loanView = nullptr; 1398 } 1399 Controller::self()->hideTabs(); // does conditional check 1400 1401 return success; 1402 } 1403 1404 void MainWindow::slotFileSave() { 1405 fileSave(); 1406 } 1407 1408 bool MainWindow::fileSave() { 1409 if(!m_editDialog->queryModified()) { 1410 return false; 1411 } 1412 slotStatusMsg(i18n("Saving file...")); 1413 1414 bool ret = true; 1415 if(isNewDocument()) { 1416 ret = fileSaveAs(); 1417 } else { 1418 // special check: if there are more than 200 images AND the "Write Images In File" config key 1419 // is set, then warn user that performance may suffer, and write result 1420 if(Config::imageLocation() == Config::ImagesInFile && 1421 Config::askWriteImagesInFile() && 1422 Data::Document::self()->imageCount() > MAX_IMAGES_WARN_PERFORMANCE) { 1423 QString msg = i18n("<qt><p>You are saving a file with many images, which causes Tellico to " 1424 "slow down significantly. Do you want to save the images separately in " 1425 "Tellico's data directory to improve performance?</p><p>Your choice can " 1426 "always be changed in the configuration dialog.</p></qt>"); 1427 1428 KGuiItem yes(i18n("Save Images Separately")); 1429 KGuiItem no(i18n("Save Images in File")); 1430 1431 #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0) 1432 auto res = KMessageBox::warningYesNo(this, msg, QString() /* caption */, yes, no); 1433 if(res == KMessageBox::No) { 1434 #else 1435 auto res = KMessageBox::warningTwoActions(this, msg, QString() /* caption */, yes, no); 1436 if(res == KMessageBox::ButtonCode::SecondaryAction) { 1437 #endif 1438 Config::setImageLocation(Config::ImagesInAppDir); 1439 } 1440 Config::setAskWriteImagesInFile(false); 1441 } 1442 1443 GUI::CursorSaver cs(Qt::WaitCursor); 1444 if(Data::Document::self()->saveDocument(Data::Document::self()->URL())) { 1445 Kernel::self()->resetHistory(); 1446 m_newDocument = false; 1447 updateCaption(false); 1448 m_fileSave->setEnabled(false); 1449 // TODO: call a method of the model instead of the view here 1450 m_detailedView->resetEntryStatus(); 1451 } else { 1452 ret = false; 1453 } 1454 } 1455 1456 StatusBar::self()->clearStatus(); 1457 return ret; 1458 } 1459 1460 void MainWindow::slotFileSaveAs() { 1461 fileSaveAs(); 1462 } 1463 1464 bool MainWindow::fileSaveAs() { 1465 if(!m_editDialog->queryModified()) { 1466 return false; 1467 } 1468 1469 slotStatusMsg(i18n("Saving file with a new filename...")); 1470 1471 QString filter = i18n("Tellico Files") + QLatin1String(" (*.tc *.bc)"); 1472 filter += QLatin1String(";;"); 1473 filter += i18n("All Files") + QLatin1String(" (*)"); 1474 1475 // keyword 'open' 1476 QString fileClass; 1477 const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///open")), fileClass); 1478 const QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save As"), startUrl, filter); 1479 1480 if(url.isEmpty()) { 1481 StatusBar::self()->clearStatus(); 1482 return false; 1483 } 1484 if(url.isLocalFile()) { 1485 KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); 1486 } 1487 1488 bool ret = true; 1489 if(url.isValid()) { 1490 GUI::CursorSaver cs(Qt::WaitCursor); 1491 m_savingImageLocationChange = true; 1492 // Overwriting an existing file was already confirmed in QFileDialog::getSaveFileUrl() 1493 if(Data::Document::self()->saveDocument(url, true /* force */)) { 1494 Kernel::self()->resetHistory(); 1495 KRecentDocument::add(url); 1496 m_fileOpenRecent->addUrl(url); 1497 updateCaption(false); 1498 m_newDocument = false; 1499 m_fileSave->setEnabled(false); 1500 m_detailedView->resetEntryStatus(); 1501 } else { 1502 ret = false; 1503 } 1504 m_savingImageLocationChange = false; 1505 } 1506 1507 StatusBar::self()->clearStatus(); 1508 return ret; 1509 } 1510 1511 void MainWindow::slotFileSaveAsTemplate() { 1512 QScopedPointer<CollectionTemplateDialog> dlg(new CollectionTemplateDialog(this)); 1513 if(dlg->exec() != QDialog::Accepted) { 1514 return; 1515 } 1516 1517 const QString templateName = dlg->templateName(); 1518 if(templateName.isEmpty()) { 1519 return; 1520 } 1521 const QString baseName = Tellico::saveLocation(QStringLiteral("collection-templates/")) + templateName; 1522 1523 // first, save the collection template, which copies the collection fields and filters, but nothing else 1524 const QString collFile = baseName + QLatin1String(".tc"); 1525 Data::Document::self()->saveDocumentTemplate(QUrl::fromLocalFile(collFile), templateName); 1526 1527 // next, save the template descriptions in a config file 1528 const QString specFile = baseName + QLatin1String(".spec"); 1529 auto spec = KSharedConfig::openConfig(specFile, KConfig::SimpleConfig)->group(QString()); 1530 spec.writeEntry("Name", templateName); 1531 spec.writeEntry("Comment", dlg->templateComment()); 1532 spec.writeEntry("Icon", dlg->templateIcon()); 1533 } 1534 1535 void MainWindow::slotFilePrint() { 1536 doPrint(Print); 1537 } 1538 1539 void MainWindow::slotFilePrintPreview() { 1540 doPrint(PrintPreview); 1541 } 1542 1543 void MainWindow::doPrint(PrintAction action_) { 1544 slotStatusMsg(i18n("Printing...")); 1545 1546 // If the collection is being filtered, warn the user 1547 if(m_detailedView->filter()) { 1548 QString str = i18n("The collection is currently being filtered to show a limited subset of " 1549 "the entries. Only the visible entries will be printed. Continue?"); 1550 int ret = KMessageBox::warningContinueCancel(this, str, QString(), KStandardGuiItem::print(), 1551 KStandardGuiItem::cancel(), QStringLiteral("WarnPrintVisible")); 1552 if(ret == KMessageBox::Cancel) { 1553 StatusBar::self()->clearStatus(); 1554 return; 1555 } 1556 } 1557 1558 PrintHandler printHandler(this); 1559 printHandler.setEntries(m_detailedView->visibleEntries()); 1560 printHandler.setColumns(m_detailedView->visibleColumns()); 1561 if(action_ == Print) { 1562 printHandler.print(); 1563 } else { 1564 printHandler.printPreview(); 1565 } 1566 1567 StatusBar::self()->clearStatus(); 1568 } 1569 1570 void MainWindow::slotFileQuit() { 1571 slotStatusMsg(i18n("Exiting...")); 1572 1573 close(); // will call queryClose() 1574 1575 StatusBar::self()->clearStatus(); 1576 } 1577 1578 void MainWindow::slotEditCut() { 1579 activateEditSlot("cut()"); 1580 } 1581 1582 void MainWindow::slotEditCopy() { 1583 activateEditSlot("copy()"); 1584 } 1585 1586 void MainWindow::slotEditPaste() { 1587 activateEditSlot("paste()"); 1588 } 1589 1590 void MainWindow::activateEditSlot(const char* slot_) { 1591 // the edit widget is the only one that copies, cuts, and pastes 1592 // the entry view can copy 1593 QWidget* w; 1594 if(m_editDialog->isVisible()) { 1595 w = m_editDialog->focusWidget(); 1596 } else { 1597 w = qApp->focusWidget(); 1598 } 1599 1600 while(w && w->isVisible()) { 1601 const QMetaObject* meta = w->metaObject(); 1602 const int idx = meta->indexOfSlot(slot_); 1603 if(idx > -1) { 1604 // myDebug() << "MainWindow invoking" << meta->method(idx).methodSignature(); 1605 meta->method(idx).invoke(w, Qt::DirectConnection); 1606 break; 1607 } else { 1608 // myDebug() << "did not find" << slot_ << "in" << meta->className(); 1609 w = qobject_cast<QWidget*>(w->parent()); 1610 } 1611 } 1612 } 1613 1614 void MainWindow::slotEditSelectAll() { 1615 m_detailedView->selectAllVisible(); 1616 } 1617 1618 void MainWindow::slotEditDeselect() { 1619 Controller::self()->slotUpdateSelection(Data::EntryList()); 1620 } 1621 1622 void MainWindow::slotToggleEntryEditor() { 1623 if(m_toggleEntryEditor->isChecked()) { 1624 m_editDialog->show(); 1625 } else { 1626 m_editDialog->hide(); 1627 } 1628 } 1629 1630 void MainWindow::slotShowConfigDialog() { 1631 if(!m_configDlg) { 1632 m_configDlg = new ConfigDialog(this); 1633 connect(m_configDlg, &ConfigDialog::signalConfigChanged, 1634 this, &MainWindow::slotHandleConfigChange); 1635 connect(m_configDlg, &QDialog::finished, 1636 this, &MainWindow::slotHideConfigDialog); 1637 } else { 1638 activateDialog(m_configDlg); 1639 } 1640 m_configDlg->show(); 1641 } 1642 1643 void MainWindow::slotHideConfigDialog() { 1644 if(m_configDlg) { 1645 m_configDlg->hide(); 1646 m_configDlg->deleteLater(); 1647 m_configDlg = nullptr; 1648 } 1649 } 1650 1651 void MainWindow::slotStatusMsg(const QString& text_) { 1652 m_statusBar->setStatus(text_); 1653 } 1654 1655 void MainWindow::slotClearStatus() { 1656 StatusBar::self()->clearStatus(); 1657 } 1658 1659 void MainWindow::slotEntryCount() { 1660 Data::CollPtr coll = Data::Document::self()->collection(); 1661 if(!coll) { 1662 return; 1663 } 1664 1665 int count = coll->entryCount(); 1666 QString text = i18n("Total entries: %1", count); 1667 1668 int selectCount = Controller::self()->selectedEntries().count(); 1669 int filterCount = m_detailedView->visibleItems(); 1670 // if more than one book is selected, add the number of selected books 1671 if(filterCount < count && selectCount > 1) { 1672 text += QLatin1Char(' '); 1673 text += i18n("(%1 filtered; %2 selected)", filterCount, selectCount); 1674 } else if(filterCount < count) { 1675 text += QLatin1Char(' '); 1676 text += i18n("(%1 filtered)", filterCount); 1677 } else if(selectCount > 1) { 1678 text += QLatin1Char(' '); 1679 text += i18n("(%1 selected)", selectCount); 1680 } 1681 1682 m_statusBar->setCount(text); 1683 } 1684 1685 void MainWindow::slotEnableOpenedActions() { 1686 slotUpdateToolbarIcons(); 1687 1688 updateCollectionActions(); 1689 1690 // close the filter dialog when a new collection is opened 1691 slotHideFilterDialog(); 1692 slotStringMacroDialogFinished(); 1693 } 1694 1695 void MainWindow::slotEnableModifiedActions(bool modified_ /*= true*/) { 1696 updateCaption(modified_); 1697 updateCollectionActions(); 1698 m_fileSave->setEnabled(modified_); 1699 } 1700 1701 void MainWindow::slotHandleConfigChange() { 1702 const int imageLocation = Config::imageLocation(); 1703 const bool autoCapitalize = Config::autoCapitalization(); 1704 const bool autoFormat = Config::autoFormat(); 1705 const QStringList articles = Config::articleList(); 1706 const QStringList nocaps = Config::noCapitalizationList(); 1707 const QStringList suffixes = Config::nameSuffixList(); 1708 const QStringList prefixes = Config::surnamePrefixList(); 1709 1710 m_configDlg->saveConfiguration(); 1711 1712 // only modified if there are entries and image location is changed 1713 if(imageLocation != Config::imageLocation() && !Data::Document::self()->isEmpty()) { 1714 slotImageLocationChanged(); 1715 } 1716 1717 if(autoCapitalize != Config::autoCapitalization() || 1718 autoFormat != Config::autoFormat() || 1719 articles != Config::articleList() || 1720 nocaps != Config::noCapitalizationList() || 1721 suffixes != Config::nameSuffixList() || 1722 prefixes != Config::surnamePrefixList()) { 1723 // invalidate all groups 1724 Data::Document::self()->collection()->invalidateGroups(); 1725 // refreshing the title causes the group view to refresh 1726 Controller::self()->slotRefreshField(Data::Document::self()->collection()->fieldByName(QStringLiteral("title"))); 1727 } 1728 1729 QString entryXSLTFile = Config::templateName(Kernel::self()->collectionType()); 1730 m_entryView->setXSLTFile(entryXSLTFile + QLatin1String(".xsl")); 1731 } 1732 1733 void MainWindow::slotUpdateCollectionToolBar(Tellico::Data::CollPtr coll_) { 1734 if(!coll_) { 1735 myWarning() << "no collection pointer!"; 1736 return; 1737 } 1738 1739 QString current = m_groupView->groupBy(); 1740 if(current.isEmpty() || !coll_->entryGroups().contains(current)) { 1741 current = coll_->defaultGroupField(); 1742 } 1743 1744 const QStringList groups = coll_->entryGroups(); 1745 if(groups.isEmpty()) { 1746 m_entryGrouping->clear(); 1747 return; 1748 } 1749 1750 QMap<QString, QString> groupMap; // use a map so they get sorted 1751 foreach(const QString& groupName, groups) { 1752 // special case for people "pseudo-group" 1753 if(groupName == Data::Collection::s_peopleGroupName) { 1754 groupMap.insert(groupName, QLatin1Char('<') + i18n("People") + QLatin1Char('>')); 1755 } else { 1756 groupMap.insert(groupName, coll_->fieldTitleByName(groupName)); 1757 } 1758 } 1759 1760 const QStringList titles = groupMap.values(); 1761 if(titles == m_entryGrouping->items()) { 1762 // no need to update anything 1763 return; 1764 } 1765 const QStringList names = groupMap.keys(); 1766 int index = names.indexOf(current); 1767 if(index == -1) { 1768 current = names[0]; 1769 index = 0; 1770 } 1771 m_entryGrouping->setItems(titles); 1772 m_entryGrouping->setCurrentItem(index); 1773 // in case the current grouping field get modified to be non-grouping... 1774 m_groupView->setGroupField(current); // don't call slotChangeGrouping() since it adds an undo item 1775 1776 // TODO::I have no idea how to get the combobox to update its size 1777 // this is the hackiest of hacks, taken from KXmlGuiWindow::saveNewToolbarConfig() 1778 // the window flickers as toolbar resizes, unavoidable? 1779 // crashes if removeClient//addClient is called here, need to do later in event loop 1780 QTimer::singleShot(0, this, &MainWindow::guiFactoryReset); 1781 } 1782 1783 void MainWindow::slotChangeGrouping() { 1784 const QString title = m_entryGrouping->currentText(); 1785 1786 QString groupName = Data::Document::self()->collection()->fieldNameByTitle(title); 1787 if(groupName.isEmpty()) { 1788 if(title == (QLatin1Char('<') + i18n("People") + QLatin1Char('>'))) { 1789 groupName = Data::Collection::s_peopleGroupName; 1790 } else { 1791 groupName = Data::Document::self()->collection()->defaultGroupField(); 1792 } 1793 } 1794 m_groupView->setGroupField(groupName); 1795 m_viewTabs->setCurrentWidget(m_groupView); 1796 } 1797 1798 void MainWindow::slotShowReportDialog() { 1799 if(!m_reportDlg) { 1800 m_reportDlg = new ReportDialog(this); 1801 connect(m_reportDlg, &QDialog::finished, 1802 this, &MainWindow::slotHideReportDialog); 1803 } else { 1804 activateDialog(m_reportDlg); 1805 } 1806 m_reportDlg->show(); 1807 } 1808 1809 void MainWindow::slotHideReportDialog() { 1810 if(m_reportDlg) { 1811 m_reportDlg->hide(); 1812 m_reportDlg->deleteLater(); 1813 m_reportDlg = nullptr; 1814 } 1815 } 1816 1817 void MainWindow::XSLTError() { 1818 QString str = i18n("Tellico encountered an error in XSLT processing.") + QLatin1Char('\n'); 1819 str += i18n("Please check your installation."); 1820 Kernel::self()->sorry(str); 1821 } 1822 1823 void MainWindow::slotShowFilterDialog() { 1824 if(!m_filterDlg) { 1825 m_filterDlg = new FilterDialog(FilterDialog::CreateFilter, this); // allow saving 1826 m_quickFilter->setEnabled(false); 1827 connect(m_filterDlg, &FilterDialog::signalCollectionModified, 1828 Data::Document::self(), &Data::Document::slotSetModified); 1829 connect(m_filterDlg, &FilterDialog::signalUpdateFilter, 1830 this, &MainWindow::slotUpdateFilter); 1831 connect(m_filterDlg, &QDialog::finished, 1832 this, &MainWindow::slotHideFilterDialog); 1833 } else { 1834 activateDialog(m_filterDlg); 1835 } 1836 m_filterDlg->setFilter(m_detailedView->filter()); 1837 m_filterDlg->show(); 1838 } 1839 1840 void MainWindow::slotHideFilterDialog() { 1841 // m_quickFilter->blockSignals(false); 1842 m_quickFilter->setEnabled(true); 1843 if(m_filterDlg) { 1844 m_filterDlg->hide(); 1845 m_filterDlg->deleteLater(); 1846 m_filterDlg = nullptr; 1847 } 1848 } 1849 1850 void MainWindow::slotQueueFilter() { 1851 if(m_dontQueueFilter) { 1852 return; 1853 } 1854 m_queuedFilters++; 1855 QTimer::singleShot(200, this, &MainWindow::slotCheckFilterQueue); 1856 } 1857 1858 void MainWindow::slotCheckFilterQueue() { 1859 m_queuedFilters--; 1860 if(m_queuedFilters > 0) { 1861 return; 1862 } 1863 1864 setFilter(m_quickFilter->text()); 1865 } 1866 1867 void MainWindow::slotUpdateFilter(FilterPtr filter_) { 1868 // Can't just block signals because clear button won't show then 1869 m_dontQueueFilter = true; 1870 if(filter_) { 1871 // for a saved filter, show the filter name and a leading icon 1872 if(m_quickFilter->actions().isEmpty()) { 1873 m_quickFilter->addAction(QIcon::fromTheme(QStringLiteral("view-filter")), QLineEdit::LeadingPosition); 1874 } 1875 m_quickFilter->setText(QLatin1Char('<') + filter_->name() + QLatin1Char('>')); 1876 } else { 1877 m_quickFilter->setText(QStringLiteral(" ")); // To be able to clear custom filter 1878 } 1879 Controller::self()->slotUpdateFilter(filter_); 1880 m_dontQueueFilter = false; 1881 } 1882 1883 void MainWindow::setFilter(const QString& text_) { 1884 // might have an "action" associated if a saved filter was displayed 1885 auto actions = m_quickFilter->actions(); 1886 if(!actions.isEmpty()) { 1887 // clear all of the saved filter name 1888 slotClearFilter(); 1889 return; 1890 } 1891 QString text = text_.trimmed(); 1892 FilterPtr filter; 1893 if(!text.isEmpty()) { 1894 filter = new Filter(Filter::MatchAll); 1895 QString fieldName; // empty field name means match on any field 1896 // if the text contains '=' assume it's a field name or title 1897 if(text.indexOf(QLatin1Char('=')) > -1) { 1898 fieldName = text.section(QLatin1Char('='), 0, 0).trimmed(); 1899 text = text.section(QLatin1Char('='), 1).trimmed(); 1900 // check that the field name might be a title 1901 if(!Data::Document::self()->collection()->hasField(fieldName)) { 1902 fieldName = Data::Document::self()->collection()->fieldNameByTitle(fieldName); 1903 } 1904 } 1905 Filter::populateQuickFilter(filter, fieldName, text, Config::quickFilterRegExp()); 1906 // also want to update the line edit in case the filter was set by DBUS 1907 if(m_quickFilter->text() != text_) { 1908 m_quickFilter->setText(text_); 1909 } 1910 } 1911 // only update filter if one exists or did exist 1912 if(filter || m_detailedView->filter()) { 1913 Controller::self()->slotUpdateFilter(filter); 1914 } 1915 } 1916 1917 void MainWindow::slotShowCollectionFieldsDialog() { 1918 if(!m_collFieldsDlg) { 1919 m_collFieldsDlg = new CollectionFieldsDialog(Data::Document::self()->collection(), this); 1920 m_collFieldsDlg->setNotifyKernel(true); 1921 connect(m_collFieldsDlg, &CollectionFieldsDialog::beginCommandGroup, 1922 Kernel::self(), &Kernel::beginCommandGroup); 1923 connect(m_collFieldsDlg, &CollectionFieldsDialog::endCommandGroup, 1924 Kernel::self(), &Kernel::endCommandGroup); 1925 connect(m_collFieldsDlg, &CollectionFieldsDialog::addField, 1926 Kernel::self(), &Kernel::addField); 1927 connect(m_collFieldsDlg, &CollectionFieldsDialog::modifyField, 1928 Kernel::self(), &Kernel::modifyField); 1929 connect(m_collFieldsDlg, &CollectionFieldsDialog::removeField, 1930 Kernel::self(), &Kernel::removeField); 1931 connect(m_collFieldsDlg, &CollectionFieldsDialog::reorderFields, 1932 Kernel::self(), &Kernel::reorderFields); 1933 connect(m_collFieldsDlg, &QDialog::finished, 1934 this, &MainWindow::slotHideCollectionFieldsDialog); 1935 } else { 1936 activateDialog(m_collFieldsDlg); 1937 } 1938 m_collFieldsDlg->show(); 1939 } 1940 1941 void MainWindow::slotHideCollectionFieldsDialog() { 1942 if(m_collFieldsDlg) { 1943 m_collFieldsDlg->hide(); 1944 m_collFieldsDlg->deleteLater(); 1945 m_collFieldsDlg = nullptr; 1946 } 1947 } 1948 1949 void MainWindow::slotFileImport(int format_) { 1950 slotStatusMsg(i18n("Importing data...")); 1951 m_quickFilter->clear(); 1952 1953 Import::Format format = static_cast<Import::Format>(format_); 1954 bool checkURL = true; 1955 QUrl url; 1956 switch(ImportDialog::importTarget(format)) { 1957 case Import::File: 1958 { 1959 QString fileClass; 1960 const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///import")), fileClass); 1961 url = QFileDialog::getOpenFileUrl(this, i18n("Import File"), startUrl, ImportDialog::fileFilter(format)); 1962 KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); 1963 } 1964 break; 1965 1966 case Import::Dir: 1967 // TODO: allow remote audiofile importing 1968 { 1969 const QString fileClass(QStringLiteral("ImportDir")); 1970 QString dirName = ImportDialog::startDir(format); 1971 if(dirName.isEmpty()) { 1972 dirName = KRecentDirs::dir(fileClass); 1973 } 1974 QString chosenDir = QFileDialog::getExistingDirectory(this, i18n("Import Directory"), dirName); 1975 url = QUrl::fromLocalFile(chosenDir); 1976 KRecentDirs::add(fileClass, chosenDir); 1977 } 1978 break; 1979 1980 case Import::None: 1981 default: 1982 checkURL = false; 1983 break; 1984 } 1985 1986 if(checkURL) { 1987 bool ok = !url.isEmpty() && url.isValid() && QFile::exists(url.toLocalFile()); 1988 if(!ok) { 1989 StatusBar::self()->clearStatus(); 1990 return; 1991 } 1992 } 1993 importFile(format, QList<QUrl>() << url); 1994 StatusBar::self()->clearStatus(); 1995 } 1996 1997 void MainWindow::slotFileExport(int format_) { 1998 slotStatusMsg(i18n("Exporting data...")); 1999 2000 Export::Format format = static_cast<Export::Format>(format_); 2001 ExportDialog dlg(format, Data::Document::self()->collection(), this); 2002 2003 if(dlg.exec() == QDialog::Rejected) { 2004 StatusBar::self()->clearStatus(); 2005 return; 2006 } 2007 2008 switch(ExportDialog::exportTarget(format)) { 2009 case Export::None: 2010 dlg.exportURL(); 2011 break; 2012 2013 case Export::Dir: 2014 myDebug() << "ExportDir not implemented!"; 2015 break; 2016 2017 case Export::File: 2018 { 2019 QString fileClass; 2020 const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///export")), fileClass); 2021 QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Export As"), startUrl, dlg.fileFilter()); 2022 if(url.isEmpty()) { 2023 StatusBar::self()->clearStatus(); 2024 return; 2025 } 2026 2027 if(url.isValid()) { 2028 if(url.isLocalFile()) { 2029 KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); 2030 } 2031 GUI::CursorSaver cs(Qt::WaitCursor); 2032 dlg.exportURL(url); 2033 } 2034 } 2035 break; 2036 } 2037 2038 StatusBar::self()->clearStatus(); 2039 } 2040 2041 void MainWindow::slotShowStringMacroDialog() { 2042 if(Data::Document::self()->collection()->type() != Data::Collection::Bibtex) { 2043 return; 2044 } 2045 2046 if(!m_stringMacroDlg) { 2047 const Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(Data::Document::self()->collection().data()); 2048 m_stringMacroDlg = new StringMapDialog(c->macroList(), this, false); 2049 m_stringMacroDlg->setWindowTitle(i18n("String Macros")); 2050 m_stringMacroDlg->setLabels(i18n("Macro"), i18n("String")); 2051 connect(m_stringMacroDlg, &QDialog::finished, this, &MainWindow::slotStringMacroDialogFinished); 2052 } else { 2053 activateDialog(m_stringMacroDlg); 2054 } 2055 m_stringMacroDlg->show(); 2056 } 2057 2058 void MainWindow::slotStringMacroDialogFinished(int result_) { 2059 // no point in checking if collection is bibtex, as dialog would never have been created 2060 if(!m_stringMacroDlg) { 2061 return; 2062 } 2063 if(result_ == QDialog::Accepted) { 2064 static_cast<Data::BibtexCollection*>(Data::Document::self()->collection().data())->setMacroList(m_stringMacroDlg->stringMap()); 2065 Data::Document::self()->setModified(true); 2066 } 2067 m_stringMacroDlg->hide(); 2068 m_stringMacroDlg->deleteLater(); 2069 m_stringMacroDlg = nullptr; 2070 } 2071 2072 void MainWindow::slotShowBibtexKeyDialog() { 2073 if(Data::Document::self()->collection()->type() != Data::Collection::Bibtex) { 2074 return; 2075 } 2076 2077 if(!m_bibtexKeyDlg) { 2078 m_bibtexKeyDlg = new BibtexKeyDialog(Data::Document::self()->collection(), this); 2079 connect(m_bibtexKeyDlg, &QDialog::finished, this, &MainWindow::slotHideBibtexKeyDialog); 2080 connect(m_bibtexKeyDlg, &BibtexKeyDialog::signalUpdateFilter, 2081 this, &MainWindow::slotUpdateFilter); 2082 } else { 2083 activateDialog(m_bibtexKeyDlg); 2084 } 2085 m_bibtexKeyDlg->show(); 2086 } 2087 2088 void MainWindow::slotHideBibtexKeyDialog() { 2089 if(m_bibtexKeyDlg) { 2090 m_bibtexKeyDlg->deleteLater(); 2091 m_bibtexKeyDlg = nullptr; 2092 } 2093 } 2094 2095 void MainWindow::slotNewEntry() { 2096 m_toggleEntryEditor->setChecked(true); 2097 slotToggleEntryEditor(); 2098 m_editDialog->slotHandleNew(); 2099 } 2100 2101 void MainWindow::slotEditDialogFinished() { 2102 m_toggleEntryEditor->setChecked(false); 2103 } 2104 2105 void MainWindow::slotShowEntryEditor() { 2106 m_toggleEntryEditor->setChecked(true); 2107 m_editDialog->show(); 2108 activateDialog(m_editDialog); 2109 } 2110 2111 void MainWindow::slotConvertToBibliography() { 2112 // only book collections can be converted to bibtex 2113 Data::CollPtr coll = Data::Document::self()->collection(); 2114 if(!coll || coll->type() != Data::Collection::Book) { 2115 return; 2116 } 2117 2118 GUI::CursorSaver cs; 2119 2120 // need to make sure all images are transferred 2121 Data::Document::self()->loadAllImagesNow(); 2122 2123 Data::CollPtr newColl = Data::BibtexCollection::convertBookCollection(coll); 2124 if(newColl) { 2125 m_newDocument = true; 2126 Kernel::self()->replaceCollection(newColl); 2127 m_fileOpenRecent->setCurrentItem(-1); 2128 slotUpdateToolbarIcons(); 2129 updateCollectionActions(); 2130 } else { 2131 myWarning() << "ERROR: no bibliography created!"; 2132 } 2133 } 2134 2135 void MainWindow::slotCiteEntry(int action_) { 2136 StatusBar::self()->setStatus(i18n("Creating citations...")); 2137 Cite::ActionManager* man = Cite::ActionManager::self(); 2138 man->cite(static_cast<Cite::CiteAction>(action_), Controller::self()->selectedEntries()); 2139 if(man->hasError()) { 2140 Kernel::self()->sorry(man->errorString()); 2141 } 2142 StatusBar::self()->clearStatus(); 2143 } 2144 2145 void MainWindow::slotShowFetchDialog() { 2146 if(!m_fetchDlg) { 2147 m_fetchDlg = new FetchDialog(this); 2148 connect(m_fetchDlg, &QDialog::finished, this, &MainWindow::slotHideFetchDialog); 2149 connect(Controller::self(), &Controller::collectionAdded, m_fetchDlg, &FetchDialog::slotResetCollection); 2150 } else { 2151 activateDialog(m_fetchDlg); 2152 } 2153 m_fetchDlg->show(); 2154 } 2155 2156 void MainWindow::slotHideFetchDialog() { 2157 if(m_fetchDlg) { 2158 m_fetchDlg->hide(); 2159 m_fetchDlg->deleteLater(); 2160 m_fetchDlg = nullptr; 2161 } 2162 } 2163 2164 bool MainWindow::importFile(Tellico::Import::Format format_, const QUrl& url_, Tellico::Import::Action action_) { 2165 // try to open document 2166 GUI::CursorSaver cs(Qt::WaitCursor); 2167 2168 bool failed = false; 2169 Data::CollPtr coll; 2170 if(!url_.isEmpty() && url_.isValid() && NetAccess::exists(url_, true, this)) { 2171 coll = ImportDialog::importURL(format_, url_); 2172 } else { 2173 Kernel::self()->sorry(i18n(errorLoad, url_.fileName())); 2174 failed = true; 2175 } 2176 2177 if(!coll && !m_initialized) { 2178 // special case on startup when openURL() is called with a command line argument 2179 // and that URL can't be opened. The window still needs to be initialized 2180 // the doc object is created with an initial book collection, continue with that 2181 Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); 2182 m_fileSave->setEnabled(false); 2183 slotEnableOpenedActions(); 2184 slotEnableModifiedActions(false); 2185 slotEntryCount(); 2186 m_fileOpenRecent->setCurrentItem(-1); 2187 m_initialized = true; 2188 failed = true; 2189 } else if(coll) { 2190 // this is rather dumb, but I'm too lazy to find the bug 2191 // if the document isn't initialized, then Tellico crashes 2192 // since Document::replaceCollection() ends up calling lots of stuff that isn't initialized 2193 if(!m_initialized) { 2194 Controller::self()->slotCollectionAdded(Data::Document::self()->collection()); 2195 m_initialized = true; 2196 } 2197 failed = !importCollection(coll, action_); 2198 } 2199 2200 StatusBar::self()->clearStatus(); 2201 return !failed; // return true means success 2202 } 2203 2204 bool MainWindow::exportCollection(Tellico::Export::Format format_, const QUrl& url_, bool filtered_) { 2205 if(!url_.isValid()) { 2206 myDebug() << "invalid URL:" << url_; 2207 return false; 2208 } 2209 2210 GUI::CursorSaver cs; 2211 const Data::CollPtr coll = Data::Document::self()->collection(); 2212 if(!coll) { 2213 return false; 2214 } 2215 2216 // only bibliographies can export to bibtex or bibtexml 2217 const bool isBibtex = (coll->type() == Data::Collection::Bibtex); 2218 if(!isBibtex && (format_ == Export::Bibtex || format_ == Export::Bibtexml)) { 2219 return false; 2220 } 2221 // only books and bibliographies can export to alexandria 2222 const bool isBook = (coll->type() == Data::Collection::Book); 2223 if(!isBibtex && !isBook && format_ == Export::Alexandria) { 2224 return false; 2225 } 2226 2227 return ExportDialog::exportCollection(coll, filtered_ ? Controller::self()->visibleEntries() : coll->entries(), 2228 format_, url_); 2229 } 2230 2231 bool MainWindow::showEntry(Data::ID id) { 2232 Data::EntryPtr entry = Data::Document::self()->collection()->entryById(id); 2233 if(entry) { 2234 m_entryView->showEntry(entry); 2235 } 2236 return entry; 2237 } 2238 2239 void MainWindow::addFilterView() { 2240 if(m_filterView) { 2241 return; 2242 } 2243 2244 m_filterView = new FilterView(m_viewTabs); 2245 Controller::self()->addObserver(m_filterView); 2246 m_viewTabs->insertTab(1, m_filterView, QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Filters")); 2247 m_filterView->setWhatsThis(i18n("<qt>The <i>Filter View</i> shows the entries which meet certain " 2248 "filter rules.</qt>")); 2249 2250 connect(m_filterView, &FilterView::signalUpdateFilter, 2251 this, &MainWindow::slotUpdateFilter); 2252 // use the EntrySelectionModel as a proxy so when entries get selected in the filter view 2253 // the edit dialog and entry view are updated 2254 // TODO: consider using KSelectionProxyModel 2255 static_cast<EntrySelectionModel*>(m_iconView->selectionModel())->addSelectionProxy(m_filterView->selectionModel()); 2256 2257 // sort by count if column = 1 2258 int sortRole = Config::filterViewSortColumn() == 0 ? static_cast<int>(Qt::DisplayRole) : static_cast<int>(RowCountRole); 2259 Qt::SortOrder sortOrder = Config::filterViewSortAscending() ? Qt::AscendingOrder : Qt::DescendingOrder; 2260 m_filterView->setSorting(sortOrder, sortRole); 2261 } 2262 2263 void MainWindow::addLoanView() { 2264 if(m_loanView) { 2265 return; 2266 } 2267 2268 m_loanView = new LoanView(m_viewTabs); 2269 Controller::self()->addObserver(m_loanView); 2270 m_viewTabs->insertTab(2, m_loanView, QIcon::fromTheme(QStringLiteral("kaddressbook")), i18n("Loans")); 2271 m_loanView->setWhatsThis(i18n("<qt>The <i>Loan View</i> shows a list of all the people who " 2272 "have borrowed items from your collection.</qt>")); 2273 2274 // use the EntrySelectionModel as a proxy so when entries get selected in the loan view 2275 // the edit dialog and entry view are updated 2276 // TODO: consider using KSelectionProxyModel 2277 static_cast<EntrySelectionModel*>(m_iconView->selectionModel())->addSelectionProxy(m_loanView->selectionModel()); 2278 2279 // sort by count if column = 1 2280 int sortRole = Config::loanViewSortColumn() == 0 ? static_cast<int>(Qt::DisplayRole) : static_cast<int>(RowCountRole); 2281 Qt::SortOrder sortOrder = Config::loanViewSortAscending() ? Qt::AscendingOrder : Qt::DescendingOrder; 2282 m_loanView->setSorting(sortOrder, sortRole); 2283 } 2284 2285 void MainWindow::updateCaption(bool modified_) { 2286 QString caption; 2287 if(Data::Document::self()->collection()) { 2288 caption = Data::Document::self()->collection()->title(); 2289 } 2290 if(!m_newDocument) { 2291 if(!caption.isEmpty()) { 2292 caption += QLatin1String(" - "); 2293 } 2294 QUrl u = Data::Document::self()->URL(); 2295 if(u.isLocalFile() && u.fileName() == i18n(Tellico::untitledFilename)) { 2296 // for new files, the filename is set to Untitled in Data::Document 2297 caption += u.fileName(); 2298 } else { 2299 caption += u.toDisplayString(QUrl::PreferLocalFile); 2300 } 2301 } 2302 setCaption(caption, modified_); 2303 } 2304 2305 void MainWindow::slotUpdateToolbarIcons() { 2306 // first change the icon for the menu item 2307 if(Kernel::self()->collectionType() == Data::Collection::Base) { 2308 m_newEntry->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 2309 } else { 2310 m_newEntry->setIcon(QIcon(QLatin1String(":/icons/") + Kernel::self()->collectionTypeName())); 2311 } 2312 } 2313 2314 void MainWindow::slotGroupLabelActivated() { 2315 // need entry grouping combo id 2316 foreach(QWidget* widget, m_entryGrouping->associatedWidgets()) { 2317 if(::qobject_cast<KToolBar*>(widget)) { 2318 QWidget* container = m_entryGrouping->requestWidget(widget); 2319 QComboBox* combo = ::qobject_cast<QComboBox*>(container); //krazy:exclude=qclasses 2320 if(combo) { 2321 combo->showPopup(); 2322 break; 2323 } 2324 } 2325 } 2326 } 2327 2328 void MainWindow::slotFilterLabelActivated() { 2329 m_quickFilter->setFocus(); 2330 m_quickFilter->selectAll(); 2331 } 2332 2333 void MainWindow::slotClearFilter() { 2334 auto actions = m_quickFilter->actions(); 2335 if(!actions.isEmpty()) { 2336 m_quickFilter->removeAction(actions.first()); 2337 } 2338 m_quickFilter->clear(); 2339 slotQueueFilter(); 2340 } 2341 2342 void MainWindow::slotRenameCollection() { 2343 Kernel::self()->renameCollection(); 2344 } 2345 2346 void MainWindow::slotImageLocationMismatch() { 2347 // TODO: having a single image location mismatch should not be reason to completely save the whole document 2348 QTimer::singleShot(0, this, &MainWindow::slotImageLocationChanged); 2349 } 2350 2351 void MainWindow::slotImageLocationChanged() { 2352 if(m_savingImageLocationChange) { 2353 return; 2354 } 2355 m_savingImageLocationChange = true; 2356 Data::Document::self()->slotSetModified(); 2357 KMessageBox::information(this, QLatin1String("<qt>") + 2358 i18n("Some images are not saved in the configured location. The current file " 2359 "must be saved and the images will be transferred to the new location.") + 2360 QLatin1String("</qt>")); 2361 fileSave(); 2362 m_savingImageLocationChange = false; 2363 } 2364 2365 void MainWindow::updateCollectionActions() { 2366 if(!Data::Document::self()->collection()) { 2367 return; 2368 } 2369 2370 stateChanged(QStringLiteral("collection_reset")); 2371 2372 Data::Collection::Type type = Data::Document::self()->collection()->type(); 2373 stateChanged(QLatin1String("is_") + CollectionFactory::typeName(type)); 2374 2375 Controller::self()->updateActions(); 2376 // special case when there are no available data sources 2377 if(m_fetchActions.isEmpty() && m_updateAll) { 2378 m_updateAll->setEnabled(false); 2379 } 2380 } 2381 2382 void MainWindow::updateEntrySources() { 2383 const QString actionListName = QStringLiteral("update_entry_actions"); 2384 unplugActionList(actionListName); 2385 foreach(QAction* action, m_fetchActions) { 2386 foreach(QWidget* widget, action->associatedWidgets()) { 2387 widget->removeAction(action); 2388 } 2389 m_updateMapper->removeMappings(action); 2390 } 2391 qDeleteAll(m_fetchActions); 2392 m_fetchActions.clear(); 2393 2394 void (QAction::* triggeredBool)(bool) = &QAction::triggered; 2395 void (QSignalMapper::* mapVoid)() = &QSignalMapper::map; 2396 Fetch::FetcherVec vec = Fetch::Manager::self()->fetchers(Kernel::self()->collectionType()); 2397 foreach(Fetch::Fetcher::Ptr fetcher, vec) { 2398 QAction* action = new QAction(Fetch::Manager::fetcherIcon(fetcher.data()), fetcher->source(), actionCollection()); 2399 action->setToolTip(i18n("Update entry data from %1", fetcher->source())); 2400 connect(action, triggeredBool, m_updateMapper, mapVoid); 2401 m_updateMapper->setMapping(action, fetcher->source()); 2402 m_fetchActions.append(action); 2403 } 2404 2405 plugActionList(actionListName, m_fetchActions); 2406 } 2407 2408 void MainWindow::importFile(Tellico::Import::Format format_, const QList<QUrl>& urls_) { 2409 QList<QUrl> urls = urls_; 2410 // update as DropHandler and Importer classes are updated 2411 if(urls_.count() > 1 && 2412 format_ != Import::Bibtex && 2413 format_ != Import::RIS && 2414 format_ != Import::CIW && 2415 format_ != Import::PDF) { 2416 QUrl u = urls_.front(); 2417 QString url = u.isLocalFile() ? u.path() : u.toDisplayString(); 2418 Kernel::self()->sorry(i18n("Tellico can only import one file of this type at a time. " 2419 "Only %1 will be imported.", url)); 2420 urls.clear(); 2421 urls += u; 2422 } 2423 2424 ImportDialog dlg(format_, urls, this); 2425 if(dlg.exec() != QDialog::Accepted) { 2426 return; 2427 } 2428 2429 // if edit dialog is saved ok and if replacing, then the doc is saved ok 2430 if(m_editDialog->queryModified() && 2431 (dlg.action() != Import::Replace || querySaveModified())) { 2432 GUI::CursorSaver cs(Qt::WaitCursor); 2433 Data::CollPtr coll = dlg.collection(); 2434 if(!coll) { 2435 if(!dlg.statusMessage().isEmpty()) { 2436 Kernel::self()->sorry(dlg.statusMessage()); 2437 } 2438 return; 2439 } 2440 importCollection(coll, dlg.action()); 2441 } 2442 } 2443 2444 void MainWindow::importText(Tellico::Import::Format format_, const QString& text_) { 2445 if(text_.isEmpty()) { 2446 return; 2447 } 2448 Data::CollPtr coll = ImportDialog::importText(format_, text_); 2449 if(coll) { 2450 importCollection(coll, Import::Merge); 2451 } 2452 } 2453 2454 bool MainWindow::importCollection(Tellico::Data::CollPtr coll_, Tellico::Import::Action action_) { 2455 bool failed = false; 2456 switch(action_) { 2457 case Import::Append: 2458 { 2459 // only append if match, but special case importing books into bibliographies 2460 Data::CollPtr c = Data::Document::self()->collection(); 2461 if(c->type() == coll_->type() 2462 || (c->type() == Data::Collection::Bibtex && coll_->type() == Data::Collection::Book)) { 2463 Kernel::self()->appendCollection(coll_); 2464 slotEnableModifiedActions(true); 2465 } else { 2466 Kernel::self()->sorry(i18n(errorAppendType)); 2467 failed = true; 2468 } 2469 } 2470 break; 2471 2472 case Import::Merge: 2473 { 2474 // only merge if match, but special case importing books into bibliographies 2475 Data::CollPtr c = Data::Document::self()->collection(); 2476 if(c->type() == coll_->type() 2477 || (c->type() == Data::Collection::Bibtex && coll_->type() == Data::Collection::Book)) { 2478 Kernel::self()->mergeCollection(coll_); 2479 slotEnableModifiedActions(true); 2480 } else { 2481 Kernel::self()->sorry(i18n(errorMergeType)); 2482 failed = true; 2483 } 2484 } 2485 break; 2486 2487 default: // replace 2488 Kernel::self()->replaceCollection(coll_); 2489 m_fileOpenRecent->setCurrentItem(-1); 2490 m_newDocument = true; 2491 slotEnableOpenedActions(); 2492 slotEnableModifiedActions(false); 2493 break; 2494 } 2495 // tell the entry views and models that there are no further images to load 2496 m_detailedView->slotRefreshImages(); 2497 return !failed; 2498 } 2499 2500 void MainWindow::slotURLAction(const QUrl& url_) { 2501 Q_ASSERT(url_.scheme() == QLatin1String("tc")); 2502 QString actionName = url_.fileName(); 2503 QAction* action = this->action(actionName.toLatin1().constData()); 2504 if(action) { 2505 action->activate(QAction::Trigger); 2506 } else { 2507 myWarning() << "unknown action: " << actionName; 2508 } 2509 } 2510 2511 bool MainWindow::eventFilter(QObject* obj_, QEvent* ev_) { 2512 if(ev_->type() == QEvent::KeyPress && obj_ == m_quickFilter) { 2513 switch(static_cast<QKeyEvent*>(ev_)->key()) { 2514 case Qt::Key_Escape: 2515 m_quickFilter->clear(); 2516 return true; 2517 } 2518 } 2519 return KXmlGuiWindow::eventFilter(obj_, ev_); 2520 } 2521 2522 void MainWindow::slotToggleFullScreen() { 2523 Qt::WindowStates ws = windowState(); 2524 setWindowState((ws & Qt::WindowFullScreen) ? (ws & ~Qt::WindowFullScreen) : (ws | Qt::WindowFullScreen)); 2525 } 2526 2527 void MainWindow::slotToggleMenuBarVisibility() { 2528 QMenuBar* mb = menuBar(); 2529 mb->isHidden() ? mb->show() : mb->hide(); 2530 } 2531 2532 void MainWindow::slotToggleLayoutLock(bool lock_) { 2533 m_groupViewDock->setLocked(lock_); 2534 m_collectionViewDock->setLocked(lock_); 2535 } 2536 2537 void MainWindow::slotResetLayout() { 2538 removeDockWidget(m_groupViewDock); 2539 addDockWidget(Qt::LeftDockWidgetArea, m_groupViewDock); 2540 m_groupViewDock->show(); 2541 2542 m_dummyWindow->removeDockWidget(m_collectionViewDock); 2543 m_dummyWindow->addDockWidget(Qt::TopDockWidgetArea, m_collectionViewDock); 2544 m_collectionViewDock->show(); 2545 } 2546 2547 void MainWindow::guiFactoryReset() { 2548 guiFactory()->removeClient(this); 2549 guiFactory()->reset(); 2550 guiFactory()->addClient(this); 2551 2552 // set up custom actions for collection templates, have to do this AFTER createGUI() or factory() reset 2553 const QString actionListName = QStringLiteral("collection_template_list"); 2554 unplugActionList(actionListName); 2555 QSignalMapper* collectionTemplateMapper = new QSignalMapper(this); 2556 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 2557 void (QSignalMapper::* mappedString)(QString) = &QSignalMapper::mapped; 2558 connect(collectionTemplateMapper, mappedString, this, &MainWindow::slotFileNewByTemplate); 2559 #else 2560 connect(collectionTemplateMapper, &QSignalMapper::mappedString, this, &MainWindow::slotFileNewByTemplate); 2561 #endif 2562 2563 void (QAction::* triggeredBool)(bool) = &QAction::triggered; 2564 void (QSignalMapper::* mapVoid)() = &QSignalMapper::map; 2565 QList<QAction*> coll_actions; 2566 const QStringList customCollections = Tellico::locateAllFiles(QStringLiteral("tellico/collection-templates/*.tc")); 2567 if(!customCollections.isEmpty()) { 2568 m_newCollectionMenu->addSeparator(); 2569 } 2570 foreach(const QString& collectionFile, customCollections) { 2571 QFileInfo info(collectionFile); 2572 auto action = new QAction(info.completeBaseName(), actionCollection()); 2573 connect(action, triggeredBool, collectionTemplateMapper, mapVoid); 2574 const QString specFile = info.canonicalPath() + QDir::separator() + info.completeBaseName() + QLatin1String(".spec"); 2575 if(QFileInfo::exists(specFile)) { 2576 KConfig config(specFile, KConfig::SimpleConfig); 2577 const KConfigGroup cg = config.group(QString()); 2578 action->setText(cg.readEntry("Name", info.completeBaseName())); 2579 action->setToolTip(cg.readEntry("Comment")); 2580 action->setIcon(QIcon::fromTheme(cg.readEntry("Icon"), QIcon::fromTheme(QStringLiteral("document-new")))); 2581 } else { 2582 myDebug() << "No spec file for" << info.completeBaseName(); 2583 action->setText(info.completeBaseName()); 2584 action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 2585 } 2586 collectionTemplateMapper->setMapping(action, collectionFile); 2587 coll_actions.append(action); 2588 m_newCollectionMenu->addAction(action); 2589 } 2590 plugActionList(actionListName, coll_actions); 2591 } 2592 2593 void MainWindow::showLog() { 2594 auto dlg = new QDialog(this); 2595 auto layout = new QVBoxLayout(); 2596 dlg->setLayout(layout); 2597 dlg->setWindowTitle(i18nc("@title:window", "Tellico Log")); 2598 2599 auto viewer = new QTextEdit(dlg); 2600 viewer->setWordWrapMode(QTextOption::NoWrap); 2601 viewer->setReadOnly(true); 2602 viewer->setStyleSheet(QStringLiteral("QTextEdit { font-family: monospace; }")); 2603 layout->addWidget(viewer); 2604 2605 auto buttonBox = new QDialogButtonBox(dlg); 2606 buttonBox->setStandardButtons(QDialogButtonBox::Close); 2607 connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); 2608 layout->addWidget(buttonBox); 2609 2610 // TODO:: monitor for updates and reload log? 2611 auto logFile = Logger::self()->logFile(); 2612 if(!logFile.isEmpty()) { 2613 Logger::self()->flush(); 2614 QFile file(logFile); 2615 if(file.open(QFile::ReadOnly | QIODevice::Text)) { 2616 QTextStream in(&file); 2617 viewer->setPlainText(in.readAll()); 2618 } 2619 } 2620 2621 dlg->setMinimumSize(800, 600); 2622 dlg->setAttribute(Qt::WA_DeleteOnClose, true); 2623 dlg->show(); 2624 }