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 }