diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e06a1be0..8ac7452d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,83 +1,81 @@ # "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) if(Qt5WebEngineWidgets_FOUND) add_definitions( -DHAVE_WEBENGINEWIDGETS ) endif(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) add_definitions( -DHAVE_WEBKITWIDGETS ) endif(Qt5WebKitWidgets_FOUND) if(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) message(STATUS "Found both QtWebEngine and QtWebKit, preferring to use QtWebEngine") else(Qt5WebKitWidgets_FOUND) message(STATUS "Found QtWebEngine, but not QtWebKit, therefore going to use QtWebEngine") endif(Qt5WebKitWidgets_FOUND) else(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) message(STATUS "Found QtWebKit, but not QtWebEngine, therefore going to use QtWebKit") else(Qt5WebKitWidgets_FOUND) message(STATUS "Found neither QtWebEngine nor QtWebKit, therefore trying to locate a KPart for HTML data") endif(Qt5WebKitWidgets_FOUND) endif(Qt5WebEngineWidgets_FOUND) add_subdirectory(global) add_subdirectory(config) add_subdirectory(data) add_subdirectory(io) -add_subdirectory( - processing -) +add_subdirectory(processing) add_subdirectory( networking ) add_subdirectory( gui ) 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/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b482a49b..535543b9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,129 +1,127 @@ # KBibTeX GUI library set( kbibtexgui_LIB_SRCS field/fieldinput.cpp field/fieldlineedit.cpp field/fieldlistedit.cpp field/colorlabelwidget.cpp file/fileview.cpp file/filedelegate.cpp file/partwidget.cpp file/findduplicatesui.cpp file/clipboard.cpp file/basicfileview.cpp file/sortfilterfilemodel.cpp element/elementeditor.cpp element/elementwidgets.cpp element/findpdfui.cpp element/associatedfilesui.cpp widgets/menulineedit.cpp widgets/filesettingswidget.cpp widgets/filterbar.cpp widgets/radiobuttontreeview.cpp widgets/hidingtabwidget.cpp widgets/starrating.cpp widgets/rangewidget.cpp config/entrylayout.cpp preferences/kbibtexpreferencesdialog.cpp preferences/settingsgeneralwidget.cpp preferences/settingsglobalkeywordswidget.cpp preferences/settingscolorlabelwidget.cpp preferences/settingsuserinterfacewidget.cpp preferences/settingsfileexporterpdfpswidget.cpp preferences/settingsfileexporterwidget.cpp preferences/settingsabstractwidget.cpp preferences/settingsidsuggestionswidget.cpp preferences/settingsidsuggestionseditor.cpp guihelper.cpp italictextitemmodel.cpp valuelistmodel.cpp delayedexecutiontimer.cpp logging_gui.cpp ) set( kbibtexgui_HDRS field/fieldinput.h field/colorlabelwidget.h field/fieldlineedit.h widgets/filterbar.h preferences/settingsuserinterfacewidget.h preferences/kbibtexpreferencesdialog.h preferences/settingsglobalkeywordswidget.h preferences/settingsfileexporterwidget.h preferences/settingsgeneralwidget.h preferences/settingsabstractwidget.h preferences/settingscolorlabelwidget.h preferences/settingsfileexporterpdfpswidget.h preferences/settingsidsuggestionswidget.h preferences/settingsidsuggestionseditor.h guihelper.h valuelistmodel.h italictextitemmodel.h delayedexecutiontimer.h file/findduplicatesui.h file/basicfileview.h file/clipboard.h file/fileview.h file/filedelegate.h file/sortfilterfilemodel.h file/partwidget.h element/findpdfui.h element/elementeditor.h element/associatedfilesui.h ) if(UNITY_BUILD) enable_unity_build(kbibtexgui kbibtexgui_LIB_SRCS) endif(UNITY_BUILD) include_directories( ${CMAKE_SOURCE_DIR}/src/networking ${CMAKE_BINARY_DIR}/src/networking - ${CMAKE_SOURCE_DIR}/src/processing - ${CMAKE_BINARY_DIR}/src/processing ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_BINARY_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/file ${CMAKE_SOURCE_DIR}/src/gui/dialogs ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/gui/field ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/gui/config ) add_library( kbibtexgui SHARED ${kbibtexgui_LIB_SRCS} ) target_link_libraries( kbibtexgui Qt5::Core KF5::IconThemes KF5::ItemViews KF5::Completion KF5::TextEditor kbibtexconfig kbibtexdata kbibtexio kbibtexnetworking - kbibtexproc + kbibtexprocessing ) set_target_properties( kbibtexgui PROPERTIES EXPORT_NAME "kbibtexgui" VERSION ${KBIBTEX_RELEASE_VERSION} SOVERSION ${KBIBTEX_SOVERSION} ) install( TARGETS kbibtexgui ${INSTALL_TARGETS_DEFAULT_ARGS} ) generate_export_header( kbibtexgui ) diff --git a/src/parts/CMakeLists.txt b/src/parts/CMakeLists.txt index 2dc2bb16..1538723c 100644 --- a/src/parts/CMakeLists.txt +++ b/src/parts/CMakeLists.txt @@ -1,84 +1,83 @@ # KBibTeX KPart set( kbibtexpart_SRCS part.cpp partfactory.cpp browserextension.cpp logging_parts.cpp ) if(UNITY_BUILD) enable_unity_build(kbibtexpart kbibtexpart_SRCS) endif(UNITY_BUILD) include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/config ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/gui/preferences ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/gui/file - ${CMAKE_SOURCE_DIR}/src/processing ${CMAKE_SOURCE_DIR}/src/networking ) # 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_CURRENT_BINARY_DIR} -P ${CMAKE_SOURCE_DIR}/src/getgit.cmake ) 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_library( kbibtexpart MODULE ${kbibtexpart_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) target_link_libraries( kbibtexpart KF5::Parts KF5::CoreAddons KF5::ItemViews kbibtexconfig kbibtexdata kbibtexio kbibtexgui - kbibtexproc + kbibtexprocessing ) install( TARGETS kbibtexpart DESTINATION ${KDE_INSTALL_PLUGINDIR} ) kcoreaddons_desktop_to_json(kbibtexpart kbibtexpart.desktop SERVICE_TYPES kpart.desktop) install( FILES kbibtexpart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES kbibtexpartui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kbibtexpart ) diff --git a/src/processing/CMakeLists.txt b/src/processing/CMakeLists.txt index a8e8d882..c447dbb0 100644 --- a/src/processing/CMakeLists.txt +++ b/src/processing/CMakeLists.txt @@ -1,61 +1,95 @@ -# KBibTeX Processing library - set( - kbibtexproc_LIB_SRCS + kbibtexprocessing_LIB_SRCS findduplicates.cpp idsuggestions.cpp lyx.cpp checkbibtex.cpp bibliographyservice.cpp journalabbreviations.cpp logging_processing.cpp ) -set( - kbibtexproc_HDRS - findduplicates.h - idsuggestions.h - lyx.h - checkbibtex.h - bibliographyservice.h - journalabbreviations.h -) - if(UNITY_BUILD) - enable_unity_build(kbibtexproc kbibtexproc_LIB_SRCS) + enable_unity_build(kbibtexproc kbibtexprocessing_LIB_SRCS) endif(UNITY_BUILD) -include_directories( - ${CMAKE_BINARY_DIR}/src/gui - ${CMAKE_SOURCE_DIR}/src/gui -) - -add_library( - kbibtexproc +add_library(kbibtexprocessing SHARED - ${kbibtexproc_LIB_SRCS} + ${kbibtexprocessing_LIB_SRCS} ) +generate_export_header(kbibtexprocessing) +add_library(KBibTeX::Processing ALIAS kbibtexprocessing) -target_link_libraries( kbibtexproc - Qt5::Core - KF5::Parts - kbibtexconfig - kbibtexdata - kbibtexio -) - -set_target_properties( - kbibtexproc +set_target_properties(kbibtexprocessing PROPERTIES - EXPORT_NAME "kbibtexproc" + EXPORT_NAME "kbibtexprocessing" VERSION ${KBIBTEX_RELEASE_VERSION} SOVERSION ${KBIBTEX_SOVERSION} ) +target_include_directories(kbibtexprocessing + INTERFACE + $ +) + +target_link_libraries(kbibtexprocessing + PUBLIC + Qt5::Core + KBibTeX::Data + PRIVATE + Qt5::Widgets + KF5::ConfigCore + KF5::ConfigGui + KF5::I18n + KF5::Parts + KF5::WidgetsAddons + KF5::XmlGui + KBibTeX::Config + KBibTeX::IO +) + install( - TARGETS - kbibtexproc - ${INSTALL_TARGETS_DEFAULT_ARGS} + TARGETS kbibtexprocessing + EXPORT kbibtexprocessing-targets + ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) -generate_export_header( kbibtexproc ) +set_target_properties(kbibtexprocessing PROPERTIES + EXPORT_NAME "Processing" +) + +ecm_generate_headers(kbibtexprocessing_HEADERS + HEADER_NAMES + BibliographyService + CheckBibTeX + FindDuplicates + IdSuggestions + JournalAbbreviations + LyX + REQUIRED_HEADERS kbibtexprocessing_HEADERS +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/kbibtexprocessing_export.h + ${kbibtexprocessing_HEADERS} + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KBibTeX/processing + COMPONENT Devel +) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXProcessing-configVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY ExactVersion +) + +configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/KBibTeXProcessing-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXProcessing-config.cmake + INSTALL_DESTINATION ${KDE_INSTALL_LIBDIR}/cmake/KBibTeX +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXProcessing-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/KBibTeXProcessing-configVersion.cmake + DESTINATION ${KDE_INSTALL_LIBDIR}/cmake/KBibTeX +) diff --git a/src/processing/bibliographyservice.h b/src/processing/bibliographyservice.h index 9dc12bfd..4ee60c7d 100644 --- a/src/processing/bibliographyservice.h +++ b/src/processing/bibliographyservice.h @@ -1,59 +1,61 @@ /*************************************************************************** * 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_PROCESSING_BIBLIOGRAPHYSERVICE_H #define KBIBTEX_PROCESSING_BIBLIOGRAPHYSERVICE_H -#include "kbibtexproc_export.h" - #include +#ifdef HAVE_KF5 +#include "kbibtexprocessing_export.h" +#endif // HAVE_KF5 + /** * To make (or test for) KBibTeX the default bibliography editor, * this class offers some essential functions. To make the association work, * KBibTeX's .desktop files have to be placed where KDE (including kbuildsycoca5) * can find them. * * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT BibliographyService : public QObject +class KBIBTEXPROCESSING_EXPORT BibliographyService : public QObject { Q_OBJECT public: explicit BibliographyService(QWidget *parentWidget); ~BibliographyService() override; /** * Set KBibTeX as default editor for supported * bibliographic file formats. */ void setKBibTeXasDefault(); /** * Test if KBibTeX is default editor for supported * bibliographic file formats. * @return true if KBibTeX is default editor, else false */ bool isKBibTeXdefault() const; private: class Private; Private *const d; }; #endif // KBIBTEX_PROCESSING_BIBLIOGRAPHYSERVICE_H diff --git a/src/processing/checkbibtex.cpp b/src/processing/checkbibtex.cpp index 5dc9618e..5c57cec0 100644 --- a/src/processing/checkbibtex.cpp +++ b/src/processing/checkbibtex.cpp @@ -1,151 +1,151 @@ /*************************************************************************** * 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 "checkbibtex.h" #include #include #include #include #include #include -#include "fileexporterbibtexoutput.h" -#include "file.h" -#include "entry.h" -#include "element.h" -#include "macro.h" +#include +#include +#include +#include +#include CheckBibTeX::CheckBibTeXResult CheckBibTeX::checkBibTeX(QSharedPointer &element, const File *file, QWidget *parent) { /// only entries are supported, no macros, preambles, ... QSharedPointer entry = element.dynamicCast(); if (entry.isNull()) return InvalidData; else return checkBibTeX(entry, file, parent); } CheckBibTeX::CheckBibTeXResult CheckBibTeX::checkBibTeX(QSharedPointer &entry, const File *file, QWidget *parent) { /// disable GUI under process QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); /// use a dummy BibTeX file to collect all elements necessary for check File dummyFile; /// create temporary entry to work with dummyFile << entry; /// fetch and inser crossref'ed entry QString crossRefStr; Value crossRefVal = entry->value(Entry::ftCrossRef); if (!crossRefVal.isEmpty() && file != nullptr) { crossRefStr = PlainTextValue::text(crossRefVal); QSharedPointer crossRefDest = file->containsKey(crossRefStr, File::etEntry).dynamicCast(); if (!crossRefDest.isNull()) dummyFile << crossRefDest; else crossRefStr.clear(); /// memorize crossref'ing failed } /// include all macro definitions, in case they are referenced if (file != nullptr) for (const auto &element : const_cast(*file)) if (Macro::isMacro(*element)) dummyFile << element; /// run special exporter to get BibTeX's output QStringList bibtexOuput; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); FileExporterBibTeXOutput exporter(FileExporterBibTeXOutput::BibTeXLogFile, parent); bool exporterResult = exporter.save(&buffer, &dummyFile, &bibtexOuput); buffer.close(); if (!exporterResult) { QApplication::restoreOverrideCursor(); KMessageBox::errorList(parent, i18n("Running BibTeX failed.\n\nSee the following output to trace the error:"), bibtexOuput, i18n("Running BibTeX failed.")); return FailedToCheck; } /// define variables how to parse BibTeX's output static const QString warningStart = QStringLiteral("Warning--"); static const QRegularExpression warningEmptyField(QStringLiteral("empty (\\w+) in ")); static const QRegularExpression warningEmptyField2(QStringLiteral("empty (\\w+) or (\\w+) in ")); static const QRegularExpression warningThereIsBut(QStringLiteral("there's a (\\w+) but no (\\w+) in")); static const QRegularExpression warningCantUseBoth(QStringLiteral("can't use both (\\w+) and (\\w+) fields")); static const QRegularExpression warningSort2(QStringLiteral("to sort, need (\\w+) or (\\w+) in ")); static const QRegularExpression warningSort3(QStringLiteral("to sort, need (\\w+), (\\w+), or (\\w+) in ")); static const QRegularExpression errorLine(QStringLiteral("---line (\\d+)")); /// go line-by-line through BibTeX output and collect warnings/errors QStringList warnings; QString errorPlainText; for (const QString &line : const_cast(bibtexOuput)) { QRegularExpressionMatch match; if ((match = errorLine.match(line)).hasMatch()) { buffer.open(QIODevice::ReadOnly); QTextStream ts(&buffer); bool ok = false; for (int i = match.captured(1).toInt(&ok); ok && i > 1; --i) { errorPlainText = ts.readLine(); buffer.close(); } } else if (line.startsWith(QStringLiteral("Warning--"))) { /// is a warning ... if ((match = warningEmptyField.match(line)).hasMatch()) { /// empty/missing field warnings << i18n("Field %1 is empty", match.captured(1)); } else if ((match = warningEmptyField2.match(line)).hasMatch()) { /// two empty/missing fields warnings << i18n("Fields %1 and %2 are empty, but at least one is required", match.captured(1), match.captured(2)); } else if ((match = warningThereIsBut.match(line)).hasMatch()) { /// there is a field which exists but another does not exist warnings << i18n("Field %1 exists, but %2 does not exist", match.captured(1), match.captured(2)); } else if ((match = warningCantUseBoth.match(line)).hasMatch()) { /// there are two conflicting fields, only one may be used warnings << i18n("Fields %1 and %2 cannot be used at the same time", match.captured(1), match.captured(2)); } else if ((match = warningSort2.match(line)).hasMatch()) { /// one out of two fields missing for sorting warnings << i18n("Fields %1 or %2 are required to sort entry", match.captured(1), match.captured(2)); } else if ((match = warningSort3.match(line)).hasMatch()) { /// one out of three fields missing for sorting warnings << i18n("Fields %1, %2, %3 are required to sort entry", match.captured(1), match.captured(2), match.captured(3)); } else { /// generic/unknown warning warnings << i18n("Unknown warning: %1", line.mid(warningStart.length())); } } } CheckBibTeXResult result = NoProblem; QApplication::restoreOverrideCursor(); if (!errorPlainText.isEmpty()) { result = BibTeXWarning; KMessageBox::information(parent, i18n("

