File indexing completed on 2024-04-28 16:32:03

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