diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1537c7da..2d7815e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,76 +1,74 @@ # "Unity build" found at # https://cheind.wordpress.com/2009/12/10/reducing-compilation-time-unity-builds/ function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME) set(files ${${SOURCE_VARIABLE_NAME}}) # Generate a unique filename for the unity build translation unit set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp) # Exclude all translation units from compilation set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) # Open the ub file file(WRITE ${unit_build_file} "// Unity Build generated by CMake\n") # Add include statement for each translation unit foreach(source_file ${files}) file(APPEND ${unit_build_file} "#include <${source_file}>\n") endforeach(source_file) # Complement list of translation units with the name of ub set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE) endfunction(enable_unity_build) # Creates kbibtex-git-info.h containing information about the source code's Git revision # (if source directory is a Git clone) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -DBINARY_DIR=${CMAKE_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/getgit.cmake COMMENT "Determine Git revision in case this source code is a Git checkout" ) set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h PROPERTIES GENERATED 1 HEADER_FILE_ONLY 1 SKIP_AUTOMOC ON SKIP_AUTOUIC ON SKIP_AUTOGEN ON ) add_custom_target(generate-kbibtex-git-info DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_subdirectory(global) add_subdirectory(config) add_subdirectory(data) add_subdirectory(io) add_subdirectory(processing) add_subdirectory(networking) add_subdirectory(gui) -add_subdirectory( - program -) +add_subdirectory(program) add_subdirectory( parts ) if( BUILD_TESTING ) add_subdirectory( test ) endif( BUILD_TESTING ) # install( # FILES # kbibtexnamespace.h # DESTINATION # ${INCLUDE_INSTALL_DIR}/kbibtex # COMPONENT # development # ) diff --git a/src/program/CMakeLists.txt b/src/program/CMakeLists.txt index c2ef4b63..f8629314 100644 --- a/src/program/CMakeLists.txt +++ b/src/program/CMakeLists.txt @@ -1,136 +1,127 @@ -# KBibTeX main program - -project( - kbibtexprogram -) - set( kbibtex_SRCS program.cpp mainwindow.cpp documentlist.cpp mdiwidget.cpp docklets/statistics.cpp docklets/referencepreview.cpp docklets/documentpreview.cpp docklets/valuelist.cpp docklets/searchform.cpp docklets/searchresults.cpp docklets/elementform.cpp docklets/filesettings.cpp docklets/zoterobrowser.cpp openfileinfo.cpp logging_program.cpp ) if(UNITY_BUILD AND NOT WIN32) # FIXME: Unity build of programs breaks on Windows enable_unity_build(kbibtex kbibtex_SRCS) endif(UNITY_BUILD AND NOT WIN32) -include_directories( - ${CMAKE_BINARY_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/src/program/docklets -) -ecm_add_app_icon( kbibtex_SRCS +ecm_add_app_icon(kbibtex_SRCS ICONS - ${CMAKE_SOURCE_DIR}/icons/128-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/16-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/22-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/32-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/48-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/64-apps-kbibtex.png + ${CMAKE_SOURCE_DIR}/icons/128-apps-kbibtex.png ) -add_executable( - kbibtex +add_executable(kbibtex ${kbibtex_SRCS} ) add_dependencies(kbibtex generate-kbibtex-git-info ) -target_link_libraries( kbibtex - Qt5::Core - Qt5::Widgets - KF5::CoreAddons - KF5::I18n - KF5::ConfigCore - KF5::Service - KF5::Parts - KF5::IconThemes - KF5::KIOCore - KF5::KIOFileWidgets - KF5::KIOWidgets - KF5::KIONTLM - KF5::Crash - KF5::Wallet - kbibtexio - kbibtexgui - kbibtexnetworking - kbibtexprocessing +target_include_directories(kbibtex + PRIVATE + ${CMAKE_BINARY_DIR} ) +target_link_libraries(kbibtex + PRIVATE + Qt5::Core + Qt5::Gui + KF5::CoreAddons + KF5::Crash + KF5::I18n + KF5::KIOCore + KF5::KIOFileWidgets + KF5::Parts + KF5::Wallet + KBibTeX::Global + KBibTeX::GUI + KBibTeX::IO + KBibTeX::Networking + KBibTeX::Processing +) if(Qt5WebEngineWidgets_FOUND) message(STATUS "Using QtWebEngine to render complex HTML content") # Once CMake 3.12.x is minimum requirement, use 'add_compile_definitions' add_definitions( -DHAVE_WEBENGINEWIDGETS ) target_link_libraries(kbibtex - Qt5::WebEngineWidgets + PRIVATE + Qt5::WebEngineWidgets ) else() if(Qt5WebKitWidgets_FOUND) message(STATUS "Using QtWebKit to render complex HTML content") # Once CMake 3.12.x is minimum requirement, use 'add_compile_definitions' add_definitions( -DHAVE_WEBKITWIDGETS ) target_link_libraries(kbibtex - Qt5::WebKitWidgets + PRIVATE + Qt5::WebKitWidgets ) else() message(STATUS "If available, using a KPart to render complex HTML content") endif() endif() install( TARGETS kbibtex - ${INSTALL_TARGETS_DEFAULT_ARGS} + ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install( PROGRAMS org.kde.kbibtex.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install( FILES kbibtexui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kbibtex ) install( FILES org.kde.kbibtex.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) ecm_install_icons( ICONS - ${CMAKE_SOURCE_DIR}/icons/128-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/16-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/22-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/32-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/48-apps-kbibtex.png ${CMAKE_SOURCE_DIR}/icons/64-apps-kbibtex.png + ${CMAKE_SOURCE_DIR}/icons/128-apps-kbibtex.png DESTINATION ${KDE_INSTALL_ICONDIR} ) diff --git a/src/program/docklets/documentpreview.cpp b/src/program/docklets/documentpreview.cpp index 62f980b2..93b689a1 100644 --- a/src/program/docklets/documentpreview.cpp +++ b/src/program/docklets/documentpreview.cpp @@ -1,717 +1,717 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "documentpreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_WEBENGINEWIDGETS #include #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS #include #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "kbibtex.h" -#include "element.h" -#include "entry.h" -#include "file.h" -#include "fileinfo.h" +#include +#include +#include +#include +#include #include "logging_program.h" ImageLabel::ImageLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f) { /// nothing } void ImageLabel::setPixmap(const QPixmap &pixmap) { m_pixmap = pixmap; if (!m_pixmap.isNull()) { setCursor(Qt::WaitCursor); QPixmap scaledPixmap = m_pixmap.width() <= width() && m_pixmap.height() <= height() ? m_pixmap : pixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QLabel::setPixmap(scaledPixmap); setMinimumSize(100, 100); unsetCursor(); } else QLabel::setPixmap(m_pixmap); } void ImageLabel::resizeEvent(QResizeEvent *event) { QLabel::resizeEvent(event); if (!m_pixmap.isNull()) { setCursor(Qt::WaitCursor); QPixmap scaledPixmap = m_pixmap.width() <= event->size().width() && m_pixmap.height() <= event->size().height() ? m_pixmap : m_pixmap.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QLabel::setPixmap(scaledPixmap); setMinimumSize(100, 100); unsetCursor(); } } class DocumentPreview::DocumentPreviewPrivate { public: struct UrlInfo { QUrl url; QString mimeType; QIcon icon; }; private: DocumentPreview *p; KSharedConfigPtr config; static const QString configGroupName; static const QString onlyLocalFilesCheckConfig; QPushButton *externalViewerButton; QStackedWidget *stackedWidget; ImageLabel *message; QMap cbxEntryToUrlInfo; QMutex addingUrlMutex; static const QString arXivPDFUrlStart; bool anyLocal; QMenuBar *menuBar; KToolBar *toolBar; KParts::ReadOnlyPart *okularPart; #ifdef HAVE_WEBENGINEWIDGETS QWebEngineView *htmlWidget; #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS QWebView *htmlWidget; #else // HAVE_WEBKITWIDGETS KParts::ReadOnlyPart *htmlPart; #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS int swpMessage, swpOkular, swpHTML; public: QComboBox *urlComboBox; QPushButton *onlyLocalFilesButton; QList runningJobs; QSharedPointer entry; QUrl baseUrl; bool anyRemote; KParts::ReadOnlyPart *locatePart(const QString &mimeType, QWidget *parentWidget) { KService::Ptr service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart")); if (service) { KParts::ReadOnlyPart *part = service->createInstance(parentWidget, p); connect(part, static_cast(&KParts::ReadOnlyPart::completed), p, &DocumentPreview::loadingFinished); return part; } else return nullptr; } DocumentPreviewPrivate(DocumentPreview *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), anyLocal(false), entry(nullptr), anyRemote(false) { setupGUI(); } /** * Create user interface for this widget. * It consists of some controlling widget on the top, * but the most space is consumed by KPart widgets * inside a QStackedWidget to show the external content * (PDF file, web page, ...). */ void setupGUI() { QVBoxLayout *layout = new QVBoxLayout(p); layout->setMargin(0); /// some widgets on the top to control the view QHBoxLayout *innerLayout = new QHBoxLayout(); layout->addLayout(innerLayout, 0); onlyLocalFilesButton = new QPushButton(QIcon::fromTheme(QStringLiteral("applications-internet")), QString(), p); onlyLocalFilesButton->setToolTip(i18n("Toggle between local files only and all documents including remote ones")); innerLayout->addWidget(onlyLocalFilesButton, 0); onlyLocalFilesButton->setCheckable(true); QSizePolicy sp = onlyLocalFilesButton->sizePolicy(); sp.setVerticalPolicy(QSizePolicy::MinimumExpanding); onlyLocalFilesButton->setSizePolicy(sp); urlComboBox = new QComboBox(p); innerLayout->addWidget(urlComboBox, 1); externalViewerButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), QString(), p); externalViewerButton->setToolTip(i18n("Open in external program")); innerLayout->addWidget(externalViewerButton, 0); sp = externalViewerButton->sizePolicy(); sp.setVerticalPolicy(QSizePolicy::MinimumExpanding); externalViewerButton->setSizePolicy(sp); menuBar = new QMenuBar(p); menuBar->setBackgroundRole(QPalette::Window); menuBar->setVisible(false); layout->addWidget(menuBar, 0); toolBar = new KToolBar(p); toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); toolBar->setBackgroundRole(QPalette::Window); toolBar->setVisible(false); layout->addWidget(toolBar, 0); /// main part of the widget stackedWidget = new QStackedWidget(p); layout->addWidget(stackedWidget, 1); /// default widget if no preview is available message = new ImageLabel(i18n("No preview available"), stackedWidget); message->setAlignment(Qt::AlignCenter); message->setWordWrap(true); swpMessage = stackedWidget->addWidget(message); connect(message, &QLabel::linkActivated, p, &DocumentPreview::linkActivated); /// add parts to stackedWidget okularPart = locatePart(QStringLiteral("application/pdf"), stackedWidget); swpOkular = (okularPart == nullptr) ? -1 : stackedWidget->addWidget(okularPart->widget()); if (okularPart == nullptr || swpOkular < 0) { qCWarning(LOG_KBIBTEX_PROGRAM) << "No 'KDE Framworks 5'-based Okular part for PDF or PostScript document preview available."; } #ifdef HAVE_WEBENGINEWIDGETS qCDebug(LOG_KBIBTEX_PROGRAM) << "WebEngine is available, using it instead of WebKit or HTML KPart (both neither considered nor tested for) for HTML/Web preview."; /// To make DrKonqi handle crashes in Chromium-based QtWebEngine, /// set a certain environment variable. For details, see here: /// https://www.dvratil.cz/2018/10/drkonqi-and-qtwebengine/ /// https://phabricator.kde.org/D16004 const auto chromiumFlags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"); if (!chromiumFlags.contains("disable-in-process-stack-traces")) { qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags + " --disable-in-process-stack-traces"); } htmlWidget = new QWebEngineView(stackedWidget); swpHTML = stackedWidget->addWidget(htmlWidget); connect(htmlWidget, &QWebEngineView::loadFinished, p, &DocumentPreview::loadingFinished); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS qCDebug(LOG_KBIBTEX_PROGRAM) << "WebKit is available, using it instead of WebEngine (missing) or HTML KPart (not considered) for HTML/Web preview."; htmlWidget = new QWebView(stackedWidget); swpHTML = stackedWidget->addWidget(htmlWidget); connect(htmlWidget, &QWebView::loadFinished, p, &DocumentPreview::loadingFinished); #else // HAVE_WEBKITWIDGETS htmlPart = locatePart(QStringLiteral("text/html"), stackedWidget); if (htmlPart != nullptr) { qCDebug(LOG_KBIBTEX_PROGRAM) << "HTML KPart is available, using it instead of WebEngine or WebKit (neither available) for HTML/Web preview."; swpHTML = stackedWidget->addWidget(htmlPart->widget()); } else { qCDebug(LOG_KBIBTEX_PROGRAM) << "No HTML viewing component is available, disabling HTML/Web preview."; swpHTML = -1; } #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS loadState(); connect(externalViewerButton, &QPushButton::clicked, p, &DocumentPreview::openExternally); connect(urlComboBox, static_cast(&QComboBox::activated), p, &DocumentPreview::comboBoxChanged); connect(onlyLocalFilesButton, &QPushButton::toggled, p, &DocumentPreview::onlyLocalFilesChanged); } bool addUrl(const struct UrlInfo &urlInfo) { bool isLocal = KBibTeX::isLocalOrRelative(urlInfo.url); anyLocal |= isLocal; if (!onlyLocalFilesButton->isChecked() && !isLocal) return true; ///< ignore URL if only local files are allowed if (isLocal) { /// create a drop-down list entry if file is a local file /// (based on patch by Luis Silva) QString fn = urlInfo.url.fileName(); QString full = urlInfo.url.url(QUrl::PreferLocalFile); QString dir = urlInfo.url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); QString text = fn.isEmpty() ? full : (dir.isEmpty() ? fn : QString(QStringLiteral("%1 [%2]")).arg(fn, dir)); urlComboBox->addItem(urlInfo.icon, text); } else { /// create a drop-down list entry if file is a remote file urlComboBox->addItem(urlInfo.icon, urlInfo.url.toDisplayString()); } urlComboBox->setEnabled(true); cbxEntryToUrlInfo.insert(urlComboBox->count() - 1, urlInfo); externalViewerButton->setEnabled(true); if (urlComboBox->count() == 1 || ///< first entry in combobox isLocal || ///< local files always preferred over URLs /// prefer arXiv summary URLs over other URLs (!anyLocal && urlInfo.url.host().contains(QStringLiteral("arxiv.org/abs")))) { showUrl(urlInfo); } return true; } void update() { p->setCursor(Qt::WaitCursor); /// reset and clear all controls if (swpOkular >= 0 && okularPart != nullptr) okularPart->closeUrl(); #ifdef HAVE_WEBENGINEWIDGETS htmlWidget->stop(); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS htmlWidget->stop(); #else // HAVE_WEBKITWIDGETS if (swpHTML >= 0 && htmlPart != nullptr) htmlPart->closeUrl(); #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS urlComboBox->setEnabled(false); urlComboBox->clear(); cbxEntryToUrlInfo.clear(); externalViewerButton->setEnabled(false); showMessage(i18n("Refreshing...")); // krazy:exclude=qmethods /// cancel/kill all running jobs auto it = runningJobs.begin(); while (it != runningJobs.end()) { (*it)->kill(); it = runningJobs.erase(it); } /// clear flag that memorizes if any local file was referenced anyLocal = false; anyRemote = false; /// do not load external reference if widget is hidden if (isVisible()) { const auto urlList = FileInfo::entryUrls(entry, baseUrl, FileInfo::TestExistenceYes); for (const QUrl &url : urlList) { bool isLocal = KBibTeX::isLocalOrRelative(url); anyRemote |= !isLocal; if (!onlyLocalFilesButton->isChecked() && !isLocal) continue; KIO::StatJob *job = KIO::stat(url, KIO::StatJob::SourceSide, 3, KIO::HideProgressInfo); runningJobs << job; KJobWidgets::setWindow(job, p); connect(job, &KIO::StatJob::result, p, &DocumentPreview::statFinished); } if (urlList.isEmpty()) { /// Case no URLs associated with this entry. /// For-loop above was never executed. showMessage(i18n("No documents to show.")); // krazy:exclude=qmethods p->setCursor(Qt::ArrowCursor); } else if (runningJobs.isEmpty()) { /// Case no stat jobs are running. As there were URLs (tested in /// previous condition), this implies that there were remote /// references that were ignored by executing "continue" above. /// Give user hint that by enabling remote files, more can be shown. showMessage(i18n("No documents to show.
Disable the restriction to local files to see remote documents.
")); // krazy:exclude=qmethods p->setCursor(Qt::ArrowCursor); } } else p->setCursor(Qt::ArrowCursor); } void showMessage(const QString &msgText) { stackedWidget->setCurrentIndex(swpMessage); message->setPixmap(QPixmap()); message->setText(msgText); if (swpOkular >= 0) stackedWidget->widget(swpOkular)->setEnabled(false); if (swpHTML >= 0) stackedWidget->widget(swpHTML)->setEnabled(false); menuBar->setVisible(false); toolBar->setVisible(true); menuBar->clear(); toolBar->clear(); } void setupToolMenuBarForPart(const KParts::ReadOnlyPart *part) { /* KAction *printAction = KStandardAction::print(part, SLOT(slotPrint()), part->actionCollection()); printAction->setEnabled(false); connect(part, SIGNAL(enablePrintAction(bool)), printAction, SLOT(setEnabled(bool))); */ QDomDocument doc = part->domDocument(); QDomElement docElem = doc.documentElement(); QDomNodeList toolbarNodes = docElem.elementsByTagName(QStringLiteral("ToolBar")); for (int i = 0; i < toolbarNodes.count(); ++i) { QDomNodeList toolbarItems = toolbarNodes.at(i).childNodes(); for (int j = 0; j < toolbarItems.count(); ++j) { QDomNode toolbarItem = toolbarItems.at(j); if (toolbarItem.nodeName() == QStringLiteral("Action")) { QString actionName = toolbarItem.attributes().namedItem(QStringLiteral("name")).nodeValue(); toolBar->addAction(part->actionCollection()->action(actionName)); } else if (toolbarItem.nodeName() == QStringLiteral("Separator")) { toolBar->addSeparator(); } } } QDomNodeList menubarNodes = docElem.elementsByTagName(QStringLiteral("MenuBar")); for (int i = 0; i < menubarNodes.count(); ++i) { QDomNodeList menubarNode = menubarNodes.at(i).childNodes(); for (int j = 0; j < menubarNode.count(); ++j) { QDomNode menubarItem = menubarNode.at(j); if (menubarItem.nodeName() == QStringLiteral("Menu")) { QDomNodeList menuNode = menubarItem.childNodes(); QString text; for (int k = 0; k < menuNode.count(); ++k) { QDomNode menuItem = menuNode.at(k); if (menuItem.nodeName() == QStringLiteral("text")) { text = menuItem.firstChild().toText().data(); break; } } QMenu *menu = menuBar->addMenu(text); for (int k = 0; k < menuNode.count(); ++k) { QDomNode menuItem = menuNode.at(k); if (menuItem.nodeName() == QStringLiteral("Action")) { QString actionName = menuItem.attributes().namedItem(QStringLiteral("name")).nodeValue(); menu->addAction(part->actionCollection()->action(actionName)); } else if (menuItem.nodeName() == QStringLiteral("Separator")) { menu->addSeparator(); } } } } } QDomNodeList actionPropertiesList = docElem.elementsByTagName(QStringLiteral("ActionProperties")); for (int i = 0; i < actionPropertiesList.count(); ++i) { QDomNodeList actionProperties = actionPropertiesList.at(i).childNodes(); for (int j = 0; j < actionProperties.count(); ++j) { QDomNode actionNode = actionProperties.at(j); if (actionNode.nodeName() == QStringLiteral("Action")) { const QString actionName = actionNode.attributes().namedItem(QStringLiteral("name")).toAttr().nodeValue(); const QString actionShortcut = actionNode.attributes().namedItem(QStringLiteral("shortcut")).toAttr().value(); QAction *action = part->actionCollection()->action(actionName); if (action != nullptr) { action->setShortcut(QKeySequence(actionShortcut)); } } } } menuBar->setVisible(true); toolBar->setVisible(true); } void showPart(const KParts::ReadOnlyPart *part, QWidget *widget) { menuBar->setVisible(false); toolBar->setVisible(false); menuBar->clear(); toolBar->clear(); if (okularPart != nullptr && part == okularPart && swpOkular >= 0) { stackedWidget->setCurrentIndex(swpOkular); stackedWidget->widget(swpOkular)->setEnabled(true); setupToolMenuBarForPart(okularPart); #ifdef HAVE_WEBENGINEWIDGETS } else if (widget == htmlWidget) { stackedWidget->setCurrentIndex(swpHTML); stackedWidget->widget(swpHTML)->setEnabled(true); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS } else if (widget == htmlWidget) { stackedWidget->setCurrentIndex(swpHTML); stackedWidget->widget(swpHTML)->setEnabled(true); #else // HAVE_WEBKITWIDGETS } else if (htmlPart != nullptr && part == htmlPart && swpHTML >= 0) { stackedWidget->setCurrentIndex(swpHTML); stackedWidget->widget(swpHTML)->setEnabled(true); setupToolMenuBarForPart(htmlPart); #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS } else if (widget == message) { stackedWidget->setCurrentIndex(swpMessage); } else showMessage(i18n("Cannot show requested part")); // krazy:exclude=qmethods } bool showUrl(const struct UrlInfo &urlInfo) { static const QStringList okularMimetypes {QStringLiteral("application/x-pdf"), QStringLiteral("application/pdf"), QStringLiteral("application/x-gzpdf"), QStringLiteral("application/x-bzpdf"), QStringLiteral("application/x-wwf"), QStringLiteral("image/vnd.djvu"), QStringLiteral("image/vnd.djvu+multipage"), QStringLiteral("application/postscript"), QStringLiteral("image/x-eps"), QStringLiteral("application/x-gzpostscript"), QStringLiteral("application/x-bzpostscript"), QStringLiteral("image/x-gzeps"), QStringLiteral("image/x-bzeps")}; static const QStringList htmlMimetypes {QStringLiteral("text/html"), QStringLiteral("application/xml"), QStringLiteral("application/xhtml+xml")}; static const QStringList imageMimetypes {QStringLiteral("image/jpeg"), QStringLiteral("image/png"), QStringLiteral("image/gif"), QStringLiteral("image/tiff")}; if (swpHTML >= 0) stackedWidget->widget(swpHTML)->setEnabled(false); if (swpOkular >= 0 && okularPart != nullptr) { stackedWidget->widget(swpOkular)->setEnabled(false); okularPart->closeUrl(); } #ifdef HAVE_WEBENGINEWIDGETS htmlWidget->stop(); #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS htmlWidget->stop(); #else // HAVE_WEBKITWIDGETS if (swpHTML >= 0 && htmlPart != nullptr) htmlPart->closeUrl(); #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS if (swpOkular >= 0 && okularPart != nullptr && okularMimetypes.contains(urlInfo.mimeType)) { p->setCursor(Qt::BusyCursor); showMessage(i18n("Loading...")); // krazy:exclude=qmethods return okularPart->openUrl(urlInfo.url); } else if (htmlMimetypes.contains(urlInfo.mimeType)) { p->setCursor(Qt::BusyCursor); showMessage(i18n("Loading...")); // krazy:exclude=qmethods #ifdef HAVE_WEBENGINEWIDGETS htmlWidget->load(urlInfo.url); return true; #else // HAVE_WEBENGINEWIDGETS #ifdef HAVE_WEBKITWIDGETS htmlWidget->load(urlInfo.url); return true; #else // HAVE_WEBKITWIDGETS return (swpHTML >= 0 && htmlPart != nullptr) ? htmlPart->openUrl(urlInfo.url) : false; #endif // HAVE_WEBKITWIDGETS #endif // HAVE_WEBENGINEWIDGETS } else if (imageMimetypes.contains(urlInfo.mimeType)) { p->setCursor(Qt::BusyCursor); message->setPixmap(QPixmap(urlInfo.url.url(QUrl::PreferLocalFile))); showPart(nullptr, message); p->unsetCursor(); return true; } else { QString additionalInformation; if (urlInfo.mimeType == QStringLiteral("application/pdf")) additionalInformation = i18nc("Additional information in case there is not KPart available for mime type 'application/pdf'", "