The following error was found:

%1
", errorPlainText), i18n("Errors found")); } else if (!warnings.isEmpty()) { KMessageBox::informationList(parent, i18n("The following warnings were found:"), warnings, i18n("Warnings found")); result = BibTeXError; } else KMessageBox::information(parent, i18n("No warnings or errors were found.%1", crossRefStr.isEmpty() ? QString() : i18n("\n\nSome fields missing in this entry were taken from the crossref'ed entry '%1'.", crossRefStr)), i18n("No Errors or Warnings")); return result; } diff --git a/src/processing/checkbibtex.h b/src/processing/checkbibtex.h index 7db6ba5b..909b1d6d 100644 --- a/src/processing/checkbibtex.h +++ b/src/processing/checkbibtex.h @@ -1,41 +1,43 @@ /*************************************************************************** * Copyright (C) 2004-2014 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_PROCESSING_CHECKBIBTEX_H #define KBIBTEX_PROCESSING_CHECKBIBTEX_H #include -#include "kbibtexproc_export.h" +#ifdef HAVE_KF5 +#include "kbibtexprocessing_export.h" +#endif // HAVE_KF5 class Entry; class Element; class File; /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT CheckBibTeX +class KBIBTEXPROCESSING_EXPORT CheckBibTeX { public: enum CheckBibTeXResult { NoProblem, BibTeXWarning, BibTeXError, InvalidData, FailedToCheck }; static CheckBibTeXResult checkBibTeX(QSharedPointer &element, const File *file, QWidget *parent); static CheckBibTeXResult checkBibTeX(QSharedPointer &entry, const File *file, QWidget *parent); }; #endif // KBIBTEX_PROCESSING_CHECKBIBTEX_H diff --git a/src/processing/cmake/KBibTeXProcessing-config.cmake.in b/src/processing/cmake/KBibTeXProcessing-config.cmake.in new file mode 100644 index 00000000..e7710bf0 --- /dev/null +++ b/src/processing/cmake/KBibTeXProcessing-config.cmake.in @@ -0,0 +1,31 @@ +@PACKAGE_INIT@ + +find_package( + Qt5 @QT_MIN_VERSION@ + CONFIG + REQUIRED + Core + Gui +) + +find_package( + KF5 @KF5_MIN_VERSION@ + CONFIG + REQUIRED + I18n +) + +find_package( + KBibTeX @PROJECT_VERSION@ EXACT + CONFIG + REQUIRED + Config + Data + Global +) + +if(NOT TARGET KBibTeX::Processing) + include("${KBibTeXProcessing_CMAKE_DIR}/KBibTeXProcessing-targets.cmake") +endif() + +set(KBibTeXProcessing_LIBRARIES KBibTeX::Processing) diff --git a/src/processing/findduplicates.cpp b/src/processing/findduplicates.cpp index 85b5ca4b..0b4f0e29 100644 --- a/src/processing/findduplicates.cpp +++ b/src/processing/findduplicates.cpp @@ -1,547 +1,547 @@ /*************************************************************************** * 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 "findduplicates.h" #include #include #include #include #include #include #include -#include "file.h" -#include "models/filemodel.h" -#include "entry.h" +#include +#include +#include EntryClique::EntryClique() { /// nothing } int EntryClique::entryCount() const { return checkedEntries.count(); } QList > EntryClique::entryList() const { return checkedEntries.keys(); } bool EntryClique::isEntryChecked(QSharedPointer entry) const { return checkedEntries[entry]; } void EntryClique::setEntryChecked(QSharedPointer entry, bool isChecked) { checkedEntries[entry] = isChecked; recalculateValueMap(); } int EntryClique::fieldCount() const { return valueMap.count(); } QList EntryClique::fieldList() const { return valueMap.keys(); } QVector EntryClique::values(const QString &field) const { return valueMap[field]; } QVector &EntryClique::values(const QString &field) { return valueMap[field]; } Value EntryClique::chosenValue(const QString &field) const { Q_ASSERT_X(chosenValueMap[field].count() == 1, "Value EntryClique::chosenValue(const QString &field) const", "Exactly one value expected in chosenValueMap"); return chosenValueMap[field].first(); } QVector EntryClique::chosenValues(const QString &field) const { return chosenValueMap[field]; } void EntryClique::setChosenValue(const QString &field, const Value &value, ValueOperation valueOperation) { switch (valueOperation) { case SetValue: { chosenValueMap[field].clear(); chosenValueMap[field] << value; break; } case AddValue: { QString text = PlainTextValue::text(value); for (const Value &value : const_cast &>(chosenValueMap[field])) if (PlainTextValue::text(value) == text) return; chosenValueMap[field] << value; break; } case RemoveValue: { QString text = PlainTextValue::text(value); for (QVector::Iterator it = chosenValueMap[field].begin(); it != chosenValueMap[field].end(); ++it) if (PlainTextValue::text(*it) == text) { chosenValueMap[field].erase(it); return; } break; } } } void EntryClique::addEntry(QSharedPointer entry) { checkedEntries.insert(entry, false); /// remember to call recalculateValueMap later } void EntryClique::recalculateValueMap() { valueMap.clear(); chosenValueMap.clear(); /// go through each and every entry ... const QList > el = entryList(); for (const auto &entry : el) if (isEntryChecked(entry)) { /// cover entry type Value v; v.append(QSharedPointer(new VerbatimText(entry->type()))); insertKeyValueToValueMap(QStringLiteral("^type"), v, entry->type(), Qt::CaseInsensitive /** entry types shall be compared case insensitive */); /// cover entry id v.clear(); v.append(QSharedPointer(new VerbatimText(entry->id()))); insertKeyValueToValueMap(QStringLiteral("^id"), v, entry->id()); /// go through each and every field of this entry for (Entry::ConstIterator fieldIt = entry->constBegin(); fieldIt != entry->constEnd(); ++fieldIt) { /// store both field name and value for later reference const QString fieldName = fieldIt.key().toLower(); const Value fieldValue = fieldIt.value(); if (fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl) { for (const auto &vi : fieldValue) { const QString text = PlainTextValue::text(*vi); Value v; v << vi; insertKeyValueToValueMap(fieldName, v, text); } } else { const QString fieldValueText = PlainTextValue::text(fieldValue); insertKeyValueToValueMap(fieldName, fieldValue, fieldValueText); } } } const auto fl = fieldList(); for (const QString &fieldName : fl) if (valueMap[fieldName].count() < 2) { valueMap.remove(fieldName); chosenValueMap.remove(fieldName); } } void EntryClique::insertKeyValueToValueMap(const QString &fieldName, const Value &fieldValue, const QString &fieldValueText, const Qt::CaseSensitivity) { if (fieldValueText.isEmpty()) return; if (valueMap.contains(fieldName)) { /// in the list of alternatives, search of a value identical /// to the current (as of fieldIt) value (to avoid duplicates) bool alreadyContained = false; QVector alternatives = valueMap[fieldName]; for (const Value &v : const_cast &>(alternatives)) if (PlainTextValue::text(v) == fieldValueText) { alreadyContained = true; break; } if (!alreadyContained) { alternatives << fieldValue; valueMap[fieldName] = alternatives; } } else { QVector alternatives = valueMap[fieldName]; alternatives << fieldValue; valueMap.insert(fieldName, alternatives); QVector chosen; chosen << fieldValue; chosenValueMap.insert(fieldName, chosen); } } class FindDuplicates::FindDuplicatesPrivate { private: const unsigned int maxDistance; int **d; static const int dsize; public: int sensitivity; QWidget *widget; FindDuplicatesPrivate(int sens, QWidget *w) : maxDistance(10000), sensitivity(sens), widget(w == nullptr ? qApp->activeWindow() : w) { d = new int *[dsize]; for (int i = 0; i < dsize; ++i) d[i] = new int[dsize]; } ~FindDuplicatesPrivate() { for (int i = 0; i < dsize; ++i) delete[] d[i]; delete [] d; } /** * Determine the Levenshtein distance between two words. * See also http://en.wikipedia.org/wiki/Levenshtein_distance * @param s first word, all chars already in lower case * @param t second word, all chars already in lower case * @return distance between both words */ double levenshteinDistanceWord(const QString &s, const QString &t) { const int m = qMin(s.length(), dsize - 1), n = qMin(t.length(), dsize - 1); if (m < 1 && n < 1) return 0.0; if (m < 1 || n < 1) return 1.0; for (int i = 0; i <= m; ++i) d[i][0] = i; for (int i = 0; i <= n; ++i) d[0][i] = i; for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) { d[i][j] = d[i - 1][j] + 1; int c = d[i][j - 1] + 1; if (c < d[i][j]) d[i][j] = c; c = d[i - 1][j - 1] + (s[i - 1] == t[j - 1] ? 0 : 1); if (c < d[i][j]) d[i][j] = c; } double result = d[m][n]; result = result / qMax(m, n); result *= result; return result; } /** * Determine the Levenshtein distance between two sentences (list of words). * See also http://en.wikipedia.org/wiki/Levenshtein_distance * @param s first sentence * @param t second sentence * @return distance between both sentences */ double levenshteinDistance(const QStringList &s, const QStringList &t) { const int m = s.size(), n = t.size(); if (m < 1 && n < 1) return 0.0; if (m < 1 || n < 1) return 1.0; double **d = new double*[m + 1]; for (int i = 0; i <= m; ++i) { d[i] = new double[n + 1]; d[i][0] = i; } for (int i = 0; i <= n; ++i) d[0][i] = i; for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) { d[i][j] = d[i - 1][j] + 1; double c = d[i][j - 1] + 1; if (c < d[i][j]) d[i][j] = c; c = d[i - 1][j - 1] + levenshteinDistanceWord(s[i - 1], t[j - 1]); if (c < d[i][j]) d[i][j] = c; } double result = d[m][n]; for (int i = 0; i <= m; ++i) delete[] d[i]; delete [] d; result = result / qMax(m, n); return result; } /** * Determine the Levenshtein distance between two sentences, * where each sentence is in a string (not split into single words). * See also http://en.wikipedia.org/wiki/Levenshtein_distance * @param s first sentence * @param t second sentence * @return distance between both sentences */ double levenshteinDistance(const QString &s, const QString &t) { static const QRegularExpression nonWordRegExp(QStringLiteral("[^a-z']+"), QRegularExpression::CaseInsensitiveOption); if (s.isEmpty() || t.isEmpty()) return 1.0; return levenshteinDistance(s.toLower().split(nonWordRegExp, QString::SkipEmptyParts), t.toLower().split(nonWordRegExp, QString::SkipEmptyParts)); } /** * Distance between two BibTeX entries, scaled by maxDistance. */ int entryDistance(Entry *entryA, Entry *entryB) { /// "distance" to be used if no value for a field is given const double neutralDistance = 0.05; /** * Get both entries' titles. If both are empty, use a "neutral * distance" otherwise compute levenshtein distance (0.0 .. 1.0). */ const QString titleA = PlainTextValue::text(entryA->value(Entry::ftTitle)); const QString titleB = PlainTextValue::text(entryB->value(Entry::ftTitle)); double titleDistance = titleA.isEmpty() && titleB.isEmpty() ? neutralDistance : levenshteinDistance(titleA, titleB); /** * Get both entries' author names. If both are empty, use a * "neutral distance" otherwise compute levenshtein distance * (0.0 .. 1.0). */ const QString authorA = PlainTextValue::text(entryA->value(Entry::ftAuthor)); const QString authorB = PlainTextValue::text(entryB->value(Entry::ftAuthor)); double authorDistance = authorA.isEmpty() && authorB.isEmpty() ? neutralDistance : levenshteinDistance(authorA, authorB); /** * Get both entries' years. If both are empty, use a * "neutral distance" otherwise compute distance as follows: * take square of difference between both years, but impose * a maximum of 100. Divide value by 100.0 to get a distance * value of 0.0 .. 1.0. */ const QString yearA = PlainTextValue::text(entryA->value(Entry::ftYear)); const QString yearB = PlainTextValue::text(entryB->value(Entry::ftYear)); bool yearAok = false, yearBok = false; int yearAint = yearA.toInt(&yearAok); int yearBint = yearB.toInt(&yearBok); double yearDistance = yearAok && yearBok ? qMin((yearBint - yearAint) * (yearBint - yearAint), 100) / 100.0 : neutralDistance; /** * Compute total distance by taking individual distances for * author, title, and year. Weight each individual distance as * follows: title => 60%, author => 30%, year => 10% * Scale distance by maximum distance and round to int; result * will be in range 0 .. maxDistance. */ int distance = static_cast(maxDistance * (titleDistance * 0.6 + authorDistance * 0.3 + yearDistance * 0.1) + 0.5); return distance; } }; const int FindDuplicates::FindDuplicatesPrivate::dsize = 32; FindDuplicates::FindDuplicates(QWidget *parent, int sensitivity) : QObject(parent), d(new FindDuplicatesPrivate(sensitivity, parent)) { /// nothing } FindDuplicates::~FindDuplicates() { delete d; } bool FindDuplicates::findDuplicateEntries(File *file, QVector &entryCliqueList) { QApplication::setOverrideCursor(Qt::WaitCursor); QScopedPointer progressDlg(new QProgressDialog(i18n("Searching ..."), i18n("Cancel"), 0, 100000 /* to be set later to actual value */, d->widget)); progressDlg->setModal(true); progressDlg->setWindowTitle(i18n("Finding Duplicates")); progressDlg->setMinimumWidth(d->widget->fontMetrics().averageCharWidth() * 48); progressDlg->setAutoReset(false); entryCliqueList.clear(); /// assemble list of entries only (ignoring comments, macros, ...) QVector > listOfEntries; listOfEntries.reserve(file->size()); for (const auto &element : const_cast(*file)) { QSharedPointer e = element.dynamicCast(); if (!e.isNull() && !e->isEmpty()) listOfEntries << e; } if (listOfEntries.isEmpty()) { /// no entries to compare found entryCliqueList.clear(); QApplication::restoreOverrideCursor(); return progressDlg->wasCanceled(); } int curProgress = 0, maxProgress = listOfEntries.count() * (listOfEntries.count() - 1) / 2; int progressDelta = 1; progressDlg->setMaximum(maxProgress); progressDlg->show(); emit maximumProgress(maxProgress); /// go through all entries ... for (const auto &entry : const_cast > &>(listOfEntries)) { QApplication::instance()->processEvents(); if (progressDlg->wasCanceled()) { entryCliqueList.clear(); break; } progressDlg->setValue(curProgress); emit currentProgress(curProgress); /// ... and find a "clique" of entries where it will match, i.e. distance is below sensitivity /// assume current entry will match in no clique bool foundClique = false; /// go through all existing cliques for (QVector::Iterator cit = entryCliqueList.begin(); cit != entryCliqueList.end(); ++cit) { /// check distance between current entry and clique's first entry if (d->entryDistance(entry.data(), (*cit)->entryList().constFirst().data()) < d->sensitivity) { /// if distance is below sensitivity, add current entry to clique foundClique = true; (*cit)->addEntry(entry); break; } QApplication::instance()->processEvents(); if (progressDlg->wasCanceled()) { entryCliqueList.clear(); break; } } if (!progressDlg->wasCanceled() && !foundClique) { /// no clique matched to current entry, so create and add new clique /// consisting only of the current entry EntryClique *newClique = new EntryClique(); newClique->addEntry(entry); entryCliqueList << newClique; } curProgress += progressDelta; ++progressDelta; progressDlg->setValue(curProgress); emit currentProgress(curProgress); } progressDlg->setValue(progressDlg->maximum()); /// remove cliques with only one element (nothing to merge here) from the list of cliques for (QVector::Iterator cit = entryCliqueList.begin(); cit != entryCliqueList.end();) if ((*cit)->entryCount() < 2) { EntryClique *ec = *cit; cit = entryCliqueList.erase(cit); delete ec; } else { /// entries have been inserted as checked, /// therefore recalculate alternatives (*cit)->recalculateValueMap(); ++cit; } QApplication::restoreOverrideCursor(); return progressDlg->wasCanceled(); } MergeDuplicates::MergeDuplicates() { /// nothing } bool MergeDuplicates::mergeDuplicateEntries(const QVector &entryCliques, FileModel *fileModel) { bool didMerge = false; for (EntryClique *entryClique : entryCliques) { /// Avoid adding fields 20 lines below /// which have been remove (not added) 10 lines below QSet coveredFields; Entry *mergedEntry = new Entry(QString(), QString()); const auto fieldList = entryClique->fieldList(); coveredFields.reserve(fieldList.size()); for (const auto &field : fieldList) { coveredFields << field; if (field == QStringLiteral("^id")) mergedEntry->setId(PlainTextValue::text(entryClique->chosenValue(field))); else if (field == QStringLiteral("^type")) mergedEntry->setType(PlainTextValue::text(entryClique->chosenValue(field))); else { Value combined; const auto chosenValues = entryClique->chosenValues(field); for (const Value &v : chosenValues) { combined.append(v); } if (!combined.isEmpty()) mergedEntry->insert(field, combined); } } bool actuallyMerged = false; int preferredInsertionRow = -1; const auto entryList = entryClique->entryList(); for (const auto &entry : entryList) { /// if merging entries with identical ids, the merged entry will not yet have an id (is null) if (mergedEntry->id().isEmpty()) mergedEntry->setId(entry->id()); /// if merging entries with identical types, the merged entry will not yet have an type (is null) if (mergedEntry->type().isEmpty()) mergedEntry->setType(entry->type()); /// add all other fields not covered by user selection /// those fields did only occur in one entry (no conflict) /// may add a lot of bloat to merged entry if (entryClique->isEntryChecked(entry)) { actuallyMerged = true; for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) if (!mergedEntry->contains(it.key()) && !coveredFields.contains(it.key())) { mergedEntry->insert(it.key(), it.value()); coveredFields << it.key(); } const int row = fileModel->row(entry); if (preferredInsertionRow < 0) preferredInsertionRow = row; fileModel->removeRow(row); } } if (actuallyMerged) { if (preferredInsertionRow < 0) preferredInsertionRow = fileModel->rowCount(); fileModel->insertRow(QSharedPointer(mergedEntry), preferredInsertionRow); } else delete mergedEntry; didMerge |= actuallyMerged; } return didMerge; } diff --git a/src/processing/findduplicates.h b/src/processing/findduplicates.h index 2d977514..90eb51d1 100644 --- a/src/processing/findduplicates.h +++ b/src/processing/findduplicates.h @@ -1,106 +1,108 @@ /*************************************************************************** * 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_PROCESSING_FINDDUPLICATES_H #define KBIBTEX_PROCESSING_FINDDUPLICATES_H -#include "kbibtexproc_export.h" - #include #include -#include "value.h" +#include + +#ifdef HAVE_KF5 +#include "kbibtexprocessing_export.h" +#endif // HAVE_KF5 class Entry; class File; class FileModel; -class KBIBTEXPROC_EXPORT FindDuplicates; +class KBIBTEXPROCESSING_EXPORT FindDuplicates; /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT EntryClique +class KBIBTEXPROCESSING_EXPORT EntryClique { friend class FindDuplicates; public: EntryClique(); enum ValueOperation { SetValue, AddValue, RemoveValue }; int entryCount() const; QList > entryList() const; bool isEntryChecked(QSharedPointer entry) const; void setEntryChecked(QSharedPointer entry, bool isChecked); int fieldCount() const; QList fieldList() const; QVector values(const QString &field) const; QVector &values(const QString &field); Value chosenValue(const QString &field) const; QVector chosenValues(const QString &field) const; void setChosenValue(const QString &field, const Value &value, ValueOperation valueOperation = SetValue); QString dump() const; protected: void addEntry(QSharedPointer entry); private: QMap, bool> checkedEntries; QMap > valueMap; QMap > chosenValueMap; void recalculateValueMap(); void insertKeyValueToValueMap(const QString &fieldName, const Value &fieldValue, const QString &fieldValueText, const Qt::CaseSensitivity = Qt::CaseSensitive); }; /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT FindDuplicates : public QObject +class KBIBTEXPROCESSING_EXPORT FindDuplicates : public QObject { Q_OBJECT public: explicit FindDuplicates(QWidget *parent, int sensitivity = 4000); ~FindDuplicates() override; bool findDuplicateEntries(File *file, QVector &entryCliqueList); signals: void maximumProgress(int maxProgress); void currentProgress(int progress); private: class FindDuplicatesPrivate; FindDuplicatesPrivate *d; }; /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT MergeDuplicates +class KBIBTEXPROCESSING_EXPORT MergeDuplicates { public: static bool mergeDuplicateEntries(const QVector &entryCliques, FileModel *fileModel); private: explicit MergeDuplicates(); }; #endif // KBIBTEX_PROCESSING_FINDDUPLICATES_H diff --git a/src/processing/idsuggestions.cpp b/src/processing/idsuggestions.cpp index 605f6104..fe0c33df 100644 --- a/src/processing/idsuggestions.cpp +++ b/src/processing/idsuggestions.cpp @@ -1,568 +1,568 @@ /*************************************************************************** * 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 "idsuggestions.h" #include #include +#include +#include #include "journalabbreviations.h" -#include "encoder.h" -#include "preferences.h" class IdSuggestions::IdSuggestionsPrivate { private: IdSuggestions *p; static const QStringList smallWords; public: IdSuggestionsPrivate(IdSuggestions *parent) : p(parent) { /// nothing } QString normalizeText(const QString &input) const { static const QRegularExpression unwantedChars(QStringLiteral("[^-_:/=+a-zA-Z0-9]+")); return Encoder::instance().convertToPlainAscii(input).remove(unwantedChars); } int numberFromEntry(const Entry &entry, const QString &field) const { static const QRegularExpression firstDigits(QStringLiteral("^[0-9]+")); const QString text = PlainTextValue::text(entry.value(field)); const QRegularExpressionMatch match = firstDigits.match(text); if (!match.hasMatch()) return -1; bool ok = false; const int result = match.captured(0).toInt(&ok); return ok ? result : -1; } QString pageNumberFromEntry(const Entry &entry) const { static const QRegularExpression whitespace(QStringLiteral("[ \t]+")); static const QRegularExpression pageNumber(QStringLiteral("[a-z0-9+:]+"), QRegularExpression::CaseInsensitiveOption); const QString text = PlainTextValue::text(entry.value(Entry::ftPages)).remove(whitespace).remove(QStringLiteral("mbox")); const QRegularExpressionMatch match = pageNumber.match(text); if (!match.hasMatch()) return QString(); return match.captured(0); } QString translateTitleToken(const Entry &entry, const struct IdSuggestionTokenInfo &tti, bool removeSmallWords) const { QString result; bool first = true; static const QRegularExpression sequenceOfSpaces(QStringLiteral("\\s+")); const QStringList titleWords = PlainTextValue::text(entry.value(Entry::ftTitle)).split(sequenceOfSpaces, QString::SkipEmptyParts); int index = 0; for (QStringList::ConstIterator it = titleWords.begin(); it != titleWords.end(); ++it, ++index) { const QString lowerText = normalizeText(*it).toLower(); if ((removeSmallWords && smallWords.contains(lowerText)) || index < tti.startWord || index > tti.endWord) continue; if (first) first = false; else result.append(tti.inBetween); QString titleComponent = lowerText.left(tti.len); if (tti.caseChange == IdSuggestions::ccToCamelCase) titleComponent = titleComponent[0].toUpper() + titleComponent.mid(1); result.append(titleComponent); } switch (tti.caseChange) { case IdSuggestions::ccToUpper: result = result.toUpper(); break; case IdSuggestions::ccToLower: result = result.toLower(); break; case IdSuggestions::ccToCamelCase: /// already processed above case IdSuggestions::ccNoChange: /// nothing break; } return result; } QString translateAuthorsToken(const Entry &entry, const struct IdSuggestionTokenInfo &ati) const { QString result; /// Already some author inserted into result? bool firstInserted = false; /// Get list of authors' last names const QStringList authors = entry.authorsLastName(); /// Keep track of which author (number/position) is processed int index = 0; /// Go through all authors for (QStringList::ConstIterator it = authors.constBegin(); it != authors.constEnd(); ++it, ++index) { /// Get current author, normalize name (remove unwanted characters), cut to maximum length QString author = normalizeText(*it).left(ati.len); /// Check if camel case is requests if (ati.caseChange == IdSuggestions::ccToCamelCase) { /// Get components of the author's last name const QStringList nameComponents = author.split(QStringLiteral(" "), QString::SkipEmptyParts); QStringList newNameComponents; newNameComponents.reserve(nameComponents.size()); /// Camel-case each name component for (const QString &nameComponent : nameComponents) { newNameComponents.append(nameComponent[0].toUpper() + nameComponent.mid(1)); } /// Re-assemble name from camel-cased components author = newNameComponents.join(QStringLiteral(" ")); } if ( (index >= ati.startWord && index <= ati.endWord) ///< check for requested author range || (ati.lastWord && index == authors.count() - 1) ///< explicitly insert last author if requested in lastWord flag ) { if (firstInserted) result.append(ati.inBetween); result.append(author); firstInserted = true; } } switch (ati.caseChange) { case IdSuggestions::ccToUpper: result = result.toUpper(); break; case IdSuggestions::ccToLower: result = result.toLower(); break; case IdSuggestions::ccToCamelCase: /// already processed above break; case IdSuggestions::ccNoChange: /// nothing break; } return result; } QString translateJournalToken(const Entry &entry, const struct IdSuggestionTokenInfo &jti, bool removeSmallWords) const { static const QRegularExpression sequenceOfSpaces(QStringLiteral("\\s+")); QString journalName = PlainTextValue::text(entry.value(Entry::ftJournal)); journalName = JournalAbbreviations::instance().toShortName(journalName); const QStringList journalWords = journalName.split(sequenceOfSpaces, QString::SkipEmptyParts); bool first = true; int index = 0; QString result; for (QStringList::ConstIterator it = journalWords.begin(); it != journalWords.end(); ++it, ++index) { QString journalComponent = normalizeText(*it); const QString lowerText = journalComponent.toLower(); if ((removeSmallWords && smallWords.contains(lowerText)) || index < jti.startWord || index > jti.endWord) continue; if (first) first = false; else result.append(jti.inBetween); /// Try to keep sequences of capital letters at the start of the journal name, /// those may already be abbreviations. unsigned int countCaptialCharsAtStart = 0; while (journalComponent[countCaptialCharsAtStart].isUpper()) ++countCaptialCharsAtStart; journalComponent = journalComponent.left(qMax(jti.len, countCaptialCharsAtStart)); if (jti.caseChange == IdSuggestions::ccToCamelCase) journalComponent = journalComponent[0].toUpper() + journalComponent.mid(1); result.append(journalComponent); } switch (jti.caseChange) { case IdSuggestions::ccToUpper: result = result.toUpper(); break; case IdSuggestions::ccToLower: result = result.toLower(); break; case IdSuggestions::ccToCamelCase: /// already processed above case IdSuggestions::ccNoChange: /// nothing break; } return result; } QString translateTypeToken(const Entry &entry, const struct IdSuggestionTokenInfo &eti) const { QString entryType(entry.type()); switch (eti.caseChange) { case IdSuggestions::ccToUpper: return entryType.toUpper().left(eti.len); case IdSuggestions::ccToLower: return entryType.toLower().left(eti.len); case IdSuggestions::ccToCamelCase: { if (entryType.isEmpty()) return QString(); ///< empty entry type? Return immediately to avoid problems with entryType[0] /// Apply some heuristic replacements to make the entry type look like CamelCase entryType = entryType.toLower(); ///< start with lower case /// Then, replace known words with their CamelCase variant entryType = entryType.replace(QStringLiteral("report"), QStringLiteral("Report")).replace(QStringLiteral("proceedings"), QStringLiteral("Proceedings")).replace(QStringLiteral("thesis"), QStringLiteral("Thesis")).replace(QStringLiteral("book"), QStringLiteral("Book")).replace(QStringLiteral("phd"), QStringLiteral("PhD")); /// Finally, guarantee that first letter is upper case entryType[0] = entryType[0].toUpper(); return entryType.left(eti.len); } default: return entryType.left(eti.len); } } QString translateToken(const Entry &entry, const QString &token) const { switch (token[0].toLatin1()) { case 'a': ///< deprecated but still supported case { /// Evaluate the token string, store information in struct IdSuggestionTokenInfo ati struct IdSuggestionTokenInfo ati = p->evalToken(token.mid(1)); ati.startWord = ati.endWord = 0; ///< only first author return translateAuthorsToken(entry, ati); } case 'A': { /// Evaluate the token string, store information in struct IdSuggestionTokenInfo ati const struct IdSuggestionTokenInfo ati = p->evalToken(token.mid(1)); return translateAuthorsToken(entry, ati); } case 'z': ///< deprecated but still supported case { /// Evaluate the token string, store information in struct IdSuggestionTokenInfo ati struct IdSuggestionTokenInfo ati = p->evalToken(token.mid(1)); /// All but first author ati.startWord = 1; ati.endWord = 0x00ffffff; return translateAuthorsToken(entry, ati); } case 'y': { int year = numberFromEntry(entry, Entry::ftYear); if (year > -1) return QString::number(year % 100 + 100).mid(1); break; } case 'Y': { const int year = numberFromEntry(entry, Entry::ftYear); if (year > -1) return QString::number(year % 10000 + 10000).mid(1); break; } case 't': case 'T': { /// Evaluate the token string, store information in struct IdSuggestionTokenInfo jti const struct IdSuggestionTokenInfo tti = p->evalToken(token.mid(1)); return translateTitleToken(entry, tti, token[0].isUpper()); } case 'j': case 'J': { /// Evaluate the token string, store information in struct IdSuggestionTokenInfo jti const struct IdSuggestionTokenInfo jti = p->evalToken(token.mid(1)); return translateJournalToken(entry, jti, token[0].isUpper()); } case 'e': { /// Evaluate the token string, store information in struct IdSuggestionTokenInfo eti const struct IdSuggestionTokenInfo eti = p->evalToken(token.mid(1)); return translateTypeToken(entry, eti); } case 'v': { return normalizeText(PlainTextValue::text(entry.value(Entry::ftVolume))); } case 'p': { return pageNumberFromEntry(entry); } case '"': return token.mid(1); } return QString(); } }; /// List of small words taken from OCLC: /// https://www.oclc.org/developer/develop/web-services/worldcat-search-api/bibliographic-resource.en.html const QStringList IdSuggestions::IdSuggestionsPrivate::smallWords = i18nc("Small words that can be removed from titles when generating id suggestions; separated by pipe symbol", "a|als|am|an|are|as|at|auf|aus|be|but|by|das|dass|de|der|des|dich|dir|du|er|es|for|from|had|have|he|her|his|how|ihr|ihre|ihres|im|in|is|ist|it|kein|la|le|les|mein|mich|mir|mit|of|on|sein|sie|that|the|this|to|un|une|von|was|wer|which|wie|wird|with|yousie|that|the|this|to|un|une|von|was|wer|which|wie|wird|with|you").split(QStringLiteral("|"), QString::SkipEmptyParts); IdSuggestions::IdSuggestions() : d(new IdSuggestionsPrivate(this)) { /// nothing } IdSuggestions::~IdSuggestions() { delete d; } QString IdSuggestions::formatId(const Entry &entry, const QString &formatStr) const { QString id; const QStringList tokenList = formatStr.split(QStringLiteral("|"), QString::SkipEmptyParts); for (const QString &token : tokenList) { id.append(d->translateToken(entry, token)); } return id; } QString IdSuggestions::defaultFormatId(const Entry &entry) const { return formatId(entry, Preferences::instance().activeIdSuggestionFormatString()); } bool IdSuggestions::hasDefaultFormat() const { return !Preferences::instance().activeIdSuggestionFormatString().isEmpty(); } bool IdSuggestions::applyDefaultFormatId(Entry &entry) const { const QString dfs = Preferences::instance().activeIdSuggestionFormatString(); if (!dfs.isEmpty()) { entry.setId(defaultFormatId(entry)); return true; } else return false; } QStringList IdSuggestions::formatIdList(const Entry &entry) const { const QStringList formatStrings = Preferences::instance().idSuggestionFormatStrings(); QStringList result; result.reserve(formatStrings.size()); for (const QString &formatString : formatStrings) { result << formatId(entry, formatString); } return result; } QStringList IdSuggestions::formatStrToHuman(const QString &formatStr) const { QStringList result; const QStringList tokenList = formatStr.split(QStringLiteral("|"), QString::SkipEmptyParts); for (const QString &token : tokenList) { QString text; if (token[0] == 'a' || token[0] == 'A' || token[0] == 'z') { struct IdSuggestionTokenInfo info = evalToken(token.mid(1)); if (token[0] == 'a') info.startWord = info.endWord = 0; else if (token[0] == 'z') { info.startWord = 1; info.endWord = 0x00ffffff; } text = formatAuthorRange(info.startWord, info.endWord, info.lastWord); int n = info.len; if (info.len < 0x00ffffff) text.append(i18np(", but only first letter of each last name", ", but only first %1 letters of each last name", n)); switch (info.caseChange) { case IdSuggestions::ccToUpper: text.append(i18n(", in upper case")); break; case IdSuggestions::ccToLower: text.append(i18n(", in lower case")); break; case IdSuggestions::ccToCamelCase: text.append(i18n(", in CamelCase")); break; case IdSuggestions::ccNoChange: break; } if (!info.inBetween.isEmpty()) text.append(i18n(", with '%1' in between", info.inBetween)); } else if (token[0] == 'y') text.append(i18n("Year (2 digits)")); else if (token[0] == 'Y') text.append(i18n("Year (4 digits)")); else if (token[0] == 't' || token[0] == 'T') { struct IdSuggestionTokenInfo info = evalToken(token.mid(1)); text.append(i18n("Title")); if (info.startWord == 0 && info.endWord <= 0xffff) text.append(i18np(", but only the first word", ", but only first %1 words", info.endWord + 1)); else if (info.startWord > 0 && info.endWord > 0xffff) text.append(i18n(", but only starting from word %1", info.startWord + 1)); else if (info.startWord > 0 && info.endWord <= 0xffff) text.append(i18n(", but only from word %1 to word %2", info.startWord + 1, info.endWord + 1)); if (info.len < 0x00ffffff) text.append(i18np(", but only first letter of each word", ", but only first %1 letters of each word", info.len)); switch (info.caseChange) { case IdSuggestions::ccToUpper: text.append(i18n(", in upper case")); break; case IdSuggestions::ccToLower: text.append(i18n(", in lower case")); break; case IdSuggestions::ccToCamelCase: text.append(i18n(", in CamelCase")); break; case IdSuggestions::ccNoChange: break; } if (!info.inBetween.isEmpty()) text.append(i18n(", with '%1' in between", info.inBetween)); if (token[0] == 'T') text.append(i18n(", small words removed")); } else if (token[0] == 'j') { struct IdSuggestionTokenInfo info = evalToken(token.mid(1)); text.append(i18n("Journal")); if (info.len < 0x00ffffff) text.append(i18np(", but only first letter of each word", ", but only first %1 letters of each word", info.len)); switch (info.caseChange) { case IdSuggestions::ccToUpper: text.append(i18n(", in upper case")); break; case IdSuggestions::ccToLower: text.append(i18n(", in lower case")); break; case IdSuggestions::ccToCamelCase: text.append(i18n(", in CamelCase")); break; case IdSuggestions::ccNoChange: break; } } else if (token[0] == 'e') { struct IdSuggestionTokenInfo info = evalToken(token.mid(1)); text.append(i18n("Type")); if (info.len < 0x00ffffff) text.append(i18np(", but only first letter of each word", ", but only first %1 letters of each word", info.len)); switch (info.caseChange) { case IdSuggestions::ccToUpper: text.append(i18n(", in upper case")); break; case IdSuggestions::ccToLower: text.append(i18n(", in lower case")); break; case IdSuggestions::ccToCamelCase: text.append(i18n(", in CamelCase")); break; default: break; } } else if (token[0] == 'v') { text.append(i18n("Volume")); } else if (token[0] == 'p') { text.append(i18n("First page number")); } else if (token[0] == '"') text.append(i18n("Text: '%1'", token.mid(1))); else text.append("?"); result.append(text); } return result; } QString IdSuggestions::formatAuthorRange(int minValue, int maxValue, bool lastAuthor) { if (minValue == 0) { if (maxValue == 0) { if (lastAuthor) return i18n("First and last authors only"); else return i18n("First author only"); } else if (maxValue > 0xffff) return i18n("All authors"); else { if (lastAuthor) return i18n("From first author to author %1 and last author", maxValue + 1); else return i18n("From first author to author %1", maxValue + 1); } } else if (minValue == 1) { if (maxValue > 0xffff) return i18n("All but first author"); else { if (lastAuthor) return i18n("From author %1 to author %2 and last author", minValue + 1, maxValue + 1); else return i18n("From author %1 to author %2", minValue + 1, maxValue + 1); } } else { if (maxValue > 0xffff) return i18n("From author %1 to last author", minValue + 1); else if (lastAuthor) return i18n("From author %1 to author %2 and last author", minValue + 1, maxValue + 1); else return i18n("From author %1 to author %2", minValue + 1, maxValue + 1); } } struct IdSuggestions::IdSuggestionTokenInfo IdSuggestions::evalToken(const QString &token) const { int pos = 0; struct IdSuggestionTokenInfo result; result.len = 0x00ffffff; result.startWord = 0; result.endWord = 0x00ffffff; result.lastWord = false; result.caseChange = IdSuggestions::ccNoChange; result.inBetween = QString(); if (token.length() > pos) { int dv = token[pos].digitValue(); if (dv > -1) { result.len = dv; ++pos; } } if (token.length() > pos) { switch (token[pos].unicode()) { case 0x006c: // 'l' result.caseChange = IdSuggestions::ccToLower; ++pos; break; case 0x0075: // 'u' result.caseChange = IdSuggestions::ccToUpper; ++pos; break; case 0x0063: // 'c' result.caseChange = IdSuggestions::ccToCamelCase; ++pos; break; default: result.caseChange = IdSuggestions::ccNoChange; } } int dvStart = -1, dvEnd = 0x00ffffff; if (token.length() > pos + 2 ///< sufficiently many characters to follow && token[pos] == 'w' ///< identifier to start specifying a range of words && (dvStart = token[pos + 1].digitValue()) > -1 ///< first word index correctly parsed && ( token[pos + 2] == QLatin1Char('I') ///< infinitely many words || (dvEnd = token[pos + 2].digitValue()) > -1) ///< last word index finite and correctly parsed ) { result.startWord = dvStart; result.endWord = dvEnd; pos += 3; /// Optionally, the last word (e.g. last author) is explicitly requested if (token.length() > pos && token[pos] == QLatin1Char('L')) { result.lastWord = true; ++pos; } } if (token.length() > pos + 1 && token[pos] == '"') result.inBetween = token.mid(pos + 1); return result; } diff --git a/src/processing/idsuggestions.h b/src/processing/idsuggestions.h index 1bcfbac1..c8f6b91b 100644 --- a/src/processing/idsuggestions.h +++ b/src/processing/idsuggestions.h @@ -1,76 +1,78 @@ /*************************************************************************** * 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_PROCESSING_IDSUGGESTIONS_H #define KBIBTEX_PROCESSING_IDSUGGESTIONS_H -#include "kbibtexproc_export.h" +#include -#include "entry.h" +#ifdef HAVE_KF5 +#include "kbibtexprocessing_export.h" +#endif // HAVE_KF5 /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT IdSuggestions +class KBIBTEXPROCESSING_EXPORT IdSuggestions { public: enum CaseChange {ccNoChange = 0, ccToUpper = 1, ccToLower = 2, ccToCamelCase = 3}; struct IdSuggestionTokenInfo { unsigned int len; int startWord, endWord; bool lastWord; CaseChange caseChange; QString inBetween; }; IdSuggestions(); IdSuggestions(const IdSuggestions &) = delete; IdSuggestions &operator= (const IdSuggestions &other) = delete; ~IdSuggestions(); QString formatId(const Entry &entry, const QString &formatStr) const; QString defaultFormatId(const Entry &entry) const; bool hasDefaultFormat() const; /** * Apply the default formatting string to the entry. * If no default formatting string is set, the entry * will stay untouched and the function return false. * If the formatting string is set, the entry's id * will be changed accordingly and the function returns true. * * @param entry entry where the id has to be set * @return true if the id was set, false otherwise */ bool applyDefaultFormatId(Entry &entry) const; QStringList formatIdList(const Entry &entry) const; QStringList formatStrToHuman(const QString &formatStr) const; static QString formatAuthorRange(int minValue, int maxValue, bool lastAuthor); protected: struct IdSuggestionTokenInfo evalToken(const QString &token) const; private: class IdSuggestionsPrivate; IdSuggestionsPrivate *d; }; #endif // KBIBTEX_PROCESSING_IDSUGGESTIONS_H diff --git a/src/processing/journalabbreviations.h b/src/processing/journalabbreviations.h index 7ad128f0..3a6cc627 100644 --- a/src/processing/journalabbreviations.h +++ b/src/processing/journalabbreviations.h @@ -1,45 +1,47 @@ /*************************************************************************** * 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_PROCESSING_JOURNALABBREVIATIONS_H #define KBIBTEX_PROCESSING_JOURNALABBREVIATIONS_H #include -#include "kbibtexproc_export.h" +#ifdef HAVE_KF5 +#include "kbibtexprocessing_export.h" +#endif // HAVE_KF5 /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT JournalAbbreviations +class KBIBTEXPROCESSING_EXPORT JournalAbbreviations { public: static const JournalAbbreviations &instance(); QString toShortName(const QString &longName) const; QString toLongName(const QString &shortName) const; protected: explicit JournalAbbreviations(); ~JournalAbbreviations(); private: class Private; Private *const d; }; #endif // KBIBTEX_PROCESSING_JOURNALABBREVIATIONS_H diff --git a/src/processing/lyx.cpp b/src/processing/lyx.cpp index d7c18cbd..ce168484 100644 --- a/src/processing/lyx.cpp +++ b/src/processing/lyx.cpp @@ -1,146 +1,146 @@ /*************************************************************************** * 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 "lyx.h" #include #include #include #include #include #include #include #include #include #include #include -#include "preferences.h" +#include class LyX::LyXPrivate { public: QWidget *widget; QAction *action; QStringList references; LyXPrivate(LyX *parent, QWidget *widget) : action(nullptr) { Q_UNUSED(parent) this->widget = widget; } QString locateConfiguredLyXPipe() { QString result; /// First, check if automatic detection is disabled. /// In this case, read the LyX pipe's path from configuration if (!Preferences::instance().lyXUseAutomaticPipeDetection()) result = Preferences::instance().lyXPipePath(); #ifdef QT_LSTAT /// Check if the result so far is empty. This means that /// either automatic detection is enabled or the path in /// the configuration is empty/invalid. Proceed with /// automatic detection in this case. if (result.isEmpty()) result = LyX::guessLyXPipeLocation(); #endif // QT_LSTAT /// Finally, even if automatic detection was preferred by the user, /// still check configuration for a path if automatic detection failed if (result.isEmpty() && Preferences::instance().lyXUseAutomaticPipeDetection()) result = Preferences::instance().lyXPipePath(); /// Return the best found LyX pipe path return result; } }; LyX::LyX(KParts::ReadOnlyPart *part, QWidget *widget) : QObject(part), d(new LyX::LyXPrivate(this, widget)) { d->action = new QAction(QIcon::fromTheme(QStringLiteral("application-x-lyx")), i18n("Send to LyX/Kile"), this); part->actionCollection()->addAction(QStringLiteral("sendtolyx"), d->action); d->action->setEnabled(false); connect(d->action, &QAction::triggered, this, &LyX::sendReferenceToLyX); widget->addAction(d->action); } LyX::~LyX() { delete d; } void LyX::setReferences(const QStringList &references) { d->references = references; d->action->setEnabled(d->widget != nullptr && !d->references.isEmpty()); } void LyX::sendReferenceToLyX() { const QString defaultHintOnLyXProblems = i18n("\n\nCheck that LyX or Kile are running and configured to receive references."); const QString msgBoxTitle = i18n("Send Reference to LyX"); /// LyX pipe name has to determined always fresh in case LyX or Kile exited const QString pipeName = d->locateConfiguredLyXPipe(); if (pipeName.isEmpty()) { KMessageBox::error(d->widget, i18n("No 'LyX server pipe' was detected.") + defaultHintOnLyXProblems, msgBoxTitle); return; } if (d->references.isEmpty()) { KMessageBox::error(d->widget, i18n("No references to send to LyX/Kile."), msgBoxTitle); return; } QFile pipe(pipeName); if (!QFileInfo::exists(pipeName) || !pipe.open(QFile::WriteOnly)) { KMessageBox::error(d->widget, i18n("Could not open LyX server pipe '%1'.", pipeName) + defaultHintOnLyXProblems, msgBoxTitle); return; } QTextStream ts(&pipe); QString msg = QString(QStringLiteral("LYXCMD:kbibtex:citation-insert:%1")).arg(d->references.join(QStringLiteral(","))); ts << msg << endl; ts.flush(); pipe.close(); } #ifdef QT_LSTAT QString LyX::guessLyXPipeLocation() { QT_STATBUF statBuffer; const QStringList nameFilter {QStringLiteral("*lyxpipe*in*")}; const QVector directoriesToScan {QDir::home(), QDir(QDir::homePath() + QStringLiteral("/.lyx")), QDir::temp()}; for (const QDir &directory : directoriesToScan) { const QStringList files = directory.entryList(nameFilter, QDir::Hidden | QDir::System | QDir::Writable, QDir::Unsorted); for (const QString &filename : files) { const QString canonicalFilename = QFileInfo(directory.absolutePath() + QDir::separator() + filename).canonicalFilePath(); if (QT_LSTAT(canonicalFilename.toLatin1(), &statBuffer) == 0 && S_ISFIFO(statBuffer.st_mode)) return canonicalFilename; } } return QString(); } #endif // QT_LSTAT diff --git a/src/processing/lyx.h b/src/processing/lyx.h index d22435b1..9c3949de 100644 --- a/src/processing/lyx.h +++ b/src/processing/lyx.h @@ -1,57 +1,59 @@ /*************************************************************************** * 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_PROCESSING_LYX_H #define KBIBTEX_PROCESSING_LYX_H -#include "kbibtexproc_export.h" - #include #include +#ifdef HAVE_KF5 +#include "kbibtexprocessing_export.h" +#endif // HAVE_KF5 + namespace KParts { class ReadOnlyPart; } class QWidget; /** * @author Thomas Fischer */ -class KBIBTEXPROC_EXPORT LyX: public QObject +class KBIBTEXPROCESSING_EXPORT LyX : public QObject { Q_OBJECT public: LyX(KParts::ReadOnlyPart *part, QWidget *widget); ~LyX() override; void setReferences(const QStringList &references); #ifdef QT_LSTAT static QString guessLyXPipeLocation(); #endif // QT_LSTAT private slots: void sendReferenceToLyX(); private: class LyXPrivate; LyXPrivate *d; }; #endif // KBIBTEX_PROCESSING_LYX_H diff --git a/src/program/CMakeLists.txt b/src/program/CMakeLists.txt index 9d5abf80..b3a94ba0 100644 --- a/src/program/CMakeLists.txt +++ b/src/program/CMakeLists.txt @@ -1,148 +1,146 @@ # 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/processing - ${CMAKE_BINARY_DIR}/src/processing ${CMAKE_SOURCE_DIR}/src/networking ${CMAKE_BINARY_DIR}/src/networking ${CMAKE_SOURCE_DIR}/src/networking/onlinesearch ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_BINARY_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/gui/file ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/program/docklets ) 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 ) # 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_CURRENT_BINARY_DIR} -P ${CMAKE_SOURCE_DIR}/src/getgit.cmake ) 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_executable( kbibtex ${kbibtex_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) 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 kbibtexio kbibtexgui kbibtexnetworking ) if(Qt5WebEngineWidgets_FOUND) target_link_libraries( kbibtex Qt5::WebEngineWidgets ) else(Qt5WebEngineWidgets_FOUND) if(Qt5WebKitWidgets_FOUND) target_link_libraries( kbibtex Qt5::WebKitWidgets ) endif(Qt5WebKitWidgets_FOUND) endif(Qt5WebEngineWidgets_FOUND) install( TARGETS kbibtex ${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 DESTINATION ${KDE_INSTALL_ICONDIR} ) diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 74d51667..2b4a2325 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,181 +1,180 @@ # KBibTeX test program project( test ) include( AddFileDependencies ) include( ECMMarkAsTest ) configure_file(test-config.h.in test-config.h @ONLY) include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/gui ${CMAKE_SOURCE_DIR}/src/gui/config ${CMAKE_SOURCE_DIR}/src/gui/bibtex ${CMAKE_SOURCE_DIR}/src/gui/element ${CMAKE_SOURCE_DIR}/src/gui/widgets ${CMAKE_SOURCE_DIR}/src/networking ${CMAKE_SOURCE_DIR}/src/networking/onlinesearch - ${CMAKE_SOURCE_DIR}/src/processing ) set( kbibtextest_SRCS main.cpp kbibtextest.cpp logging_test.cpp ) set( kbibtexfilestest_SRCS kbibtexfilestest.cpp kbibtexfilestest-rawdata.h ) set( kbibtexnetworkingtest_SRCS kbibtexnetworkingtest.cpp ) set( kbibtexiotest_SRCS kbibtexiotest.cpp ) set( kbibtexdatatest_SRCS kbibtexdatatest.cpp ) if(UNITY_BUILD AND NOT WIN32) # FIXME: Unity build of programs breaks on Windows enable_unity_build(kbibtextest kbibtextest_SRCS) enable_unity_build(kbibtexfilestest kbibtexfilestest_SRCS) enable_unity_build(kbibtexnetworkingtest kbibtexnetworkingtest_SRCS) enable_unity_build(kbibtexiotest kbibtexiotest_SRCS) enable_unity_build(kbibtexdatatest kbibtexdatatest_SRCS) endif(UNITY_BUILD AND NOT WIN32) # 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_CURRENT_BINARY_DIR} -P ${CMAKE_SOURCE_DIR}/src/getgit.cmake ) 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_executable( kbibtextest ${kbibtextest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexfilestest ${kbibtexfilestest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexnetworkingtest ${kbibtexnetworkingtest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexiotest ${kbibtexiotest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) add_executable( kbibtexdatatest ${kbibtexdatatest_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/kbibtex-git-info.h ) target_link_libraries( kbibtextest Qt5::Core KF5::KIOCore kbibtexconfig kbibtexdata kbibtexio - kbibtexproc + kbibtexprocessing kbibtexgui kbibtexnetworking ) target_link_libraries( kbibtexfilestest Qt5::Test kbibtexdata kbibtexio ) target_link_libraries( kbibtexnetworkingtest Qt5::Test kbibtexnetworking ) target_link_libraries( kbibtexiotest Qt5::Test kbibtexio ) target_link_libraries( kbibtexdatatest Qt5::Test kbibtexdata ) ecm_mark_as_test( kbibtexfilestest kbibtexnetworkingtest kbibtexiotest kbibtexdatatest ) add_test( NAME kbibtexfilestest COMMAND kbibtexfilestest ) add_test( NAME kbibtexnetworkingtest COMMAND kbibtexnetworkingtest ) add_test( NAME kbibtexiotest COMMAND kbibtexiotest ) add_test( NAME kbibtexdatatest COMMAND kbibtexdatatest )