diff --git a/CMakeLists.txt b/CMakeLists.txt index 74fedb7f..66e3be95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,197 +1,199 @@ cmake_minimum_required(VERSION 3.7.2) if(POLICY CMP0048) cmake_policy(SET CMP0048 NEW) endif(POLICY CMP0048) project( kbibtex VERSION 0.9.50 LANGUAGES CXX ) set(CMAKE_CXX_STANDARD 11) set(QT_MIN_VERSION 5.9.0) # Somewhat arbitrary chosen version number ... set(KF5_MIN_VERSION 5.51) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) add_definitions( -DTRANSLATION_DOMAIN="kbibtex" ) set( CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_MODULE_PATH} ) set(KDE_INSTALL_DIRS_NO_DEPRECATED TRUE) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMGenerateHeaders) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddAppIcon) include(GenerateExportHeader) ecm_setup_version( PROJECT VARIABLE_PREFIX KBIBTEX SOVERSION ${KBIBTEX_VERSION_MAJOR} VERSION_HEADER "${CMAKE_BINARY_DIR}/kbibtex-version.h" PACKAGE_VERSION_FILE "${CMAKE_BINARY_DIR}/KBibTeXConfigVersion.cmake" ) install( FILES ${CMAKE_BINARY_DIR}/kbibtex-version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KBibTeX # FIXME is "Devel" standard? COMPONENT Devel ) if("${KBIBTEX_VERSION_PATCH}" STREQUAL "") # Patch level is not set for version numbers like "0.9", # so set the patch level manually to 0 set(KBIBTEX_VERSION_PATCH 0) endif() if((${KBIBTEX_VERSION_PATCH} GREATER 50) OR (${KBIBTEX_VERSION_PATCH} EQUAL 50)) # If the version number indicates a pre-release version such as # '0.7.90', i.e. a beta version for the major release 0.8, # increment release version from 0.7 to 0.8 math(EXPR KBIBTEX_RELEASE_VERSION_MINOR "${KBIBTEX_VERSION_MINOR} + 1") set( KBIBTEX_RELEASE_VERSION ${KBIBTEX_VERSION_MAJOR}.${KBIBTEX_RELEASE_VERSION_MINOR} ) else() set( KBIBTEX_RELEASE_VERSION ${KBIBTEX_VERSION_MAJOR}.${KBIBTEX_VERSION_MINOR} ) endif() option( UNITY_BUILD "Compile multiple C++ files in one big, merged file (\"Unity build\")\nSee also http://t-fischer.dreamwidth.org/3054.html" ) if(UNITY_BUILD) message(STATUS "Unity build enabled") else(UNITY_BUILD) message(STATUS "Unity build disabled (default), use option UNITY_BUILD to enable it") endif(UNITY_BUILD) find_package( Qt5 ${QT_MIN_VERSION} CONFIG COMPONENTS Core Widgets Network XmlPatterns Concurrent NetworkAuth OPTIONAL_COMPONENTS WebEngineWidgets WebKitWidgets Test ) add_definitions(-DHAVE_QTWIDGETS) find_package( KF5 ${KF5_MIN_VERSION} MODULE - REQUIRED + COMPONENTS I18n XmlGui KIO IconThemes Parts CoreAddons Service Wallet Crash DocTools TextEditor + OPTIONAL_COMPONENTS + Kirigami2 ) add_definitions(-DHAVE_KF5) find_package( Poppler MODULE REQUIRED Qt5 ) find_package( ICU MODULE OPTIONAL_COMPONENTS uc i18n ) if(ICU_FOUND) add_definitions(-DHAVE_ICU) endif() option( BUILD_TESTING "Build automated and interactive tests" OFF ) if (MSVC) MESSAGE( STATUS "Disabling building tests when using Microsoft Visual Studio C++ compiler" ) # Note to any developer: Try to enable building tests and see which issues you may encounter. # Examples may include: (1) char* texts which exceed the size limit supported by MSVC which # is about 2^16 bytes and (2) characters in strings written in \uXXXX notation not supported # in 1252 encoding as assumed by MSVC for C++ source files. set(BUILD_TESTING OFF) endif() if(NOT BUILD_TESTING AND Qt5Test_FOUND) message(STATUS "Testing is disabled, but can be enabled as the Qt5::Test library is available" ) endif() if(BUILD_TESTING AND NOT Qt5Test_FOUND) message(STATUS "Disabling building tests as Qt5::Test library is not available" ) set(BUILD_TESTING OFF) endif() if(BUILD_TESTING) if (WRITE_RAWDATAFILE) add_definitions(-DWRITE_RAWDATAFILE) endif(WRITE_RAWDATAFILE) set( TESTSET_DIRECTORY "" CACHE PATH "Directory where the local checkout of Git repository 'kbibtex-testset' is located" ) endif() add_subdirectory( config ) add_subdirectory( src ) add_subdirectory( xslt ) add_subdirectory( mime ) if(KF5DocTools_FOUND) add_subdirectory(doc) endif() # macro_optional_add_subdirectory( # po # ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/mobile/sailfishos/qml/pages/SearchForm.qml b/mobile/sailfishos/qml/pages/SearchForm.qml index 7d034fed..47f7a708 100644 --- a/mobile/sailfishos/qml/pages/SearchForm.qml +++ b/mobile/sailfishos/qml/pages/SearchForm.qml @@ -1,119 +1,119 @@ /*************************************************************************** * Copyright (C) 2016-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 . * ***************************************************************************/ import QtQuick 2.0 import Sailfish.Silica 1.0 Page { id: searchForm allowedOrientations: Orientation.All SilicaFlickable { - id: resultList + id: searchFormList anchors.fill: parent contentHeight: content.height function startSearching() { bibliographyModel.clear() bibliographyModel.startSearch(inputFreeText.text, inputTitle.text, inputAuthor.text) pop() } Column { id: content width: parent.width PageHeader { //% "Search Parameters" title: qsTrId("searchparameters-title") } ValueButton { //% "Search Engines" label: qsTrId("label-search-engines") value: searchEngineList.searchEngineCount === 0 //% "None selected" ? qsTrId("selected-count-none") //% "%1 selected" : qsTrId("selected-count-numarg").arg(searchEngineList.searchEngineCount) description: searchEngineList.searchEngineCount === 0 //% "At least one search engine must be selected." ? qsTrId("label-selected-atleastone") : searchEngineList.humanReadableSearchEngines() onClicked: { pageStack.push("SearchEngineListView.qml") } } TextField { id: inputFreeText //% "Free Text" label: qsTrId("label-free-text") placeholderText: label width: parent.width focus: true enabled: searchEngineList.searchEngineCount > 0 EnterKey.enabled: text.length > 0 EnterKey.iconSource: "image://theme/icon-m-search" - EnterKey.onClicked: resultList.startSearching() + EnterKey.onClicked: searchFormList.startSearching() } TextField { id: inputTitle //% "Title" label: qsTrId("label-title") placeholderText: label width: parent.width enabled: searchEngineList.searchEngineCount > 0 EnterKey.enabled: text.length > 0 EnterKey.iconSource: "image://theme/icon-m-search" - EnterKey.onClicked: resultList.startSearching() + EnterKey.onClicked: searchFormList.startSearching() } TextField { id: inputAuthor //% "Author" label: qsTrId("label-author") placeholderText: label width: parent.width enabled: searchEngineList.searchEngineCount > 0 EnterKey.enabled: text.length > 0 EnterKey.iconSource: "image://theme/icon-m-search" - EnterKey.onClicked: resultList.startSearching() + EnterKey.onClicked: searchFormList.startSearching() } } PullDownMenu { MenuItem { id: menuItemStartSearching //% "Start Searching" text: qsTrId("pulldownmenu-start-searching") enabled: (inputFreeText.text.length > 0 || inputTitle.text.length > 0 || inputAuthor.text.length > 0) && searchEngineList.searchEngineCount > 0 - onClicked: resultList.startSearching() + onClicked: searchFormList.startSearching() } visible: menuItemStartSearching.enabled } } } diff --git a/qml/kirigami2/KBibTeX.qml b/qml/kirigami2/KBibTeX.qml new file mode 100644 index 00000000..414d26cd --- /dev/null +++ b/qml/kirigami2/KBibTeX.qml @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 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 . * + ***************************************************************************/ + +import QtQuick 2.7 +import org.kde.kirigami 2.6 as Kirigami +import kirigami2.kbibtex 1.0 +import "pages" + +Kirigami.ApplicationWindow { + id: root + + SortedBibliographyModel { + id: bibliographyModel + } + + globalDrawer: Kirigami.GlobalDrawer { + title: "KBibTeX" + + actions: [ + Kirigami.Action { + text: "New Search" + onTriggered: { + pageStack.clear() + pageStack.push(searchForm) + } + }, + Kirigami.Action { + text: "About" + onTriggered: { + pageStack.clear() + pageStack.push(aboutPage) + } + } + ] + } + + Component { + id: resultPage + BibliographyListView { + } + } + + Component { + id: searchForm + SearchForm { + } + } + + Kirigami.AboutPage { + id: aboutPage + aboutData: { + "displayName" : "KBibTeX", + "shortDescription" : "A mobile version of KBibTeX", + "homepage": "https://userbase.kde.org/KBibTeX", + "version": "0.1", + "licenses": [{ + "name": "GPL v2 or later", + "text": "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.", + "spdx": "GPL-2.0-or-later" + }], + "copyrightStatement": "© 2019 Thomas Fischer and others", + "authors": [{ + "name": "Thomas Fischer", + "emailAddress": "fischer@unix-ag.uni-kl.de" + }] + } + } +} diff --git a/qml/kirigami2/pages/BibliographyListView.qml b/qml/kirigami2/pages/BibliographyListView.qml new file mode 100644 index 00000000..347a4147 --- /dev/null +++ b/qml/kirigami2/pages/BibliographyListView.qml @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (C) 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 . * + ***************************************************************************/ + +import QtQuick 2.7 +import org.kde.kirigami 2.4 as Kirigami + +Kirigami.ScrollablePage { + id: resultPage + + implicitWidth: Kirigami.Units.gridUnit * 20 + background: Rectangle { + color: Kirigami.Theme.backgroundColor + } + + ListView { + id: bibliographyListView + model: bibliographyModel + focus: true + + delegate: Kirigami.BasicListItem { + id: delegate + width: parent.width + + label: title + } + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c780f4cd..358b115e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,57 +1,60 @@ # "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(parts) +if(KF5Kirigami2_FOUND) + add_subdirectory(kirigami2) +endif() if(BUILD_TESTING) add_subdirectory(test) endif() diff --git a/src/kirigami2/bibliographymodel.cpp b/src/kirigami2/bibliographymodel.cpp new file mode 100644 index 00000000..3af1ee53 --- /dev/null +++ b/src/kirigami2/bibliographymodel.cpp @@ -0,0 +1,418 @@ +/*************************************************************************** + * Copyright (C) 2016-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 "bibliographymodel.h" + +#include + +#include +#include + +const int SortedBibliographyModel::SortAuthorNewestTitle = 0; +const int SortedBibliographyModel::SortAuthorOldestTitle = 1; +const int SortedBibliographyModel::SortNewestAuthorTitle = 2; +const int SortedBibliographyModel::SortOldestAuthorTitle = 3; + +SortedBibliographyModel::SortedBibliographyModel() + : QSortFilterProxyModel(), m_sortOrder(SortedBibliographyModel::SortAuthorNewestTitle), model(new BibliographyModel()) { + setSourceModel(model); + setDynamicSortFilter(true); + sort(0); + connect(model, &BibliographyModel::busyChanged, this, &SortedBibliographyModel::busyChanged); + connect(model, &BibliographyModel::progressChanged, this, &SortedBibliographyModel::progressChanged); +} + +SortedBibliographyModel::~SortedBibliographyModel() { + delete model; +} + +QHash SortedBibliographyModel::roleNames() const { + QHash roles; + roles[BibTeXIdRole] = "bibtexid"; + roles[FoundViaRole] = "foundVia"; + roles[TitleRole] = "title"; + roles[AuthorRole] = "author"; + roles[AuthorShortRole] = "authorShort"; + roles[YearRole] = "year"; + roles[WherePublishedRole] = "wherePublished"; + roles[UrlRole] = "url"; + roles[DoiRole] = "doi"; + return roles; +} + +bool SortedBibliographyModel::isBusy() const { + if (model != nullptr) + return model->isBusy(); + else + return false; +} + +int SortedBibliographyModel::progress() const { + if (model != nullptr) + return model->progress(); + else + return -1; +} + +int SortedBibliographyModel::sortOrder() const { + return m_sortOrder; +} + +void SortedBibliographyModel::setSortOrder(int _sortOrder) { + if (_sortOrder != m_sortOrder) { + m_sortOrder = _sortOrder; + invalidate(); + emit sortOrderChanged(m_sortOrder); + } +} + +void SortedBibliographyModel::startSearch(const QString &freeText, const QString &title, const QString &author) { + if (model != nullptr) + model->startSearch(freeText, title, author); +} + +void SortedBibliographyModel::clear() { + if (model != nullptr) + model->clear(); +} + +bool SortedBibliographyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { + if (model == nullptr) + return source_left.row() < source_right.row(); + + const QSharedPointer entryLeft = model->entry(source_left.row()); + const QSharedPointer entryRight = model->entry(source_right.row()); + if (entryLeft.isNull() || entryRight.isNull()) + return source_left.row() < source_right.row(); + + SortingTriState sortingTriState = Undecided; + switch (m_sortOrder) { + case SortAuthorNewestTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareYears(entryLeft, entryRight, MostRecentFirst, compareAuthors(entryLeft, entryRight))); + break; + case SortAuthorOldestTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareYears(entryLeft, entryRight, LeastRecentFirst, compareAuthors(entryLeft, entryRight))); + break; + case SortNewestAuthorTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareAuthors(entryLeft, entryRight, compareYears(entryLeft, entryRight, MostRecentFirst))); + break; + case SortOldestAuthorTitle: + sortingTriState = compareTitles(entryLeft, entryRight, compareAuthors(entryLeft, entryRight, compareYears(entryLeft, entryRight, LeastRecentFirst))); + break; + default: + sortingTriState = Undecided; + } + + switch (sortingTriState) { + case True: + return true; + case False: + return false; + default: + return (source_left.row() < source_right.row()); + } +} + +SortedBibliographyModel::SortingTriState SortedBibliographyModel::compareAuthors(const QSharedPointer entryLeft, const QSharedPointer entryRight, SortedBibliographyModel::SortingTriState previousDecision) const { + if (previousDecision != Undecided) return previousDecision; + + const Value authorsLeft = entryLeft->operator[](Entry::ftAuthor); + const Value authorsRight = entryRight->operator[](Entry::ftAuthor); + int p = 0; + while (p < authorsLeft.count() && p < authorsRight.count()) { + const QSharedPointer personLeft = authorsLeft.at(p).dynamicCast(); + const QSharedPointer personRight = authorsRight.at(p).dynamicCast(); + if (personLeft.isNull() || personRight.isNull()) + return Undecided; + + const int cmpLast = removeNobiliaryParticle(personLeft->lastName()).localeAwareCompare(removeNobiliaryParticle(personRight->lastName())); + if (cmpLast < 0) return True; + else if (cmpLast > 0) return False; + const int cmpFirst = personLeft->firstName().left(1).localeAwareCompare(personRight->firstName().left(1)); + if (cmpFirst < 0) return True; + else if (cmpFirst > 0) return False; + + ++p; + } + if (authorsLeft.count() < authorsRight.count()) return True; + else if (authorsLeft.count() > authorsRight.count()) return False; + + return Undecided; +} + +SortedBibliographyModel::SortingTriState SortedBibliographyModel::compareYears(const QSharedPointer entryLeft, const QSharedPointer entryRight, AgeSorting ageSorting, SortedBibliographyModel::SortingTriState previousDecision) const { + if (previousDecision != Undecided) return previousDecision; + + bool yearLeftOk = false, yearRightOk = false; + const int yearLeft = BibliographyModel::valueToText(entryLeft->operator[](Entry::ftYear)).toInt(&yearLeftOk); + const int yearRight = BibliographyModel::valueToText(entryRight->operator[](Entry::ftYear)).toInt(&yearRightOk); + if (yearLeftOk && yearRightOk && yearLeft > 1000 && yearRight > 1000 && yearLeft < 3000 && yearRight < 3000) { + if (yearLeft < yearRight) return ageSorting == LeastRecentFirst ? True : False; + else if (yearLeft > yearRight) return ageSorting == LeastRecentFirst ? False : True; + } + + return Undecided; +} + +SortedBibliographyModel::SortingTriState SortedBibliographyModel::compareTitles(const QSharedPointer entryLeft, const QSharedPointer entryRight, SortedBibliographyModel::SortingTriState previousDecision) const { + if (previousDecision != Undecided) return previousDecision; + + const QString titleLeft = BibliographyModel::valueToText(entryLeft->operator[](Entry::ftTitle)); + const QString titleRight = BibliographyModel::valueToText(entryRight->operator[](Entry::ftTitle)); + const int titleCmp = titleLeft.localeAwareCompare(titleRight); + if (titleCmp < 0) return True; + else if (titleCmp > 0) return False; + + return Undecided; +} + +QString SortedBibliographyModel::removeNobiliaryParticle(const QString &lastname) const { + static const QStringList nobiliaryParticles {QStringLiteral("af "), QStringLiteral("d'"), QStringLiteral("de "), QStringLiteral("di "), QStringLiteral("du "), QStringLiteral("of "), QStringLiteral("van "), QStringLiteral("von "), QStringLiteral("zu ")}; + for (QStringList::ConstIterator it = nobiliaryParticles.constBegin(); it != nobiliaryParticles.constEnd(); ++it) + if (lastname.startsWith(*it)) + return lastname.mid(it->length()); + return lastname; +} + +BibliographyModel::BibliographyModel() { + m_file = new File(); + m_runningSearches = 0; + + m_searchEngineList = new SearchEngineList(); + connect(m_searchEngineList, &SearchEngineList::foundEntry, this, &BibliographyModel::newEntry); + connect(m_searchEngineList, &SearchEngineList::busyChanged, this, &BibliographyModel::busyChanged); + connect(m_searchEngineList, &SearchEngineList::progressChanged, this, &BibliographyModel::progressChanged); +} + +BibliographyModel::~BibliographyModel() { + delete m_file; + delete m_searchEngineList; +} + +int BibliographyModel::rowCount(const QModelIndex &parent) const { + if (parent == QModelIndex()) + return m_file->count(); + else + return 0; +} + +QVariant BibliographyModel::data(const QModelIndex &index, int role) const { + if (index.row() < 0 || index.row() >= m_file->count() || index.column() != 0) + return QVariant(); + + const QSharedPointer curEntry = entry(index.row()); + + if (!curEntry.isNull()) { + QString fieldName; + switch (role) { + case Qt::DisplayRole: /// fall-through on purpose + case TitleRole: fieldName = Entry::ftTitle; break; + case AuthorRole: fieldName = Entry::ftAuthor; break; + case YearRole: fieldName = Entry::ftYear; break; + } + if (!fieldName.isEmpty()) + return valueToText(curEntry->operator[](fieldName)); + + if (role == BibTeXIdRole) { + return curEntry->id(); + } else if (role == FoundViaRole) { + const QString foundVia = valueToText(curEntry->operator[](QStringLiteral("x-fetchedfrom"))); + if (!foundVia.isEmpty()) return foundVia; + } else if (role == AuthorRole) { + const QString authors = valueToText(curEntry->operator[](Entry::ftAuthor)); + if (!authors.isEmpty()) return authors; + else return valueToText(curEntry->operator[](Entry::ftEditor)); + } else if (role == WherePublishedRole) { + const QString journal = valueToText(curEntry->operator[](Entry::ftJournal)); + if (!journal.isEmpty()) { + const QString volume = valueToText(curEntry->operator[](Entry::ftVolume)); + const QString issue = valueToText(curEntry->operator[](Entry::ftNumber)); + if (volume.isEmpty()) + return journal; + else if (issue.isEmpty()) /// but 'volume' is not empty + return journal + QStringLiteral(" ") + volume; + else /// both 'volume' and 'issue' are not empty + return journal + QStringLiteral(" ") + volume + QStringLiteral(" (") + issue + QStringLiteral(")"); + } + const QString bookTitle = valueToText(curEntry->operator[](Entry::ftBookTitle)); + if (!bookTitle.isEmpty()) return bookTitle; + const bool isPhdThesis = curEntry->type() == Entry::etPhDThesis; + if (isPhdThesis) { + const QString school = valueToText(curEntry->operator[](Entry::ftSchool)); + if (school.isEmpty()) { + //% "Doctoral dissertation" + return i18n("wherepublished-doctoral-dissertation"); + } else { + //% "Doctoral dissertation (%1)" + return i18n("wherepublished-doctoral-dissertation-arg").arg(school); + } + } + const QString school = valueToText(curEntry->operator[](Entry::ftSchool)); + if (!school.isEmpty()) return school; + const QString publisher = valueToText(curEntry->operator[](Entry::ftPublisher)); + if (!publisher.isEmpty()) return publisher; + return QStringLiteral(""); + } else if (role == AuthorShortRole) { + const QStringList authors = valueToList(curEntry->operator[](Entry::ftAuthor)); + switch (authors.size()) { + case 0: return QString(); ///< empty list of authors + case 1: return authors.first(); ///< single author + case 2: + //% "%1 and %2" + return i18n("shortauthors-two-author-args").arg(authors.first()).arg(authors[1]); ///< two authors + default: + //% "%1 and %2 more" + return i18n("shortauthors-one-author-arg-and-n-others-arg").arg(authors.first()).arg(QString::number(authors.size() - 1)); ///< three or more authors + } + } else if (role == UrlRole) { + const QStringList doiList = valueToList(curEntry->operator[](Entry::ftDOI)); + if (!doiList.isEmpty()) return QStringLiteral("http://dx.doi.org/") + doiList.first(); + const QStringList urlList = valueToList(curEntry->operator[](Entry::ftUrl)); + if (!urlList.isEmpty()) return urlList.first(); + const QStringList bibUrlList = valueToList(curEntry->operator[](QStringLiteral("biburl"))); + if (!bibUrlList.isEmpty()) return bibUrlList.first(); + return QString(); + } else if (role == DoiRole) { + const QStringList doiList = valueToList(curEntry->operator[](Entry::ftDOI)); + if (!doiList.isEmpty()) return doiList.first(); + return QString(); + } + } + + return QVariant(); +} + +const QSharedPointer BibliographyModel::entry(int row) const { + const QSharedPointer element = m_file->at(row); + const QSharedPointer result = element.dynamicCast(); + return result; +} + +void BibliographyModel::startSearch(const QString &freeText, const QString &title, const QString &author) { + QMap query; + query[OnlineSearchAbstract::queryKeyFreeText] = freeText; + query[OnlineSearchAbstract::queryKeyTitle] = title; + query[OnlineSearchAbstract::queryKeyAuthor] = author; + + m_searchEngineList->resetProgress(); + + m_runningSearches = 0; + for (int i = 0; i < m_searchEngineList->size(); ++i) { + OnlineSearchAbstract *osa = m_searchEngineList->at(i); + // TODO make it configurable which search engines to start + osa->startSearch(query, 10 /** TODO make number configurable */); + ++m_runningSearches; + } +} + +void BibliographyModel::clear() { + beginResetModel(); + m_file->clear(); + endResetModel(); +} + +bool BibliographyModel::isBusy() const { + for (QVector::ConstIterator it = m_searchEngineList->constBegin(); it != m_searchEngineList->constEnd(); ++it) { + if ((*it)->busy()) return true; + } + return false; +} + +int BibliographyModel::progress() const { + return m_searchEngineList->progress(); +} + +void BibliographyModel::searchFinished() { + --m_runningSearches; +} + +void BibliographyModel::newEntry(QSharedPointer e) { + const int n = m_file->count(); + beginInsertRows(QModelIndex(), n, n); + m_file->insert(n, e); + endInsertRows(); +} + +QString BibliographyModel::valueToText(const Value &value) { + return valueToList(value).join(QStringLiteral(", ")); +} + + +QStringList BibliographyModel::valueToList(const Value &value) { + if (value.isEmpty()) return QStringList(); + + QStringList resultItems; + + const QSharedPointer firstPerson = value.first().dynamicCast(); + if (!firstPerson.isNull()) { + /// First item in value is a Person, assume all other items are Persons as well + for (Value::ConstIterator it = value.constBegin(); it != value.constEnd(); ++it) { + QSharedPointer person = (*it).dynamicCast(); + if (person.isNull()) continue; + const QString name = personToText(person); + if (name.isEmpty()) continue; + resultItems.append(beautifyLaTeX(name)); + } + } else { + for (Value::ConstIterator it = value.constBegin(); it != value.constEnd(); ++it) { + const QString valueItem = valueItemToText(*it); + if (valueItem.isEmpty()) continue; + resultItems.append(beautifyLaTeX(valueItem)); + } + } + + return resultItems; +} + +QString BibliographyModel::personToText(const QSharedPointer &person) { + if (person.isNull()) return QString(); + QString name = person->lastName(); + if (name.isEmpty()) return QString(); + const QString firstName = person->firstName().left(1); + if (!firstName.isEmpty()) + name = name.prepend(QStringLiteral(". ")).prepend(firstName); + return name; +} + +QString BibliographyModel::valueItemToText(const QSharedPointer &valueItem) { + const QSharedPointer plainText = valueItem.dynamicCast<PlainText>(); + if (!plainText.isNull()) + return plainText->text(); + else { + const QSharedPointer<VerbatimText> verbatimText = valueItem.dynamicCast<VerbatimText>(); + if (!verbatimText.isNull()) + return verbatimText->text(); + else { + const QSharedPointer<MacroKey> macroKey = valueItem.dynamicCast<MacroKey>(); + if (!macroKey.isNull()) + return macroKey->text(); + else { + // TODO + return QString(); + } + } + } +} + +QString BibliographyModel::beautifyLaTeX(const QString &input) { + QString output = input; + static const QStringList toBeRemoved {QStringLiteral("\\textsuperscript{"), QStringLiteral("\\}"), QStringLiteral("\\{"), QStringLiteral("}"), QStringLiteral("{")}; + for (QStringList::ConstIterator it = toBeRemoved.constBegin(); it != toBeRemoved.constEnd(); ++it) + output = output.remove(*it); + + return output; +} diff --git a/src/kirigami2/bibliographymodel.h b/src/kirigami2/bibliographymodel.h new file mode 100644 index 00000000..f4b85728 --- /dev/null +++ b/src/kirigami2/bibliographymodel.h @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (C) 2016-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * * + * 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 <https://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifndef BIBLIOGRAPHY_MODEL_H +#define BIBLIOGRAPHY_MODEL_H + +#include <QAbstractListModel> +#include <QSortFilterProxyModel> +#include <QSet> + +#include <File> +#include <Value> +#include "searchenginelist.h" + +class OnlineSearchAbstract; +class BibliographyModel; + +class BibliographyRoles +{ +public: + enum Roles {BibTeXIdRole = Qt::UserRole + 100, FoundViaRole = Qt::UserRole + 102, EntryRole = Qt::UserRole + 999, TitleRole = Qt::UserRole + 1000, AuthorRole = Qt::UserRole + 1010, AuthorShortRole = Qt::UserRole + 1011, YearRole = Qt::UserRole + 1020, WherePublishedRole = Qt::UserRole + 1100, UrlRole = Qt::UserRole + 1110, DoiRole = Qt::UserRole + 1111}; +}; + +class SortedBibliographyModel : public QSortFilterProxyModel, public BibliographyRoles { + Q_OBJECT +public: + static const int SortAuthorNewestTitle, SortAuthorOldestTitle, SortNewestAuthorTitle, SortOldestAuthorTitle; + + SortedBibliographyModel(); + ~SortedBibliographyModel() override; + + virtual QHash<int, QByteArray> roleNames() const override; + + bool isBusy() const; + int progress() const; + int sortOrder() const; + void setSortOrder(int sortOrder); + Q_PROPERTY(int sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) + + Q_INVOKABLE void startSearch(const QString &freeText, const QString &title, const QString &author); + Q_INVOKABLE void clear(); + Q_PROPERTY(bool busy READ isBusy NOTIFY busyChanged) + Q_PROPERTY(int progress READ progress NOTIFY progressChanged) + +signals: + void busyChanged(); + void progressChanged(); + void sortOrderChanged(int sortOrder); + +protected: + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private: + enum SortingTriState {True = -1, Undecided = 0, False = 1}; + enum AgeSorting {MostRecentFirst = 0, LeastRecentFirst = 1}; + + SortingTriState compareAuthors(const QSharedPointer<const Entry> entryLeft, const QSharedPointer<const Entry> entryRight, SortingTriState previousDecision = Undecided) const; + SortingTriState compareYears(const QSharedPointer<const Entry> entryLeft, const QSharedPointer<const Entry> entryRight, AgeSorting ageSorting, SortingTriState previousDecision = Undecided) const; + SortingTriState compareTitles(const QSharedPointer<const Entry> entryLeft, const QSharedPointer<const Entry> entryRight, SortingTriState previousDecision = Undecided) const; + + int m_sortOrder; + BibliographyModel *model; + + QString removeNobiliaryParticle(const QString &lastname) const; +}; + +class BibliographyModel : public QAbstractListModel, public BibliographyRoles { + Q_OBJECT +public: + explicit BibliographyModel(); + ~BibliographyModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + const QSharedPointer<const Entry> entry(int row) const; + static QString valueToText(const Value &value); + + bool isBusy() const; + int progress() const; + + void startSearch(const QString &freeText, const QString &title, const QString &author); + void clear(); + +signals: + void busyChanged(); + void progressChanged(); + +private slots: + void newEntry(QSharedPointer<Entry>); + void searchFinished(); + +private: + File *m_file; + int m_runningSearches; + SearchEngineList *m_searchEngineList; + + static QStringList valueToList(const Value &value); + static QString personToText(const QSharedPointer<const Person> &person); + static QString valueItemToText(const QSharedPointer<ValueItem> &valueItem); + static QString beautifyLaTeX(const QString &input); +}; + +#endif // BIBLIOGRAPHY_MODEL_H diff --git a/src/kirigami2/main.cpp b/src/kirigami2/main.cpp new file mode 100644 index 00000000..75e45f90 --- /dev/null +++ b/src/kirigami2/main.cpp @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * * + * 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 <https://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include <QApplication> +#include <QQmlApplicationEngine> +#include <QtQml> + +#include "bibliographymodel.h" + +Q_DECL_EXPORT int main(int argc, char *argv[]) +{ + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + + QQmlApplicationEngine engine; + + qmlRegisterType<SortedBibliographyModel>("kirigami2.kbibtex", 1, 0, "SortedBibliographyModel"); + + engine.load(QUrl(QStringLiteral("qrc:///KBibTeX.qml"))); + + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +} diff --git a/src/kirigami2/resources.qrc b/src/kirigami2/resources.qrc new file mode 100644 index 00000000..5b40965c --- /dev/null +++ b/src/kirigami2/resources.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file alias="KBibTeX.qml">../../qml/kirigami2/KBibTeX.qml</file> + <file alias="./pages/BibliographyListView.qml">../../qml/kirigami2/pages/BibliographyListView.qml</file> + <file alias="./pages/SearchForm.qml">../../qml/kirigami2/pages/SearchForm.qml</file> + </qresource> +</RCC> diff --git a/src/kirigami2/searchenginelist.cpp b/src/kirigami2/searchenginelist.cpp new file mode 100644 index 00000000..d790c6e4 --- /dev/null +++ b/src/kirigami2/searchenginelist.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** + * Copyright (C) 2017-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * * + * 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 <https://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "searchenginelist.h" + +#include <KLocalizedString> + +#include "onlinesearch/onlinesearchabstract.h" +#include "onlinesearch/onlinesearchacmportal.h" +#include "onlinesearch/onlinesearcharxiv.h" +#include "onlinesearch/onlinesearchbibsonomy.h" +#include "onlinesearch/onlinesearchgooglescholar.h" +#include "onlinesearch/onlinesearchieeexplore.h" +#include "onlinesearch/onlinesearchingentaconnect.h" +#include "onlinesearch/onlinesearchjstor.h" +#include "onlinesearch/onlinesearchpubmed.h" +#include "onlinesearch/onlinesearchsciencedirect.h" +#include "onlinesearch/onlinesearchspringerlink.h" +#include "bibliographymodel.h" + +SearchEngineList::SearchEngineList() +{ + OnlineSearchAbstract *osa = new OnlineSearchAcmPortal(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchArXiv(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchBibsonomy(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchGoogleScholar(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchIEEEXplore(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchIngentaConnect(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchJStor(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchScienceDirect(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchPubMed(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + osa = new OnlineSearchSpringerLink(this); + append(osa); + connect(osa, &OnlineSearchAbstract::foundEntry, this, &SearchEngineList::foundEntry); + connect(osa, &OnlineSearchAbstract::busyChanged, this, &SearchEngineList::busyChanged); + connect(osa, &OnlineSearchAbstract::progress, this, &SearchEngineList::collectingProgress); + + connect(this, &SearchEngineList::dataChanged, [this](const QModelIndex &, const QModelIndex &, const QVector<int> &roles = QVector<int>()) { + if (roles.contains(EngineEnabledRole)) + emit searchEngineCountChanged(); + }); +} + +SearchEngineList::SearchEngineList(const SearchEngineList &other) + : QAbstractListModel(), QVector<OnlineSearchAbstract *>(other) +{ + /// Nothing to do here, QVector constructor does the heavy lifting +} + +SearchEngineList *SearchEngineList::operator =(const SearchEngineList *other) { + /// Not much to do here, QVector constructor does the heavy lifting + QVector<OnlineSearchAbstract *>::operator =(*other); + return this; +} + +int SearchEngineList::rowCount(const QModelIndex &parent) const { + return parent == QModelIndex() ? size() : 0; +} + +QVariant SearchEngineList::data(const QModelIndex &index, int role) const { + if (index.row() < 0 || index.row() >= size() || index.parent() != QModelIndex() || index.column() != 0) + return QVariant(); + const int row = index.row(); + + switch (role) { + case Qt::DisplayRole: + return at(row)->label(); + case EngineEnabledRole: { + // TODO make it configurable which search engines to start + return true; + } + default: + return QVariant(); + } +} + +bool SearchEngineList::setData(const QModelIndex &index, const QVariant &value, int role) { + if (index.row() < 0 || index.row() >= size() || index.parent() != QModelIndex() || index.column() != 0 || role != EngineEnabledRole) + return false; + + // TODO make it configurable which search engines to start + Q_UNUSED(value) + + return true; +} + +QString SearchEngineList::humanReadableSearchEngines() const { + if (empty()) return QString(); + + QStringList enabledSearchEnginesLabels; + for (int i = 0; i < size(); ++i) + // TODO make it configurable which search engines to start/enable + enabledSearchEnginesLabels.append(at(i)->label()); + + switch (enabledSearchEnginesLabels.size()) { + case 0: return QString(); ///< empty selection + case 1: return enabledSearchEnginesLabels.first(); ///< just one search engine selected + case 2: + //: Two search engines selected + //% "%1 and %2" + return i18n("human-readable-two-search-engines").arg(enabledSearchEnginesLabels.at(0), enabledSearchEnginesLabels.at(1)); + case 3: + //: Three search engines selected + //% "%1, %2, and %3" + return i18n("human-readable-three-search-engines").arg(enabledSearchEnginesLabels.at(0), enabledSearchEnginesLabels.at(1), enabledSearchEnginesLabels.at(2)); + case 4: + //: Four search engines selected + //% "%1, %2, %3, and %4" + return i18n("human-readable-four-search-engines").arg(enabledSearchEnginesLabels.at(0), enabledSearchEnginesLabels.at(1), enabledSearchEnginesLabels.at(2), enabledSearchEnginesLabels.at(3)); + case 5: + //: Five search engines selected + //% "%1, %2, %3, %4, and %5" + return i18n("human-readable-five-search-engines").arg(enabledSearchEnginesLabels.at(0), enabledSearchEnginesLabels.at(1), enabledSearchEnginesLabels.at(2), enabledSearchEnginesLabels.at(3), enabledSearchEnginesLabels.at(4)); + case 6: + //: Six search engines selected + //% "%1, %2, %3, %4, %5, and %6" + return i18n("human-readable-six-search-engines").arg(enabledSearchEnginesLabels.at(0), enabledSearchEnginesLabels.at(1), enabledSearchEnginesLabels.at(2), enabledSearchEnginesLabels.at(3), enabledSearchEnginesLabels.at(4), enabledSearchEnginesLabels.at(5)); + case 7: + //: Seven search engines selected + //% "%1, %2, %3, %4, %5, %6, and %7" + return i18n("human-readable-seven-search-engines").arg(enabledSearchEnginesLabels.at(0), enabledSearchEnginesLabels.at(1), enabledSearchEnginesLabels.at(2), enabledSearchEnginesLabels.at(3), enabledSearchEnginesLabels.at(4), enabledSearchEnginesLabels.at(5), enabledSearchEnginesLabels.at(6)); + default: + return enabledSearchEnginesLabels.join(", "); + } +} + +int SearchEngineList::getSearchEngineCount() const { + if (empty()) return 0; + + int count = 0; + for (int i = 0; i < size(); ++i) + // TODO make it configurable which search engines to start/count + ++count; + return count; +} + +QHash<int, QByteArray> SearchEngineList::roleNames() const { + QHash<int, QByteArray> roles; + roles[LabelRole] = "label"; + roles[EngineEnabledRole] = "engineEnabled"; + return roles; +} + +void SearchEngineList::resetProgress() { + m_collectedProgress.clear(); +} + +int SearchEngineList::progress() const { + int count = 0, sum = 0; + for (QHash<QObject *, int>::ConstIterator it = m_collectedProgress.constBegin(); it != m_collectedProgress.constEnd(); ++it, ++count) + sum += it.value(); + return count > 0 ? sum / count : 0; +} + +void SearchEngineList::collectingProgress(int cur, int total) { + if (cur > total) cur = total; + m_collectedProgress.insert(sender(), total > 0 ? cur * 1000 / total : 0); + emit progressChanged(); +} diff --git a/src/kirigami2/searchenginelist.h b/src/kirigami2/searchenginelist.h new file mode 100644 index 00000000..048e0013 --- /dev/null +++ b/src/kirigami2/searchenginelist.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2017-2019 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * + * * + * 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 <https://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifndef SEARCHENGINELIST_H +#define SEARCHENGINELIST_H + +#include <QAbstractListModel> +#include <QHash> + +#include "entry.h" + +class OnlineSearchAbstract; + +class SearchEngineList : public QAbstractListModel, public QVector<OnlineSearchAbstract *> +{ + Q_OBJECT + Q_PROPERTY(int searchEngineCount READ getSearchEngineCount NOTIFY searchEngineCountChanged) + +public: + enum Roles {LabelRole = Qt::DisplayRole, EngineEnabledRole = Qt::UserRole + 1000}; + + explicit SearchEngineList(); + explicit SearchEngineList(const SearchEngineList &); + + SearchEngineList *operator =(const SearchEngineList *); + + Q_INVOKABLE virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_INVOKABLE virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_INVOKABLE virtual bool setData(const QModelIndex &index, const QVariant &value, int role = EngineEnabledRole) override; + + Q_INVOKABLE QString humanReadableSearchEngines() const; + int getSearchEngineCount() const; + + QHash<int, QByteArray> roleNames() const override; + + void resetProgress(); + int progress() const; + +signals: + void foundEntry(QSharedPointer<Entry>); + void busyChanged(); + void progressChanged(); + void searchEngineCountChanged(); + +private slots: + void collectingProgress(int, int); + +private: + QHash<QObject *, int> m_collectedProgress; +}; + +Q_DECLARE_METATYPE(SearchEngineList) + +#endif // SEARCHENGINELIST_H