Please install Okular for KDE Frameworks 5 to make use of its PDF viewing component.
Okular for KDE 4 will not work."); showMessage(i18nc("First parameter is mime type, second parameter is optional information (may be empty)", "Don't know how to show mimetype '%1'.%2", urlInfo.mimeType, additionalInformation)); // krazy:exclude=qmethods } return false; } void openExternally() { QUrl url(cbxEntryToUrlInfo[urlComboBox->currentIndex()].url); /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(url); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(url, mimeTypeName, p, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(url, mimeTypeName, p, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } UrlInfo urlMetaInfo(const QUrl &url) { UrlInfo result; result.url = url; if (!KBibTeX::isLocalOrRelative(url) && url.fileName().isEmpty()) { /// URLs not pointing to a specific file should be opened with a web browser component result.icon = QIcon::fromTheme(QStringLiteral("text-html")); result.mimeType = QStringLiteral("text/html"); return result; } QMimeType mimeType = FileInfo::mimeTypeForUrl(url); // FIXME accuracy, necessary: /* if (accuracy < 50) { QMimeDatabase db; mimeType = db.mimeTypeForFile(url.fileName()); } */ result.mimeType = mimeType.name(); result.icon = QIcon::fromTheme(mimeType.iconName()); if (result.mimeType == QStringLiteral("application/octet-stream")) { /// application/octet-stream is a fall-back if KDE did not know better result.icon = QIcon::fromTheme(QStringLiteral("text-html")); result.mimeType = QStringLiteral("text/html"); } else if ((result.mimeType.isEmpty() || result.mimeType == QStringLiteral("inode/directory")) && (result.url.scheme() == QStringLiteral("http") || result.url.scheme() == QStringLiteral("https"))) { /// directory via http means normal webpage (not browsable directory) result.icon = QIcon::fromTheme(QStringLiteral("text-html")); result.mimeType = QStringLiteral("text/html"); } if (url.url(QUrl::PreferLocalFile).startsWith(arXivPDFUrlStart)) { result.icon = QIcon::fromTheme(QStringLiteral("application-pdf")); result.mimeType = QStringLiteral("application/pdf"); } return result; } void comboBoxChanged(int index) { showUrl(cbxEntryToUrlInfo[index]); } bool isVisible() { /// get dock where this widget is inside /// static cast is save as constructor requires parent to be QDockWidget QDockWidget *pp = static_cast(p->parent()); return pp != nullptr && !pp->isHidden(); } void loadState() { KConfigGroup configGroup(config, configGroupName); onlyLocalFilesButton->setChecked(!configGroup.readEntry(onlyLocalFilesCheckConfig, true)); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(onlyLocalFilesCheckConfig, !onlyLocalFilesButton->isChecked()); config->sync(); } }; const QString DocumentPreview::DocumentPreviewPrivate::arXivPDFUrlStart = QStringLiteral("http://arxiv.org/pdf/"); const QString DocumentPreview::DocumentPreviewPrivate::configGroupName = QStringLiteral("URL Preview"); const QString DocumentPreview::DocumentPreviewPrivate::onlyLocalFilesCheckConfig = QStringLiteral("OnlyLocalFiles"); DocumentPreview::DocumentPreview(QDockWidget *parent) : QWidget(parent), d(new DocumentPreviewPrivate(this)) { connect(parent, &QDockWidget::visibilityChanged, this, &DocumentPreview::visibilityChanged); } DocumentPreview::~DocumentPreview() { delete d; } void DocumentPreview::setElement(QSharedPointer element, const File *) { d->entry = element.dynamicCast(); d->update(); } void DocumentPreview::openExternally() { d->openExternally(); } void DocumentPreview::setBibTeXUrl(const QUrl &url) { d->baseUrl = url; } void DocumentPreview::onlyLocalFilesChanged() { d->saveState(); d->update(); } void DocumentPreview::visibilityChanged(bool) { d->update(); } void DocumentPreview::comboBoxChanged(int index) { d->comboBoxChanged(index); } void DocumentPreview::statFinished(KJob *kjob) { KIO::StatJob *job = static_cast(kjob); d->runningJobs.removeOne(job); if (!job->error()) { const QUrl url = job->mostLocalUrl(); DocumentPreviewPrivate::UrlInfo urlInfo = d->urlMetaInfo(url); setCursor(d->runningJobs.isEmpty() ? Qt::ArrowCursor : Qt::BusyCursor); d->addUrl(urlInfo); } else { qCWarning(LOG_KBIBTEX_PROGRAM) << job->error() << job->errorString(); } if (d->runningJobs.isEmpty()) { /// If this was the last background stat job ... setCursor(Qt::ArrowCursor); if (d->urlComboBox->count() < 1) { /// In case that no valid references were found by the stat jobs ... if (d->anyRemote && !d->onlyLocalFilesButton->isChecked()) { /// There are some remote URLs to probe, /// but user was only looking for local files d->showMessage(i18n("No documents to show.
Disable the restriction to local files to see remote documents.
")); // krazy:exclude=qmethods } else { /// No stat job at all succeeded. Show message to user. d->showMessage(i18n("No documents to show.\nSome URLs or files could not be retrieved.")); // krazy:exclude=qmethods } } } } void DocumentPreview::loadingFinished() { setCursor(Qt::ArrowCursor); d->showPart(qobject_cast(sender()), qobject_cast(sender())); } void DocumentPreview::linkActivated(const QString &link) { if (link == QStringLiteral("disableonlylocalfiles")) d->onlyLocalFilesButton->setChecked(true); else if (link.startsWith(QStringLiteral("http://")) || link.startsWith(QStringLiteral("https://"))) { const QUrl urlToOpen = QUrl::fromUserInput(link); if (urlToOpen.isValid()) { /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(urlToOpen); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(urlToOpen, mimeTypeName, this, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(urlToOpen, mimeTypeName, this, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } } } diff --git a/src/program/docklets/documentpreview.h b/src/program/docklets/documentpreview.h index e5f45785..e8baef9f 100644 --- a/src/program/docklets/documentpreview.h +++ b/src/program/docklets/documentpreview.h @@ -1,81 +1,80 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #ifndef KBIBTEX_PROGRAM_DOCKLET_DOCUMENTPREVIEW_H #define KBIBTEX_PROGRAM_DOCKLET_DOCUMENTPREVIEW_H #include #include #include - #include class QDockWidget; class QResizeEvent; class KJob; namespace KIO { class Job; } class Element; class File; class ImageLabel : public QLabel { Q_OBJECT public: explicit ImageLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = 0); void setPixmap(const QPixmap &pixmap); protected: void resizeEvent(QResizeEvent *event) override; private: QPixmap m_pixmap; }; class DocumentPreview : public QWidget { Q_OBJECT public: explicit DocumentPreview(QDockWidget *parent); ~DocumentPreview() override; public slots: void setElement(QSharedPointer, const File *); void setBibTeXUrl(const QUrl &); private: class DocumentPreviewPrivate; DocumentPreviewPrivate *d; QString mimeType(const QUrl &url); private slots: void openExternally(); void onlyLocalFilesChanged(); void visibilityChanged(bool); void comboBoxChanged(int); void statFinished(KJob *); void loadingFinished(); void linkActivated(const QString &link); }; #endif // KBIBTEX_PROGRAM_DOCKLET_DOCUMENTPREVIEW_H diff --git a/src/program/docklets/elementform.cpp b/src/program/docklets/elementform.cpp index e9084b44..a9242836 100644 --- a/src/program/docklets/elementform.cpp +++ b/src/program/docklets/elementform.cpp @@ -1,270 +1,270 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "elementform.h" #include #include #include #include #include #include #include #include #include #include -#include "entry.h" -#include "element/elementeditor.h" +#include +#include #include "mdiwidget.h" class ElementForm::ElementFormPrivate { private: ElementForm *p; QGridLayout *layout; const File *file; public: ElementEditor *elementEditor; MDIWidget *mdiWidget; QCheckBox *checkBoxAutoApply; QPushButton *buttonApply, *buttonReset; QWidget *widgetUnmodifiedChanges; bool gotModified; QSharedPointer element; KSharedConfigPtr config; /// Group name in configuration file for all settings for this form static const QString configGroupName; /// Key to store/retrieve setting whether changes in form should be automatically applied to element or not static const QString configKeyAutoApply; ElementFormPrivate(MDIWidget *_mdiWidget, ElementForm *parent) : p(parent), file(nullptr), mdiWidget(_mdiWidget), gotModified(false), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))) { KConfigGroup configGroup(config, configGroupName); layout = new QGridLayout(p); layout->setColumnStretch(0, 10); layout->setColumnStretch(1, 0); layout->setColumnStretch(2, 0); layout->setColumnStretch(3, 0); elementEditor = new ElementEditor(true, p); layout->addWidget(elementEditor, 0, 0, 1, 4); elementEditor->setEnabled(false); elementEditor->layout()->setMargin(0); connect(elementEditor, &ElementEditor::modified, p, &ElementForm::modified); /// Checkbox enabling/disabling setting to automatically apply changes in form to element checkBoxAutoApply = new QCheckBox(i18n("Automatically apply changes"), p); checkBoxAutoApply->setChecked(configGroup.readEntry(configKeyAutoApply, false)); layout->addWidget(checkBoxAutoApply, 1, 0, 1, 1); /// Create a special widget that shows a small icon and a text /// stating that there are unsaved changes. It will be shown /// simultaneously when the Apply and Reset buttons are enabled. // TODO nearly identical code as in SearchResultsPrivate constructor, create common class widgetUnmodifiedChanges = new QWidget(p); layout->addWidget(widgetUnmodifiedChanges, 1, 1, 1, 1); QBoxLayout *layoutUnmodifiedChanges = new QHBoxLayout(widgetUnmodifiedChanges); layoutUnmodifiedChanges->addSpacing(32); QLabel *label = new QLabel(widgetUnmodifiedChanges); label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); label->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-information"), KIconLoader::Dialog, KIconLoader::SizeSmall)); layoutUnmodifiedChanges->addWidget(label); label = new QLabel(i18n("There are unsaved changes. Please press either 'Apply' or 'Reset'."), widgetUnmodifiedChanges); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); layoutUnmodifiedChanges->addWidget(label); buttonApply = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("Apply"), p); layout->addWidget(buttonApply, 1, 2, 1, 1); buttonReset = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Reset"), p); layout->addWidget(buttonReset, 1, 3, 1, 1); connect(checkBoxAutoApply, &QCheckBox::toggled, p, &ElementForm::autoApplyToggled); connect(buttonApply, &QPushButton::clicked, p, &ElementForm::validateAndOnlyThenApply); connect(buttonReset, &QPushButton::clicked, p, &ElementForm::reset); } ~ElementFormPrivate() { delete elementEditor; } void refreshElement() { loadElement(element, file); } void loadElement(QSharedPointer element, const File *file) { /// store both element and file for later refresh this->element = element; this->file = file; /// skip whole process of loading an element if not visible if (isVisible()) p->setEnabled(true); else { p->setEnabled(false); return; } elementEditor->setElement(element, file); elementEditor->setEnabled(!element.isNull()); /// make apply and reset buttons aware of new element editor buttonApply->setEnabled(false); buttonReset->setEnabled(false); widgetUnmodifiedChanges->setVisible(false); gotModified = false; } bool isVisible() { /// get dock where this widget is inside /// static cast is save as constructor requires parent to be QDockWidget QDockWidget *pp = static_cast(p->parent()); return pp != nullptr && !pp->isHidden(); } void apply() { elementEditor->apply(); buttonApply->setEnabled(false); buttonReset->setEnabled(false); gotModified = false; widgetUnmodifiedChanges->setVisible(false); } void reset() { elementEditor->reset(); buttonApply->setEnabled(false); buttonReset->setEnabled(false); gotModified = false; widgetUnmodifiedChanges->setVisible(false); } }; const QString ElementForm::ElementFormPrivate::configGroupName = QStringLiteral("ElementForm"); const QString ElementForm::ElementFormPrivate::configKeyAutoApply = QStringLiteral("AutoApply"); ElementForm::ElementForm(MDIWidget *mdiWidget, QDockWidget *parent) : QWidget(parent), d(new ElementFormPrivate(mdiWidget, this)) { connect(parent, &QDockWidget::visibilityChanged, this, &ElementForm::visibilityChanged); } ElementForm::~ElementForm() { delete d; } void ElementForm::setElement(QSharedPointer element, const File *file) { /// Test if previous element (1) got modified, (2) the new element isn't /// the same as the new one, and (3) the user confirms to apply those /// changes rather than to discard them -> apply changes in previous element. /// FIXME If the previous element got delete from the file and therefore a different /// element gets set, changes will be still applied to the element to-be-deleted. if (d->gotModified && element != d->element && KMessageBox::questionYesNo(this, i18n("The current element got modified.\nApply or discard changes?"), i18n("Element modified"), KGuiItem(i18n("Apply changes"), QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))), KGuiItem(i18n("Discard changes"), QIcon::fromTheme(QStringLiteral("edit-undo")))) == KMessageBox::Yes) { d->apply(); } if (element != d->element) { /// Ignore loading the same element again d->loadElement(element, file); } } void ElementForm::refreshElement() { d->refreshElement(); } /** * Fetch the modified signal from the editing widget. * @param gotModified true if widget was modified by user, false if modified status was reset by e.g. apply operation */ void ElementForm::modified(bool gotModified) { /// Only interested in modifications, not resets of modified status if (!gotModified) return; if (d->checkBoxAutoApply->isChecked()) { /// User wants to automatically apply changes, so do it // FIXME validateAndOnlyThenApply(); apply(); } else { /// No automatic apply, therefore enable buttons where user can /// apply or reset changes, plus show warning label about unsaved changes d->buttonApply->setEnabled(true); d->buttonReset->setEnabled(true); d->widgetUnmodifiedChanges->setVisible(true); d->gotModified = true; } } void ElementForm::apply() { d->apply(); /// Notify rest of program (esp. main list) about changes emit elementModified(); } bool ElementForm::validateAndOnlyThenApply() { const bool isValid = d->elementEditor->validate(); if (isValid) apply(); return isValid; } void ElementForm::reset() { d->reset(); } void ElementForm::visibilityChanged(bool) { d->refreshElement(); } /** * React on toggles of checkbox for auto-apply. * @param isChecked true if checkbox got checked, false if checkbox got unchecked */ void ElementForm::autoApplyToggled(bool isChecked) { if (isChecked) { /// Got toggled to check state if (!d->element.isNull()) { validateAndOnlyThenApply(); } else { /// The following settings would happen when calling apply(), /// but as no valid element is edited, perform settings here instead d->buttonApply->setEnabled(false); d->buttonReset->setEnabled(false); d->widgetUnmodifiedChanges->setVisible(false); d->gotModified = false; } } /// Save changed status of checkbox in configuration settings KConfigGroup configGroup(d->config, ElementFormPrivate::configGroupName); configGroup.writeEntry(ElementFormPrivate::configKeyAutoApply, d->checkBoxAutoApply->isChecked()); configGroup.sync(); } diff --git a/src/program/docklets/filesettings.cpp b/src/program/docklets/filesettings.cpp index 9391efad..9faabf9b 100644 --- a/src/program/docklets/filesettings.cpp +++ b/src/program/docklets/filesettings.cpp @@ -1,77 +1,77 @@ /***************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * *****************************************************************************/ #include "filesettings.h" #include #include #include #include -#include "preferences.h" -#include "guihelper.h" -#include "italictextitemmodel.h" -#include "file/fileview.h" -#include "models/filemodel.h" -#include "value.h" -#include "file.h" +#include +#include +#include +#include +#include +#include +#include FileSettings::FileSettings(QWidget *parent) : FileSettingsWidget(parent), m_fileView(nullptr) { setEnabled(false); connect(this, &FileSettings::widgetsChanged, this, &FileSettings::widgetsChangedSlot); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::currentChanged, this, &FileSettings::currentFileChangedSlot); /// Monitoring file flag changes to get notified of /// "Save As" operations where the file settings /// may get changed (requires a reload of properties) connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &FileSettings::flagsChangedSlot); } void FileSettings::setFileView(FileView *fileView) { m_fileView = fileView; currentFileChangedSlot(); } void FileSettings::widgetsChangedSlot() { File *file = m_fileView != nullptr && m_fileView->fileModel() != nullptr ? m_fileView->fileModel()->bibliographyFile() : nullptr; if (file != nullptr) { saveProperties(file); /// Notify main view about change it its data m_fileView->externalModification(); } } void FileSettings::currentFileChangedSlot() { File *file = m_fileView != nullptr && m_fileView->fileModel() != nullptr ? m_fileView->fileModel()->bibliographyFile() : nullptr; loadProperties(file); setEnabled(file != nullptr); } void FileSettings::flagsChangedSlot(const OpenFileInfo::StatusFlags statusFlags) { if (statusFlags.testFlag(OpenFileInfo::Open)) { File *file = m_fileView != nullptr && m_fileView->fileModel() != nullptr ? m_fileView->fileModel()->bibliographyFile() : nullptr; loadProperties(file); } } diff --git a/src/program/docklets/filesettings.h b/src/program/docklets/filesettings.h index 2a4a8a97..c7909803 100644 --- a/src/program/docklets/filesettings.h +++ b/src/program/docklets/filesettings.h @@ -1,50 +1,48 @@ /***************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * - * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * *****************************************************************************/ #ifndef KBIBTEX_PROGRAM_DOCKLET_FILESETTINGS_H #define KBIBTEX_PROGRAM_DOCKLET_FILESETTINGS_H -#include "widgets/filesettingswidget.h" - +#include #include "openfileinfo.h" class FileView; class File; /** * @author Thomas Fischer */ class FileSettings : public FileSettingsWidget { Q_OBJECT public: explicit FileSettings(QWidget *parent); void setFileView(FileView *fileView); private slots: void widgetsChangedSlot(); void currentFileChangedSlot(); void flagsChangedSlot(const OpenFileInfo::StatusFlags statusFlags); private: FileView *m_fileView; }; #endif // KBIBTEX_PROGRAM_DOCKLET_FILESETTINGS_H diff --git a/src/program/docklets/referencepreview.cpp b/src/program/docklets/referencepreview.cpp index 97ec7680..4dca7f7c 100644 --- a/src/program/docklets/referencepreview.cpp +++ b/src/program/docklets/referencepreview.cpp @@ -1,419 +1,419 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * and contributors * * * * Contributions to this file were made by * * - Jurgen Spitzmuller * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "referencepreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "xsltransform.h" -#include "fileexporterbibtex.h" -#include "fileexporterbibtex2html.h" -#include "fileexporterris.h" -#include "fileexporterxslt.h" -#include "element.h" -#include "file.h" -#include "entry.h" -#include "file/fileview.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "logging_program.h" static const struct PreviewStyles { QString label, style, type; } previewStyles[] = { {i18n("Source (BibTeX)"), QStringLiteral("bibtex"), QStringLiteral("exporter")}, {i18n("Source (RIS)"), QStringLiteral("ris"), QStringLiteral("exporter")}, {QStringLiteral("abbrv"), QStringLiteral("abbrv"), QStringLiteral("bibtex2html")}, {QStringLiteral("acm"), QStringLiteral("acm"), QStringLiteral("bibtex2html")}, {QStringLiteral("alpha"), QStringLiteral("alpha"), QStringLiteral("bibtex2html")}, {QStringLiteral("apalike"), QStringLiteral("apalike"), QStringLiteral("bibtex2html")}, {QStringLiteral("ieeetr"), QStringLiteral("ieeetr"), QStringLiteral("bibtex2html")}, {QStringLiteral("plain"), QStringLiteral("plain"), QStringLiteral("bibtex2html")}, {QStringLiteral("siam"), QStringLiteral("siam"), QStringLiteral("bibtex2html")}, {QStringLiteral("unsrt"), QStringLiteral("unsrt"), QStringLiteral("bibtex2html")}, {i18n("Standard"), QStringLiteral("standard"), QStringLiteral("xml")}, {i18n("Fancy"), QStringLiteral("fancy"), QStringLiteral("xml")}, {i18n("Wikipedia Citation"), QStringLiteral("wikipedia-cite"), QStringLiteral("plain_xml")}, {i18n("Abstract-only"), QStringLiteral("abstractonly"), QStringLiteral("xml")} }; Q_DECLARE_METATYPE(PreviewStyles) class ReferencePreview::ReferencePreviewPrivate { private: ReferencePreview *p; public: KSharedConfigPtr config; const QString configGroupName; const QString configKeyName; QPushButton *buttonOpen, *buttonSaveAsHTML; QString htmlText; QUrl baseUrl; QTextDocument *htmlDocument; KTextEdit *htmlView; QComboBox *comboBox; QSharedPointer element; const File *file; FileView *fileView; const QColor textColor; const int defaultFontSize; const QString htmlStart; const QString notAvailableMessage; ReferencePreviewPrivate(ReferencePreview *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Reference Preview Docklet")), configKeyName(QStringLiteral("Style")), file(nullptr), fileView(nullptr), textColor(QApplication::palette().text().color()), defaultFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize()), htmlStart(QStringLiteral("\n\n\n\n\n")), notAvailableMessage(htmlStart + QStringLiteral("

") + i18n("No preview available") + QStringLiteral("

") + i18n("Reason:") + QStringLiteral(" %1

")) { QGridLayout *gridLayout = new QGridLayout(p); gridLayout->setMargin(0); gridLayout->setColumnStretch(0, 1); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); comboBox = new QComboBox(p); gridLayout->addWidget(comboBox, 0, 0, 1, 3); QFrame *frame = new QFrame(p); gridLayout->addWidget(frame, 1, 0, 1, 3); frame->setFrameShadow(QFrame::Sunken); frame->setFrameShape(QFrame::StyledPanel); QVBoxLayout *layout = new QVBoxLayout(frame); layout->setMargin(0); htmlView = new KTextEdit(frame); htmlView->setReadOnly(true); htmlDocument = new QTextDocument(htmlView); htmlView->setDocument(htmlDocument); layout->addWidget(htmlView); buttonOpen = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open"), p); buttonOpen->setToolTip(i18n("Open reference in web browser.")); gridLayout->addWidget(buttonOpen, 2, 1, 1, 1); buttonSaveAsHTML = new QPushButton(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as HTML"), p); buttonSaveAsHTML->setToolTip(i18n("Save reference as HTML fragment.")); gridLayout->addWidget(buttonSaveAsHTML, 2, 2, 1, 1); } bool saveHTML(const QUrl &url) const { QTemporaryFile tempFile; tempFile.setAutoRemove(true); bool result = saveHTML(tempFile); if (result) { KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(tempFile.fileName()), url, KIO::Overwrite); KJobWidgets::setWindow(copyJob, p); result = copyJob->exec(); } return result; } bool saveHTML(QTemporaryFile &tempFile) const { if (tempFile.open()) { QTextStream ts(&tempFile); ts.setCodec("utf-8"); static const QRegularExpression kbibtexHrefRegExp(QStringLiteral("]+href=\"kbibtex:[^>]+>(.+?)")); QString modifiedHtmlText = htmlText; modifiedHtmlText = modifiedHtmlText.replace(kbibtexHrefRegExp, QStringLiteral("\\1")); ts << modifiedHtmlText; tempFile.close(); return true; } return false; } void loadState() { static bool hasBibTeX2HTML = !QStandardPaths::findExecutable(QStringLiteral("bibtex2html")).isEmpty(); KConfigGroup configGroup(config, configGroupName); const QString previousStyle = configGroup.readEntry(configKeyName, QString()); comboBox->clear(); int styleIndex = 0, c = 0; for (const PreviewStyles &previewStyle : previewStyles) { if (!hasBibTeX2HTML && previewStyle.type.contains(QStringLiteral("bibtex2html"))) continue; comboBox->addItem(previewStyle.label, QVariant::fromValue(previewStyle)); if (previousStyle == previewStyle.style) styleIndex = c; ++c; } comboBox->setCurrentIndex(styleIndex); } void saveState() { KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(configKeyName, comboBox->itemData(comboBox->currentIndex()).value().style); config->sync(); } }; ReferencePreview::ReferencePreview(QWidget *parent) : QWidget(parent), d(new ReferencePreviewPrivate(this)) { d->loadState(); connect(d->buttonOpen, &QPushButton::clicked, this, &ReferencePreview::openAsHTML); connect(d->buttonSaveAsHTML, &QPushButton::clicked, this, &ReferencePreview::saveAsHTML); connect(d->comboBox, static_cast(&QComboBox::currentIndexChanged), this, &ReferencePreview::renderHTML); setEnabled(false); } ReferencePreview::~ReferencePreview() { delete d; } void ReferencePreview::setHtml(const QString &html, bool buttonsEnabled) { d->htmlText = QString(html).remove(QStringLiteral("")); d->htmlDocument->setHtml(d->htmlText); d->buttonOpen->setEnabled(buttonsEnabled); d->buttonSaveAsHTML->setEnabled(buttonsEnabled); } void ReferencePreview::setEnabled(bool enabled) { if (enabled) setHtml(d->htmlText, true); else setHtml(d->notAvailableMessage.arg(i18n("Preview disabled")), false); d->htmlView->setEnabled(enabled); d->comboBox->setEnabled(enabled); } void ReferencePreview::setElement(QSharedPointer element, const File *file) { d->element = element; d->file = file; renderHTML(); } void ReferencePreview::renderHTML() { enum { ignore, /// do not include crossref'ed entry's values (one entry) /// NOT USED: add, /// feed both the current entry as well as the crossref'ed entry into the exporter (two entries) merge /// merge the crossref'ed entry's values into the current entry (one entry) } crossRefHandling = ignore; if (d->element.isNull()) { setHtml(d->notAvailableMessage.arg(i18n("No element selected")), false); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); FileExporter *exporter = nullptr; const PreviewStyles previewStyle = d->comboBox->itemData(d->comboBox->currentIndex()).value(); if (previewStyle.type == QStringLiteral("exporter")) { if (previewStyle.style == QStringLiteral("bibtex")) { FileExporterBibTeX *exporterBibTeX = new FileExporterBibTeX(this); exporterBibTeX->setEncoding(QStringLiteral("utf-8")); exporter = exporterBibTeX; } else if (previewStyle.style == QStringLiteral("ris")) exporter = new FileExporterRIS(this); else qCWarning(LOG_KBIBTEX_PROGRAM) << "Don't know how to handle output style " << previewStyle.style << " for type " << previewStyle.type; } else if (previewStyle.type == QStringLiteral("bibtex2html")) { crossRefHandling = merge; FileExporterBibTeX2HTML *exporterHTML = new FileExporterBibTeX2HTML(this); exporterHTML->setLaTeXBibliographyStyle(previewStyle.style); exporter = exporterHTML; } else if (previewStyle.type == QStringLiteral("xml") || previewStyle.type.endsWith(QStringLiteral("_xml"))) { crossRefHandling = merge; const QString filename = previewStyle.style + QStringLiteral(".xsl"); exporter = new FileExporterXSLT(XSLTransform::locateXSLTfile(filename), this); } else qCWarning(LOG_KBIBTEX_PROGRAM) << "Don't know how to handle output type " << previewStyle.type; if (exporter != nullptr) { QBuffer buffer(this); buffer.open(QBuffer::WriteOnly); bool exporterResult = false; QStringList errorLog; QSharedPointer entry = d->element.dynamicCast(); /** NOT USED if (crossRefHandling == add && !entry.isNull()) { QString crossRef = PlainTextValue::text(entry->value(QStringLiteral("crossref"))); QSharedPointer crossRefEntry = d->file == NULL ? QSharedPointer() : d->file->containsKey(crossRef) .dynamicCast(); if (!crossRefEntry.isNull()) { File file; file.append(QSharedPointer(new Entry(*entry))); file.append(QSharedPointer(new Entry(*crossRefEntry))); exporterResult = exporter->save(&buffer, &file, &errorLog); } else exporterResult = exporter->save(&buffer, d->element, d->file, &errorLog); } else */ if (crossRefHandling == merge && !entry.isNull()) { QSharedPointer merged = QSharedPointer(entry->resolveCrossref(d->file)); exporterResult = exporter->save(&buffer, merged, d->file, &errorLog); } else exporterResult = exporter->save(&buffer, d->element, d->file, &errorLog); buffer.close(); delete exporter; buffer.open(QBuffer::ReadOnly); QString text = QString::fromUtf8(buffer.readAll().constData()); buffer.close(); bool buttonsEnabled = true; if (!exporterResult || text.isEmpty()) { /// something went wrong, no output ... text = d->notAvailableMessage.arg(i18n("No output generated")); buttonsEnabled = false; qCDebug(LOG_KBIBTEX_PROGRAM) << errorLog.join(QStringLiteral("\n")); } else { /// beautify text text.replace(QStringLiteral("``"), QStringLiteral("“")); text.replace(QStringLiteral("''"), QStringLiteral("”")); static const QRegularExpression openingSingleQuotationRegExp(QStringLiteral("(^|[> ,.;:!?])`(\\S)")); static const QRegularExpression closingSingleQuotationRegExp(QStringLiteral("(\\S)'([ ,.;:!?<]|$)")); text.replace(openingSingleQuotationRegExp, QStringLiteral("\\1‘\\2")); text.replace(closingSingleQuotationRegExp, QStringLiteral("\\1’\\2")); if (previewStyle.style == QStringLiteral("wikipedia-cite")) text.remove(QStringLiteral("\n")); if (text.contains(QStringLiteral("{{cite FIXME"))) { /// Wikipedia {{cite ...}} command had problems (e.g. unknown entry type) text = d->notAvailableMessage.arg(i18n("This type of element is not supported by Wikipedia's {{cite}} command.")); } else if (previewStyle.type == QStringLiteral("exporter") || previewStyle.type.startsWith(QStringLiteral("plain_"))) { /// source text.prepend(QStringLiteral("';\">")); text.prepend(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); text.prepend(QStringLiteral("
htmlStart);
                 text.append(QStringLiteral("
")); } else if (previewStyle.type == QStringLiteral("bibtex2html")) { /// bibtex2html /// remove "generated by" line from HTML code if BibTeX2HTML was used text.remove(QRegularExpression(QStringLiteral("

.*

"))); text.remove(QRegularExpression(QStringLiteral("<[/]?(font)[^>]*>"))); text.remove(QRegularExpression(QStringLiteral("^.*?"))); text.remove(QRegularExpression(QStringLiteral(".*$"))); text.remove(QRegularExpression(QStringLiteral("\\[ \\]"))); /// replace ASCII art with Unicode characters text.replace(QStringLiteral("---"), QString(QChar(0x2014))); text.replace(QStringLiteral("--"), QString(QChar(0x2013))); text.prepend(d->htmlStart); text.append(""); } else if (previewStyle.type == QStringLiteral("xml")) { /// XML/XSLT text.prepend(d->htmlStart); text.append(""); } /// adopt current color scheme text.replace(QStringLiteral("color: black;"), QString(QStringLiteral("color: %1;")).arg(d->textColor.name())); } setHtml(text, buttonsEnabled); d->saveState(); } else { /// something went wrong, no exporter ... setHtml(d->notAvailableMessage.arg(i18n("No output generated")), false); } QApplication::restoreOverrideCursor(); } void ReferencePreview::openAsHTML() { QTemporaryFile file(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + QStringLiteral("referencePreview-openAsHTML-XXXXXX.html")); file.setAutoRemove(false); /// let file stay alive for browser d->saveHTML(file); /// Ask KDE subsystem to open url in viewer matching mime type QUrl url(file.fileName()); #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(url, QStringLiteral("text/html"), this, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(url, QStringLiteral("text/html"), this, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } void ReferencePreview::saveAsHTML() { QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save as HTML"), QUrl(), QStringLiteral("text/html")); if (url.isValid()) d->saveHTML(url); } void ReferencePreview::linkClicked(const QUrl &url) { QString text = url.toDisplayString(); if (text.startsWith(QStringLiteral("kbibtex:filter:"))) { text = text.mid(15); if (d->fileView != nullptr) { int p = text.indexOf(QStringLiteral("=")); SortFilterFileModel::FilterQuery fq; fq.terms << text.mid(p + 1); fq.combination = SortFilterFileModel::EveryTerm; fq.field = text.left(p); fq.searchPDFfiles = false; d->fileView->setFilterBarFilter(fq); } } } void ReferencePreview::setFileView(FileView *fileView) { d->fileView = fileView; } diff --git a/src/program/docklets/searchform.cpp b/src/program/docklets/searchform.cpp index 492cb997..09bd4a59 100644 --- a/src/program/docklets/searchform.cpp +++ b/src/program/docklets/searchform.cpp @@ -1,515 +1,514 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "searchform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "element.h" -#include "file.h" -#include "comment.h" -#include "fileexporterbibtex.h" -#include "onlinesearch/onlinesearchabstract.h" -#include "onlinesearch/onlinesearchgeneral.h" -#include "onlinesearch/onlinesearchbibsonomy.h" -#include "onlinesearch/onlinesearchgooglescholar.h" -#include "onlinesearch/onlinesearchpubmed.h" -#include "onlinesearch/onlinesearchieeexplore.h" -#include "onlinesearch/onlinesearchacmportal.h" -#include "onlinesearch/onlinesearchsciencedirect.h" -#include "onlinesearch/onlinesearchspringerlink.h" -#include "onlinesearch/onlinesearcharxiv.h" -#include "onlinesearch/onlinesearchjstor.h" -#include "onlinesearch/onlinesearchmathscinet.h" -#include "onlinesearch/onlinesearchmrlookup.h" -#include "onlinesearch/onlinesearchinspirehep.h" -#include "onlinesearch/onlinesearchcernds.h" -#include "onlinesearch/onlinesearchingentaconnect.h" -#include "onlinesearch/onlinesearchsoanasaads.h" -#include "onlinesearch/onlinesearchisbndb.h" -#include "onlinesearch/onlinesearchideasrepec.h" -#include "onlinesearch/onlinesearchdoi.h" -#include "onlinesearch/onlinesearchbiorxiv.h" -#include "onlinesearch/onlinesearchsemanticscholar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "openfileinfo.h" -#include "file/fileview.h" -#include "models/filemodel.h" #include "searchresults.h" #include "logging_program.h" class SearchForm::SearchFormPrivate { private: SearchForm *p; QStackedWidget *queryTermsStack; QWidget *listContainer; QListWidget *enginesList; QLabel *whichEnginesLabel; QAction *actionOpenHomepage; public: KSharedConfigPtr config; const QString configGroupName; SearchResults *sr; QMap itemToOnlineSearch; QSet runningSearches; QPushButton *searchButton; QPushButton *useEntryButton; OnlineSearchQueryFormGeneral *generalQueryTermsForm; QTabWidget *tabWidget; QSharedPointer currentEntry; QProgressBar *progressBar; QMap progressMap; QMap formToScrollArea; enum SearchFormPrivateRole { /// Homepage of a search engine HomepageRole = Qt::UserRole + 5, /// Special widget for a search engine WidgetRole = Qt::UserRole + 6, /// Name of a search engine NameRole = Qt::UserRole + 7 }; SearchFormPrivate(SearchResults *searchResults, SearchForm *parent) : p(parent), whichEnginesLabel(nullptr), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Search Engines Docklet")), sr(searchResults), searchButton(nullptr), useEntryButton(nullptr), currentEntry(nullptr) { createGUI(); } OnlineSearchQueryFormAbstract *currentQueryForm() { QScrollArea *area = qobject_cast(queryTermsStack->currentWidget()); return formToScrollArea.key(area, nullptr); } QScrollArea *wrapInScrollArea(OnlineSearchQueryFormAbstract *form, QWidget *parent) { QScrollArea *scrollArea = new QScrollArea(parent); form->setParent(scrollArea); scrollArea->setWidget(form); scrollArea->setWidgetResizable(true); scrollArea->setFrameShape(QFrame::NoFrame); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); formToScrollArea.insert(form, scrollArea); return scrollArea; } QWidget *createQueryTermsStack(QWidget *parent) { QWidget *container = new QWidget(parent); QVBoxLayout *vLayout = new QVBoxLayout(container); whichEnginesLabel = new QLabel(container); whichEnginesLabel->setWordWrap(true); vLayout->addWidget(whichEnginesLabel); vLayout->setStretchFactor(whichEnginesLabel, 0); connect(whichEnginesLabel, &QLabel::linkActivated, p, &SearchForm::switchToEngines); vLayout->addSpacing(8); queryTermsStack = new QStackedWidget(container); vLayout->addWidget(queryTermsStack); vLayout->setStretchFactor(queryTermsStack, 5); QScrollArea *scrollArea = wrapInScrollArea(createGeneralQueryTermsForm(queryTermsStack), queryTermsStack); queryTermsStack->addWidget(scrollArea); return container; } OnlineSearchQueryFormAbstract *createGeneralQueryTermsForm(QWidget *parent = nullptr) { generalQueryTermsForm = new OnlineSearchQueryFormGeneral(parent); return generalQueryTermsForm; } QWidget *createEnginesGUI(QWidget *parent) { listContainer = new QWidget(parent); QGridLayout *layout = new QGridLayout(listContainer); layout->setRowStretch(0, 1); layout->setRowStretch(1, 0); enginesList = new QListWidget(listContainer); layout->addWidget(enginesList, 0, 0, 1, 1); connect(enginesList, &QListWidget::itemChanged, p, &SearchForm::itemCheckChanged); connect(enginesList, &QListWidget::currentItemChanged, p, &SearchForm::enginesListCurrentChanged); enginesList->setSelectionMode(QAbstractItemView::NoSelection); actionOpenHomepage = new QAction(QIcon::fromTheme(QStringLiteral("internet-web-browser")), i18n("Go to Homepage"), p); connect(actionOpenHomepage, &QAction::triggered, p, &SearchForm::openHomepage); enginesList->addAction(actionOpenHomepage); enginesList->setContextMenuPolicy(Qt::ActionsContextMenu); return listContainer; } void createGUI() { QGridLayout *layout = new QGridLayout(p); layout->setMargin(0); layout->setRowStretch(0, 1); layout->setRowStretch(1, 0); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 1); layout->setColumnStretch(2, 0); tabWidget = new QTabWidget(p); tabWidget->setDocumentMode(true); layout->addWidget(tabWidget, 0, 0, 1, 3); QWidget *widget = createQueryTermsStack(tabWidget); tabWidget->addTab(widget, QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Query Terms")); QWidget *listContainer = createEnginesGUI(tabWidget); tabWidget->addTab(listContainer, QIcon::fromTheme(QStringLiteral("applications-engineering")), i18n("Engines")); connect(tabWidget, &QTabWidget::currentChanged, p, &SearchForm::tabSwitched); useEntryButton = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up")), i18n("Use Entry"), p); layout->addWidget(useEntryButton, 1, 0, 1, 1); useEntryButton->setEnabled(false); connect(useEntryButton, &QPushButton::clicked, p, &SearchForm::copyFromEntry); progressBar = new QProgressBar(p); layout->addWidget(progressBar, 1, 1, 1, 1); progressBar->setMaximum(1000); progressBar->hide(); searchButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search"), p); layout->addWidget(searchButton, 1, 2, 1, 1); connect(generalQueryTermsForm, &OnlineSearchQueryFormGeneral::returnPressed, searchButton, &QPushButton::click); updateGUI(); } void loadEngines() { enginesList->clear(); addEngine(new OnlineSearchAcmPortal(p)); addEngine(new OnlineSearchArXiv(p)); addEngine(new OnlineSearchBioRxiv(p)); addEngine(new OnlineSearchBibsonomy(p)); addEngine(new OnlineSearchGoogleScholar(p)); addEngine(new OnlineSearchIEEEXplore(p)); addEngine(new OnlineSearchIngentaConnect(p)); addEngine(new OnlineSearchJStor(p)); addEngine(new OnlineSearchMathSciNet(p)); addEngine(new OnlineSearchMRLookup(p)); addEngine(new OnlineSearchInspireHep(p)); addEngine(new OnlineSearchCERNDS(p)); addEngine(new OnlineSearchPubMed(p)); addEngine(new OnlineSearchScienceDirect(p)); addEngine(new OnlineSearchSpringerLink(p)); addEngine(new OnlineSearchSOANASAADS(p)); /// addEngine(new OnlineSearchIsbnDB(p)); /// disabled as provider switched to a paid model on 2017-12-26 addEngine(new OnlineSearchIDEASRePEc(p)); addEngine(new OnlineSearchDOI(p)); addEngine(new OnlineSearchSemanticScholar(p)); p->itemCheckChanged(nullptr); updateGUI(); } void addEngine(OnlineSearchAbstract *engine) { KConfigGroup configGroup(config, configGroupName); /// Disable signals while updating the widget and its items enginesList->blockSignals(true); QListWidgetItem *item = new QListWidgetItem(engine->label(), enginesList); static const QSet enginesEnabledByDefault {QStringLiteral("GoogleScholar"), QStringLiteral("Bibsonomy")}; item->setCheckState(configGroup.readEntry(engine->name(), enginesEnabledByDefault.contains(engine->name())) ? Qt::Checked : Qt::Unchecked); item->setIcon(engine->icon(item)); item->setToolTip(engine->label()); item->setData(HomepageRole, engine->homepage()); item->setData(NameRole, engine->name()); OnlineSearchQueryFormAbstract *widget = engine->customWidget(queryTermsStack); item->setData(WidgetRole, QVariant::fromValue(widget)); if (widget != nullptr) { connect(widget, &OnlineSearchQueryFormAbstract::returnPressed, searchButton, &QPushButton::click); QScrollArea *scrollArea = wrapInScrollArea(widget, queryTermsStack); queryTermsStack->addWidget(scrollArea); } itemToOnlineSearch.insert(item, engine); connect(engine, &OnlineSearchAbstract::foundEntry, p, &SearchForm::foundEntry); connect(engine, &OnlineSearchAbstract::stoppedSearch, p, &SearchForm::stoppedSearch); connect(engine, &OnlineSearchAbstract::progress, p, &SearchForm::updateProgress); /// Re-enable signals after updating the widget and its items enginesList->blockSignals(false); } void switchToSearch() { for (QMap::ConstIterator it = itemToOnlineSearch.constBegin(); it != itemToOnlineSearch.constEnd(); ++it) disconnect(searchButton, &QPushButton::clicked, it.value(), &OnlineSearchAbstract::cancel); connect(searchButton, &QPushButton::clicked, p, &SearchForm::startSearch); searchButton->setText(i18n("Search")); searchButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); for (int i = tabWidget->count() - 1; i >= 0; --i) tabWidget->widget(i)->setEnabled(true); tabWidget->unsetCursor(); } void switchToCancel() { disconnect(searchButton, &QPushButton::clicked, p, &SearchForm::startSearch); for (QMap::ConstIterator it = itemToOnlineSearch.constBegin(); it != itemToOnlineSearch.constEnd(); ++it) connect(searchButton, &QPushButton::clicked, it.value(), &OnlineSearchAbstract::cancel); searchButton->setText(i18n("Stop")); searchButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); for (int i = tabWidget->count() - 1; i >= 0; --i) tabWidget->widget(i)->setEnabled(false); tabWidget->setCursor(Qt::WaitCursor); } void switchToEngines() { tabWidget->setCurrentWidget(listContainer); } void updateGUI() { if (whichEnginesLabel == nullptr) return; QStringList checkedEngines; QListWidgetItem *cursor = nullptr; for (QMap::ConstIterator it = itemToOnlineSearch.constBegin(); it != itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) { checkedEngines << it.key()->text(); cursor = it.key(); } switch (checkedEngines.size()) { case 0: whichEnginesLabel->setText(i18n("No search engine selected (change).")); break; case 1: whichEnginesLabel->setText(i18n("Search engine %1 is selected (change).", checkedEngines.first())); break; case 2: whichEnginesLabel->setText(i18n("Search engines %1 and %2 are selected (change).", checkedEngines.first(), checkedEngines.at(1))); break; case 3: whichEnginesLabel->setText(i18n("Search engines %1, %2, and %3 are selected (change).", checkedEngines.first(), checkedEngines.at(1), checkedEngines.at(2))); break; default: whichEnginesLabel->setText(i18n("Search engines %1, %2, and more are selected (change).", checkedEngines.first(), checkedEngines.at(1))); break; } OnlineSearchQueryFormAbstract *currentQueryWidget = nullptr; if (cursor != nullptr && checkedEngines.size() == 1) currentQueryWidget = cursor->data(WidgetRole).value(); if (currentQueryWidget == nullptr) currentQueryWidget = generalQueryTermsForm; QScrollArea *area = formToScrollArea.value(currentQueryWidget, nullptr); if (area != nullptr) queryTermsStack->setCurrentWidget(area); if (useEntryButton != nullptr) useEntryButton->setEnabled(!currentEntry.isNull() && tabWidget->currentIndex() == 0); } void openHomepage() { QListWidgetItem *item = enginesList->currentItem(); if (item != nullptr) { QUrl url = item->data(HomepageRole).toUrl(); /// Guess mime type for url to open QMimeType mimeType = FileInfo::mimeTypeForUrl(url); const QString mimeTypeName = mimeType.name(); /// Ask KDE subsystem to open url in viewer matching mime type #if KIO_VERSION < 0x051f00 // < 5.31.0 KRun::runUrl(url, mimeTypeName, p, false, false); #else // KIO_VERSION < 0x051f00 // >= 5.31.0 KRun::runUrl(url, mimeTypeName, p, KRun::RunFlags()); #endif // KIO_VERSION < 0x051f00 } } void enginesListCurrentChanged(QListWidgetItem *current) { actionOpenHomepage->setEnabled(current != nullptr); } }; SearchForm::SearchForm(SearchResults *searchResults, QWidget *parent) : QWidget(parent), d(new SearchFormPrivate(searchResults, this)) { d->loadEngines(); d->switchToSearch(); } SearchForm::~SearchForm() { delete d; } void SearchForm::updatedConfiguration() { d->loadEngines(); } void SearchForm::setElement(QSharedPointer element, const File *) { d->currentEntry = element.dynamicCast(); d->useEntryButton->setEnabled(!d->currentEntry.isNull() && d->tabWidget->currentIndex() == 0); } void SearchForm::switchToEngines() { d->switchToEngines(); } void SearchForm::startSearch() { OnlineSearchQueryFormAbstract *currentForm = d->currentQueryForm(); if (!currentForm->readyToStart()) { KMessageBox::sorry(this, i18n("Could not start searching the Internet:\nThe search terms are not complete or invalid."), i18n("Searching the Internet")); return; } d->runningSearches.clear(); d->sr->clear(); d->progressBar->setValue(0); d->progressMap.clear(); d->useEntryButton->hide(); d->progressBar->show(); if (currentForm == d->generalQueryTermsForm) { /// start search using the general-purpose form's values QMap queryTerms = d->generalQueryTermsForm->getQueryTerms(); int numResults = d->generalQueryTermsForm->getNumResults(); for (QMap::ConstIterator it = d->itemToOnlineSearch.constBegin(); it != d->itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) { it.value()->startSearch(queryTerms, numResults); d->runningSearches.insert(it.value()); } if (d->runningSearches.isEmpty()) { /// if no search engine has been checked (selected), something went wrong return; } } else { /// use the single selected search engine's specific form for (QMap::ConstIterator it = d->itemToOnlineSearch.constBegin(); it != d->itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) { it.value()->startSearchFromForm(); d->runningSearches.insert(it.value()); } if (d->runningSearches.isEmpty()) { /// if no search engine has been checked (selected), something went wrong return; } } d->switchToCancel(); } void SearchForm::foundEntry(QSharedPointer entry) { d->sr->insertElement(entry); } void SearchForm::stoppedSearch(int) { OnlineSearchAbstract *engine = static_cast(sender()); if (d->runningSearches.remove(engine)) { if (d->runningSearches.isEmpty()) { /// last search engine stopped d->switchToSearch(); emit doneSearching(); QTimer::singleShot(1000, d->progressBar, &QProgressBar::hide); QTimer::singleShot(1100, d->useEntryButton, &QPushButton::show); } else { QStringList remainingEngines; remainingEngines.reserve(d->runningSearches.size()); for (OnlineSearchAbstract *running : const_cast &>(d->runningSearches)) { remainingEngines.append(running->label()); } if (!remainingEngines.isEmpty()) qCDebug(LOG_KBIBTEX_PROGRAM) << "Remaining running engines:" << remainingEngines.join(QStringLiteral(", ")); } } } void SearchForm::tabSwitched(int newTab) { Q_UNUSED(newTab); d->updateGUI(); } void SearchForm::itemCheckChanged(QListWidgetItem *item) { int numCheckedEngines = 0; for (QMap::ConstIterator it = d->itemToOnlineSearch.constBegin(); it != d->itemToOnlineSearch.constEnd(); ++it) if (it.key()->checkState() == Qt::Checked) ++numCheckedEngines; d->searchButton->setEnabled(numCheckedEngines > 0); if (item != nullptr) { KConfigGroup configGroup(d->config, d->configGroupName); QString name = item->data(SearchForm::SearchFormPrivate::NameRole).toString(); configGroup.writeEntry(name, item->checkState() == Qt::Checked); d->config->sync(); } } void SearchForm::openHomepage() { d->openHomepage(); } void SearchForm::enginesListCurrentChanged(QListWidgetItem *current, QListWidgetItem *) { d->enginesListCurrentChanged(current); } void SearchForm::copyFromEntry() { Q_ASSERT_X(!d->currentEntry.isNull(), "SearchForm::copyFromEntry", "d->currentEntry is NULL"); d->currentQueryForm()->copyFromEntry(*(d->currentEntry)); } void SearchForm::updateProgress(int cur, int total) { OnlineSearchAbstract *ws = static_cast(sender()); d->progressMap[ws] = total > 0 ? cur * 1000 / total : 0; int progress = 0, count = 0; for (QMap::ConstIterator it = d->progressMap.constBegin(); it != d->progressMap.constEnd(); ++it, ++count) progress += it.value(); d->progressBar->setValue(count >= 1 ? progress / count : 0); } diff --git a/src/program/docklets/searchresults.cpp b/src/program/docklets/searchresults.cpp index fa77aa61..7bd48c35 100644 --- a/src/program/docklets/searchresults.cpp +++ b/src/program/docklets/searchresults.cpp @@ -1,224 +1,224 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "searchresults.h" #include #include #include #include #include #include -#include "file.h" -#include "file/clipboard.h" -#include "file/fileview.h" -#include "file/filedelegate.h" -#include "models/filemodel.h" -#include "idsuggestions.h" +#include +#include +#include +#include +#include +#include #include "logging_program.h" class SearchResults::SearchResultsPrivate { private: // UNUSED SearchResults *p; Clipboard *clipboard; public: MDIWidget *m; File *file; QWidget *widgetCannotImport; QLabel *labelCannotImportMsg; QPushButton *buttonImport; FileView *resultList, *mainEditor; QAction *actionViewCurrent, *actionImportSelected, *actionCopySelected; SearchResultsPrivate(MDIWidget *mdiWidget, SearchResults *parent) : /* UNUSED p(parent),*/ m(mdiWidget), file(new File()), mainEditor(nullptr) { QGridLayout *layout = new QGridLayout(parent); layout->setMargin(0); layout->setColumnStretch(0, 1); layout->setColumnStretch(1, 0); resultList = new FileView(QStringLiteral("SearchResults"), parent); resultList->setItemDelegate(new FileDelegate(resultList)); resultList->setReadOnly(true); resultList->setFrameShadow(QFrame::Sunken); resultList->setFrameShape(QFrame::StyledPanel); resultList->setContextMenuPolicy(Qt::ActionsContextMenu); layout->addWidget(resultList, 0, 0, 1, 2); clipboard = new Clipboard(resultList); /// Create a special widget that shows a small icon and a text /// stating that there are no search results. It will only be /// shown until the first search results arrive. // TODO nearly identical code as in ElementFormPrivate constructor, create common class widgetCannotImport = new QWidget(parent); layout->addWidget(widgetCannotImport, 1, 0, 1, 1); QBoxLayout *layoutCannotImport = new QHBoxLayout(widgetCannotImport); layoutCannotImport->addStretch(10); QLabel *label = new QLabel(widgetCannotImport); label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); label->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::Dialog, KIconLoader::SizeSmall)); layoutCannotImport->addWidget(label); labelCannotImportMsg = new QLabel(widgetCannotImport); labelCannotImportMsg->setAlignment(Qt::AlignRight | Qt::AlignVCenter); layoutCannotImport->addWidget(labelCannotImportMsg); /// see also updateCannotImportMessage() buttonImport = new QPushButton(QIcon::fromTheme(QStringLiteral("svn-update")), i18n("Import"), parent); layout->addWidget(buttonImport, 1, 1, 1, 1); buttonImport->setEnabled(false); SortFilterFileModel *model = new SortFilterFileModel(parent); FileModel *fileModel = new FileModel(parent); fileModel->setBibliographyFile(file); model->setSourceModel(fileModel); resultList->setModel(model); actionViewCurrent = new QAction(QIcon::fromTheme(QStringLiteral("document-preview")), i18n("View Element"), parent); resultList->addAction(actionViewCurrent); actionViewCurrent->setEnabled(false); connect(actionViewCurrent, &QAction::triggered, resultList, &FileView::viewCurrentElement); actionImportSelected = new QAction(QIcon::fromTheme(QStringLiteral("svn-update")), i18n("Import"), parent); resultList->addAction(actionImportSelected); actionImportSelected->setEnabled(false); connect(actionImportSelected, &QAction::triggered, parent, &SearchResults::importSelected); actionCopySelected = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"), parent); resultList->addAction(actionCopySelected); actionCopySelected->setEnabled(false); connect(actionCopySelected, &QAction::triggered, clipboard, &Clipboard::copy); connect(resultList, &FileView::doubleClicked, resultList, &FileView::viewCurrentElement); connect(resultList, &FileView::selectedElementsChanged, parent, &SearchResults::updateGUI); connect(buttonImport, &QPushButton::clicked, parent, &SearchResults::importSelected); updateCannotImportMessage(); } ~SearchResultsPrivate() { delete file; } void clear() { FileModel *model = resultList->fileModel(); if (model != nullptr) model->clear(); } bool insertElement(QSharedPointer element) { static IdSuggestions idSuggestions; FileModel *model = resultList->fileModel(); /// If the user had configured a default formatting string /// for entry ids, apply this formatting strings here QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) idSuggestions.applyDefaultFormatId(*entry.data()); bool result = model != nullptr ? model->insertRow(element, model->rowCount()) : false; if (result) resultList->sortFilterProxyModel()->invalidate(); return result; } void updateCannotImportMessage() { const bool isEmpty = resultList->model()->rowCount() == 0; if (mainEditor == nullptr && isEmpty) labelCannotImportMsg->setText(i18n("No results to import and no bibliography open to import to.")); else if (mainEditor == nullptr) labelCannotImportMsg->setText(i18n("No bibliography open to import to.")); else if (isEmpty) labelCannotImportMsg->setText(i18n("No results to import.")); widgetCannotImport->setVisible(mainEditor == nullptr || isEmpty); } }; SearchResults::SearchResults(MDIWidget *mdiWidget, QWidget *parent) : QWidget(parent), d(new SearchResultsPrivate(mdiWidget, this)) { /// nothing } SearchResults::~SearchResults() { delete d; } void SearchResults::clear() { d->clear(); } bool SearchResults::insertElement(QSharedPointer element) { const bool success = d->insertElement(element); if (success) d->updateCannotImportMessage(); return success; } void SearchResults::documentSwitched(FileView *oldEditor, FileView *newEditor) { Q_UNUSED(oldEditor); d->mainEditor = newEditor; updateGUI(); } void SearchResults::updateGUI() { d->updateCannotImportMessage(); d->buttonImport->setEnabled(d->mainEditor != nullptr && !d->resultList->selectedElements().isEmpty()); d->actionImportSelected->setEnabled(d->buttonImport->isEnabled()); d->actionCopySelected->setEnabled(!d->resultList->selectedElements().isEmpty()); d->actionViewCurrent->setEnabled(d->resultList->currentElement() != nullptr); } void SearchResults::importSelected() { Q_ASSERT_X(d->mainEditor != nullptr, "SearchResults::importSelected", "d->mainEditor is NULL"); FileModel *targetModel = d->mainEditor->fileModel(); FileModel *sourceModel = d->resultList->fileModel(); if (targetModel == nullptr || sourceModel == nullptr) return; ///< either source or target model is invalid bool atLeastOneSuccessfullInsertion = false; const QModelIndexList selList = d->resultList->selectionModel()->selectedRows(); for (const QModelIndex &modelIndex : selList) { /// Map from visible row to 'real' row /// that may be hidden through sorting int row = d->resultList->sortFilterProxyModel()->mapToSource(modelIndex).row(); /// Should only be an Entry, /// everthing else is unexpected QSharedPointer entry = sourceModel->element(row).dynamicCast(); if (!entry.isNull()) { /// Important: make clone of entry before inserting /// in main list, otherwise data would be shared QSharedPointer clone(new Entry(*entry)); atLeastOneSuccessfullInsertion |= targetModel->insertRow(clone, targetModel->rowCount()); } else qCWarning(LOG_KBIBTEX_PROGRAM) << "Trying to import something that isn't an Entry"; } if (atLeastOneSuccessfullInsertion) d->mainEditor->externalModification(); } diff --git a/src/program/docklets/statistics.cpp b/src/program/docklets/statistics.cpp index 99a6b704..5ec4a2a0 100644 --- a/src/program/docklets/statistics.cpp +++ b/src/program/docklets/statistics.cpp @@ -1,175 +1,175 @@ /*************************************************************************** - * Copyright (C) 2004-2018 by Thomas Fischer * + * Copyright (C) 2004-2019 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "statistics.h" #include #include #include #include #include -#include "file.h" -#include "file/fileview.h" -#include "entry.h" -#include "macro.h" -#include "comment.h" +#include +#include +#include +#include +#include #include "openfileinfo.h" class Statistics::StatisticsPrivate { private: // UNUSED Statistics *p; QLabel *labelNumberOfElements, *labelNumberOfEntries, *labelNumberOfJournalArticles, *labelNumberOfConferencePublications, *labelNumberOfBooks, *labelNumberOfOtherEntries, *labelNumberOfComments, *labelNumberOfMacros; public: FileView *fileView; const File *file; const QItemSelectionModel *selectionModel; StatisticsPrivate(Statistics *parent) : /* UNUSED p(parent),*/ fileView(nullptr), file(nullptr), selectionModel(nullptr) { QFormLayout *layout = new QFormLayout(parent); labelNumberOfElements = new QLabel(parent); setBold(labelNumberOfElements); layout->addRow(i18n("Number of Elements:"), labelNumberOfElements); labelNumberOfEntries = new QLabel(parent); setBold(labelNumberOfEntries); layout->addRow(i18n("Number of Entries:"), labelNumberOfEntries); labelNumberOfJournalArticles = new QLabel(parent); layout->addRow(i18n("Journal Articles:"), labelNumberOfJournalArticles); labelNumberOfConferencePublications = new QLabel(parent); layout->addRow(i18n("Conference Publications:"), labelNumberOfConferencePublications); labelNumberOfBooks = new QLabel(parent); layout->addRow(i18n("Books:"), labelNumberOfBooks); labelNumberOfOtherEntries = new QLabel(parent); layout->addRow(i18n("Other Entries:"), labelNumberOfOtherEntries); labelNumberOfComments = new QLabel(parent); setBold(labelNumberOfComments); layout->addRow(i18n("Number of Comments:"), labelNumberOfComments); labelNumberOfMacros = new QLabel(parent); setBold(labelNumberOfMacros); layout->addRow(i18n("Number of Macros:"), labelNumberOfMacros); } void setBold(QLabel *label) { QFont font = label->font(); font.setBold(true); label->setFont(font); } void update() { file = fileView != nullptr && fileView->fileModel() != nullptr ? fileView->fileModel()->bibliographyFile() : nullptr; selectionModel = fileView != nullptr ? fileView->selectionModel() : nullptr; if (file != nullptr) { int numElements, numEntries, numJournalArticles, numConferencePublications, numBooks, numComments, numMacros; countElementTypes(numElements, numEntries, numJournalArticles, numConferencePublications, numBooks, numComments, numMacros); labelNumberOfElements->setText(QString::number(numElements)); labelNumberOfEntries->setText(QString::number(numEntries)); labelNumberOfJournalArticles->setText(QString::number(numJournalArticles)); labelNumberOfConferencePublications->setText(QString::number(numConferencePublications)); labelNumberOfBooks->setText(QString::number(numBooks)); labelNumberOfOtherEntries->setText(QString::number(numEntries - numJournalArticles - numConferencePublications - numBooks)); labelNumberOfComments->setText(QString::number(numComments)); labelNumberOfMacros->setText(QString::number(numMacros)); } else { labelNumberOfElements->setText(QChar(0x2013)); labelNumberOfEntries->setText(QChar(0x2013)); labelNumberOfJournalArticles->setText(QChar(0x2013)); labelNumberOfConferencePublications->setText(QChar(0x2013)); labelNumberOfBooks->setText(QChar(0x2013)); labelNumberOfOtherEntries->setText(QChar(0x2013)); labelNumberOfComments->setText(QChar(0x2013)); labelNumberOfMacros->setText(QChar(0x2013)); } } void countElement(const QSharedPointer &element, int &numEntries, int &numJournalArticles, int &numConferencePublications, int &numBooks, int &numComments, int &numMacros) { const QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { if (entry->type().toLower() == Entry::etArticle) ++numJournalArticles; else if (entry->type().toLower() == Entry::etInProceedings) ++numConferencePublications; else if (entry->type().toLower() == Entry::etBook) ++numBooks; ++numEntries; } else if (!element.dynamicCast().isNull()) ++numMacros; else if (!element.dynamicCast().isNull()) ++numComments; } void countElementTypes(int &numElements, int &numEntries, int &numJournalArticles, int &numConferencePublications, int &numBooks, int &numComments, int &numMacros) { numElements = numEntries = numJournalArticles = numConferencePublications = numBooks = numComments = numMacros = 0; Q_ASSERT_X(file != nullptr, "Statistics::StatisticsPrivate::countElementTypes(..)", "Function was called with file==NULL"); if (selectionModel != nullptr && selectionModel->selectedRows().count() > 1) { /// Use selected items for statistics if selection contains at least two elements const auto selectedRows = selectionModel->selectedRows(); numElements = selectedRows.count(); for (const QModelIndex &index : selectedRows) { const int row = index.row(); if (row >= 0 && row < file->count()) countElement(file->at(row), numEntries, numJournalArticles, numConferencePublications, numBooks, numComments, numMacros); } } else { /// Default/fall-back: use whole file for statistics numElements = file->count(); for (const auto &element : const_cast(*file)) countElement(element, numEntries, numJournalArticles, numConferencePublications, numBooks, numComments, numMacros); } } }; Statistics::Statistics(QWidget *parent) : QWidget(parent), d(new StatisticsPrivate(this)) { d->update(); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::currentChanged, this, &Statistics::update); } Statistics::~Statistics() { delete d; } void Statistics::setFileView(FileView *fileView) { if (d->fileView != nullptr) disconnect(d->fileView, &FileView::selectedElementsChanged, this, &Statistics::update); d->fileView = fileView; if (d->fileView != nullptr) connect(d->fileView, &FileView::selectedElementsChanged, this, &Statistics::update); d->update(); } void Statistics::update() { d->update(); } diff --git a/src/program/docklets/valuelist.cpp b/src/program/docklets/valuelist.cpp index 59f10be0..5ec37c01 100644 --- a/src/program/docklets/valuelist.cpp +++ b/src/program/docklets/valuelist.cpp @@ -1,485 +1,485 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "valuelist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "bibtexfields.h" -#include "entry.h" -#include "file/fileview.h" -#include "valuelistmodel.h" -#include "models/filemodel.h" +#include +#include +#include +#include +#include class ValueList::ValueListPrivate { private: ValueList *p; ValueListDelegate *delegate; public: KSharedConfigPtr config; const QString configGroupName; const QString configKeyFieldName, configKeyShowCountColumn, configKeySortByCountAction, configKeyHeaderState; FileView *fileView; QTreeView *treeviewFieldValues; ValueListModel *model; QSortFilterProxyModel *sortingModel; QComboBox *comboboxFieldNames; QLineEdit *lineeditFilter; const int countWidth; QAction *assignSelectionAction; QAction *removeSelectionAction; KToggleAction *showCountColumnAction; KToggleAction *sortByCountAction; ValueListPrivate(ValueList *parent) : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Value List Docklet")), configKeyFieldName(QStringLiteral("FieldName")), configKeyShowCountColumn(QStringLiteral("ShowCountColumn")), configKeySortByCountAction(QStringLiteral("SortByCountAction")), configKeyHeaderState(QStringLiteral("HeaderState")), fileView(nullptr), model(nullptr), sortingModel(nullptr), countWidth(8 + parent->fontMetrics().width(i18n("Count"))) { setupGUI(); initialize(); } void setupGUI() { QBoxLayout *layout = new QVBoxLayout(p); layout->setMargin(0); comboboxFieldNames = new QComboBox(p); comboboxFieldNames->setEditable(true); layout->addWidget(comboboxFieldNames); lineeditFilter = new QLineEdit(p); layout->addWidget(lineeditFilter); lineeditFilter->setClearButtonEnabled(true); lineeditFilter->setPlaceholderText(i18n("Filter value list")); treeviewFieldValues = new QTreeView(p); layout->addWidget(treeviewFieldValues); treeviewFieldValues->setEditTriggers(QAbstractItemView::EditKeyPressed); treeviewFieldValues->setSortingEnabled(true); treeviewFieldValues->sortByColumn(0, Qt::AscendingOrder); delegate = new ValueListDelegate(treeviewFieldValues); treeviewFieldValues->setItemDelegate(delegate); treeviewFieldValues->setRootIsDecorated(false); treeviewFieldValues->setSelectionMode(QTreeView::ExtendedSelection); treeviewFieldValues->setAlternatingRowColors(true); treeviewFieldValues->header()->setSectionResizeMode(QHeaderView::Fixed); treeviewFieldValues->setContextMenuPolicy(Qt::ActionsContextMenu); /// create context menu item to start renaming QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Replace all occurrences"), p); connect(action, &QAction::triggered, p, &ValueList::startItemRenaming); treeviewFieldValues->addAction(action); /// create context menu item to delete value action = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Delete all occurrences"), p); connect(action, &QAction::triggered, p, &ValueList::deleteAllOccurrences); treeviewFieldValues->addAction(action); /// create context menu item to search for multiple selections action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search for selected values"), p); connect(action, &QAction::triggered, p, &ValueList::searchSelection); treeviewFieldValues->addAction(action); /// create context menu item to assign value to selected bibliography elements assignSelectionAction = new QAction(QIcon::fromTheme(QStringLiteral("emblem-new")), i18n("Add value to selected entries"), p); connect(assignSelectionAction, &QAction::triggered, p, &ValueList::assignSelection); treeviewFieldValues->addAction(assignSelectionAction); /// create context menu item to remove value from selected bibliography elements removeSelectionAction = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove value from selected entries"), p); connect(removeSelectionAction, &QAction::triggered, p, &ValueList::removeSelection); treeviewFieldValues->addAction(removeSelectionAction); p->setEnabled(false); connect(comboboxFieldNames, static_cast(&QComboBox::activated), p, &ValueList::fieldNamesChanged); connect(comboboxFieldNames, static_cast(&QComboBox::activated), lineeditFilter, &QLineEdit::clear); connect(treeviewFieldValues, &QTreeView::activated, p, &ValueList::listItemActivated); connect(delegate, &ValueListDelegate::closeEditor, treeviewFieldValues, &QTreeView::reset); /// add context menu to header treeviewFieldValues->header()->setContextMenuPolicy(Qt::ActionsContextMenu); showCountColumnAction = new KToggleAction(i18n("Show Count Column"), treeviewFieldValues); connect(showCountColumnAction, &QAction::triggered, p, &ValueList::showCountColumnToggled); treeviewFieldValues->header()->addAction(showCountColumnAction); sortByCountAction = new KToggleAction(i18n("Sort by Count"), treeviewFieldValues); connect(sortByCountAction, &QAction::triggered, p, &ValueList::sortByCountToggled); treeviewFieldValues->header()->addAction(sortByCountAction); } void setComboboxFieldNamesCurrentItem(const QString &text) { int index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchExactly); if (index < 0) index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchStartsWith); if (index < 0) index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchContains); if (index >= 0) comboboxFieldNames->setCurrentIndex(index); } void initialize() { lineeditFilter->clear(); comboboxFieldNames->clear(); for (const auto &fd : const_cast(BibTeXFields::instance())) { if (!fd.upperCamelCaseAlt.isEmpty()) continue; /// keep only "single" fields and not combined ones like "Author or Editor" if (fd.upperCamelCase.startsWith('^')) continue; /// skip "type" and "id" comboboxFieldNames->addItem(fd.label, fd.upperCamelCase); } /// Sort the combo box locale-aware. Thus we need a SortFilterProxyModel QSortFilterProxyModel *proxy = new QSortFilterProxyModel(comboboxFieldNames); proxy->setSortLocaleAware(true); proxy->setSourceModel(comboboxFieldNames->model()); comboboxFieldNames->model()->setParent(proxy); comboboxFieldNames->setModel(proxy); comboboxFieldNames->model()->sort(0); KConfigGroup configGroup(config, configGroupName); QString fieldName = configGroup.readEntry(configKeyFieldName, QString(Entry::ftAuthor)); setComboboxFieldNamesCurrentItem(fieldName); if (allowsMultipleValues(fieldName)) assignSelectionAction->setText(i18n("Add value to selected entries")); else assignSelectionAction->setText(i18n("Replace value of selected entries")); showCountColumnAction->setChecked(configGroup.readEntry(configKeyShowCountColumn, true)); sortByCountAction->setChecked(configGroup.readEntry(configKeySortByCountAction, false)); sortByCountAction->setEnabled(!showCountColumnAction->isChecked()); QByteArray headerState = configGroup.readEntry(configKeyHeaderState, QByteArray()); treeviewFieldValues->header()->restoreState(headerState); connect(treeviewFieldValues->header(), &QHeaderView::sortIndicatorChanged, p, &ValueList::columnsChanged); } void update() { QString text = comboboxFieldNames->itemData(comboboxFieldNames->currentIndex()).toString(); if (text.isEmpty()) text = comboboxFieldNames->currentText(); delegate->setFieldName(text); model = fileView == nullptr ? nullptr : fileView->valueListModel(text); QAbstractItemModel *usedModel = model; if (usedModel != nullptr) { model->setShowCountColumn(showCountColumnAction->isChecked()); model->setSortBy(sortByCountAction->isChecked() ? ValueListModel::SortByCount : ValueListModel::SortByText); if (sortingModel != nullptr) delete sortingModel; sortingModel = new QSortFilterProxyModel(p); sortingModel->setSourceModel(model); if (treeviewFieldValues->header()->isSortIndicatorShown()) sortingModel->sort(treeviewFieldValues->header()->sortIndicatorSection(), treeviewFieldValues->header()->sortIndicatorOrder()); else sortingModel->sort(1, Qt::DescendingOrder); sortingModel->setSortRole(ValueListModel::SortRole); sortingModel->setFilterKeyColumn(0); sortingModel->setFilterCaseSensitivity(Qt::CaseInsensitive); sortingModel->setFilterRole(ValueListModel::SearchTextRole); connect(lineeditFilter, &QLineEdit::textEdited, sortingModel, &QSortFilterProxyModel::setFilterFixedString); sortingModel->setSortLocaleAware(true); usedModel = sortingModel; } treeviewFieldValues->setModel(usedModel); KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(configKeyFieldName, text); config->sync(); } bool allowsMultipleValues(const QString &field) const { return (field.compare(Entry::ftAuthor, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftEditor, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftUrl, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftFile, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftLocalFile, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftDOI, Qt::CaseInsensitive) == 0 || field.compare(Entry::ftKeywords, Qt::CaseInsensitive) == 0); } }; ValueList::ValueList(QWidget *parent) : QWidget(parent), d(new ValueListPrivate(this)) { QTimer::singleShot(500, this, &ValueList::delayedResize); } ValueList::~ValueList() { delete d; } void ValueList::setFileView(FileView *fileView) { if (d->fileView != nullptr) disconnect(d->fileView, &FileView::selectedElementsChanged, this, &ValueList::editorSelectionChanged); d->fileView = fileView; if (d->fileView != nullptr) { connect(d->fileView, &FileView::selectedElementsChanged, this, &ValueList::editorSelectionChanged); connect(d->fileView, &FileView::destroyed, this, &ValueList::editorDestroyed); } editorSelectionChanged(); update(); resizeEvent(nullptr); } void ValueList::update() { d->update(); setEnabled(d->fileView != nullptr); } void ValueList::resizeEvent(QResizeEvent *) { int widgetWidth = d->treeviewFieldValues->size().width() - d->treeviewFieldValues->verticalScrollBar()->size().width() - 8; d->treeviewFieldValues->setColumnWidth(0, widgetWidth - d->countWidth); d->treeviewFieldValues->setColumnWidth(1, d->countWidth); } void ValueList::listItemActivated(const QModelIndex &index) { setEnabled(false); QString itemText = d->sortingModel->mapToSource(index).data(ValueListModel::SearchTextRole).toString(); QVariant fieldVar = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()); QString fieldText = fieldVar.toString(); if (fieldText.isEmpty()) fieldText = d->comboboxFieldNames->currentText(); SortFilterFileModel::FilterQuery fq; fq.terms << itemText; fq.combination = SortFilterFileModel::EveryTerm; fq.field = fieldText; fq.searchPDFfiles = false; d->fileView->setFilterBarFilter(fq); setEnabled(true); } void ValueList::searchSelection() { QVariant fieldVar = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()); QString fieldText = fieldVar.toString(); if (fieldText.isEmpty()) fieldText = d->comboboxFieldNames->currentText(); SortFilterFileModel::FilterQuery fq; fq.combination = SortFilterFileModel::EveryTerm; fq.field = fieldText; const auto selectedIndexes = d->treeviewFieldValues->selectionModel()->selectedIndexes(); for (const QModelIndex &index : selectedIndexes) { if (index.column() == 0) { QString itemText = index.data(ValueListModel::SearchTextRole).toString(); fq.terms << itemText; } } fq.searchPDFfiles = false; if (!fq.terms.isEmpty()) d->fileView->setFilterBarFilter(fq); } void ValueList::assignSelection() { QString field = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()).toString(); if (field.isEmpty()) field = d->comboboxFieldNames->currentText(); if (field.isEmpty()) return; ///< empty field is invalid; quit const Value toBeAssignedValue = d->sortingModel->mapToSource(d->treeviewFieldValues->currentIndex()).data(Qt::EditRole).value(); if (toBeAssignedValue.isEmpty()) return; ///< empty value is invalid; quit const QString toBeAssignedValueText = PlainTextValue::text(toBeAssignedValue); /// Keep track if any modifications were made to the bibliography file bool madeModification = false; /// Go through all selected elements in current editor const QList > &selection = d->fileView->selectedElements(); for (const auto &element : selection) { /// Only entries (not macros or comments) are of interest QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { /// Fields are separated into two categories: /// 1. Where more values can be appended, like authors or URLs /// 2. Where values should be replaced, like title, year, or journal if (d->allowsMultipleValues(field)) { /// Fields for which multiple values are valid bool valueItemAlreadyContained = false; ///< add only if to-be-assigned value is not yet contained Value entrysValueForField = entry->value(field); for (const auto &containedValueItem : const_cast(entrysValueForField)) { valueItemAlreadyContained |= PlainTextValue::text(containedValueItem) == toBeAssignedValueText; if (valueItemAlreadyContained) break; } if (!valueItemAlreadyContained) { /// Add each ValueItem from the to-be-assigned value to the entry's value for this field entrysValueForField.reserve(toBeAssignedValue.size()); for (const auto &newValueItem : toBeAssignedValue) { entrysValueForField.append(newValueItem); } /// "Write back" value to field in entry entry->remove(field); entry->insert(field, entrysValueForField); /// Keep track that bibliography file has been modified madeModification = true; } } else { /// Fields for which only value is valid, thus the old value will be replaced entry->remove(field); entry->insert(field, toBeAssignedValue); /// Keep track that bibliography file has been modified madeModification = true; } } } if (madeModification) { /// Notify main editor about change it its data d->fileView->externalModification(); } } void ValueList::removeSelection() { QString field = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()).toString(); if (field.isEmpty()) field = d->comboboxFieldNames->currentText(); if (field.isEmpty()) return; ///< empty field is invalid; quit const Value toBeRemovedValue = d->sortingModel->mapToSource(d->treeviewFieldValues->currentIndex()).data(Qt::EditRole).value(); if (toBeRemovedValue.isEmpty()) return; ///< empty value is invalid; quit const QString toBeRemovedValueText = PlainTextValue::text(toBeRemovedValue); /// Keep track if any modifications were made to the bibliography file bool madeModification = false; /// Go through all selected elements in current editor const QList > &selection = d->fileView->selectedElements(); for (const auto &element : selection) { /// Only entries (not macros or comments) are of interest QSharedPointer entry = element.dynamicCast(); if (!entry.isNull()) { Value entrysValueForField = entry->value(field); bool valueModified = false; for (int i = 0; i < entrysValueForField.count(); ++i) { const QString valueItemText = PlainTextValue::text(entrysValueForField[i]); if (valueItemText == toBeRemovedValueText) { valueModified = true; entrysValueForField.remove(i); break; } } if (valueModified) { entry->remove(field); entry->insert(field, entrysValueForField); madeModification = true; } } } if (madeModification) { update(); /// Notify main editor about change it its data d->fileView->externalModification(); } } void ValueList::startItemRenaming() { /// Get current index from sorted model QModelIndex sortedIndex = d->treeviewFieldValues->currentIndex(); /// Make the tree view start and editing delegate on the index d->treeviewFieldValues->edit(sortedIndex); } void ValueList::deleteAllOccurrences() { /// Get current index from sorted model QModelIndex sortedIndex = d->treeviewFieldValues->currentIndex(); /// Get "real" index from original model, but resort to sibling in first column QModelIndex realIndex = d->sortingModel->mapToSource(sortedIndex); realIndex = realIndex.sibling(realIndex.row(), 0); /// Remove current index from data model d->model->removeValue(realIndex); /// Notify main editor about change it its data d->fileView->externalModification(); } void ValueList::showCountColumnToggled() { if (d->model != nullptr) d->model->setShowCountColumn(d->showCountColumnAction->isChecked()); if (d->showCountColumnAction->isChecked()) resizeEvent(nullptr); d->sortByCountAction->setEnabled(!d->showCountColumnAction->isChecked()); KConfigGroup configGroup(d->config, d->configGroupName); configGroup.writeEntry(d->configKeyShowCountColumn, d->showCountColumnAction->isChecked()); d->config->sync(); } void ValueList::sortByCountToggled() { if (d->model != nullptr) d->model->setSortBy(d->sortByCountAction->isChecked() ? ValueListModel::SortByCount : ValueListModel::SortByText); KConfigGroup configGroup(d->config, d->configGroupName); configGroup.writeEntry(d->configKeySortByCountAction, d->sortByCountAction->isChecked()); d->config->sync(); } void ValueList::delayedResize() { resizeEvent(nullptr); } void ValueList::columnsChanged() { QByteArray headerState = d->treeviewFieldValues->header()->saveState(); KConfigGroup configGroup(d->config, d->configGroupName); configGroup.writeEntry(d->configKeyHeaderState, headerState); d->config->sync(); resizeEvent(nullptr); } void ValueList::editorSelectionChanged() { const bool selectedElements = d->fileView == nullptr ? false : d->fileView->selectedElements().count() > 0; d->assignSelectionAction->setEnabled(selectedElements); d->removeSelectionAction->setEnabled(selectedElements); } void ValueList::editorDestroyed() { /// Reset internal variable to NULL to avoid /// accessing invalid pointer/data later d->fileView = nullptr; editorSelectionChanged(); } void ValueList::fieldNamesChanged(int i) { const QString field = d->comboboxFieldNames->itemData(i).toString(); if (d->allowsMultipleValues(field)) d->assignSelectionAction->setText(i18n("Add value to selected entries")); else d->assignSelectionAction->setText(i18n("Replace value of selected entries")); update(); } diff --git a/src/program/docklets/zoterobrowser.cpp b/src/program/docklets/zoterobrowser.cpp index e9f50c6b..5b90803a 100644 --- a/src/program/docklets/zoterobrowser.cpp +++ b/src/program/docklets/zoterobrowser.cpp @@ -1,441 +1,441 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "zoterobrowser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "element.h" +#include #include "searchresults.h" #include "zotero/collectionmodel.h" #include "zotero/collection.h" #include "zotero/items.h" #include "zotero/groups.h" #include "zotero/tags.h" #include "zotero/tagmodel.h" #include "zotero/api.h" #include "zotero/oauthwizard.h" using KWallet::Wallet; class ZoteroBrowser::Private { private: ZoteroBrowser *p; public: Zotero::Items *items; Zotero::Groups *groups; Zotero::Tags *tags; Zotero::TagModel *tagModel; Zotero::Collection *collection; Zotero::CollectionModel *collectionModel; QSharedPointer api; bool needToApplyCredentials; SearchResults *searchResults; QTabWidget *tabWidget; QTreeView *collectionBrowser; QListView *tagBrowser; QLineEdit *lineEditNumericUserId; QLineEdit *lineEditApiKey; QRadioButton *radioPersonalLibrary; QRadioButton *radioGroupLibrary; bool comboBoxGroupListInitialized; QComboBox *comboBoxGroupList; QCursor nonBusyCursor; Wallet *wallet; static const QString walletFolderOAuth, walletEntryKBibTeXZotero, walletKeyZoteroId, walletKeyZoteroApiKey; Private(SearchResults *sr, ZoteroBrowser *parent) : p(parent), items(nullptr), groups(nullptr), tags(nullptr), tagModel(nullptr), collection(nullptr), collectionModel(nullptr), needToApplyCredentials(true), searchResults(sr), comboBoxGroupListInitialized(false), nonBusyCursor(p->cursor()), wallet(nullptr) { setupGUI(); } ~Private() { if (wallet != nullptr) delete wallet; if (items != nullptr) delete items; if (groups != nullptr) delete groups; if (tags != nullptr) delete tags; if (tagModel != nullptr) delete tagModel; if (collection != nullptr) delete collection; if (collectionModel != nullptr) delete collectionModel; api.clear(); } void setupGUI() { QBoxLayout *layout = new QVBoxLayout(p); tabWidget = new QTabWidget(p); layout->addWidget(tabWidget); QWidget *container = new QWidget(tabWidget); tabWidget->addTab(container, QIcon::fromTheme(QStringLiteral("preferences-web-browser-identification")), i18n("Library")); connect(tabWidget, &QTabWidget::currentChanged, p, &ZoteroBrowser::tabChanged); QBoxLayout *containerLayout = new QVBoxLayout(container); /// Personal or Group Library QGridLayout *gridLayout = new QGridLayout(); containerLayout->addLayout(gridLayout); gridLayout->setMargin(0); gridLayout->setColumnMinimumWidth(0, 16); // TODO determine size of a radio button radioPersonalLibrary = new QRadioButton(i18n("Personal library"), container); gridLayout->addWidget(radioPersonalLibrary, 0, 0, 1, 2); radioGroupLibrary = new QRadioButton(i18n("Group library"), container); gridLayout->addWidget(radioGroupLibrary, 1, 0, 1, 2); comboBoxGroupList = new QComboBox(container); gridLayout->addWidget(comboBoxGroupList, 2, 1, 1, 1); QSizePolicy sizePolicy = comboBoxGroupList->sizePolicy(); sizePolicy.setHorizontalPolicy(QSizePolicy::MinimumExpanding); comboBoxGroupList->setSizePolicy(sizePolicy); radioPersonalLibrary->setChecked(true); comboBoxGroupList->setEnabled(false); comboBoxGroupList->addItem(i18n("No groups available")); connect(radioGroupLibrary, &QRadioButton::toggled, p, &ZoteroBrowser::radioButtonsToggled); connect(radioPersonalLibrary, &QRadioButton::toggled, p, &ZoteroBrowser::radioButtonsToggled); connect(comboBoxGroupList, static_cast(&QComboBox::currentIndexChanged), p, &ZoteroBrowser::groupListChanged); containerLayout->addStretch(10); /// Credentials QFormLayout *containerForm = new QFormLayout(); containerLayout->addLayout(containerForm, 1); containerForm->setMargin(0); lineEditNumericUserId = new QLineEdit(container); lineEditNumericUserId->setSizePolicy(sizePolicy); lineEditNumericUserId->setReadOnly(true); containerForm->addRow(i18n("Numeric user id:"), lineEditNumericUserId); connect(lineEditNumericUserId, &QLineEdit::textChanged, p, &ZoteroBrowser::invalidateGroupList); lineEditApiKey = new QLineEdit(container); lineEditApiKey->setSizePolicy(sizePolicy); lineEditApiKey->setReadOnly(true); containerForm->addRow(i18n("API key:"), lineEditApiKey); connect(lineEditApiKey, &QLineEdit::textChanged, p, &ZoteroBrowser::invalidateGroupList); QBoxLayout *containerButtonLayout = new QHBoxLayout(); containerLayout->addLayout(containerButtonLayout, 0); containerButtonLayout->setMargin(0); QPushButton *buttonGetOAuthCredentials = new QPushButton(QIcon::fromTheme(QStringLiteral("preferences-web-browser-identification")), i18n("Get New Credentials"), container); containerButtonLayout->addWidget(buttonGetOAuthCredentials, 0); connect(buttonGetOAuthCredentials, &QPushButton::clicked, p, &ZoteroBrowser::getOAuthCredentials); containerButtonLayout->addStretch(1); /// Collection browser collectionBrowser = new QTreeView(tabWidget); tabWidget->addTab(collectionBrowser, QIcon::fromTheme(QStringLiteral("folder-yellow")), i18n("Collections")); collectionBrowser->setHeaderHidden(true); collectionBrowser->setExpandsOnDoubleClick(false); connect(collectionBrowser, &QTreeView::doubleClicked, p, &ZoteroBrowser::collectionDoubleClicked); /// Tag browser tagBrowser = new QListView(tabWidget); tabWidget->addTab(tagBrowser, QIcon::fromTheme(QStringLiteral("mail-tagged")), i18n("Tags")); connect(tagBrowser, &QListView::doubleClicked, p, &ZoteroBrowser::tagDoubleClicked); } void queueReadOAuthCredentials() { if (wallet != nullptr && wallet->isOpen()) p->readOAuthCredentials(true); else { /// Wallet is closed or not initialized if (wallet != nullptr) /// Delete existing but closed wallet, will be replaced by new, open wallet soon delete wallet; p->setEnabled(false); p->setCursor(Qt::WaitCursor); wallet = Wallet::openWallet(Wallet::NetworkWallet(), p->winId(), Wallet::Asynchronous); connect(wallet, &Wallet::walletOpened, p, &ZoteroBrowser::readOAuthCredentials); } } void queueWriteOAuthCredentials() { if (wallet != nullptr && wallet->isOpen()) p->writeOAuthCredentials(true); else { /// Wallet is closed or not initialized if (wallet != nullptr) /// Delete existing but closed wallet, will be replaced by new, open wallet soon delete wallet; p->setEnabled(false); p->setCursor(Qt::WaitCursor); wallet = Wallet::openWallet(Wallet::NetworkWallet(), p->winId(), Wallet::Asynchronous); connect(wallet, &Wallet::walletOpened, p, &ZoteroBrowser::writeOAuthCredentials); } } }; const QString ZoteroBrowser::Private::walletFolderOAuth = QStringLiteral("OAuth"); const QString ZoteroBrowser::Private::walletEntryKBibTeXZotero = QStringLiteral("KBibTeX/Zotero"); const QString ZoteroBrowser::Private::walletKeyZoteroId = QStringLiteral("UserId"); const QString ZoteroBrowser::Private::walletKeyZoteroApiKey = QStringLiteral("ApiKey"); ZoteroBrowser::ZoteroBrowser(SearchResults *searchResults, QWidget *parent) : QWidget(parent), d(new ZoteroBrowser::Private(searchResults, this)) { /// Forece GUI update updateButtons(); radioButtonsToggled(); } ZoteroBrowser::~ZoteroBrowser() { delete d; } void ZoteroBrowser::visibiltyChanged(bool v) { if (v && d->lineEditApiKey->text().isEmpty()) /// If Zotero dock became visible and no API key is set, check KWallet for credentials d->queueReadOAuthCredentials(); } void ZoteroBrowser::modelReset() { if (!d->collection->busy() && !d->tags->busy()) { setCursor(d->nonBusyCursor); setEnabled(true); } else { setCursor(Qt::WaitCursor); setEnabled(false); } if (!d->tags->busy() && !d->collection->busy() && !(d->collection->initialized() && d->tags->initialized())) KMessageBox::information(this, i18n("KBibTeX failed to retrieve the bibliography from Zotero. Please check that the provided user id and API key are valid."), i18n("Failed to retrieve data from Zotero")); } void ZoteroBrowser::collectionDoubleClicked(const QModelIndex &index) { setCursor(Qt::WaitCursor); setEnabled(false); ///< will be re-enabled when item retrieve got finished (slot reenableWidget) const QString collectionId = index.data(Zotero::CollectionModel::CollectionIdRole).toString(); d->searchResults->clear(); d->items->retrieveItemsByCollection(collectionId); } void ZoteroBrowser::tagDoubleClicked(const QModelIndex &index) { setCursor(Qt::WaitCursor); setEnabled(false); ///< will be re-enabled when item retrieve got finished (slot reenableWidget) const QString tag = index.data(Zotero::TagModel::TagRole).toString(); d->searchResults->clear(); d->items->retrieveItemsByTag(tag); } void ZoteroBrowser::showItem(QSharedPointer e) { d->searchResults->insertElement(e); emit itemToShow(); } void ZoteroBrowser::reenableWidget() { setCursor(d->nonBusyCursor); setEnabled(true); } void ZoteroBrowser::updateButtons() { const bool validNumericIdAndApiKey = !d->lineEditNumericUserId->text().isEmpty() && !d->lineEditApiKey->text().isEmpty(); d->radioGroupLibrary->setEnabled(validNumericIdAndApiKey); d->radioPersonalLibrary->setEnabled(validNumericIdAndApiKey); d->needToApplyCredentials = true; } bool ZoteroBrowser::applyCredentials() { bool ok = false; const int userId = d->lineEditNumericUserId->text().toInt(&ok); const QString apiKey = d->lineEditApiKey->text(); if (ok && !apiKey.isEmpty()) { setCursor(Qt::WaitCursor); setEnabled(false); ok = false; int groupId = d->comboBoxGroupList->itemData(d->comboBoxGroupList->currentIndex()).toInt(&ok); if (!ok) groupId = -1; disconnect(d->tags, &Zotero::Tags::finishedLoading, this, &ZoteroBrowser::reenableWidget); disconnect(d->items, &Zotero::Items::stoppedSearch, this, &ZoteroBrowser::reenableWidget); disconnect(d->items, &Zotero::Items::foundElement, this, &ZoteroBrowser::showItem); disconnect(d->tagModel, &Zotero::TagModel::modelReset, this, &ZoteroBrowser::modelReset); disconnect(d->collectionModel, &Zotero::CollectionModel::modelReset, this, &ZoteroBrowser::modelReset); d->collection->deleteLater(); d->items->deleteLater(); d->tags->deleteLater(); d->collectionModel->deleteLater(); d->tagModel->deleteLater(); d->api.clear(); const bool makeGroupRequest = d->radioGroupLibrary->isChecked() && groupId > 0; d->api = QSharedPointer(new Zotero::API(makeGroupRequest ? Zotero::API::GroupRequest : Zotero::API::UserRequest, makeGroupRequest ? groupId : userId, d->lineEditApiKey->text(), this)); d->items = new Zotero::Items(d->api, this); d->tags = new Zotero::Tags(d->api, this); d->tagModel = new Zotero::TagModel(d->tags, this); d->tagBrowser->setModel(d->tagModel); d->collection = new Zotero::Collection(d->api, this); d->collectionModel = new Zotero::CollectionModel(d->collection, this); d->collectionBrowser->setModel(d->collectionModel); connect(d->collectionModel, &Zotero::CollectionModel::modelReset, this, &ZoteroBrowser::modelReset); connect(d->tagModel, &Zotero::TagModel::modelReset, this, &ZoteroBrowser::modelReset); connect(d->items, &Zotero::Items::foundElement, this, &ZoteroBrowser::showItem); connect(d->items, &Zotero::Items::stoppedSearch, this, &ZoteroBrowser::reenableWidget); connect(d->tags, &Zotero::Tags::finishedLoading, this, &ZoteroBrowser::reenableWidget); d->needToApplyCredentials = false; return true; } else return false; } void ZoteroBrowser::radioButtonsToggled() { d->comboBoxGroupList->setEnabled(d->comboBoxGroupListInitialized && d->comboBoxGroupList->count() > 0 && d->radioGroupLibrary->isChecked()); if (!d->comboBoxGroupListInitialized && d->radioGroupLibrary->isChecked()) retrieveGroupList(); d->needToApplyCredentials = true; } void ZoteroBrowser::groupListChanged() { d->needToApplyCredentials = true; } void ZoteroBrowser::retrieveGroupList() { bool ok = false; const int userId = d->lineEditNumericUserId->text().toInt(&ok); if (ok) { setCursor(Qt::WaitCursor); setEnabled(false); d->comboBoxGroupList->clear(); d->comboBoxGroupListInitialized = false; disconnect(d->groups, &Zotero::Groups::finishedLoading, this, &ZoteroBrowser::gotGroupList); d->groups->deleteLater(); d->api.clear(); d->api = QSharedPointer(new Zotero::API(Zotero::API::UserRequest, userId, d->lineEditApiKey->text(), this)); d->groups = new Zotero::Groups(d->api, this); connect(d->groups, &Zotero::Groups::finishedLoading, this, &ZoteroBrowser::gotGroupList); } } void ZoteroBrowser::invalidateGroupList() { d->comboBoxGroupList->clear(); d->comboBoxGroupListInitialized = false; d->comboBoxGroupList->addItem(i18n("No groups available or no permissions")); d->comboBoxGroupList->setEnabled(false); d->radioPersonalLibrary->setChecked(true); } void ZoteroBrowser::gotGroupList() { const QMap groupMap = d->groups->groups(); for (QMap::ConstIterator it = groupMap.constBegin(); it != groupMap.constEnd(); ++it) { d->comboBoxGroupList->addItem(it.value(), QVariant::fromValue(it.key())); } if (groupMap.isEmpty()) { invalidateGroupList(); } else { d->comboBoxGroupListInitialized = true; d->comboBoxGroupList->setEnabled(true); d->needToApplyCredentials = true; } reenableWidget(); } void ZoteroBrowser::getOAuthCredentials() { QPointer wizard = new Zotero::OAuthWizard(this); if (wizard->exec() && !wizard->apiKey().isEmpty() && wizard->userId() >= 0) { d->lineEditApiKey->setText(wizard->apiKey()); d->lineEditNumericUserId->setText(QString::number(wizard->userId())); d->queueWriteOAuthCredentials(); updateButtons(); retrieveGroupList(); } delete wizard; } void ZoteroBrowser::readOAuthCredentials(bool ok) { /// Do not call this slot a second time disconnect(d->wallet, &Wallet::walletOpened, this, &ZoteroBrowser::readOAuthCredentials); if (ok && (d->wallet->hasFolder(ZoteroBrowser::Private::walletFolderOAuth) || d->wallet->createFolder(ZoteroBrowser::Private::walletFolderOAuth)) && d->wallet->setFolder(ZoteroBrowser::Private::walletFolderOAuth)) { if (d->wallet->hasEntry(ZoteroBrowser::Private::walletEntryKBibTeXZotero)) { QMap map; if (d->wallet->readMap(ZoteroBrowser::Private::walletEntryKBibTeXZotero, map) == 0) { if (map.contains(ZoteroBrowser::Private::walletKeyZoteroId) && map.contains(ZoteroBrowser::Private::walletKeyZoteroApiKey)) { d->lineEditNumericUserId->setText(map.value(ZoteroBrowser::Private::walletKeyZoteroId, QString())); d->lineEditApiKey->setText(map.value(ZoteroBrowser::Private::walletKeyZoteroApiKey, QString())); updateButtons(); retrieveGroupList(); } else qWarning() << "Failed to locate Zotero Id and/or API key in KWallet"; } else qWarning() << "Failed to access Zotero data in KWallet"; } else qDebug() << "No Zotero credentials stored in KWallet"; } else qWarning() << "Accessing KWallet to sync API key did not succeed"; reenableWidget(); } void ZoteroBrowser::writeOAuthCredentials(bool ok) { disconnect(d->wallet, &Wallet::walletOpened, this, &ZoteroBrowser::writeOAuthCredentials); if (ok && (d->wallet->hasFolder(ZoteroBrowser::Private::walletFolderOAuth) || d->wallet->createFolder(ZoteroBrowser::Private::walletFolderOAuth)) && d->wallet->setFolder(ZoteroBrowser::Private::walletFolderOAuth)) { QMap map; map.insert(ZoteroBrowser::Private::walletKeyZoteroId, d->lineEditNumericUserId->text()); map.insert(ZoteroBrowser::Private::walletKeyZoteroApiKey, d->lineEditApiKey->text()); if (d->wallet->writeMap(ZoteroBrowser::Private::walletEntryKBibTeXZotero, map) != 0) qWarning() << "Writing API key to KWallet failed"; } else qWarning() << "Accessing KWallet to sync API key did not succeed"; reenableWidget(); } void ZoteroBrowser::tabChanged(int newTabIndex) { if (newTabIndex > 0 /** tabs after credential tab*/ && d->needToApplyCredentials) { const bool success = applyCredentials(); for (int i = 1; i < d->tabWidget->count(); ++i) d->tabWidget->widget(i)->setEnabled(success); } } diff --git a/src/program/documentlist.cpp b/src/program/documentlist.cpp index 79c5c073..b6593481 100644 --- a/src/program/documentlist.cpp +++ b/src/program/documentlist.cpp @@ -1,425 +1,425 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "documentlist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "kbibtex.h" +#include class DirOperatorWidget : public QWidget { Q_OBJECT public: KDirOperator *dirOperator; DirOperatorWidget(QWidget *parent) : QWidget(parent) { QGridLayout *layout = new QGridLayout(this); layout->setMargin(0); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 0); layout->setColumnStretch(2, 1); QPushButton *buttonUp = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up")), QString(), this); buttonUp->setToolTip(i18n("One level up")); layout->addWidget(buttonUp, 0, 0, 1, 1); QPushButton *buttonHome = new QPushButton(QIcon::fromTheme(QStringLiteral("user-home")), QString(), this); buttonHome->setToolTip(i18n("Go to Home folder")); layout->addWidget(buttonHome, 0, 1, 1, 1); dirOperator = new KDirOperator(QUrl(QStringLiteral("file:") + QDir::homePath()), this); layout->addWidget(dirOperator, 1, 0, 1, 3); dirOperator->setView(KFile::Detail); connect(buttonUp, &QPushButton::clicked, dirOperator, &KDirOperator::cdUp); connect(buttonHome, &QPushButton::clicked, dirOperator, &KDirOperator::home); } }; DocumentListDelegate::DocumentListDelegate(QObject *parent) : QStyledItemDelegate(parent) { /// nothing } void DocumentListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int height = option.rect.height(); QStyle *style = QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); painter->save(); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlightedText().color())); } else { painter->setPen(QPen(option.palette.text().color())); } OpenFileInfo *ofi = qvariant_cast(index.data(Qt::UserRole)); if (OpenFileInfoManager::instance().currentFile() == ofi) { /// for the currently open file, use a bold font to write file name QFont font = painter->font(); font.setBold(true); painter->setFont(font); } QRect textRect = option.rect; textRect.setLeft(textRect.left() + height + 4); textRect.setHeight(height / 2); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString()); textRect = option.rect; textRect.setLeft(textRect.left() + height + 4); textRect.setTop(textRect.top() + height / 2); textRect.setHeight(height * 3 / 8); QFont font = painter->font(); font.setPointSize(font.pointSize() * 7 / 8); painter->setFont(font); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, ofi->fullCaption()); QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); painter->drawPixmap(option.rect.left() + 1, option.rect.top() + 1, height - 2, height - 2, icon.pixmap(height - 2, height - 2)); painter->restore(); } QSize DocumentListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); size.setHeight(size.height() * 9 / 4); return size; } class DocumentListModel::DocumentListModelPrivate { public: OpenFileInfo::StatusFlag sf; OpenFileInfoManager::OpenFileInfoList ofiList; public: DocumentListModelPrivate(OpenFileInfo::StatusFlag statusFlag, DocumentListModel *parent) : sf(statusFlag) { Q_UNUSED(parent) } }; DocumentListModel::DocumentListModel(OpenFileInfo::StatusFlag statusFlag, QObject *parent) : QAbstractListModel(parent), d(new DocumentListModel::DocumentListModelPrivate(statusFlag, this)) { listsChanged(d->sf); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &DocumentListModel::listsChanged); } DocumentListModel::~DocumentListModel() { delete d; } int DocumentListModel::rowCount(const QModelIndex &parent) const { if (parent != QModelIndex()) return 0; return d->ofiList.count(); } QVariant DocumentListModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= rowCount()) return QVariant(); OpenFileInfo *openFileInfo = d->ofiList[index.row()]; const QString iconName = openFileInfo->mimeType().replace(QLatin1Char('/'), QLatin1Char('-')); switch (role) { case Qt::DisplayRole: return openFileInfo->shortCaption(); case Qt::DecorationRole: { /// determine mime type-based icon and overlays (e.g. for modified files) QStringList overlays; if (openFileInfo->flags().testFlag(OpenFileInfo::Favorite)) overlays << QStringLiteral("favorites"); else overlays << QString(); if (openFileInfo->flags().testFlag(OpenFileInfo::RecentlyUsed)) overlays << QStringLiteral("document-open-recent"); else overlays << QString(); if (openFileInfo->flags().testFlag(OpenFileInfo::Open)) overlays << QStringLiteral("folder-open"); else overlays << QString(); if (openFileInfo->isModified()) overlays << QStringLiteral("document-save"); else overlays << QString(); return KDE::icon(iconName, overlays, nullptr); } case Qt::ToolTipRole: { QString htmlText(QString(QStringLiteral(" %2")).arg(KIconLoader::global()->iconPath(iconName, KIconLoader::Small), openFileInfo->shortCaption())); const QUrl url = openFileInfo->url(); if (url.isValid()) { QString path(QFileInfo(url.path()).path()); if (!path.endsWith(QLatin1Char('/'))) path.append(QLatin1Char('/')); htmlText.append(i18n("
located in %1", path)); } QStringList flagListItems; if (openFileInfo->flags().testFlag(OpenFileInfo::Favorite)) flagListItems << i18n("Favorite"); if (openFileInfo->flags().testFlag(OpenFileInfo::RecentlyUsed)) flagListItems << i18n("Recently Used"); if (openFileInfo->flags().testFlag(OpenFileInfo::Open)) flagListItems << i18n("Open"); if (openFileInfo->isModified()) flagListItems << i18n("Modified"); if (!flagListItems.empty()) { htmlText.append(QStringLiteral("
    ")); for (const QString &flagListItem : const_cast(flagListItems)) { htmlText.append(QString(QStringLiteral("
  • %1
  • ")).arg(flagListItem)); } htmlText.append(QStringLiteral("
")); } htmlText.append(QStringLiteral("
")); return htmlText; } case Qt::UserRole: return qVariantFromValue(openFileInfo); default: return QVariant(); } } QVariant DocumentListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || section != 0 || role != Qt::DisplayRole) return QVariant(); return QVariant("List of Files"); } void DocumentListModel::listsChanged(OpenFileInfo::StatusFlags statusFlags) { if (statusFlags.testFlag(d->sf)) { beginResetModel(); d->ofiList = OpenFileInfoManager::instance().filteredItems(d->sf); endResetModel(); } } class DocumentListView::DocumentListViewPrivate { private: DocumentListView *p; public: QAction *actionAddToFav, *actionRemFromFav; QAction *actionCloseFile, *actionOpenFile; KActionMenu *actionOpenMenu; QList openMenuActions; KService::List openMenuServices; QSignalMapper openMenuSignalMapper; DocumentListViewPrivate(DocumentListView *parent) : p(parent), actionAddToFav(nullptr), actionRemFromFav(nullptr), actionCloseFile(nullptr), actionOpenFile(nullptr), actionOpenMenu(nullptr) { connect(&openMenuSignalMapper, static_cast(&QSignalMapper::mapped), p, &DocumentListView::openFileWithService); } }; DocumentListView::DocumentListView(OpenFileInfo::StatusFlag statusFlag, QWidget *parent) : QListView(parent), d(new DocumentListViewPrivate(this)) { setContextMenuPolicy(Qt::ActionsContextMenu); setItemDelegate(new DocumentListDelegate(this)); if (statusFlag == OpenFileInfo::Open) { d->actionCloseFile = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close File"), this); connect(d->actionCloseFile, &QAction::triggered, this, &DocumentListView::closeFile); addAction(d->actionCloseFile); } else { d->actionOpenFile = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open File"), this); connect(d->actionOpenFile, &QAction::triggered, this, &DocumentListView::openFile); addAction(d->actionOpenFile); } d->actionOpenMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open with"), this); addAction(d->actionOpenMenu); if (statusFlag == OpenFileInfo::Favorite) { d->actionRemFromFav = new QAction(QIcon::fromTheme(QStringLiteral("favorites")), i18n("Remove from Favorites"), this); connect(d->actionRemFromFav, &QAction::triggered, this, &DocumentListView::removeFromFavorites); addAction(d->actionRemFromFav); } else { d->actionAddToFav = new QAction(QIcon::fromTheme(QStringLiteral("favorites")), i18n("Add to Favorites"), this); connect(d->actionAddToFav, &QAction::triggered, this, &DocumentListView::addToFavorites); addAction(d->actionAddToFav); } connect(this, &DocumentListView::activated, this, &DocumentListView::openFile); currentChanged(QModelIndex(), QModelIndex()); } DocumentListView::~DocumentListView() { delete d; } void DocumentListView::addToFavorites() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); ofi->addFlags(OpenFileInfo::Favorite); } } void DocumentListView::removeFromFavorites() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); ofi->removeFlags(OpenFileInfo::Favorite); } } void DocumentListView::openFile() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); OpenFileInfoManager::instance().setCurrentFile(ofi); } } void DocumentListView::openFileWithService(int i) { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); if (!ofi->isModified() || (KMessageBox::questionYesNo(this, i18n("The current document has to be saved before switching the viewer/editor component."), i18n("Save before switching?"), KGuiItem(i18n("Save document"), QIcon::fromTheme(QStringLiteral("document-save"))), KGuiItem(i18n("Do not switch"), QIcon::fromTheme(QStringLiteral("dialog-cancel")))) == KMessageBox::Yes && ofi->save())) OpenFileInfoManager::instance().setCurrentFile(ofi, d->openMenuServices[i]); } } void DocumentListView::closeFile() { QModelIndex modelIndex = currentIndex(); if (modelIndex != QModelIndex()) { OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); OpenFileInfoManager::instance().close(ofi); } } void DocumentListView::currentChanged(const QModelIndex ¤t, const QModelIndex &) { bool hasCurrent = current != QModelIndex(); OpenFileInfo *ofi = hasCurrent ? qvariant_cast(current.data(Qt::UserRole)) : nullptr; bool isOpen = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::Open) : false; bool isFavorite = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::Favorite) : false; bool hasName = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::HasName) : false; if (d->actionOpenFile != nullptr) d->actionOpenFile->setEnabled(hasCurrent && !isOpen); if (d->actionCloseFile != nullptr) d->actionCloseFile->setEnabled(hasCurrent && isOpen); if (d->actionAddToFav != nullptr) d->actionAddToFav->setEnabled(hasCurrent && !isFavorite && hasName); if (d->actionRemFromFav != nullptr) d->actionRemFromFav->setEnabled(hasCurrent && isFavorite); for (QAction *action : const_cast &>(d->openMenuActions)) { d->actionOpenMenu->removeAction(action); } d->openMenuServices.clear(); if (ofi != nullptr) { d->openMenuServices = ofi->listOfServices(); int i = 0; for (KService::Ptr servicePtr : const_cast(d->openMenuServices)) { QAction *menuItem = new QAction(QIcon::fromTheme(servicePtr->icon()), servicePtr->name(), this); d->actionOpenMenu->addAction(menuItem); d->openMenuActions << menuItem; d->openMenuSignalMapper.setMapping(menuItem, i); connect(menuItem, &QAction::triggered, &d->openMenuSignalMapper, static_cast(&QSignalMapper::map)); ++i; } } d->actionOpenMenu->setEnabled(!d->openMenuActions.isEmpty()); } class DocumentList::DocumentListPrivate { public: DocumentListView *listOpenFiles; DocumentListView *listRecentFiles; DocumentListView *listFavorites; DirOperatorWidget *dirOperator; DocumentListPrivate(DocumentList *p) { listOpenFiles = new DocumentListView(OpenFileInfo::Open, p); DocumentListModel *model = new DocumentListModel(OpenFileInfo::Open, listOpenFiles); listOpenFiles->setModel(model); p->addTab(listOpenFiles, QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Files")); listRecentFiles = new DocumentListView(OpenFileInfo::RecentlyUsed, p); model = new DocumentListModel(OpenFileInfo::RecentlyUsed, listRecentFiles); listRecentFiles->setModel(model); p->addTab(listRecentFiles, QIcon::fromTheme(QStringLiteral("document-open-recent")), i18n("Recently Used")); listFavorites = new DocumentListView(OpenFileInfo::Favorite, p); model = new DocumentListModel(OpenFileInfo::Favorite, listFavorites); listFavorites->setModel(model); p->addTab(listFavorites, QIcon::fromTheme(QStringLiteral("favorites")), i18n("Favorites")); dirOperator = new DirOperatorWidget(p); p->addTab(dirOperator, QIcon::fromTheme(QStringLiteral("system-file-manager")), i18n("Filesystem Browser")); connect(dirOperator->dirOperator, &KDirOperator::fileSelected, p, &DocumentList::fileSelected); } }; DocumentList::DocumentList(QWidget *parent) : QTabWidget(parent), d(new DocumentListPrivate(this)) { setDocumentMode(true); } void DocumentList::fileSelected(const KFileItem &item) { if (item.isFile() && item.isReadable()) emit openFile(item.url()); } #include "documentlist.moc" diff --git a/src/program/mainwindow.cpp b/src/program/mainwindow.cpp index ac515057..f30b99c9 100644 --- a/src/program/mainwindow.cpp +++ b/src/program/mainwindow.cpp @@ -1,480 +1,481 @@ /*************************************************************************** - * Copyright (C) 2004-2018 by Thomas Fischer * + * Copyright (C) 2004-2019 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "kbibtex.h" -#include "preferences/kbibtexpreferencesdialog.h" -#include "valuelist.h" -#include "zoterobrowser.h" -#include "statistics.h" +#include +#include +#include +#include +#include +#include +#include "docklets/referencepreview.h" +#include "docklets/documentpreview.h" +#include "docklets/searchform.h" +#include "docklets/searchresults.h" +#include "docklets/elementform.h" +#include "docklets/documentpreview.h" +#include "docklets/statistics.h" +#include "docklets/filesettings.h" +#include "docklets/valuelist.h" +#include "docklets/zoterobrowser.h" #include "documentlist.h" #include "mdiwidget.h" -#include "referencepreview.h" -#include "documentpreview.h" -#include "searchform.h" -#include "searchresults.h" -#include "elementform.h" -#include "file/fileview.h" -#include "filesettings.h" -#include "xsltransform.h" -#include "bibliographyservice.h" -#include "bibutils.h" class KBibTeXMainWindow::KBibTeXMainWindowPrivate { private: KBibTeXMainWindow *p; public: QAction *actionClose; QDockWidget *dockDocumentList; QDockWidget *dockReferencePreview; QDockWidget *dockDocumentPreview; QDockWidget *dockValueList; QDockWidget *dockZotero; QDockWidget *dockStatistics; QDockWidget *dockSearchForm; QDockWidget *dockSearchResults; QDockWidget *dockElementForm; QDockWidget *dockFileSettings; DocumentList *listDocumentList; MDIWidget *mdiWidget; ReferencePreview *referencePreview; DocumentPreview *documentPreview; FileSettings *fileSettings; ValueList *valueList; ZoteroBrowser *zotero; Statistics *statistics; SearchForm *searchForm; SearchResults *searchResults; ElementForm *elementForm; QMenu *actionMenuRecentFilesMenu; KBibTeXMainWindowPrivate(KBibTeXMainWindow *parent) : p(parent) { mdiWidget = new MDIWidget(p); KActionMenu *showPanelsAction = new KActionMenu(i18n("Show Panels"), p); p->actionCollection()->addAction(QStringLiteral("settings_shown_panels"), showPanelsAction); QMenu *showPanelsMenu = new QMenu(showPanelsAction->text(), p->widget()); showPanelsAction->setMenu(showPanelsMenu); KActionMenu *actionMenuRecentFiles = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-open-recent")), i18n("Recently used files"), p); p->actionCollection()->addAction(QStringLiteral("file_open_recent"), actionMenuRecentFiles); actionMenuRecentFilesMenu = new QMenu(actionMenuRecentFiles->text(), p->widget()); actionMenuRecentFiles->setMenu(actionMenuRecentFilesMenu); /** * Docklets (a.k.a. panels) will be added by default to the following * positions unless otherwise configured by the user. * - "List of Values" on the left * - "Statistics" on the left * - "List of Documents" on the left in the same tab * - "Online Search" on the left in a new tab * - "Reference Preview" on the left in the same tab * - "Search Results" on the bottom * - "Document Preview" is hidden * - "Element Editor" is hidden */ dockDocumentList = new QDockWidget(i18n("List of Documents"), p); dockDocumentList->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockDocumentList); listDocumentList = new DocumentList(dockDocumentList); dockDocumentList->setWidget(listDocumentList); dockDocumentList->setObjectName(QStringLiteral("dockDocumentList")); dockDocumentList->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); connect(listDocumentList, &DocumentList::openFile, p, &KBibTeXMainWindow::openDocument); showPanelsMenu->addAction(dockDocumentList->toggleViewAction()); dockValueList = new QDockWidget(i18n("List of Values"), p); dockValueList->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockValueList); valueList = new ValueList(dockValueList); dockValueList->setWidget(valueList); dockValueList->setObjectName(QStringLiteral("dockValueList")); dockValueList->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockValueList->toggleViewAction()); dockStatistics = new QDockWidget(i18n("Statistics"), p); dockStatistics->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockStatistics); statistics = new Statistics(dockStatistics); dockStatistics->setWidget(statistics); dockStatistics->setObjectName(QStringLiteral("dockStatistics")); dockStatistics->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockStatistics->toggleViewAction()); dockSearchResults = new QDockWidget(i18n("Search Results"), p); dockSearchResults->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::BottomDockWidgetArea, dockSearchResults); dockSearchResults->hide(); searchResults = new SearchResults(mdiWidget, dockSearchResults); dockSearchResults->setWidget(searchResults); dockSearchResults->setObjectName(QStringLiteral("dockResultsFrom")); dockSearchResults->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockSearchResults->toggleViewAction()); connect(mdiWidget, &MDIWidget::documentSwitched, searchResults, &SearchResults::documentSwitched); dockSearchForm = new QDockWidget(i18n("Online Search"), p); dockSearchForm->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockSearchForm); searchForm = new SearchForm(searchResults, dockSearchForm); connect(searchForm, &SearchForm::doneSearching, p, &KBibTeXMainWindow::showSearchResults); dockSearchForm->setWidget(searchForm); dockSearchForm->setObjectName(QStringLiteral("dockSearchFrom")); dockSearchForm->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockSearchForm->toggleViewAction()); dockZotero = new QDockWidget(i18n("Zotero"), p); dockZotero->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockZotero); zotero = new ZoteroBrowser(searchResults, dockZotero); connect(dockZotero, &QDockWidget::visibilityChanged, zotero, &ZoteroBrowser::visibiltyChanged); connect(zotero, &ZoteroBrowser::itemToShow, p, &KBibTeXMainWindow::showSearchResults); dockZotero->setWidget(zotero); dockZotero->setObjectName(QStringLiteral("dockZotero")); dockZotero->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockZotero->toggleViewAction()); dockReferencePreview = new QDockWidget(i18n("Reference Preview"), p); dockReferencePreview->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockReferencePreview); referencePreview = new ReferencePreview(dockReferencePreview); dockReferencePreview->setWidget(referencePreview); dockReferencePreview->setObjectName(QStringLiteral("dockReferencePreview")); dockReferencePreview->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockReferencePreview->toggleViewAction()); dockDocumentPreview = new QDockWidget(i18n("Document Preview"), p); dockDocumentPreview->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::RightDockWidgetArea, dockDocumentPreview); dockDocumentPreview->hide(); documentPreview = new DocumentPreview(dockDocumentPreview); dockDocumentPreview->setWidget(documentPreview); dockDocumentPreview->setObjectName(QStringLiteral("dockDocumentPreview")); dockDocumentPreview->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockDocumentPreview->toggleViewAction()); p->actionCollection()->setDefaultShortcut(dockDocumentPreview->toggleViewAction(), Qt::CTRL + Qt::SHIFT + Qt::Key_D); dockElementForm = new QDockWidget(i18n("Element Editor"), p); dockElementForm->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::BottomDockWidgetArea, dockElementForm); dockElementForm->hide(); elementForm = new ElementForm(mdiWidget, dockElementForm); dockElementForm->setWidget(elementForm); dockElementForm->setObjectName(QStringLiteral("dockElementFrom")); dockElementForm->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockElementForm->toggleViewAction()); dockFileSettings = new QDockWidget(i18n("File Settings"), p); dockFileSettings->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); p->addDockWidget(Qt::LeftDockWidgetArea, dockFileSettings); fileSettings = new FileSettings(dockFileSettings); dockFileSettings->setWidget(fileSettings); dockFileSettings->setObjectName(QStringLiteral("dockFileSettings")); dockFileSettings->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); showPanelsMenu->addAction(dockFileSettings->toggleViewAction()); p->tabifyDockWidget(dockFileSettings, dockSearchForm); p->tabifyDockWidget(dockZotero, dockSearchForm); p->tabifyDockWidget(dockValueList, dockStatistics); p->tabifyDockWidget(dockStatistics, dockFileSettings); p->tabifyDockWidget(dockSearchForm, dockReferencePreview); p->tabifyDockWidget(dockFileSettings, dockDocumentList); QAction *action = p->actionCollection()->addAction(KStandardAction::New); connect(action, &QAction::triggered, p, &KBibTeXMainWindow::newDocument); action = p->actionCollection()->addAction(KStandardAction::Open); connect(action, &QAction::triggered, p, &KBibTeXMainWindow::openDocumentDialog); actionClose = p->actionCollection()->addAction(KStandardAction::Close); connect(actionClose, &QAction::triggered, p, &KBibTeXMainWindow::closeDocument); actionClose->setEnabled(false); action = p->actionCollection()->addAction(KStandardAction::Quit); connect(action, &QAction::triggered, p, &KBibTeXMainWindow::queryCloseAll); action = p->actionCollection()->addAction(KStandardAction::Preferences); connect(action, &QAction::triggered, p, &KBibTeXMainWindow::showPreferences); } ~KBibTeXMainWindowPrivate() { elementForm->deleteLater(); delete mdiWidget; // TODO other deletes } }; KBibTeXMainWindow::KBibTeXMainWindow(QWidget *parent) : KParts::MainWindow(parent, (Qt::WindowFlags)KDE_DEFAULT_WINDOWFLAGS), d(new KBibTeXMainWindowPrivate(this)) { setObjectName(QStringLiteral("KBibTeXShell")); setXMLFile(QStringLiteral("kbibtexui.rc")); setCentralWidget(d->mdiWidget); connect(d->mdiWidget, &MDIWidget::documentSwitched, this, &KBibTeXMainWindow::documentSwitched); connect(d->mdiWidget, &MDIWidget::activePartChanged, this, &KBibTeXMainWindow::createGUI); ///< actually: KParts::MainWindow::createGUI connect(d->mdiWidget, &MDIWidget::documentNew, this, &KBibTeXMainWindow::newDocument); connect(d->mdiWidget, &MDIWidget::documentOpen, this, &KBibTeXMainWindow::openDocumentDialog); connect(d->mdiWidget, &MDIWidget::documentOpenURL, this, &KBibTeXMainWindow::openDocument); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::currentChanged, d->mdiWidget, &MDIWidget::setFile); connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &KBibTeXMainWindow::documentListsChanged); connect(d->mdiWidget, &MDIWidget::setCaption, this, static_cast(&KMainWindow::setCaption)); ///< actually: KMainWindow::setCaption documentListsChanged(OpenFileInfo::RecentlyUsed); /// force initialization of menu of recently used files setupControllers(); setupGUI(KXmlGuiWindow::Create | KXmlGuiWindow::Save | KXmlGuiWindow::Keys | KXmlGuiWindow::ToolBar); setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); setAcceptDrops(true); QTimer::singleShot(500, this, &KBibTeXMainWindow::delayed); } KBibTeXMainWindow::~KBibTeXMainWindow() { delete d; } void KBibTeXMainWindow::setupControllers() { // TODO } void KBibTeXMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) event->acceptProposedAction(); } void KBibTeXMainWindow::dropEvent(QDropEvent *event) { QList urlList = event->mimeData()->urls(); if (urlList.isEmpty()) { const QUrl url(event->mimeData()->text()); if (url.isValid()) urlList << url; } if (!urlList.isEmpty()) for (const QUrl &url : const_cast &>(urlList)) openDocument(url); } void KBibTeXMainWindow::newDocument() { const QString mimeType = FileInfo::mimetypeBibTeX; OpenFileInfo *openFileInfo = OpenFileInfoManager::instance().createNew(mimeType); if (openFileInfo) OpenFileInfoManager::instance().setCurrentFile(openFileInfo); else KMessageBox::error(this, i18n("Creating a new document of mime type '%1' failed as no editor component could be instantiated.", mimeType), i18n("Creating document failed")); } void KBibTeXMainWindow::openDocumentDialog() { OpenFileInfo *currFile = OpenFileInfoManager::instance().currentFile(); QUrl currFileUrl = currFile == nullptr ? QUrl() : currFile->url(); QString startDir = currFileUrl.isValid() ? QUrl(currFileUrl.url()).path() : QString(); OpenFileInfo *ofi = OpenFileInfoManager::instance().currentFile(); if (ofi != nullptr) { QUrl url = ofi->url(); if (url.isValid()) startDir = url.path(); } /// Assemble list of supported mimetypes QStringList supportedMimeTypes {QStringLiteral("text/x-bibtex"), QStringLiteral("application/x-research-info-systems"), QStringLiteral("application/xml")}; if (BibUtils::available()) { supportedMimeTypes.append(QStringLiteral("application/x-isi-export-format")); supportedMimeTypes.append(QStringLiteral("application/x-endnote-refer")); } supportedMimeTypes.append(QStringLiteral("application/pdf")); supportedMimeTypes.append(QStringLiteral("all/all")); QPointer dlg = new QFileDialog(this, i18n("Open file") /* TODO better text */, startDir); dlg->setMimeTypeFilters(supportedMimeTypes); dlg->setFileMode(QFileDialog::ExistingFile); const bool dialogAccepted = dlg->exec() != 0; const QUrl url = (dialogAccepted && !dlg->selectedUrls().isEmpty()) ? dlg->selectedUrls().first() : QUrl(); delete dlg; if (!url.isEmpty()) openDocument(url); } void KBibTeXMainWindow::openDocument(const QUrl &url) { OpenFileInfo *openFileInfo = OpenFileInfoManager::instance().open(url); OpenFileInfoManager::instance().setCurrentFile(openFileInfo); } void KBibTeXMainWindow::closeDocument() { OpenFileInfoManager::instance().close(OpenFileInfoManager::instance().currentFile()); } void KBibTeXMainWindow::closeEvent(QCloseEvent *event) { KMainWindow::closeEvent(event); if (OpenFileInfoManager::instance().queryCloseAll()) event->accept(); else event->ignore(); } void KBibTeXMainWindow::showPreferences() { QPointer dlg = new KBibTeXPreferencesDialog(this); dlg->exec(); delete dlg; } void KBibTeXMainWindow::documentSwitched(FileView *oldFileView, FileView *newFileView) { OpenFileInfo *openFileInfo = d->mdiWidget->currentFile(); bool validFile = openFileInfo != nullptr; d->actionClose->setEnabled(validFile); setCaption(validFile ? i18n("%1 - KBibTeX", openFileInfo->shortCaption()) : i18n("KBibTeX")); d->fileSettings->setEnabled(newFileView != nullptr); d->referencePreview->setEnabled(newFileView != nullptr); d->elementForm->setEnabled(newFileView != nullptr); d->documentPreview->setEnabled(newFileView != nullptr); if (oldFileView != nullptr) { disconnect(newFileView, &FileView::currentElementChanged, d->referencePreview, &ReferencePreview::setElement); disconnect(newFileView, &FileView::currentElementChanged, d->elementForm, &ElementForm::setElement); disconnect(newFileView, &FileView::currentElementChanged, d->documentPreview, &DocumentPreview::setElement); disconnect(newFileView, &FileView::currentElementChanged, d->searchForm, &SearchForm::setElement); disconnect(newFileView, &FileView::modified, d->valueList, &ValueList::update); disconnect(newFileView, &FileView::modified, d->statistics, &Statistics::update); // FIXME disconnect(oldEditor, SIGNAL(modified()), d->elementForm, SLOT(refreshElement())); disconnect(d->elementForm, &ElementForm::elementModified, newFileView, &FileView::externalModification); } if (newFileView != nullptr) { connect(newFileView, &FileView::currentElementChanged, d->referencePreview, &ReferencePreview::setElement); connect(newFileView, &FileView::currentElementChanged, d->elementForm, &ElementForm::setElement); connect(newFileView, &FileView::currentElementChanged, d->documentPreview, &DocumentPreview::setElement); connect(newFileView, &FileView::currentElementChanged, d->searchForm, &SearchForm::setElement); connect(newFileView, &FileView::modified, d->valueList, &ValueList::update); connect(newFileView, &FileView::modified, d->statistics, &Statistics::update); // FIXME connect(newEditor, SIGNAL(modified()), d->elementForm, SLOT(refreshElement())); connect(d->elementForm, &ElementForm::elementModified, newFileView, &FileView::externalModification); connect(d->elementForm, &ElementForm::elementModified, newFileView, &FileView::externalModification); } d->documentPreview->setBibTeXUrl(validFile ? openFileInfo->url() : QUrl()); d->referencePreview->setElement(QSharedPointer(), nullptr); d->elementForm->setElement(QSharedPointer(), nullptr); d->documentPreview->setElement(QSharedPointer(), nullptr); d->valueList->setFileView(newFileView); d->fileSettings->setFileView(newFileView); d->statistics->setFileView(newFileView); d->referencePreview->setFileView(newFileView); } void KBibTeXMainWindow::showSearchResults() { d->dockSearchResults->show(); } void KBibTeXMainWindow::documentListsChanged(OpenFileInfo::StatusFlags statusFlags) { if (statusFlags.testFlag(OpenFileInfo::RecentlyUsed)) { const OpenFileInfoManager::OpenFileInfoList list = OpenFileInfoManager::instance().filteredItems(OpenFileInfo::RecentlyUsed); d->actionMenuRecentFilesMenu->clear(); for (OpenFileInfo *cur : list) { /// Fixing bug 19511: too long filenames make menu too large, /// therefore squeeze text if it is longer than squeezeLen. const int squeezeLen = 64; const QString squeezedShortCap = squeeze_text(cur->shortCaption(), squeezeLen); const QString squeezedFullCap = squeeze_text(cur->fullCaption(), squeezeLen); QAction *action = new QAction(QString(QStringLiteral("%1 [%2]")).arg(squeezedShortCap, squeezedFullCap), this); action->setData(cur->url()); action->setIcon(QIcon::fromTheme(cur->mimeType().replace(QLatin1Char('/'), QLatin1Char('-')))); d->actionMenuRecentFilesMenu->addAction(action); connect(action, &QAction::triggered, this, &KBibTeXMainWindow::openRecentFile); } } } void KBibTeXMainWindow::openRecentFile() { QAction *action = static_cast(sender()); QUrl url = action->data().toUrl(); openDocument(url); } void KBibTeXMainWindow::queryCloseAll() { if (OpenFileInfoManager::instance().queryCloseAll()) qApp->quit(); } void KBibTeXMainWindow::delayed() { /// Static variable, memorizes the dynamically created /// BibliographyService instance and allows to tell if /// this slot was called for the first or second time. static BibliographyService *bs = nullptr; if (bs == nullptr) { /// First call to this slot bs = new BibliographyService(this); if (!bs->isKBibTeXdefault() && KMessageBox::questionYesNo(this, i18n("KBibTeX is not the default editor for its bibliography formats like BibTeX or RIS."), i18n("Default Bibliography Editor"), KGuiItem(i18n("Set as Default Editor")), KGuiItem(i18n("Keep settings unchanged"))) == KMessageBox::Yes) { bs->setKBibTeXasDefault(); /// QTimer calls this slot again, but as 'bs' will not be NULL, /// the 'if' construct's 'else' path will be followed. QTimer::singleShot(5000, this, &KBibTeXMainWindow::delayed); } else { /// KBibTeX is default application or user doesn't care, /// therefore clean up memory delete bs; bs = nullptr; } } else { /// Second call to this slot. This time, clean up memory. bs->deleteLater(); bs = nullptr; } } diff --git a/src/program/mdiwidget.cpp b/src/program/mdiwidget.cpp index adfa3f61..ba5689cb 100644 --- a/src/program/mdiwidget.cpp +++ b/src/program/mdiwidget.cpp @@ -1,342 +1,342 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "mdiwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "kbibtex.h" -#include "file/partwidget.h" +#include +#include #include "logging_program.h" class LRUItemModel : public QAbstractItemModel { Q_OBJECT public: static const int URLRole = Qt::UserRole + 235; static const int SortRole = Qt::UserRole + 236; LRUItemModel(QObject *parent = nullptr) : QAbstractItemModel(parent) { /// nothing } void reset() { beginResetModel(); endResetModel(); } int columnCount(const QModelIndex &) const override { return 3; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { OpenFileInfoManager::OpenFileInfoList ofiList = OpenFileInfoManager::instance().filteredItems(OpenFileInfo::RecentlyUsed); if (index.row() < ofiList.count()) { OpenFileInfo *ofiItem = ofiList[index.row()]; if (index.column() == 0) { if (role == Qt::DisplayRole || role == SortRole) { const QUrl url = ofiItem->url(); const QString fileName = url.fileName(); return fileName.isEmpty() ? squeeze_text(url.url(QUrl::PreferLocalFile), 32) : fileName; } else if (role == Qt::DecorationRole) return QIcon::fromTheme(ofiItem->mimeType().replace(QLatin1Char('/'), QLatin1Char('-'))); else if (role == Qt::ToolTipRole) return squeeze_text(ofiItem->url().url(QUrl::PreferLocalFile), 64); } else if (index.column() == 1) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) return ofiItem->lastAccess().toString(Qt::TextDate); else if (role == SortRole) return ofiItem->lastAccess().toTime_t(); } else if (index.column() == 2) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole || role == SortRole) return ofiItem->url().url(QUrl::PreferLocalFile); } if (role == URLRole) return ofiItem->url(); } return QVariant(); } QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const override { if (role != Qt::DisplayRole || section > 2) return QVariant(); else if (section == 0) return i18n("Filename"); else if (section == 1) return i18n("Date/time of last use"); else return i18n("Full filename"); } QModelIndex index(int row, int column, const QModelIndex &) const override { return createIndex(row, column, row); } QModelIndex parent(const QModelIndex &) const override { return QModelIndex(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { if (parent == QModelIndex()) return OpenFileInfoManager::instance().filteredItems(OpenFileInfo::RecentlyUsed).count(); else return 0; } }; class MDIWidget::MDIWidgetPrivate { private: QTreeView *listLRU; LRUItemModel *modelLRU; QVector welcomeWidgets; static const QString configGroupName; static const QString configHeaderState; void createWelcomeWidget() { welcomeWidget = new QWidget(p); QGridLayout *layout = new QGridLayout(welcomeWidget); layout->setRowStretch(0, 1); layout->setRowStretch(1, 0); layout->setRowStretch(2, 0); layout->setRowStretch(3, 0); layout->setRowStretch(4, 10); layout->setRowStretch(5, 1); layout->setColumnStretch(0, 1); layout->setColumnStretch(1, 10); layout->setColumnStretch(2, 1); layout->setColumnStretch(3, 0); layout->setColumnStretch(4, 1); layout->setColumnStretch(5, 10); layout->setColumnStretch(6, 1); QLabel *label = new QLabel(i18n("Welcome to KBibTeX"), welcomeWidget); layout->addWidget(label, 1, 2, 1, 3, Qt::AlignHCenter | Qt::AlignTop); QPushButton *buttonNew = new QPushButton(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New"), welcomeWidget); layout->addWidget(buttonNew, 2, 2, 1, 1, Qt::AlignLeft | Qt::AlignBottom); connect(buttonNew, &QPushButton::clicked, p, &MDIWidget::documentNew); QPushButton *buttonOpen = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open..."), welcomeWidget); layout->addWidget(buttonOpen, 2, 4, 1, 1, Qt::AlignRight | Qt::AlignBottom); connect(buttonOpen, &QPushButton::clicked, p, &MDIWidget::documentOpen); label = new QLabel(i18n("List of recently used files:"), welcomeWidget); layout->addWidget(label, 3, 1, 1, 5, Qt::AlignLeft | Qt::AlignBottom); listLRU = new QTreeView(p); listLRU->setRootIsDecorated(false); listLRU->setSortingEnabled(true); listLRU->header()->setSectionResizeMode(QHeaderView::ResizeToContents); layout->addWidget(listLRU, 4, 1, 1, 5); connect(listLRU, &QTreeView::activated, p, &MDIWidget::slotOpenLRU); label->setBuddy(listLRU); p->addWidget(welcomeWidget); } void saveColumnsState() { QByteArray headerState = listLRU->header()->saveState(); KConfigGroup configGroup(config, configGroupName); configGroup.writeEntry(configHeaderState, headerState); config->sync(); } void restoreColumnsState() { KConfigGroup configGroup(config, configGroupName); QByteArray headerState = configGroup.readEntry(configHeaderState, QByteArray()); if (!headerState.isEmpty()) listLRU->header()->restoreState(headerState); } public: MDIWidget *p; OpenFileInfo *currentFile; QWidget *welcomeWidget; QSignalMapper signalMapperCompleted; KSharedConfigPtr config; MDIWidgetPrivate(MDIWidget *parent) : p(parent), currentFile(nullptr), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))) { createWelcomeWidget(); modelLRU = new LRUItemModel(listLRU); QSortFilterProxyModel *sfpm = new QSortFilterProxyModel(listLRU); sfpm->setSourceModel(modelLRU); sfpm->setSortRole(LRUItemModel::SortRole); listLRU->setModel(sfpm); connect(&signalMapperCompleted, static_cast(&QSignalMapper::mapped), p, &MDIWidget::slotCompleted); restoreColumnsState(); } ~MDIWidgetPrivate() { saveColumnsState(); delete welcomeWidget; } void addToMapper(OpenFileInfo *openFileInfo) { KParts::ReadOnlyPart *part = openFileInfo->part(p); signalMapperCompleted.setMapping(part, openFileInfo); connect(part, static_cast(&KParts::ReadOnlyPart::completed), &signalMapperCompleted, static_cast(&QSignalMapper::map)); } void updateLRU() { modelLRU->reset(); } }; const QString MDIWidget::MDIWidgetPrivate::configGroupName = QStringLiteral("WelcomeWidget"); const QString MDIWidget::MDIWidgetPrivate::configHeaderState = QStringLiteral("LRUlistHeaderState"); MDIWidget::MDIWidget(QWidget *parent) : QStackedWidget(parent), d(new MDIWidgetPrivate(this)) { connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &MDIWidget::slotStatusFlagsChanged); } MDIWidget::~MDIWidget() { delete d; } /// Clazy warns: "Missing reference on non-trivial type" for argument 'servicePtr', /// but type 'KService::Ptr' is actually a pointer (QExplicitlySharedDataPointer). void MDIWidget::setFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr) { KParts::Part *part = openFileInfo == nullptr ? nullptr : openFileInfo->part(this, servicePtr); /// 'part' will only be NULL if no OpenFileInfo object was given, /// or if the OpenFileInfo could not locate a KPart QWidget *widget = d->welcomeWidget; ///< by default use Welcome widget if (part != nullptr) { /// KPart object was located, use its object (instead of Welcome widget) widget = part->widget(); } else if (openFileInfo != nullptr) { /// A valid OpenFileInfo was given, but no KPart could be located OpenFileInfoManager::instance().close(openFileInfo); // FIXME does not close correctly if file is new const QString filename = openFileInfo->url().fileName(); if (filename.isEmpty()) KMessageBox::error(this, i18n("No part available for file of mime type '%1'.", openFileInfo->mimeType()), i18n("No Part Available")); else KMessageBox::error(this, i18n("No part available for file '%1'.", filename), i18n("No Part Available")); return; } FileView *oldEditor = nullptr; bool hasChanged = true; if (indexOf(widget) >= 0) { /// Chosen widget is already known (Welcome widget or a previously used KPart) /// In case previous (still current) widget was a KBibTeX Part, remember its editor PartWidget *currentPartWidget = qobject_cast(currentWidget()); oldEditor = currentPartWidget == nullptr ? nullptr : currentPartWidget->fileView(); /// Record if set widget is different from previous (still current) widget hasChanged = widget != currentWidget(); } else if (openFileInfo != nullptr) { /// Widget was not known previously, but a valid (new?) OpenFileInfo was given addWidget(widget); d->addToMapper(openFileInfo); } setCurrentWidget(widget); d->currentFile = openFileInfo; if (hasChanged) { /// This signal gets forwarded to KParts::MainWindow::createGUI(KParts::Part*) emit activePartChanged(part); /// If new widget comes from a KBibTeX Part, retrieve its editor PartWidget *newPartWidget = qobject_cast(widget); FileView *newEditor = newPartWidget == nullptr ? nullptr : newPartWidget->fileView(); emit documentSwitched(oldEditor, newEditor); } /// Notify main window about a change of current file, /// so that the title may contain the current file's URL. /// This signal will be connected to KMainWindow::setCaption. if (openFileInfo != nullptr) { QUrl url = openFileInfo->url(); if (url.isValid()) emit setCaption(QString(QStringLiteral("%1 [%2]")).arg(openFileInfo->shortCaption(), squeeze_text(openFileInfo->fullCaption(), 64))); else emit setCaption(openFileInfo->shortCaption()); } else emit setCaption(QString()); } FileView *MDIWidget::fileView() { OpenFileInfo *ofi = OpenFileInfoManager::instance().currentFile(); return qobject_cast(ofi->part(this)->widget())->fileView(); } OpenFileInfo *MDIWidget::currentFile() { return d->currentFile; } void MDIWidget::slotCompleted(QObject *obj) { OpenFileInfo *ofi = static_cast(obj); QUrl oldUrl = ofi->url(); QUrl newUrl = ofi->part(this)->url(); if (oldUrl != newUrl) { qCDebug(LOG_KBIBTEX_PROGRAM) << "Url changed from " << oldUrl.url(QUrl::PreferLocalFile) << " to " << newUrl.url(QUrl::PreferLocalFile) << endl; OpenFileInfoManager::instance().changeUrl(ofi, newUrl); /// completely opened or saved files should be marked as "recently used" ofi->addFlags(OpenFileInfo::RecentlyUsed); emit setCaption(QString(QStringLiteral("%1 [%2]")).arg(ofi->shortCaption(), squeeze_text(ofi->fullCaption(), 64))); } } void MDIWidget::slotStatusFlagsChanged(OpenFileInfo::StatusFlags statusFlags) { if (statusFlags.testFlag(OpenFileInfo::RecentlyUsed)) d->updateLRU(); } void MDIWidget::slotOpenLRU(const QModelIndex &index) { QUrl url = index.data(LRUItemModel::URLRole).toUrl(); if (url.isValid()) emit documentOpenURL(url); } #include "mdiwidget.moc" diff --git a/src/program/mdiwidget.h b/src/program/mdiwidget.h index 7475f1c8..3a3559f2 100644 --- a/src/program/mdiwidget.h +++ b/src/program/mdiwidget.h @@ -1,78 +1,78 @@ /*************************************************************************** * Copyright (C) 2004-2017 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #ifndef KBIBTEX_PROGRAM_MDIWIDGET_H #define KBIBTEX_PROGRAM_MDIWIDGET_H #include #include - #include + #include #include "openfileinfo.h" class FileView; namespace KParts { class Part; } class OpenFileInfo; class MDIWidget : public QStackedWidget { Q_OBJECT public: explicit MDIWidget(QWidget *parent); ~MDIWidget() override; FileView *fileView(); OpenFileInfo *currentFile(); public slots: /** * Make the MDI widget show a different file using a part * as specified by a service. * If the OpenFileInfo object is NULL, show no file but a * Welcome widget instead. * Retrieving the part to be used will be delegated to the * OpenFileInfo object. */ void setFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr = KService::Ptr()); signals: void setCaption(const QString &); void documentSwitched(FileView *, FileView *); void activePartChanged(KParts::Part *); void documentNew(); void documentOpen(); void documentOpenURL(const QUrl &); private: class MDIWidgetPrivate; MDIWidgetPrivate *d; private slots: void slotCompleted(QObject *); void slotStatusFlagsChanged(OpenFileInfo::StatusFlags); void slotOpenLRU(const QModelIndex &); }; #endif // KBIBTEX_PROGRAM_MDIWIDGET_H diff --git a/src/program/openfileinfo.cpp b/src/program/openfileinfo.cpp index 0b936c9a..bd5065ac 100644 --- a/src/program/openfileinfo.cpp +++ b/src/program/openfileinfo.cpp @@ -1,717 +1,717 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #include "openfileinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "fileimporterpdf.h" +#include #include "logging_program.h" class OpenFileInfo::OpenFileInfoPrivate { private: static int globalCounter; int m_counter; public: static const QString keyLastAccess; static const QString keyURL; static const QString dateTimeFormat; OpenFileInfo *p; KParts::ReadOnlyPart *part; KService::Ptr internalServicePtr; QWidget *internalWidgetParent; QDateTime lastAccessDateTime; StatusFlags flags; OpenFileInfoManager *openFileInfoManager; QString mimeType; QUrl url; OpenFileInfoPrivate(OpenFileInfoManager *openFileInfoManager, const QUrl &url, const QString &mimeType, OpenFileInfo *p) : m_counter(-1), p(p), part(nullptr), internalServicePtr(KService::Ptr()), internalWidgetParent(nullptr), flags(nullptr) { this->openFileInfoManager = openFileInfoManager; this->url = url; if (this->url.isValid() && this->url.scheme().isEmpty()) qCWarning(LOG_KBIBTEX_PROGRAM) << "No scheme specified for URL" << this->url.toDisplayString(); this->mimeType = mimeType; } ~OpenFileInfoPrivate() { if (part != nullptr) { KParts::ReadWritePart *rwp = qobject_cast(part); if (rwp != nullptr) rwp->closeUrl(true); delete part; } } KParts::ReadOnlyPart *createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr()) { if (!p->flags().testFlag(OpenFileInfo::Open)) { qCWarning(LOG_KBIBTEX_PROGRAM) << "Cannot create part for a file which is not open"; return nullptr; } Q_ASSERT_X(internalWidgetParent == nullptr || internalWidgetParent == newWidgetParent, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr())", "internal widget should be either NULL or the same one as supplied as \"newWidgetParent\""); /** use cached part for this parent if possible */ if (internalWidgetParent == newWidgetParent && (newServicePtr == KService::Ptr() || internalServicePtr == newServicePtr)) { Q_ASSERT_X(part != nullptr, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr())", "Part is NULL"); return part; } else if (part != nullptr) { KParts::ReadWritePart *rwp = qobject_cast(part); if (rwp != nullptr) rwp->closeUrl(true); part->deleteLater(); part = nullptr; } /// reset to invalid values in case something goes wrong internalServicePtr = KService::Ptr(); internalWidgetParent = nullptr; if (!newServicePtr) { /// no valid KService has been passed /// try to find a read-write part to open file newServicePtr = p->defaultService(); } if (!newServicePtr) { qCDebug(LOG_KBIBTEX_PROGRAM) << "PATH=" << getenv("PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "LD_LIBRARY_PATH=" << getenv("LD_LIBRARY_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "XDG_DATA_DIRS=" << getenv("XDG_DATA_DIRS"); qCDebug(LOG_KBIBTEX_PROGRAM) << "QT_PLUGIN_PATH=" << getenv("QT_PLUGIN_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "KDEDIRS=" << getenv("KDEDIRS"); qCCritical(LOG_KBIBTEX_PROGRAM) << "Cannot find service to handle mimetype " << mimeType << endl; return nullptr; } QString errorString; part = newServicePtr->createInstance(newWidgetParent, (QObject *)newWidgetParent, QVariantList(), &errorString); if (part == nullptr) { qCDebug(LOG_KBIBTEX_PROGRAM) << "PATH=" << getenv("PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "LD_LIBRARY_PATH=" << getenv("LD_LIBRARY_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "XDG_DATA_DIRS=" << getenv("XDG_DATA_DIRS"); qCDebug(LOG_KBIBTEX_PROGRAM) << "QT_PLUGIN_PATH=" << getenv("QT_PLUGIN_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "KDEDIRS=" << getenv("KDEDIRS"); qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not instantiate read-write part for service" << newServicePtr->name() << "(mimeType=" << mimeType << ", library=" << newServicePtr->library() << ", error msg=" << errorString << ")"; /// creating a read-write part failed, so maybe it is read-only (like Okular's PDF viewer)? part = newServicePtr->createInstance(newWidgetParent, (QObject *)newWidgetParent, QVariantList(), &errorString); } if (part == nullptr) { /// still cannot create part, must be error qCCritical(LOG_KBIBTEX_PROGRAM) << "Could not instantiate part for service" << newServicePtr->name() << "(mimeType=" << mimeType << ", library=" << newServicePtr->library() << ", error msg=" << errorString << ")"; return nullptr; } if (url.isValid()) { /// open URL in part part->openUrl(url); /// update document list widget accordingly p->addFlags(OpenFileInfo::RecentlyUsed); p->addFlags(OpenFileInfo::HasName); } else { /// initialize part with empty document part->openUrl(QUrl()); } p->addFlags(OpenFileInfo::Open); internalServicePtr = newServicePtr; internalWidgetParent = newWidgetParent; Q_ASSERT_X(part != nullptr, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr())", "Creation of part failed, is NULL"); /// test should not be necessary, but just to be save ... return part; } int counter() { if (!url.isValid() && m_counter < 0) m_counter = ++globalCounter; else if (url.isValid()) qCWarning(LOG_KBIBTEX_PROGRAM) << "This function should not be called if URL is valid"; return m_counter; } }; int OpenFileInfo::OpenFileInfoPrivate::globalCounter = 0; const QString OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat = QStringLiteral("yyyy-MM-dd-hh-mm-ss-zzz"); const QString OpenFileInfo::OpenFileInfoPrivate::keyLastAccess = QStringLiteral("LastAccess"); const QString OpenFileInfo::OpenFileInfoPrivate::keyURL = QStringLiteral("URL"); OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QUrl &url) : d(new OpenFileInfoPrivate(openFileInfoManager, url, FileInfo::mimeTypeForUrl(url).name(), this)) { /// nothing } OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QString &mimeType) : d(new OpenFileInfoPrivate(openFileInfoManager, QUrl(), mimeType, this)) { /// nothing } OpenFileInfo::~OpenFileInfo() { delete d; } void OpenFileInfo::setUrl(const QUrl &url) { Q_ASSERT_X(url.isValid(), "void OpenFileInfo::setUrl(const QUrl&)", "URL is not valid"); d->url = url; if (d->url.scheme().isEmpty()) qCWarning(LOG_KBIBTEX_PROGRAM) << "No scheme specified for URL" << d->url.toDisplayString(); d->mimeType = FileInfo::mimeTypeForUrl(url).name(); addFlags(OpenFileInfo::HasName); } QUrl OpenFileInfo::url() const { return d->url; } bool OpenFileInfo::isModified() const { KParts::ReadWritePart *rwPart = qobject_cast< KParts::ReadWritePart *>(d->part); if (rwPart == nullptr) return false; else return rwPart->isModified(); } bool OpenFileInfo::save() { KParts::ReadWritePart *rwPart = qobject_cast< KParts::ReadWritePart *>(d->part); if (rwPart == nullptr) return true; else return rwPart->save(); } bool OpenFileInfo::close() { if (d->part == nullptr) { /// if there is no part, closing always "succeeds" return true; } KParts::ReadWritePart *rwp = qobject_cast(d->part); if (rwp == nullptr || rwp->closeUrl(true)) { d->part->deleteLater(); d->part = nullptr; d->internalWidgetParent = nullptr; return true; } return false; } QString OpenFileInfo::mimeType() const { return d->mimeType; } QString OpenFileInfo::shortCaption() const { if (d->url.isValid()) return d->url.fileName(); else return i18n("Unnamed-%1", d->counter()); } QString OpenFileInfo::fullCaption() const { if (d->url.isValid()) return d->url.url(QUrl::PreferLocalFile); else return shortCaption(); } /// Clazy warns: "Missing reference on non-trivial type" for argument 'servicePtr', /// but type 'KService::Ptr' is actually a pointer (QExplicitlySharedDataPointer). KParts::ReadOnlyPart *OpenFileInfo::part(QWidget *parent, KService::Ptr servicePtr) { return d->createPart(parent, servicePtr); } OpenFileInfo::StatusFlags OpenFileInfo::flags() const { return d->flags; } void OpenFileInfo::setFlags(StatusFlags statusFlags) { /// disallow files without name or valid url to become favorites if (!d->url.isValid() || !d->flags.testFlag(HasName)) statusFlags &= ~Favorite; /// files that got opened are by definition recently used files if (!d->url.isValid() && d->flags.testFlag(Open)) statusFlags &= RecentlyUsed; bool hasChanged = d->flags != statusFlags; d->flags = statusFlags; if (hasChanged) emit flagsChanged(statusFlags); } void OpenFileInfo::addFlags(StatusFlags statusFlags) { /// disallow files without name or valid url to become favorites if (!d->url.isValid() || !d->flags.testFlag(HasName)) statusFlags &= ~Favorite; bool hasChanged = (~d->flags & statusFlags) > 0; d->flags |= statusFlags; if (hasChanged) emit flagsChanged(statusFlags); } void OpenFileInfo::removeFlags(StatusFlags statusFlags) { bool hasChanged = (d->flags & statusFlags) > 0; d->flags &= ~statusFlags; if (hasChanged) emit flagsChanged(statusFlags); } QDateTime OpenFileInfo::lastAccess() const { return d->lastAccessDateTime; } void OpenFileInfo::setLastAccess(const QDateTime &dateTime) { d->lastAccessDateTime = dateTime; emit flagsChanged(OpenFileInfo::RecentlyUsed); } KService::List OpenFileInfo::listOfServices() { const QString mt = mimeType(); /// First, try to locate KPart that can both read and write the queried MIME type KService::List result = KMimeTypeTrader::self()->query(mt, QStringLiteral("KParts/ReadWritePart")); if (result.isEmpty()) { /// Second, if no 'writing' KPart was found, try to locate KPart that can at least read the queried MIME type result = KMimeTypeTrader::self()->query(mt, QStringLiteral("KParts/ReadOnlyPart")); if (result.isEmpty()) { /// If not even a 'reading' KPart was found, something is off, so warn the user and stop here qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not find any KPart that reads or writes mimetype" << mt; return result; } } /// Always include KBibTeX's KPart in list of services: /// First, check if KBibTeX's KPart is already in list as returned by /// KMimeTypeTrader::self()->query(..) bool listIncludesKBibTeXPart = false; for (KService::List::ConstIterator it = result.constBegin(); it != result.constEnd(); ++it) { qCDebug(LOG_KBIBTEX_PROGRAM) << "Found library for" << mt << ":" << (*it)->library(); listIncludesKBibTeXPart |= (*it)->library() == QStringLiteral("kbibtexpart"); } /// Then, if KBibTeX's KPart is not in the list, try to located it by desktop name if (!listIncludesKBibTeXPart) { KService::Ptr kbibtexpartByDesktopName = KService::serviceByDesktopName(QStringLiteral("kbibtexpart")); if (kbibtexpartByDesktopName != nullptr) { result << kbibtexpartByDesktopName; qCDebug(LOG_KBIBTEX_PROGRAM) << "Adding library for" << mt << ":" << kbibtexpartByDesktopName->library(); } else { qCDebug(LOG_KBIBTEX_PROGRAM) << "Could not locate KBibTeX's KPart neither by MIME type search, nor by desktop name"; } } return result; } KService::Ptr OpenFileInfo::defaultService() { const QString mt = mimeType(); KService::Ptr result; if (mt == QStringLiteral("application/pdf") || mt == QStringLiteral("text/x-bibtex")) { /// If either a BibTeX file or a PDF file is to be opened, enforce using /// KBibTeX's part over anything else. /// KBibTeX has a FileImporterPDF which allows it to load .pdf file /// that got generated with KBibTeX and contain the original /// .bib file as an 'attachment'. /// This importer does not work with any other .pdf files!!! result = KService::serviceByDesktopName(QStringLiteral("kbibtexpart")); } if (result == nullptr) { /// First, try to locate KPart that can both read and write the queried MIME type result = KMimeTypeTrader::self()->preferredService(mt, QStringLiteral("KParts/ReadWritePart")); if (result == nullptr) { /// Second, if no 'writing' KPart was found, try to locate KPart that can at least read the queried MIME type result = KMimeTypeTrader::self()->preferredService(mt, QStringLiteral("KParts/ReadOnlyPart")); if (result == nullptr && mt == QStringLiteral("text/x-bibtex")) /// Third, if MIME type is for BibTeX files, try loading KBibTeX part via desktop name result = KService::serviceByDesktopName(QStringLiteral("kbibtexpart")); } } if (result != nullptr) qCDebug(LOG_KBIBTEX_PROGRAM) << "Using service" << result->name() << "(" << result->comment() << ") for mime type" << mt << "through library" << result->library(); else qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not find service for mime type" << mt; return result; } KService::Ptr OpenFileInfo::currentService() { return d->internalServicePtr; } class OpenFileInfoManager::OpenFileInfoManagerPrivate { private: static const QString configGroupNameRecentlyUsed; static const QString configGroupNameFavorites; static const QString configGroupNameOpen; static const int maxNumRecentlyUsedFiles, maxNumFavoriteFiles, maxNumOpenFiles; public: OpenFileInfoManager *p; OpenFileInfoManager::OpenFileInfoList openFileInfoList; OpenFileInfo *currentFileInfo; OpenFileInfoManagerPrivate(OpenFileInfoManager *parent) : p(parent), currentFileInfo(nullptr) { /// nothing } ~OpenFileInfoManagerPrivate() { for (OpenFileInfoManager::OpenFileInfoList::Iterator it = openFileInfoList.begin(); it != openFileInfoList.end();) { OpenFileInfo *ofi = *it; delete ofi; it = openFileInfoList.erase(it); } } static bool byNameLessThan(const OpenFileInfo *left, const OpenFileInfo *right) { return left->shortCaption() < right->shortCaption(); } static bool byLRULessThan(const OpenFileInfo *left, const OpenFileInfo *right) { return left->lastAccess() > right->lastAccess(); /// reverse sorting! } void readConfig() { readConfig(OpenFileInfo::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); readConfig(OpenFileInfo::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); readConfig(OpenFileInfo::Open, configGroupNameOpen, maxNumOpenFiles); } void writeConfig() { writeConfig(OpenFileInfo::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); writeConfig(OpenFileInfo::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); writeConfig(OpenFileInfo::Open, configGroupNameOpen, maxNumOpenFiles); } void readConfig(OpenFileInfo::StatusFlag statusFlag, const QString &configGroupName, int maxNumFiles) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); bool isFirst = true; KConfigGroup cg(config, configGroupName); for (int i = 0; i < maxNumFiles; ++i) { QUrl fileUrl = QUrl(cg.readEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), "")); if (!fileUrl.isValid()) break; if (fileUrl.scheme().isEmpty()) fileUrl.setScheme(QStringLiteral("file")); /// For local files, test if they exist; ignore local files that do not exist if (fileUrl.isLocalFile()) { if (!QFileInfo::exists(fileUrl.toLocalFile())) continue; } OpenFileInfo *ofi = p->contains(fileUrl); if (ofi == nullptr) { ofi = p->open(fileUrl); } ofi->addFlags(statusFlag); ofi->addFlags(OpenFileInfo::HasName); ofi->setLastAccess(QDateTime::fromString(cg.readEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ""), OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); if (isFirst) { isFirst = false; if (statusFlag == OpenFileInfo::Open) p->setCurrentFile(ofi); } } } void writeConfig(OpenFileInfo::StatusFlag statusFlag, const QString &configGroupName, int maxNumFiles) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); KConfigGroup cg(config, configGroupName); OpenFileInfoManager::OpenFileInfoList list = p->filteredItems(statusFlag); int i = 0; for (OpenFileInfoManager::OpenFileInfoList::ConstIterator it = list.constBegin(); i < maxNumFiles && it != list.constEnd(); ++it, ++i) { OpenFileInfo *ofi = *it; cg.writeEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), ofi->url().url(QUrl::PreferLocalFile)); cg.writeEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ofi->lastAccess().toString(OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); } for (; i < maxNumFiles; ++i) { cg.deleteEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i)); cg.deleteEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i)); } config->sync(); } }; const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameRecentlyUsed = QStringLiteral("DocumentList-RecentlyUsed"); const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameFavorites = QStringLiteral("DocumentList-Favorites"); const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameOpen = QStringLiteral("DocumentList-Open"); const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumFavoriteFiles = 256; const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumRecentlyUsedFiles = 8; const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumOpenFiles = 16; OpenFileInfoManager::OpenFileInfoManager(QObject *parent) : QObject(parent), d(new OpenFileInfoManagerPrivate(this)) { QTimer::singleShot(300, this, &OpenFileInfoManager::delayedReadConfig); } OpenFileInfoManager &OpenFileInfoManager::instance() { /// Allocate this singleton on heap not stack like most other singletons /// Supposedly, QCoreApplication will clean this singleton at application's end static OpenFileInfoManager *singleton = new OpenFileInfoManager(QCoreApplication::instance()); return *singleton; } OpenFileInfoManager::~OpenFileInfoManager() { delete d; } OpenFileInfo *OpenFileInfoManager::createNew(const QString &mimeType) { OpenFileInfo *result = new OpenFileInfo(this, mimeType); connect(result, &OpenFileInfo::flagsChanged, this, &OpenFileInfoManager::flagsChanged); d->openFileInfoList << result; result->setLastAccess(); return result; } OpenFileInfo *OpenFileInfoManager::open(const QUrl &url) { Q_ASSERT_X(url.isValid(), "OpenFileInfo *OpenFileInfoManager::open(const QUrl&)", "URL is not valid"); OpenFileInfo *result = contains(url); if (result == nullptr) { /// file not yet open result = new OpenFileInfo(this, url); connect(result, &OpenFileInfo::flagsChanged, this, &OpenFileInfoManager::flagsChanged); d->openFileInfoList << result; } /// else: file was already open, re-use and return existing OpenFileInfo pointer result->setLastAccess(); return result; } OpenFileInfo *OpenFileInfoManager::contains(const QUrl &url) const { if (!url.isValid()) return nullptr; /// can only be unnamed file for (auto *ofi : const_cast(d->openFileInfoList)) { if (ofi->url() == url) return ofi; } return nullptr; } bool OpenFileInfoManager::changeUrl(OpenFileInfo *openFileInfo, const QUrl &url) { OpenFileInfo *previouslyContained = contains(url); /// check if old url differs from new url and old url is valid if (previouslyContained != nullptr && previouslyContained->flags().testFlag(OpenFileInfo::Open) && previouslyContained != openFileInfo) { qCWarning(LOG_KBIBTEX_PROGRAM) << "Open file with same URL already exists, forcefully closing it" << endl; close(previouslyContained); } QUrl oldUrl = openFileInfo->url(); openFileInfo->setUrl(url); if (url != oldUrl && oldUrl.isValid()) { /// current document was most probabily renamed (e.g. due to "Save As") /// add old URL to recently used files, but exclude the open files list OpenFileInfo *ofi = open(oldUrl); // krazy:exclude=syscalls OpenFileInfo::StatusFlags statusFlags = (openFileInfo->flags() & (~OpenFileInfo::Open)) | OpenFileInfo::RecentlyUsed; ofi->setFlags(statusFlags); } if (previouslyContained != nullptr) { /// keep Favorite flag if set in file that have previously same URL if (previouslyContained->flags().testFlag(OpenFileInfo::Favorite)) openFileInfo->setFlags(openFileInfo->flags() | OpenFileInfo::Favorite); /// remove the old entry with the same url has it will be replaced by the new one d->openFileInfoList.remove(d->openFileInfoList.indexOf(previouslyContained)); previouslyContained->deleteLater(); OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::Open; statusFlags |= OpenFileInfo::RecentlyUsed; statusFlags |= OpenFileInfo::Favorite; emit flagsChanged(statusFlags); } if (openFileInfo == d->currentFileInfo) emit currentChanged(openFileInfo, KService::Ptr()); emit flagsChanged(openFileInfo->flags()); return true; } bool OpenFileInfoManager::close(OpenFileInfo *openFileInfo) { if (openFileInfo == nullptr) { qCWarning(LOG_KBIBTEX_PROGRAM) << "void OpenFileInfoManager::close(OpenFileInfo *openFileInfo): Cannot close openFileInfo which is NULL"; return false; } bool isClosing = false; openFileInfo->setLastAccess(); /// remove flag "open" from file to be closed and determine which file to show instead OpenFileInfo *nextCurrent = (d->currentFileInfo == openFileInfo) ? nullptr : d->currentFileInfo; for (OpenFileInfo *ofi : const_cast(d->openFileInfoList)) { if (!isClosing && ofi == openFileInfo && openFileInfo->close()) { isClosing = true; /// Mark file as closed (i.e. not open) openFileInfo->removeFlags(OpenFileInfo::Open); /// If file has a filename, remember as recently used if (openFileInfo->flags().testFlag(OpenFileInfo::HasName)) openFileInfo->addFlags(OpenFileInfo::RecentlyUsed); } else if (nextCurrent == nullptr && ofi->flags().testFlag(OpenFileInfo::Open)) nextCurrent = ofi; } /// If the current document is to be closed, /// switch over to the next available one if (isClosing) setCurrentFile(nextCurrent); return isClosing; } bool OpenFileInfoManager::queryCloseAll() { /// Assume that all closing operations succeed bool isClosing = true; /// For keeping track of files that get closed here OpenFileInfoList restoreLaterList; /// For each file known ... for (OpenFileInfo *openFileInfo : const_cast(d->openFileInfoList)) { /// Check only open file (ignore recently used, favorites, ...) if (openFileInfo->flags().testFlag(OpenFileInfo::Open)) { if (openFileInfo->close()) { /// If file could be closed without user canceling the operation ... /// Mark file as closed (i.e. not open) openFileInfo->removeFlags(OpenFileInfo::Open); /// If file has a filename, remember as recently used if (openFileInfo->flags().testFlag(OpenFileInfo::HasName)) openFileInfo->addFlags(OpenFileInfo::RecentlyUsed); /// Remember file as to be marked as open later restoreLaterList.append(openFileInfo); } else { /// User chose to cancel closing operation, /// stop everything here isClosing = false; break; } } } if (isClosing) { /// Closing operation was not cancelled, therefore mark /// all files that were open before as open now. /// This makes the files to be reopened when KBibTeX is /// restarted again (assuming that this function was /// called when KBibTeX is exiting). for (OpenFileInfo *openFileInfo : const_cast(restoreLaterList)) { openFileInfo->addFlags(OpenFileInfo::Open); } d->writeConfig(); } return isClosing; } OpenFileInfo *OpenFileInfoManager::currentFile() const { return d->currentFileInfo; } /// Clazy warns: "Missing reference on non-trivial type" for argument 'servicePtr', /// but type 'KService::Ptr' is actually a pointer (QExplicitlySharedDataPointer). void OpenFileInfoManager::setCurrentFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr) { bool hasChanged = d->currentFileInfo != openFileInfo; OpenFileInfo *previous = d->currentFileInfo; d->currentFileInfo = openFileInfo; if (d->currentFileInfo != nullptr) { d->currentFileInfo->addFlags(OpenFileInfo::Open); d->currentFileInfo->setLastAccess(); } if (hasChanged) { if (previous != nullptr) previous->setLastAccess(); emit currentChanged(openFileInfo, servicePtr); } else if (openFileInfo != nullptr && servicePtr != openFileInfo->currentService()) emit currentChanged(openFileInfo, servicePtr); } OpenFileInfoManager::OpenFileInfoList OpenFileInfoManager::filteredItems(OpenFileInfo::StatusFlags required, OpenFileInfo::StatusFlags forbidden) { OpenFileInfoList result; for (OpenFileInfoList::Iterator it = d->openFileInfoList.begin(); it != d->openFileInfoList.end(); ++it) { OpenFileInfo *ofi = *it; if ((ofi->flags() & required) == required && (ofi->flags() & forbidden) == 0) result << ofi; } if (required == OpenFileInfo::RecentlyUsed) qSort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byLRULessThan); else if (required == OpenFileInfo::Favorite || required == OpenFileInfo::Open) qSort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byNameLessThan); return result; } void OpenFileInfoManager::deferredListsChanged() { OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::Open; statusFlags |= OpenFileInfo::RecentlyUsed; statusFlags |= OpenFileInfo::Favorite; emit flagsChanged(statusFlags); } void OpenFileInfoManager::delayedReadConfig() { d->readConfig(); } diff --git a/src/program/openfileinfo.h b/src/program/openfileinfo.h index 869c3982..73805a4c 100644 --- a/src/program/openfileinfo.h +++ b/src/program/openfileinfo.h @@ -1,169 +1,168 @@ /*************************************************************************** * Copyright (C) 2004-2018 by Thomas Fischer * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ***************************************************************************/ #ifndef KBIBTEX_PROGRAM_OPENFILEINFO_H #define KBIBTEX_PROGRAM_OPENFILEINFO_H #include #include #include #include #include #include - -#include "fileinfo.h" +#include namespace KParts { class ReadOnlyPart; } class OpenFileInfoManager; class OpenFileInfo : public QObject { Q_OBJECT public: enum StatusFlag { Open = 0x1, RecentlyUsed = 0x2, Favorite = 0x4, HasName = 0x8 }; Q_DECLARE_FLAGS(StatusFlags, StatusFlag) ~OpenFileInfo() override; KParts::ReadOnlyPart *part(QWidget *parent, KService::Ptr servicePtr = KService::Ptr()); QString shortCaption() const; QString fullCaption() const; QString mimeType() const; QUrl url() const; bool isModified() const; bool save(); /** * Close the current file. The user may interrupt the closing * if the file is modified and he/she presses "Cancel" when asked * to close the modified file. * * @return returns true if the closing was successful, otherwise false */ bool close(); StatusFlags flags() const; void setFlags(StatusFlags statusFlags); void addFlags(StatusFlags statusFlags); void removeFlags(StatusFlags statusFlags); QDateTime lastAccess() const; void setLastAccess(const QDateTime &dateTime = QDateTime::currentDateTime()); KService::List listOfServices(); KService::Ptr defaultService(); KService::Ptr currentService(); friend class OpenFileInfoManager; signals: void flagsChanged(OpenFileInfo::StatusFlags statusFlags); protected: OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QUrl &url); OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QString &mimeType = FileInfo::mimetypeBibTeX); void setUrl(const QUrl &url); private: class OpenFileInfoPrivate; OpenFileInfoPrivate *d; }; Q_DECLARE_METATYPE(OpenFileInfo *) class OpenFileInfoManager: public QObject { Q_OBJECT public: typedef QVector OpenFileInfoList; static OpenFileInfoManager &instance(); ~OpenFileInfoManager() override; OpenFileInfo *createNew(const QString &mimeType = FileInfo::mimetypeBibTeX); /** * Open the given bibliography file as specified in the URL. * If the file is already open, an existing OpenFileInfo pointer will be * returned. If the file was not yet open, a new OpenFileInfo object will * be created and returned. * There shall be no two different OpenFileInfo objects representing * the same file. * @param url URL to bibliography file to open * @return an OpenFileInfo object representing the opened file */ OpenFileInfo *open(const QUrl &url); OpenFileInfo *contains(const QUrl &url) const; OpenFileInfo *currentFile() const; bool changeUrl(OpenFileInfo *openFileInfo, const QUrl &url); bool close(OpenFileInfo *openFileInfo); /** * Try to close all open files. If a file is modified, the user will be asked * if the file shall be saved first. Depending on the KPart, the user may opt * to cancel the closing operation for any modified file. * If the user chooses to cancel, this function quits the closing process and * returns false. Files closed so far stay closed, the remaining open files stay * open. * If the user does not interrupt this function (e.g. no file was modified or * the user confirmed to save or to discard changes), all files get closed. * However, all files that were open before will be marked as opened * again. This could render the user interface inconsistent (therefore this * function should be called only when exiting KBibTeX), but it allows to * easily reopen in the next session all files that were open at the time * this function was called in this session. * @return true if all files could be closed successfully, else false. */ bool queryCloseAll(); void setCurrentFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr = KService::Ptr()); OpenFileInfoList filteredItems(OpenFileInfo::StatusFlags required, OpenFileInfo::StatusFlags forbidden = nullptr); friend class OpenFileInfo; signals: void currentChanged(OpenFileInfo *, KService::Ptr); void flagsChanged(OpenFileInfo::StatusFlags statusFlags); protected: explicit OpenFileInfoManager(QObject *parent); private: class OpenFileInfoManagerPrivate; OpenFileInfoManagerPrivate *d; private slots: void deferredListsChanged(); void delayedReadConfig(); }; #endif // KBIBTEX_PROGRAM_OPENFILEINFO_H