diff --git a/.qmake.conf b/.qmake.conf index 5fbbff49d..e96e51b13 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,2 +1,5 @@ SRC_ROOT=$$PWD BUILD_ROOT=$$shadowed($$PWD) + +ZEAL_COMMON_PRI = $$SRC_ROOT/qmake/common.pri +ZEAL_LIBRARY_PRI = $$SRC_ROOT/qmake/library.pri diff --git a/.shippable.yml b/.shippable.yml index eca162338..1a1ff3619 100644 --- a/.shippable.yml +++ b/.shippable.yml @@ -1,7 +1,9 @@ -before_install: - - shippable_retry sudo apt-get -y -qq update -install: - - shippable_retry sudo apt-get -y -qq install --no-install-recommends qt5-default libqt5webkit5-dev libqt5x11extras5-dev libarchive-dev libxcb-keysyms1-dev -script: - - qmake - - make +language: c +compiler: + - gcc +build: + ci: + - shippable_retry sudo apt-get -y -qq update + - shippable_retry sudo apt-get -y -qq install --no-install-recommends qt5-default libqt5webkit5-dev libqt5x11extras5-dev libarchive-dev libxcb-keysyms1-dev + - qmake + - make diff --git a/README.md b/README.md index 611f2feb5..05b87eae2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Zeal -[![IRC](https://img.shields.io/badge/irc-%23zealdocs-blue.svg?style=flat-square)](https://kiwiirc.com/client/irc.freenode.net/#zealdocs) +[![Changelog](https://img.shields.io/github/release/zealdocs/zeal.svg?style=flat-square)](https://github.com/zealdocs/zeal/releases) +[![IRC](https://img.shields.io/badge/chat-on%20irc-blue.svg?style=flat-square)](https://kiwiirc.com/client/irc.freenode.net/#zealdocs) +[![Telegram Chat](https://img.shields.io/badge/chat-on%20telegram-179cde.svg?style=flat-square)](https://telegram.me/zealdocschat) +[![Telegram Channel](https://img.shields.io/badge/follow-on%20telegram-179cde.svg?style=flat-square)](https://telegram.me/zealdocs) +[![Twitter](https://img.shields.io/badge/follow-on%20twitter-1da1f2.svg?style=flat-square)](https://twitter.com/zealdocs) > **zeal** *noun* > @@ -12,7 +16,7 @@ Zeal is a simple offline documentation browser inspired by [Dash](https://kapeli.com/dash). -![Screenshot](https://i.imgur.com/SiLvpz8.png) +![Screenshot](https://i.imgur.com/v63u1ZJ.png) [More screenshots](https://imgur.com/a/eVi97) @@ -22,7 +26,7 @@ Zeal is a simple offline documentation browser inspired by [Dash](https://kapeli OS \ Branch | master | stable ------------|--------|------- -Linux | [![Shippable](https://img.shields.io/shippable/54ac2ce4d46935d5fbc19b84/master.svg?style=flat-square)](https://app.shippable.com/builds/54ac2ce4d46935d5fbc19b84) | [![Shippable](https://img.shields.io/shippable/54ac2ce4d46935d5fbc19b84/stable.svg?style=flat-square)](https://app.shippable.com/builds/54ac2ce4d46935d5fbc19b84) +Linux | [![Shippable](https://img.shields.io/shippable/54ac2ce4d46935d5fbc19b84/master.svg?style=flat-square)](https://app.shippable.com/projects/54ac2ce4d46935d5fbc19b84) | [![Shippable](https://img.shields.io/shippable/54ac2ce4d46935d5fbc19b84/stable.svg?style=flat-square)](https://app.shippable.com/projects/54ac2ce4d46935d5fbc19b84) Windows | [![AppVeyor](https://img.shields.io/appveyor/ci/trollixx/zeal/master.svg?style=flat-square)](https://ci.appveyor.com/project/trollixx/zeal) | [![AppVeyor](https://img.shields.io/appveyor/ci/trollixx/zeal/stable.svg?style=flat-square)](https://ci.appveyor.com/project/trollixx/zeal) ## Download diff --git a/assets/assets.pro b/assets/assets.pro new file mode 100644 index 000000000..bb39f61e8 --- /dev/null +++ b/assets/assets.pro @@ -0,0 +1,23 @@ +include($$ZEAL_COMMON_PRI) + +TEMPLATE = aux + +unix:!macx { + TARGET = zeal + appicons16.files=freedesktop/appicons/16/* + appicons24.files=freedesktop/appicons/24/* + appicons32.files=freedesktop/appicons/32/* + appicons64.files=freedesktop/appicons/64/* + appicons128.files=freedesktop/appicons/128/* + + appicons16.path=$$PREFIX/share/icons/hicolor/16x16/apps + appicons24.path=$$PREFIX/share/icons/hicolor/24x24/apps + appicons32.path=$$PREFIX/share/icons/hicolor/32x32/apps + appicons64.path=$$PREFIX/share/icons/hicolor/64x64/apps + appicons128.path=$$PREFIX/share/icons/hicolor/128x128/apps + + desktop.files=freedesktop/zeal.desktop + desktop.path=$$PREFIX/share/applications + + INSTALLS += appicons16 appicons24 appicons32 appicons64 appicons128 desktop +} diff --git a/src/appicons/128/zeal.png b/assets/freedesktop/appicons/128/zeal.png similarity index 100% rename from src/appicons/128/zeal.png rename to assets/freedesktop/appicons/128/zeal.png diff --git a/src/appicons/16/zeal.png b/assets/freedesktop/appicons/16/zeal.png similarity index 100% rename from src/appicons/16/zeal.png rename to assets/freedesktop/appicons/16/zeal.png diff --git a/src/appicons/24/zeal.png b/assets/freedesktop/appicons/24/zeal.png similarity index 100% rename from src/appicons/24/zeal.png rename to assets/freedesktop/appicons/24/zeal.png diff --git a/src/appicons/32/zeal.png b/assets/freedesktop/appicons/32/zeal.png similarity index 100% rename from src/appicons/32/zeal.png rename to assets/freedesktop/appicons/32/zeal.png diff --git a/src/appicons/64/zeal.png b/assets/freedesktop/appicons/64/zeal.png similarity index 100% rename from src/appicons/64/zeal.png rename to assets/freedesktop/appicons/64/zeal.png diff --git a/src/zeal.desktop b/assets/freedesktop/zeal.desktop similarity index 100% rename from src/zeal.desktop rename to assets/freedesktop/zeal.desktop diff --git a/qmake/common.pri b/qmake/common.pri new file mode 100644 index 000000000..4e26c76b7 --- /dev/null +++ b/qmake/common.pri @@ -0,0 +1,65 @@ +# Shared build options +# +# This file must be included at the top of every non-subdirs .pro file. +# Use: +# include($$ZEAL_COMMON_PRI) + +# Compilation settings +CONFIG += c++11 silent + +# Shared include path +INCLUDEPATH += $$SRC_ROOT/src/libs +LIBS = -L$$BUILD_ROOT/.lib + +# QString options +DEFINES *= QT_USE_QSTRINGBUILDER +DEFINES *= QT_RESTRICTED_CAST_FROM_ASCII +DEFINES *= QT_NO_CAST_TO_ASCII +DEFINES *= QT_NO_URL_CAST_FROM_STRING + +# Workaround for AppVeyor: Do not warn if the library had to be created. +# Based on https://codereview.qt-project.org/150326 +win32-g++:lessThan(QT_VERSION, "5.6.0") { + QMAKE_LIB = ar -rc +} + +# Keep build directory clean +MOC_DIR = $$BUILD_ROOT/.moc +OBJECTS_DIR = $$BUILD_ROOT/.obj +RCC_DIR = $$BUILD_ROOT/.rcc +UI_DIR = $$BUILD_ROOT/.ui + +# Application version +VERSION = 0.3.0 +DEFINES += ZEAL_VERSION=\\\"$${VERSION}\\\" + +# Browser engine +CONFIG(zeal_qtwebengine) { + qtHaveModule(webenginewidgets): BROWSER_ENGINE = qtwebengine + else: error("Qt WebEngine is not available.") +} else { + qtHaveModule(webkitwidgets): BROWSER_ENGINE = qtwebkit + else: qtHaveModule(webenginewidgets): BROWSER_ENGINE = qtwebengine + else: error("Zeal requires Qt WebEngine or Qt WebKit.") +} + +equals(BROWSER_ENGINE, qtwebengine) { + message("Browser engine: Qt WebEngine.") + QT += webenginewidgets + DEFINES += USE_WEBENGINE +} else { + message("Browser engine: Qt WebKit.") + QT += webkitwidgets + DEFINES += USE_WEBKIT +} + +# Portable build +CONFIG(zeal_portable) { + message("Portable build: Yes.") + DEFINES += PORTABLE_BUILD +} + +# Unix installation prefix +unix:!macx { + isEmpty(PREFIX): PREFIX = /usr +} diff --git a/qmake/library.pri b/qmake/library.pri new file mode 100644 index 000000000..7effb1c8c --- /dev/null +++ b/qmake/library.pri @@ -0,0 +1,8 @@ +include($$ZEAL_COMMON_PRI) +include($$replace(_PRO_FILE_PWD_, ([^/]+$), \\1/\\1.pri)) + +TEMPLATE = lib +CONFIG += staticlib + +DESTDIR = $$BUILD_ROOT/.lib +TARGET = $$ZEAL_LIB_NAME diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri deleted file mode 100644 index 44257cfd9..000000000 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri +++ /dev/null @@ -1,20 +0,0 @@ -INCLUDEPATH += $$PWD - -HEADERS += $$files($$PWD/*.h) -SOURCES += $$PWD/qxtglobalshortcut.cpp - -unix:!macx { - QT += x11extras - - CONFIG += link_pkgconfig - PKGCONFIG += x11 xcb xcb-keysyms - - SOURCES += $$PWD/qxtglobalshortcut_x11.cpp -} - -win32:SOURCES += $$PWD/qxtglobalshortcut_win.cpp - -macx { - SOURCES += $$PWD/qxtglobalshortcut_mac.cpp - LIBS += -framework Carbon -} diff --git a/src/app/app.pro b/src/app/app.pro new file mode 100644 index 000000000..7dfd39a05 --- /dev/null +++ b/src/app/app.pro @@ -0,0 +1,42 @@ +include($$ZEAL_COMMON_PRI) + +TEMPLATE = app + +QT += gui widgets sql concurrent + +SOURCES += \ + main.cpp + +RESOURCES += \ + resources/zeal.qrc + +DESTDIR = $$BUILD_ROOT/bin + +unix:!macx { + TARGET = zeal + target.path = $$PREFIX/bin + + INSTALLS += target +} + +win32 { + TARGET = zeal + RC_ICONS = resources/zeal.ico +} + +macx { + TARGET = Zeal + ICON = resources/zeal.icns +} + +# FIXME: Hardcoded link line & cyclic dependencies. +LIBS += -lCore -lUi -lRegistry -lUtil + +# Depend on all dependencies of libraries +for(lib_dir, $$list($$files($$SRC_ROOT/src/libs/*))) { + !equals(lib_dir, $$SRC_ROOT/src/libs/libs.pro) { + include($$lib_dir/$$basename(lib_dir).pri) + PRE_TARGETDEPS += $$BUILD_ROOT/.lib/lib$${ZEAL_LIB_NAME}.a + # LIBS += -l$$ZEAL_LIB_NAME + } +} diff --git a/src/main.cpp b/src/app/main.cpp similarity index 54% rename from src/main.cpp rename to src/app/main.cpp index 917ce66df..0ca13fd54 100644 --- a/src/main.cpp +++ b/src/app/main.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,35 +17,41 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ -#include "core/application.h" -#include "registry/searchquery.h" +#include +#include +#include #include #include -#include #include #include -#include +#include #include #include -#include #include #include #ifdef Q_OS_WIN32 -#include #include -#include #endif +#include + +using namespace Zeal; + +namespace { +const char contactUrl[] = "https://go.zealdocs.org/l/contact"; +} + struct CommandLineParameters { bool force; - Zeal::SearchQuery query; + bool preventActivation; + Registry::SearchQuery query; #ifdef Q_OS_WIN32 bool registerProtocolHandlers; bool unregisterProtocolHandlers; @@ -69,13 +75,10 @@ CommandLineParameters parseCommandLine(const QStringList &arguments) parser.addHelpOption(); parser.addVersionOption(); - /// TODO: [Qt 5.4] parser.addOption({{"f", "force"}, "Force the application run."}); + // TODO: [Qt 5.4] parser.addOption({{"f", "force"}, "Force the application run."}); parser.addOption(QCommandLineOption({QStringLiteral("f"), QStringLiteral("force")}, QObject::tr("Force the application run."))); - /// TODO: [0.3.0] Remove --query support - parser.addOption(QCommandLineOption({QStringLiteral("q"), QStringLiteral("query")}, - QObject::tr("[DEPRECATED] Query ."), - QStringLiteral("term"))); + #ifdef Q_OS_WIN32 parser.addOption(QCommandLineOption({QStringLiteral("register")}, QObject::tr("Register protocol handlers"))); @@ -87,6 +90,7 @@ CommandLineParameters parseCommandLine(const QStringList &arguments) CommandLineParameters clParams; clParams.force = parser.isSet(QStringLiteral("force")); + clParams.preventActivation = false; #ifdef Q_OS_WIN32 clParams.registerProtocolHandlers = parser.isSet(QStringLiteral("register")); @@ -98,23 +102,25 @@ CommandLineParameters parseCommandLine(const QStringList &arguments) } #endif - if (parser.isSet(QStringLiteral("query"))) { - clParams.query.setQuery(parser.value(QStringLiteral("query"))); + // TODO: Support dash-feed:// protocol + const QString arg + = QUrl::fromPercentEncoding(parser.positionalArguments().value(0).toUtf8()); + if (arg.startsWith(QLatin1String("dash:"))) { + clParams.query.setQuery(stripParameterUrl(arg, QStringLiteral("dash"))); + } else if (arg.startsWith(QLatin1String("dash-plugin:"))) { + const QUrlQuery urlQuery(stripParameterUrl(arg, QStringLiteral("dash-plugin"))); + + const QString keys = urlQuery.queryItemValue(QStringLiteral("keys")); + if (!keys.isEmpty()) + clParams.query.setKeywords(keys.split(QLatin1Char(','))); + + clParams.query.setQuery(urlQuery.queryItemValue(QStringLiteral("query"))); + + const QString preventActivation + = urlQuery.queryItemValue(QStringLiteral("prevent_activation")); + clParams.preventActivation = preventActivation == QLatin1String("true"); } else { - /// TODO: Support dash-feed:// protocol - const QString arg - = QUrl::fromPercentEncoding(parser.positionalArguments().value(0).toUtf8()); - if (arg.startsWith(QLatin1String("dash:"))) { - clParams.query.setQuery(stripParameterUrl(arg, QStringLiteral("dash"))); - } else if (arg.startsWith(QLatin1String("dash-plugin:"))) { - const QUrlQuery urlQuery(stripParameterUrl(arg, QStringLiteral("dash-plugin"))); - const QString keys = urlQuery.queryItemValue(QStringLiteral("keys")); - if (!keys.isEmpty()) - clParams.query.setKeywords(keys.split(QLatin1Char(','))); - clParams.query.setQuery(urlQuery.queryItemValue(QStringLiteral("query"))); - } else { - clParams.query.setQuery(arg); - } + clParams.query.setQuery(arg); } return clParams; @@ -138,7 +144,7 @@ void registerProtocolHandler(const QString &scheme, const QString &description) reg->beginGroup(QStringLiteral("shell")); reg->beginGroup(QStringLiteral("open")); reg->beginGroup(QStringLiteral("command")); - reg->setValue(QStringLiteral("Default"), appPath + QLatin1String(" %1")); + reg->setValue(QStringLiteral("Default"), QVariant(appPath + QLatin1String(" %1"))); } void registerProtocolHandlers(const QHash &protocols, bool force = false) @@ -161,28 +167,13 @@ void unregisterProtocolHandlers(const QHash &protocols) for (auto it = protocols.cbegin(); it != protocols.cend(); ++it) reg->remove(it.key()); } - -/// TODO: Verify if this bug still exists in Qt 5.2+ -class ZealProxyStyle : public QProxyStyle -{ -public: - void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, - const QWidget *widget = nullptr) const - { - if (element == PE_FrameLineEdit && option->styleObject) { - option->styleObject->setProperty("_q_no_animation", true); - // Workaround for a probable bug in QWindowsVistaStyle - for example opening the 'String (CommandEvent)' - // item from wxPython docset (available at http://wxpython.org/Phoenix/docsets) causes very high CPU usage. - // Some rough debugging shows that the 'd->startAnimation(t);' call in QWindowsVistaStyle::drawPrimitive - // is the cuplrit and setting _q_no_animation to true here fixes the issue. - } - return QProxyStyle::drawPrimitive(element, option, painter, widget); - } -}; #endif int main(int argc, char *argv[]) { + // Do not allow Qt version lower than the app was compiled with. + QT_REQUIRE_VERSION(argc, argv, QT_VERSION_STR); + QCoreApplication::setApplicationName(QStringLiteral("Zeal")); QCoreApplication::setApplicationVersion(ZEAL_VERSION); QCoreApplication::setOrganizationDomain(QStringLiteral("zealdocs.org")); @@ -200,44 +191,82 @@ int main(int argc, char *argv[]) if (clParams.unregisterProtocolHandlers) { unregisterProtocolHandlers(protocols); - ::exit(EXIT_SUCCESS); + return EXIT_SUCCESS; } else { registerProtocolHandlers(protocols, clParams.registerProtocolHandlers); if (clParams.registerProtocolHandlers) - ::exit(EXIT_SUCCESS); + return EXIT_SUCCESS; } - - qapp->setStyle(new ZealProxyStyle()); #endif + // Detect already running instance and optionally send the search query to it. + if (!clParams.force && Core::LocalServer::sendQuery(clParams.query, clParams.preventActivation)) + return 0; - // Detect already running instance and optionally pass a search query to it. - if (!clParams.force) { - QScopedPointer socket(new QLocalSocket()); - socket->connectToServer(Zeal::Core::Application::localServerName()); + // Set application-wide window icon. All message boxes and other windows will use it by default. + qapp->setWindowIcon(QIcon::fromTheme(QStringLiteral("zeal"), + QIcon(QStringLiteral(":/zeal.ico")))); + + QScopedPointer localServer(new Core::LocalServer()); + if (!localServer->start()) { + QScopedPointer msgBox(new QMessageBox()); + msgBox->setWindowTitle(QStringLiteral("Zeal")); + + msgBox->setIcon(QMessageBox::Warning); + msgBox->setText(QObject::tr("Another application instance can be still running, " + "or has crashed.
Make sure to start Zeal only once.")); + msgBox->addButton(QMessageBox::Help); + msgBox->addButton(QMessageBox::Retry); + QPushButton *quitButton = msgBox->addButton(QObject::tr("&Quit"), + QMessageBox::DestructiveRole); + msgBox->setDefaultButton(quitButton); + + switch (msgBox->exec()) { + case QMessageBox::Rejected: + return EXIT_SUCCESS; + case QMessageBox::Help: + QDesktopServices::openUrl(QUrl(contactUrl)); + } - if (socket->waitForConnected(500)) { - QDataStream out(socket.data()); - out << clParams.query; - socket->flush(); - return 0; + msgBox->removeButton(msgBox->button(QMessageBox::Retry)); + + if (!localServer->start(true)) { + msgBox->setIcon(QMessageBox::Critical); + msgBox->setText(QObject::tr("Zeal is unable to start. Please report the issue " + "providing the details below.")); + msgBox->setDetailedText(localServer->errorString()); + + if (msgBox->exec() == QMessageBox::Help) + QDesktopServices::openUrl(QUrl(contactUrl)); + + return EXIT_SUCCESS; } } // Check for SQLite plugin - /// TODO: Specific to docset format and should be handled accordingly in the future + // TODO: Specific to docset format and should be handled accordingly in the future if (!QSqlDatabase::isDriverAvailable(QStringLiteral("QSQLITE"))) { - const int ret = QMessageBox::critical(nullptr, QStringLiteral("Zeal"), - QObject::tr("Qt SQLite driver is not available."), - QMessageBox::Close, QMessageBox::Help); - if (ret == QMessageBox::Help) - QDesktopServices::openUrl(QStringLiteral("https://zealdocs.org/contact.html")); - return 0; + QScopedPointer msgBox(new QMessageBox()); + msgBox->setWindowTitle(QStringLiteral("Zeal")); + msgBox->setIcon(QMessageBox::Critical); + msgBox->setText(QObject::tr("Qt SQLite driver is not available.")); + msgBox->addButton(QMessageBox::Help); + QPushButton *quitButton = msgBox->addButton(QObject::tr("&Quit"), + QMessageBox::DestructiveRole); + msgBox->setDefaultButton(quitButton); + if (msgBox->exec() == QMessageBox::Help) + QDesktopServices::openUrl(QUrl(contactUrl)); + return EXIT_SUCCESS; } QDir::setSearchPaths(QStringLiteral("typeIcon"), {QStringLiteral(":/icons/type")}); - QScopedPointer app(new Zeal::Core::Application(clParams.query)); + QScopedPointer app(new Core::Application()); + QObject::connect(localServer.data(), &Core::LocalServer::newQuery, + app.data(), &Core::Application::executeQuery); + + if (!clParams.query.isEmpty()) + Core::LocalServer::sendQuery(clParams.query, clParams.preventActivation); return qapp->exec(); } diff --git a/src/resources/browser/highlight.css b/src/app/resources/browser/highlight.css similarity index 100% rename from src/resources/browser/highlight.css rename to src/app/resources/browser/highlight.css diff --git a/src/resources/browser/main.css b/src/app/resources/browser/main.css similarity index 100% rename from src/resources/browser/main.css rename to src/app/resources/browser/main.css diff --git a/src/resources/browser/start.html b/src/app/resources/browser/start.html similarity index 94% rename from src/resources/browser/start.html rename to src/app/resources/browser/start.html index 326ec2b0f..2a53b774c 100644 --- a/src/resources/browser/start.html +++ b/src/app/resources/browser/start.html @@ -1,6 +1,6 @@ - Zeal - Start Page + Start Page diff --git a/src/app/resources/icons/logo/128x128.png b/src/app/resources/icons/logo/128x128.png new file mode 100644 index 000000000..a7d7dd556 Binary files /dev/null and b/src/app/resources/icons/logo/128x128.png differ diff --git a/src/app/resources/icons/logo/64x64.png b/src/app/resources/icons/logo/64x64.png new file mode 100644 index 000000000..601eadcff Binary files /dev/null and b/src/app/resources/icons/logo/64x64.png differ diff --git a/src/app/resources/icons/logo/icon.png b/src/app/resources/icons/logo/icon.png new file mode 100644 index 000000000..4bd3ce273 Binary files /dev/null and b/src/app/resources/icons/logo/icon.png differ diff --git a/src/app/resources/icons/logo/icon@2x.png b/src/app/resources/icons/logo/icon@2x.png new file mode 100644 index 000000000..28b878550 Binary files /dev/null and b/src/app/resources/icons/logo/icon@2x.png differ diff --git a/src/app/resources/icons/type/Abbreviation.png b/src/app/resources/icons/type/Abbreviation.png new file mode 100644 index 000000000..9f72492f8 Binary files /dev/null and b/src/app/resources/icons/type/Abbreviation.png differ diff --git a/src/resources/icons/type/Abbreviation@2x.png b/src/app/resources/icons/type/Abbreviation@2x.png similarity index 59% rename from src/resources/icons/type/Abbreviation@2x.png rename to src/app/resources/icons/type/Abbreviation@2x.png index e66bf08ab..51e78a500 100644 Binary files a/src/resources/icons/type/Abbreviation@2x.png and b/src/app/resources/icons/type/Abbreviation@2x.png differ diff --git a/src/app/resources/icons/type/Alias.png b/src/app/resources/icons/type/Alias.png new file mode 100644 index 000000000..25444691f Binary files /dev/null and b/src/app/resources/icons/type/Alias.png differ diff --git a/src/resources/icons/type/Alias@2x.png b/src/app/resources/icons/type/Alias@2x.png similarity index 59% rename from src/resources/icons/type/Alias@2x.png rename to src/app/resources/icons/type/Alias@2x.png index 68c459323..ce1a37dbe 100644 Binary files a/src/resources/icons/type/Alias@2x.png and b/src/app/resources/icons/type/Alias@2x.png differ diff --git a/src/app/resources/icons/type/Annotation.png b/src/app/resources/icons/type/Annotation.png new file mode 100644 index 000000000..25444691f Binary files /dev/null and b/src/app/resources/icons/type/Annotation.png differ diff --git a/src/resources/icons/type/Annotation@2x.png b/src/app/resources/icons/type/Annotation@2x.png similarity index 59% rename from src/resources/icons/type/Annotation@2x.png rename to src/app/resources/icons/type/Annotation@2x.png index 68c459323..ce1a37dbe 100644 Binary files a/src/resources/icons/type/Annotation@2x.png and b/src/app/resources/icons/type/Annotation@2x.png differ diff --git a/src/app/resources/icons/type/Attribute.png b/src/app/resources/icons/type/Attribute.png new file mode 100644 index 000000000..89ce4af5c Binary files /dev/null and b/src/app/resources/icons/type/Attribute.png differ diff --git a/src/resources/icons/type/Attribute@2x.png b/src/app/resources/icons/type/Attribute@2x.png similarity index 59% rename from src/resources/icons/type/Attribute@2x.png rename to src/app/resources/icons/type/Attribute@2x.png index 2beee3d54..2e4aaa7ee 100644 Binary files a/src/resources/icons/type/Attribute@2x.png and b/src/app/resources/icons/type/Attribute@2x.png differ diff --git a/src/app/resources/icons/type/Axiom.png b/src/app/resources/icons/type/Axiom.png new file mode 100644 index 000000000..25444691f Binary files /dev/null and b/src/app/resources/icons/type/Axiom.png differ diff --git a/src/resources/icons/type/Axiom@2x.png b/src/app/resources/icons/type/Axiom@2x.png similarity index 59% rename from src/resources/icons/type/Axiom@2x.png rename to src/app/resources/icons/type/Axiom@2x.png index 68c459323..ce1a37dbe 100644 Binary files a/src/resources/icons/type/Axiom@2x.png and b/src/app/resources/icons/type/Axiom@2x.png differ diff --git a/src/app/resources/icons/type/Binding.png b/src/app/resources/icons/type/Binding.png new file mode 100644 index 000000000..b18b85bda Binary files /dev/null and b/src/app/resources/icons/type/Binding.png differ diff --git a/src/app/resources/icons/type/Binding@2x.png b/src/app/resources/icons/type/Binding@2x.png new file mode 100644 index 000000000..77b161e18 Binary files /dev/null and b/src/app/resources/icons/type/Binding@2x.png differ diff --git a/src/resources/icons/type/Bookmark.png b/src/app/resources/icons/type/Bookmark.png similarity index 67% rename from src/resources/icons/type/Bookmark.png rename to src/app/resources/icons/type/Bookmark.png index 6f41ba0fb..9be3830e5 100644 Binary files a/src/resources/icons/type/Bookmark.png and b/src/app/resources/icons/type/Bookmark.png differ diff --git a/src/resources/icons/type/Bookmark@2x.png b/src/app/resources/icons/type/Bookmark@2x.png similarity index 80% rename from src/resources/icons/type/Bookmark@2x.png rename to src/app/resources/icons/type/Bookmark@2x.png index 3e4adbb78..9748bc288 100644 Binary files a/src/resources/icons/type/Bookmark@2x.png and b/src/app/resources/icons/type/Bookmark@2x.png differ diff --git a/src/app/resources/icons/type/Builtin.png b/src/app/resources/icons/type/Builtin.png new file mode 100644 index 000000000..8f52dc09a Binary files /dev/null and b/src/app/resources/icons/type/Builtin.png differ diff --git a/src/app/resources/icons/type/Builtin@2x.png b/src/app/resources/icons/type/Builtin@2x.png new file mode 100644 index 000000000..5628543c5 Binary files /dev/null and b/src/app/resources/icons/type/Builtin@2x.png differ diff --git a/src/app/resources/icons/type/Callback.png b/src/app/resources/icons/type/Callback.png new file mode 100644 index 000000000..377b08e0e Binary files /dev/null and b/src/app/resources/icons/type/Callback.png differ diff --git a/src/resources/icons/type/Callback@2x.png b/src/app/resources/icons/type/Callback@2x.png similarity index 57% rename from src/resources/icons/type/Callback@2x.png rename to src/app/resources/icons/type/Callback@2x.png index b52723988..87f08c9a2 100644 Binary files a/src/resources/icons/type/Callback@2x.png and b/src/app/resources/icons/type/Callback@2x.png differ diff --git a/src/app/resources/icons/type/Category.png b/src/app/resources/icons/type/Category.png new file mode 100644 index 000000000..f12144ea5 Binary files /dev/null and b/src/app/resources/icons/type/Category.png differ diff --git a/src/app/resources/icons/type/Category@2x.png b/src/app/resources/icons/type/Category@2x.png new file mode 100644 index 000000000..7cdf9953c Binary files /dev/null and b/src/app/resources/icons/type/Category@2x.png differ diff --git a/src/app/resources/icons/type/Class.png b/src/app/resources/icons/type/Class.png new file mode 100644 index 000000000..46e4bf7e7 Binary files /dev/null and b/src/app/resources/icons/type/Class.png differ diff --git a/src/app/resources/icons/type/Class@2x.png b/src/app/resources/icons/type/Class@2x.png new file mode 100644 index 000000000..4180f7ea7 Binary files /dev/null and b/src/app/resources/icons/type/Class@2x.png differ diff --git a/src/app/resources/icons/type/Collection.png b/src/app/resources/icons/type/Collection.png new file mode 100644 index 000000000..428fd1eb1 Binary files /dev/null and b/src/app/resources/icons/type/Collection.png differ diff --git a/src/app/resources/icons/type/Collection@2x.png b/src/app/resources/icons/type/Collection@2x.png new file mode 100644 index 000000000..0e7efa6b3 Binary files /dev/null and b/src/app/resources/icons/type/Collection@2x.png differ diff --git a/src/app/resources/icons/type/Column.png b/src/app/resources/icons/type/Column.png new file mode 100644 index 000000000..9cc37d618 Binary files /dev/null and b/src/app/resources/icons/type/Column.png differ diff --git a/src/app/resources/icons/type/Column@2x.png b/src/app/resources/icons/type/Column@2x.png new file mode 100644 index 000000000..8ada6f043 Binary files /dev/null and b/src/app/resources/icons/type/Column@2x.png differ diff --git a/src/app/resources/icons/type/Command.png b/src/app/resources/icons/type/Command.png new file mode 100644 index 000000000..6724a8150 Binary files /dev/null and b/src/app/resources/icons/type/Command.png differ diff --git a/src/resources/icons/type/Command@2x.png b/src/app/resources/icons/type/Command@2x.png similarity index 57% rename from src/resources/icons/type/Command@2x.png rename to src/app/resources/icons/type/Command@2x.png index b88e7ad9c..f355eb099 100644 Binary files a/src/resources/icons/type/Command@2x.png and b/src/app/resources/icons/type/Command@2x.png differ diff --git a/src/app/resources/icons/type/Component.png b/src/app/resources/icons/type/Component.png new file mode 100644 index 000000000..9cc37d618 Binary files /dev/null and b/src/app/resources/icons/type/Component.png differ diff --git a/src/app/resources/icons/type/Component@2x.png b/src/app/resources/icons/type/Component@2x.png new file mode 100644 index 000000000..8ada6f043 Binary files /dev/null and b/src/app/resources/icons/type/Component@2x.png differ diff --git a/src/app/resources/icons/type/Constant.png b/src/app/resources/icons/type/Constant.png new file mode 100644 index 000000000..1d56e2cfd Binary files /dev/null and b/src/app/resources/icons/type/Constant.png differ diff --git a/src/app/resources/icons/type/Constant@2x.png b/src/app/resources/icons/type/Constant@2x.png new file mode 100644 index 000000000..8540dab0e Binary files /dev/null and b/src/app/resources/icons/type/Constant@2x.png differ diff --git a/src/app/resources/icons/type/Constructor.png b/src/app/resources/icons/type/Constructor.png new file mode 100644 index 000000000..cb08c47da Binary files /dev/null and b/src/app/resources/icons/type/Constructor.png differ diff --git a/src/app/resources/icons/type/Constructor@2x.png b/src/app/resources/icons/type/Constructor@2x.png new file mode 100644 index 000000000..cee38eb9d Binary files /dev/null and b/src/app/resources/icons/type/Constructor@2x.png differ diff --git a/src/app/resources/icons/type/Conversion.png b/src/app/resources/icons/type/Conversion.png new file mode 100644 index 000000000..5b929d89b Binary files /dev/null and b/src/app/resources/icons/type/Conversion.png differ diff --git a/src/resources/icons/type/Conversion@2x.png b/src/app/resources/icons/type/Conversion@2x.png similarity index 57% rename from src/resources/icons/type/Conversion@2x.png rename to src/app/resources/icons/type/Conversion@2x.png index 2f8586c44..839b60aa0 100644 Binary files a/src/resources/icons/type/Conversion@2x.png and b/src/app/resources/icons/type/Conversion@2x.png differ diff --git a/src/app/resources/icons/type/Database.png b/src/app/resources/icons/type/Database.png new file mode 100644 index 000000000..f090f83d8 Binary files /dev/null and b/src/app/resources/icons/type/Database.png differ diff --git a/src/resources/icons/type/Database@2x.png b/src/app/resources/icons/type/Database@2x.png similarity index 54% rename from src/resources/icons/type/Database@2x.png rename to src/app/resources/icons/type/Database@2x.png index fbbb943ba..535ac3aa0 100644 Binary files a/src/resources/icons/type/Database@2x.png and b/src/app/resources/icons/type/Database@2x.png differ diff --git a/src/app/resources/icons/type/Decorator.png b/src/app/resources/icons/type/Decorator.png new file mode 100644 index 000000000..7b88c5db9 Binary files /dev/null and b/src/app/resources/icons/type/Decorator.png differ diff --git a/src/app/resources/icons/type/Decorator@2x.png b/src/app/resources/icons/type/Decorator@2x.png new file mode 100644 index 000000000..08243f82d Binary files /dev/null and b/src/app/resources/icons/type/Decorator@2x.png differ diff --git a/src/app/resources/icons/type/Define.png b/src/app/resources/icons/type/Define.png new file mode 100644 index 000000000..e13cc63ec Binary files /dev/null and b/src/app/resources/icons/type/Define.png differ diff --git a/src/app/resources/icons/type/Define@2x.png b/src/app/resources/icons/type/Define@2x.png new file mode 100644 index 000000000..1bc6183ac Binary files /dev/null and b/src/app/resources/icons/type/Define@2x.png differ diff --git a/src/app/resources/icons/type/Delegate.png b/src/app/resources/icons/type/Delegate.png new file mode 100644 index 000000000..a6d7808cb Binary files /dev/null and b/src/app/resources/icons/type/Delegate.png differ diff --git a/src/app/resources/icons/type/Delegate@2x.png b/src/app/resources/icons/type/Delegate@2x.png new file mode 100644 index 000000000..97d30f5c3 Binary files /dev/null and b/src/app/resources/icons/type/Delegate@2x.png differ diff --git a/src/app/resources/icons/type/DeletedSnippet.png b/src/app/resources/icons/type/DeletedSnippet.png new file mode 100644 index 000000000..91c802efe Binary files /dev/null and b/src/app/resources/icons/type/DeletedSnippet.png differ diff --git a/src/app/resources/icons/type/DeletedSnippet@2x.png b/src/app/resources/icons/type/DeletedSnippet@2x.png new file mode 100644 index 000000000..7be2bc3a9 Binary files /dev/null and b/src/app/resources/icons/type/DeletedSnippet@2x.png differ diff --git a/src/app/resources/icons/type/Diagram.png b/src/app/resources/icons/type/Diagram.png new file mode 100644 index 000000000..7b88c5db9 Binary files /dev/null and b/src/app/resources/icons/type/Diagram.png differ diff --git a/src/app/resources/icons/type/Diagram@2x.png b/src/app/resources/icons/type/Diagram@2x.png new file mode 100644 index 000000000..08243f82d Binary files /dev/null and b/src/app/resources/icons/type/Diagram@2x.png differ diff --git a/src/app/resources/icons/type/Directive.png b/src/app/resources/icons/type/Directive.png new file mode 100644 index 000000000..f090f83d8 Binary files /dev/null and b/src/app/resources/icons/type/Directive.png differ diff --git a/src/resources/icons/type/Directive@2x.png b/src/app/resources/icons/type/Directive@2x.png similarity index 54% rename from src/resources/icons/type/Directive@2x.png rename to src/app/resources/icons/type/Directive@2x.png index fbbb943ba..535ac3aa0 100644 Binary files a/src/resources/icons/type/Directive@2x.png and b/src/app/resources/icons/type/Directive@2x.png differ diff --git a/src/app/resources/icons/type/Element.png b/src/app/resources/icons/type/Element.png new file mode 100644 index 000000000..68dc09ce2 Binary files /dev/null and b/src/app/resources/icons/type/Element.png differ diff --git a/src/app/resources/icons/type/Element@2x.png b/src/app/resources/icons/type/Element@2x.png new file mode 100644 index 000000000..3fb4a9714 Binary files /dev/null and b/src/app/resources/icons/type/Element@2x.png differ diff --git a/src/app/resources/icons/type/Entry.png b/src/app/resources/icons/type/Entry.png new file mode 100644 index 000000000..ffc001204 Binary files /dev/null and b/src/app/resources/icons/type/Entry.png differ diff --git a/src/app/resources/icons/type/Entry@2x.png b/src/app/resources/icons/type/Entry@2x.png new file mode 100644 index 000000000..e1a0f1a1e Binary files /dev/null and b/src/app/resources/icons/type/Entry@2x.png differ diff --git a/src/resources/icons/type/Enumeration.png b/src/app/resources/icons/type/Enumeration.png similarity index 57% rename from src/resources/icons/type/Enumeration.png rename to src/app/resources/icons/type/Enumeration.png index 47ef92023..e8e7f58dd 100644 Binary files a/src/resources/icons/type/Enumeration.png and b/src/app/resources/icons/type/Enumeration.png differ diff --git a/src/app/resources/icons/type/Enumeration@2x.png b/src/app/resources/icons/type/Enumeration@2x.png new file mode 100644 index 000000000..42f71ed42 Binary files /dev/null and b/src/app/resources/icons/type/Enumeration@2x.png differ diff --git a/src/app/resources/icons/type/Environment.png b/src/app/resources/icons/type/Environment.png new file mode 100644 index 000000000..ffc001204 Binary files /dev/null and b/src/app/resources/icons/type/Environment.png differ diff --git a/src/app/resources/icons/type/Environment@2x.png b/src/app/resources/icons/type/Environment@2x.png new file mode 100644 index 000000000..e1a0f1a1e Binary files /dev/null and b/src/app/resources/icons/type/Environment@2x.png differ diff --git a/src/app/resources/icons/type/Error.png b/src/app/resources/icons/type/Error.png new file mode 100644 index 000000000..45b9bce17 Binary files /dev/null and b/src/app/resources/icons/type/Error.png differ diff --git a/src/resources/icons/type/Error@2x.png b/src/app/resources/icons/type/Error@2x.png similarity index 75% rename from src/resources/icons/type/Error@2x.png rename to src/app/resources/icons/type/Error@2x.png index cde9f9d44..d0bb08081 100644 Binary files a/src/resources/icons/type/Error@2x.png and b/src/app/resources/icons/type/Error@2x.png differ diff --git a/src/app/resources/icons/type/Event.png b/src/app/resources/icons/type/Event.png new file mode 100644 index 000000000..19116ccb2 Binary files /dev/null and b/src/app/resources/icons/type/Event.png differ diff --git a/src/resources/icons/type/Event@2x.png b/src/app/resources/icons/type/Event@2x.png similarity index 55% rename from src/resources/icons/type/Event@2x.png rename to src/app/resources/icons/type/Event@2x.png index 27f1d4e85..14caba71f 100644 Binary files a/src/resources/icons/type/Event@2x.png and b/src/app/resources/icons/type/Event@2x.png differ diff --git a/src/app/resources/icons/type/Exception.png b/src/app/resources/icons/type/Exception.png new file mode 100644 index 000000000..5bf1ea2a4 Binary files /dev/null and b/src/app/resources/icons/type/Exception.png differ diff --git a/src/resources/icons/type/Exception@2x.png b/src/app/resources/icons/type/Exception@2x.png similarity index 58% rename from src/resources/icons/type/Exception@2x.png rename to src/app/resources/icons/type/Exception@2x.png index 015047c88..d451af706 100644 Binary files a/src/resources/icons/type/Exception@2x.png and b/src/app/resources/icons/type/Exception@2x.png differ diff --git a/src/app/resources/icons/type/Extension.png b/src/app/resources/icons/type/Extension.png new file mode 100644 index 000000000..b550fcafb Binary files /dev/null and b/src/app/resources/icons/type/Extension.png differ diff --git a/src/resources/icons/type/Extension@2x.png b/src/app/resources/icons/type/Extension@2x.png similarity index 60% rename from src/resources/icons/type/Extension@2x.png rename to src/app/resources/icons/type/Extension@2x.png index d826c9745..2de3c6e93 100644 Binary files a/src/resources/icons/type/Extension@2x.png and b/src/app/resources/icons/type/Extension@2x.png differ diff --git a/src/app/resources/icons/type/Field.png b/src/app/resources/icons/type/Field.png new file mode 100644 index 000000000..3938859f1 Binary files /dev/null and b/src/app/resources/icons/type/Field.png differ diff --git a/src/app/resources/icons/type/Field@2x.png b/src/app/resources/icons/type/Field@2x.png new file mode 100644 index 000000000..7b7d466b8 Binary files /dev/null and b/src/app/resources/icons/type/Field@2x.png differ diff --git a/src/app/resources/icons/type/File.png b/src/app/resources/icons/type/File.png new file mode 100644 index 000000000..c1d61e7d2 Binary files /dev/null and b/src/app/resources/icons/type/File.png differ diff --git a/src/app/resources/icons/type/File@2x.png b/src/app/resources/icons/type/File@2x.png new file mode 100644 index 000000000..c977228af Binary files /dev/null and b/src/app/resources/icons/type/File@2x.png differ diff --git a/src/app/resources/icons/type/Filter.png b/src/app/resources/icons/type/Filter.png new file mode 100644 index 000000000..3938859f1 Binary files /dev/null and b/src/app/resources/icons/type/Filter.png differ diff --git a/src/app/resources/icons/type/Filter@2x.png b/src/app/resources/icons/type/Filter@2x.png new file mode 100644 index 000000000..7b7d466b8 Binary files /dev/null and b/src/app/resources/icons/type/Filter@2x.png differ diff --git a/src/app/resources/icons/type/Foreign Key.png b/src/app/resources/icons/type/Foreign Key.png new file mode 100644 index 000000000..3938859f1 Binary files /dev/null and b/src/app/resources/icons/type/Foreign Key.png differ diff --git a/src/app/resources/icons/type/Foreign Key@2x.png b/src/app/resources/icons/type/Foreign Key@2x.png new file mode 100644 index 000000000..7b7d466b8 Binary files /dev/null and b/src/app/resources/icons/type/Foreign Key@2x.png differ diff --git a/src/app/resources/icons/type/Framework.png b/src/app/resources/icons/type/Framework.png new file mode 100644 index 000000000..c1d61e7d2 Binary files /dev/null and b/src/app/resources/icons/type/Framework.png differ diff --git a/src/app/resources/icons/type/Framework@2x.png b/src/app/resources/icons/type/Framework@2x.png new file mode 100644 index 000000000..c977228af Binary files /dev/null and b/src/app/resources/icons/type/Framework@2x.png differ diff --git a/src/app/resources/icons/type/Function.png b/src/app/resources/icons/type/Function.png new file mode 100644 index 000000000..d09ea3a53 Binary files /dev/null and b/src/app/resources/icons/type/Function.png differ diff --git a/src/app/resources/icons/type/Function@2x.png b/src/app/resources/icons/type/Function@2x.png new file mode 100644 index 000000000..136800065 Binary files /dev/null and b/src/app/resources/icons/type/Function@2x.png differ diff --git a/src/app/resources/icons/type/Global.png b/src/app/resources/icons/type/Global.png new file mode 100644 index 000000000..f74d2be22 Binary files /dev/null and b/src/app/resources/icons/type/Global.png differ diff --git a/src/resources/icons/type/Global@2x.png b/src/app/resources/icons/type/Global@2x.png similarity index 57% rename from src/resources/icons/type/Global@2x.png rename to src/app/resources/icons/type/Global@2x.png index 32627a345..822ef7621 100644 Binary files a/src/resources/icons/type/Global@2x.png and b/src/app/resources/icons/type/Global@2x.png differ diff --git a/src/app/resources/icons/type/Guide.png b/src/app/resources/icons/type/Guide.png new file mode 100644 index 000000000..579fa6389 Binary files /dev/null and b/src/app/resources/icons/type/Guide.png differ diff --git a/src/resources/icons/type/Guide@2x.png b/src/app/resources/icons/type/Guide@2x.png similarity index 92% rename from src/resources/icons/type/Guide@2x.png rename to src/app/resources/icons/type/Guide@2x.png index d2b2025de..df2fe0c68 100644 Binary files a/src/resources/icons/type/Guide@2x.png and b/src/app/resources/icons/type/Guide@2x.png differ diff --git a/src/app/resources/icons/type/Helper.png b/src/app/resources/icons/type/Helper.png new file mode 100644 index 000000000..4c366ab54 Binary files /dev/null and b/src/app/resources/icons/type/Helper.png differ diff --git a/src/app/resources/icons/type/Helper@2x.png b/src/app/resources/icons/type/Helper@2x.png new file mode 100644 index 000000000..ede52e355 Binary files /dev/null and b/src/app/resources/icons/type/Helper@2x.png differ diff --git a/src/resources/icons/type/Hook.png b/src/app/resources/icons/type/Hook.png similarity index 61% rename from src/resources/icons/type/Hook.png rename to src/app/resources/icons/type/Hook.png index b338384f5..b088fcea9 100644 Binary files a/src/resources/icons/type/Hook.png and b/src/app/resources/icons/type/Hook.png differ diff --git a/src/resources/icons/type/Hook@2x.png b/src/app/resources/icons/type/Hook@2x.png similarity index 70% rename from src/resources/icons/type/Hook@2x.png rename to src/app/resources/icons/type/Hook@2x.png index c2467961f..b0173cb45 100644 Binary files a/src/resources/icons/type/Hook@2x.png and b/src/app/resources/icons/type/Hook@2x.png differ diff --git a/src/app/resources/icons/type/Index.png b/src/app/resources/icons/type/Index.png new file mode 100644 index 000000000..6e2fd9b4a Binary files /dev/null and b/src/app/resources/icons/type/Index.png differ diff --git a/src/resources/icons/type/Index@2x.png b/src/app/resources/icons/type/Index@2x.png similarity index 56% rename from src/resources/icons/type/Index@2x.png rename to src/app/resources/icons/type/Index@2x.png index 599b9ec9c..63c84035b 100644 Binary files a/src/resources/icons/type/Index@2x.png and b/src/app/resources/icons/type/Index@2x.png differ diff --git a/src/app/resources/icons/type/Indirection.png b/src/app/resources/icons/type/Indirection.png new file mode 100644 index 000000000..bbe095a22 Binary files /dev/null and b/src/app/resources/icons/type/Indirection.png differ diff --git a/src/resources/icons/type/Indirection@2x.png b/src/app/resources/icons/type/Indirection@2x.png similarity index 56% rename from src/resources/icons/type/Indirection@2x.png rename to src/app/resources/icons/type/Indirection@2x.png index fc4312c69..e65480386 100644 Binary files a/src/resources/icons/type/Indirection@2x.png and b/src/app/resources/icons/type/Indirection@2x.png differ diff --git a/src/app/resources/icons/type/Inductive.png b/src/app/resources/icons/type/Inductive.png new file mode 100644 index 000000000..bbe095a22 Binary files /dev/null and b/src/app/resources/icons/type/Inductive.png differ diff --git a/src/resources/icons/type/Inductive@2x.png b/src/app/resources/icons/type/Inductive@2x.png similarity index 56% rename from src/resources/icons/type/Inductive@2x.png rename to src/app/resources/icons/type/Inductive@2x.png index fc4312c69..e65480386 100644 Binary files a/src/resources/icons/type/Inductive@2x.png and b/src/app/resources/icons/type/Inductive@2x.png differ diff --git a/src/app/resources/icons/type/Instance.png b/src/app/resources/icons/type/Instance.png new file mode 100644 index 000000000..199b1f66e Binary files /dev/null and b/src/app/resources/icons/type/Instance.png differ diff --git a/src/resources/icons/type/Instance@2x.png b/src/app/resources/icons/type/Instance@2x.png similarity index 56% rename from src/resources/icons/type/Instance@2x.png rename to src/app/resources/icons/type/Instance@2x.png index c206248c2..a2d2e9693 100644 Binary files a/src/resources/icons/type/Instance@2x.png and b/src/app/resources/icons/type/Instance@2x.png differ diff --git a/src/app/resources/icons/type/Instruction.png b/src/app/resources/icons/type/Instruction.png new file mode 100644 index 000000000..6e2fd9b4a Binary files /dev/null and b/src/app/resources/icons/type/Instruction.png differ diff --git a/src/resources/icons/type/Instruction@2x.png b/src/app/resources/icons/type/Instruction@2x.png similarity index 56% rename from src/resources/icons/type/Instruction@2x.png rename to src/app/resources/icons/type/Instruction@2x.png index 599b9ec9c..63c84035b 100644 Binary files a/src/resources/icons/type/Instruction@2x.png and b/src/app/resources/icons/type/Instruction@2x.png differ diff --git a/src/app/resources/icons/type/Interface.png b/src/app/resources/icons/type/Interface.png new file mode 100644 index 000000000..b4843b6ef Binary files /dev/null and b/src/app/resources/icons/type/Interface.png differ diff --git a/src/resources/icons/type/Interface@2x.png b/src/app/resources/icons/type/Interface@2x.png similarity index 53% rename from src/resources/icons/type/Interface@2x.png rename to src/app/resources/icons/type/Interface@2x.png index 53ab08b13..af43e55f3 100644 Binary files a/src/resources/icons/type/Interface@2x.png and b/src/app/resources/icons/type/Interface@2x.png differ diff --git a/src/app/resources/icons/type/Keyword.png b/src/app/resources/icons/type/Keyword.png new file mode 100644 index 000000000..2c0490116 Binary files /dev/null and b/src/app/resources/icons/type/Keyword.png differ diff --git a/src/app/resources/icons/type/Keyword@2x.png b/src/app/resources/icons/type/Keyword@2x.png new file mode 100644 index 000000000..069281065 Binary files /dev/null and b/src/app/resources/icons/type/Keyword@2x.png differ diff --git a/src/app/resources/icons/type/Lemma.png b/src/app/resources/icons/type/Lemma.png new file mode 100644 index 000000000..a9d3fd2fb Binary files /dev/null and b/src/app/resources/icons/type/Lemma.png differ diff --git a/src/app/resources/icons/type/Lemma@2x.png b/src/app/resources/icons/type/Lemma@2x.png new file mode 100644 index 000000000..7a9ea0d04 Binary files /dev/null and b/src/app/resources/icons/type/Lemma@2x.png differ diff --git a/src/app/resources/icons/type/Library.png b/src/app/resources/icons/type/Library.png new file mode 100644 index 000000000..bf458ccfb Binary files /dev/null and b/src/app/resources/icons/type/Library.png differ diff --git a/src/app/resources/icons/type/Library@2x.png b/src/app/resources/icons/type/Library@2x.png new file mode 100644 index 000000000..923d19903 Binary files /dev/null and b/src/app/resources/icons/type/Library@2x.png differ diff --git a/src/app/resources/icons/type/Literal.png b/src/app/resources/icons/type/Literal.png new file mode 100644 index 000000000..bd3b28ebc Binary files /dev/null and b/src/app/resources/icons/type/Literal.png differ diff --git a/src/app/resources/icons/type/Literal@2x.png b/src/app/resources/icons/type/Literal@2x.png new file mode 100644 index 000000000..f8824f518 Binary files /dev/null and b/src/app/resources/icons/type/Literal@2x.png differ diff --git a/src/app/resources/icons/type/Macro.png b/src/app/resources/icons/type/Macro.png new file mode 100644 index 000000000..e13cc63ec Binary files /dev/null and b/src/app/resources/icons/type/Macro.png differ diff --git a/src/app/resources/icons/type/Macro@2x.png b/src/app/resources/icons/type/Macro@2x.png new file mode 100644 index 000000000..1bc6183ac Binary files /dev/null and b/src/app/resources/icons/type/Macro@2x.png differ diff --git a/src/app/resources/icons/type/Method.png b/src/app/resources/icons/type/Method.png new file mode 100644 index 000000000..38ce04947 Binary files /dev/null and b/src/app/resources/icons/type/Method.png differ diff --git a/src/app/resources/icons/type/Method@2x.png b/src/app/resources/icons/type/Method@2x.png new file mode 100644 index 000000000..623b7e9a0 Binary files /dev/null and b/src/app/resources/icons/type/Method@2x.png differ diff --git a/src/app/resources/icons/type/Mixin.png b/src/app/resources/icons/type/Mixin.png new file mode 100644 index 000000000..41d520205 Binary files /dev/null and b/src/app/resources/icons/type/Mixin.png differ diff --git a/src/app/resources/icons/type/Mixin@2x.png b/src/app/resources/icons/type/Mixin@2x.png new file mode 100644 index 000000000..2832b04c7 Binary files /dev/null and b/src/app/resources/icons/type/Mixin@2x.png differ diff --git a/src/app/resources/icons/type/Modifier.png b/src/app/resources/icons/type/Modifier.png new file mode 100644 index 000000000..61830023a Binary files /dev/null and b/src/app/resources/icons/type/Modifier.png differ diff --git a/src/app/resources/icons/type/Modifier@2x.png b/src/app/resources/icons/type/Modifier@2x.png new file mode 100644 index 000000000..9e6cf0fb1 Binary files /dev/null and b/src/app/resources/icons/type/Modifier@2x.png differ diff --git a/src/app/resources/icons/type/Module.png b/src/app/resources/icons/type/Module.png new file mode 100644 index 000000000..658fdc99b Binary files /dev/null and b/src/app/resources/icons/type/Module.png differ diff --git a/src/app/resources/icons/type/Module@2x.png b/src/app/resources/icons/type/Module@2x.png new file mode 100644 index 000000000..cd5c70bad Binary files /dev/null and b/src/app/resources/icons/type/Module@2x.png differ diff --git a/src/app/resources/icons/type/Namespace.png b/src/app/resources/icons/type/Namespace.png new file mode 100644 index 000000000..ece329284 Binary files /dev/null and b/src/app/resources/icons/type/Namespace.png differ diff --git a/src/app/resources/icons/type/Namespace@2x.png b/src/app/resources/icons/type/Namespace@2x.png new file mode 100644 index 000000000..0cbb09794 Binary files /dev/null and b/src/app/resources/icons/type/Namespace@2x.png differ diff --git a/src/app/resources/icons/type/NewSnippet.png b/src/app/resources/icons/type/NewSnippet.png new file mode 100644 index 000000000..9148b87cc Binary files /dev/null and b/src/app/resources/icons/type/NewSnippet.png differ diff --git a/src/app/resources/icons/type/NewSnippet@2x.png b/src/app/resources/icons/type/NewSnippet@2x.png new file mode 100644 index 000000000..914695d3b Binary files /dev/null and b/src/app/resources/icons/type/NewSnippet@2x.png differ diff --git a/src/app/resources/icons/type/Notation.png b/src/app/resources/icons/type/Notation.png new file mode 100644 index 000000000..d4dcf8192 Binary files /dev/null and b/src/app/resources/icons/type/Notation.png differ diff --git a/src/app/resources/icons/type/Notation@2x.png b/src/app/resources/icons/type/Notation@2x.png new file mode 100644 index 000000000..a417138aa Binary files /dev/null and b/src/app/resources/icons/type/Notation@2x.png differ diff --git a/src/app/resources/icons/type/Object.png b/src/app/resources/icons/type/Object.png new file mode 100644 index 000000000..a68029b06 Binary files /dev/null and b/src/app/resources/icons/type/Object.png differ diff --git a/src/app/resources/icons/type/Object@2x.png b/src/app/resources/icons/type/Object@2x.png new file mode 100644 index 000000000..e2a2064ba Binary files /dev/null and b/src/app/resources/icons/type/Object@2x.png differ diff --git a/src/app/resources/icons/type/Operator.png b/src/app/resources/icons/type/Operator.png new file mode 100644 index 000000000..a797cd887 Binary files /dev/null and b/src/app/resources/icons/type/Operator.png differ diff --git a/src/app/resources/icons/type/Operator@2x.png b/src/app/resources/icons/type/Operator@2x.png new file mode 100644 index 000000000..99f517178 Binary files /dev/null and b/src/app/resources/icons/type/Operator@2x.png differ diff --git a/src/app/resources/icons/type/Option.png b/src/app/resources/icons/type/Option.png new file mode 100644 index 000000000..6a4b57ed8 Binary files /dev/null and b/src/app/resources/icons/type/Option.png differ diff --git a/src/app/resources/icons/type/Option@2x.png b/src/app/resources/icons/type/Option@2x.png new file mode 100644 index 000000000..5c4350c21 Binary files /dev/null and b/src/app/resources/icons/type/Option@2x.png differ diff --git a/src/app/resources/icons/type/Package.png b/src/app/resources/icons/type/Package.png new file mode 100644 index 000000000..9c215dc04 Binary files /dev/null and b/src/app/resources/icons/type/Package.png differ diff --git a/src/app/resources/icons/type/Package@2x.png b/src/app/resources/icons/type/Package@2x.png new file mode 100644 index 000000000..1dc2294db Binary files /dev/null and b/src/app/resources/icons/type/Package@2x.png differ diff --git a/src/app/resources/icons/type/Parameter.png b/src/app/resources/icons/type/Parameter.png new file mode 100644 index 000000000..2c8e5069e Binary files /dev/null and b/src/app/resources/icons/type/Parameter.png differ diff --git a/src/app/resources/icons/type/Parameter@2x.png b/src/app/resources/icons/type/Parameter@2x.png new file mode 100644 index 000000000..c3689e4e6 Binary files /dev/null and b/src/app/resources/icons/type/Parameter@2x.png differ diff --git a/src/app/resources/icons/type/Pattern.png b/src/app/resources/icons/type/Pattern.png new file mode 100644 index 000000000..25c6a615e Binary files /dev/null and b/src/app/resources/icons/type/Pattern.png differ diff --git a/src/app/resources/icons/type/Pattern@2x.png b/src/app/resources/icons/type/Pattern@2x.png new file mode 100644 index 000000000..a8a983932 Binary files /dev/null and b/src/app/resources/icons/type/Pattern@2x.png differ diff --git a/src/app/resources/icons/type/Plugin.png b/src/app/resources/icons/type/Plugin.png new file mode 100644 index 000000000..9c215dc04 Binary files /dev/null and b/src/app/resources/icons/type/Plugin.png differ diff --git a/src/app/resources/icons/type/Plugin@2x.png b/src/app/resources/icons/type/Plugin@2x.png new file mode 100644 index 000000000..1dc2294db Binary files /dev/null and b/src/app/resources/icons/type/Plugin@2x.png differ diff --git a/src/app/resources/icons/type/Procedure.png b/src/app/resources/icons/type/Procedure.png new file mode 100644 index 000000000..e6f0525e0 Binary files /dev/null and b/src/app/resources/icons/type/Procedure.png differ diff --git a/src/app/resources/icons/type/Procedure@2x.png b/src/app/resources/icons/type/Procedure@2x.png new file mode 100644 index 000000000..19bdfb107 Binary files /dev/null and b/src/app/resources/icons/type/Procedure@2x.png differ diff --git a/src/app/resources/icons/type/Projection.png b/src/app/resources/icons/type/Projection.png new file mode 100644 index 000000000..e6f0525e0 Binary files /dev/null and b/src/app/resources/icons/type/Projection.png differ diff --git a/src/app/resources/icons/type/Projection@2x.png b/src/app/resources/icons/type/Projection@2x.png new file mode 100644 index 000000000..19bdfb107 Binary files /dev/null and b/src/app/resources/icons/type/Projection@2x.png differ diff --git a/src/app/resources/icons/type/Property.png b/src/app/resources/icons/type/Property.png new file mode 100644 index 000000000..719967d92 Binary files /dev/null and b/src/app/resources/icons/type/Property.png differ diff --git a/src/app/resources/icons/type/Property@2x.png b/src/app/resources/icons/type/Property@2x.png new file mode 100644 index 000000000..a47a3e8ce Binary files /dev/null and b/src/app/resources/icons/type/Property@2x.png differ diff --git a/src/app/resources/icons/type/Protocol.png b/src/app/resources/icons/type/Protocol.png new file mode 100644 index 000000000..a12b9dde8 Binary files /dev/null and b/src/app/resources/icons/type/Protocol.png differ diff --git a/src/app/resources/icons/type/Protocol@2x.png b/src/app/resources/icons/type/Protocol@2x.png new file mode 100644 index 000000000..dce85d2c5 Binary files /dev/null and b/src/app/resources/icons/type/Protocol@2x.png differ diff --git a/src/app/resources/icons/type/Provider.png b/src/app/resources/icons/type/Provider.png new file mode 100644 index 000000000..2c8e5069e Binary files /dev/null and b/src/app/resources/icons/type/Provider.png differ diff --git a/src/app/resources/icons/type/Provider@2x.png b/src/app/resources/icons/type/Provider@2x.png new file mode 100644 index 000000000..c3689e4e6 Binary files /dev/null and b/src/app/resources/icons/type/Provider@2x.png differ diff --git a/src/app/resources/icons/type/Provisioner.png b/src/app/resources/icons/type/Provisioner.png new file mode 100644 index 000000000..e6f0525e0 Binary files /dev/null and b/src/app/resources/icons/type/Provisioner.png differ diff --git a/src/app/resources/icons/type/Provisioner@2x.png b/src/app/resources/icons/type/Provisioner@2x.png new file mode 100644 index 000000000..19bdfb107 Binary files /dev/null and b/src/app/resources/icons/type/Provisioner@2x.png differ diff --git a/src/app/resources/icons/type/Query.png b/src/app/resources/icons/type/Query.png new file mode 100644 index 000000000..5eff5167e Binary files /dev/null and b/src/app/resources/icons/type/Query.png differ diff --git a/src/app/resources/icons/type/Query@2x.png b/src/app/resources/icons/type/Query@2x.png new file mode 100644 index 000000000..eef6cb644 Binary files /dev/null and b/src/app/resources/icons/type/Query@2x.png differ diff --git a/src/resources/icons/type/Record.png b/src/app/resources/icons/type/Record.png similarity index 69% rename from src/resources/icons/type/Record.png rename to src/app/resources/icons/type/Record.png index cb84bf665..bb51ab5f8 100644 Binary files a/src/resources/icons/type/Record.png and b/src/app/resources/icons/type/Record.png differ diff --git a/src/resources/icons/type/Record@2x.png b/src/app/resources/icons/type/Record@2x.png similarity index 78% rename from src/resources/icons/type/Record@2x.png rename to src/app/resources/icons/type/Record@2x.png index bdfc1a0ef..b8ce0176d 100644 Binary files a/src/resources/icons/type/Record@2x.png and b/src/app/resources/icons/type/Record@2x.png differ diff --git a/src/resources/icons/type/Relationship.png b/src/app/resources/icons/type/Relationship.png similarity index 69% rename from src/resources/icons/type/Relationship.png rename to src/app/resources/icons/type/Relationship.png index cb84bf665..bb51ab5f8 100644 Binary files a/src/resources/icons/type/Relationship.png and b/src/app/resources/icons/type/Relationship.png differ diff --git a/src/resources/icons/type/Relationship@2x.png b/src/app/resources/icons/type/Relationship@2x.png similarity index 78% rename from src/resources/icons/type/Relationship@2x.png rename to src/app/resources/icons/type/Relationship@2x.png index bdfc1a0ef..b8ce0176d 100644 Binary files a/src/resources/icons/type/Relationship@2x.png and b/src/app/resources/icons/type/Relationship@2x.png differ diff --git a/src/resources/icons/type/Report.png b/src/app/resources/icons/type/Report.png similarity index 69% rename from src/resources/icons/type/Report.png rename to src/app/resources/icons/type/Report.png index cb84bf665..bb51ab5f8 100644 Binary files a/src/resources/icons/type/Report.png and b/src/app/resources/icons/type/Report.png differ diff --git a/src/resources/icons/type/Report@2x.png b/src/app/resources/icons/type/Report@2x.png similarity index 78% rename from src/resources/icons/type/Report@2x.png rename to src/app/resources/icons/type/Report@2x.png index bdfc1a0ef..b8ce0176d 100644 Binary files a/src/resources/icons/type/Report@2x.png and b/src/app/resources/icons/type/Report@2x.png differ diff --git a/src/app/resources/icons/type/Request.png b/src/app/resources/icons/type/Request.png new file mode 100644 index 000000000..bb51ab5f8 Binary files /dev/null and b/src/app/resources/icons/type/Request.png differ diff --git a/src/app/resources/icons/type/Request@2x.png b/src/app/resources/icons/type/Request@2x.png new file mode 100644 index 000000000..b8ce0176d Binary files /dev/null and b/src/app/resources/icons/type/Request@2x.png differ diff --git a/src/resources/icons/type/Resource.png b/src/app/resources/icons/type/Resource.png similarity index 69% rename from src/resources/icons/type/Resource.png rename to src/app/resources/icons/type/Resource.png index cb84bf665..bb51ab5f8 100644 Binary files a/src/resources/icons/type/Resource.png and b/src/app/resources/icons/type/Resource.png differ diff --git a/src/resources/icons/type/Resource@2x.png b/src/app/resources/icons/type/Resource@2x.png similarity index 78% rename from src/resources/icons/type/Resource@2x.png rename to src/app/resources/icons/type/Resource@2x.png index bdfc1a0ef..b8ce0176d 100644 Binary files a/src/resources/icons/type/Resource@2x.png and b/src/app/resources/icons/type/Resource@2x.png differ diff --git a/src/app/resources/icons/type/Sample.png b/src/app/resources/icons/type/Sample.png new file mode 100644 index 000000000..8f8c54fbb Binary files /dev/null and b/src/app/resources/icons/type/Sample.png differ diff --git a/src/resources/icons/type/Sample@2x.png b/src/app/resources/icons/type/Sample@2x.png similarity index 70% rename from src/resources/icons/type/Sample@2x.png rename to src/app/resources/icons/type/Sample@2x.png index d97ee9509..de38d7ab7 100644 Binary files a/src/resources/icons/type/Sample@2x.png and b/src/app/resources/icons/type/Sample@2x.png differ diff --git a/src/app/resources/icons/type/Schema.png b/src/app/resources/icons/type/Schema.png new file mode 100644 index 000000000..87f6d189b Binary files /dev/null and b/src/app/resources/icons/type/Schema.png differ diff --git a/src/app/resources/icons/type/Schema@2x.png b/src/app/resources/icons/type/Schema@2x.png new file mode 100644 index 000000000..f545cfd12 Binary files /dev/null and b/src/app/resources/icons/type/Schema@2x.png differ diff --git a/src/app/resources/icons/type/Script.png b/src/app/resources/icons/type/Script.png new file mode 100644 index 000000000..f58a61795 Binary files /dev/null and b/src/app/resources/icons/type/Script.png differ diff --git a/src/app/resources/icons/type/Script@2x.png b/src/app/resources/icons/type/Script@2x.png new file mode 100644 index 000000000..416723398 Binary files /dev/null and b/src/app/resources/icons/type/Script@2x.png differ diff --git a/src/app/resources/icons/type/Section.png b/src/app/resources/icons/type/Section.png new file mode 100644 index 000000000..77ff762bf Binary files /dev/null and b/src/app/resources/icons/type/Section.png differ diff --git a/src/resources/icons/type/Section@2x.png b/src/app/resources/icons/type/Section@2x.png similarity index 77% rename from src/resources/icons/type/Section@2x.png rename to src/app/resources/icons/type/Section@2x.png index 37dc5cd35..bcb647b9e 100644 Binary files a/src/resources/icons/type/Section@2x.png and b/src/app/resources/icons/type/Section@2x.png differ diff --git a/src/app/resources/icons/type/Service.png b/src/app/resources/icons/type/Service.png new file mode 100644 index 000000000..87f6d189b Binary files /dev/null and b/src/app/resources/icons/type/Service.png differ diff --git a/src/app/resources/icons/type/Service@2x.png b/src/app/resources/icons/type/Service@2x.png new file mode 100644 index 000000000..f545cfd12 Binary files /dev/null and b/src/app/resources/icons/type/Service@2x.png differ diff --git a/src/app/resources/icons/type/Setting.png b/src/app/resources/icons/type/Setting.png new file mode 100644 index 000000000..6a6460812 Binary files /dev/null and b/src/app/resources/icons/type/Setting.png differ diff --git a/src/app/resources/icons/type/Setting@2x.png b/src/app/resources/icons/type/Setting@2x.png new file mode 100644 index 000000000..65d421626 Binary files /dev/null and b/src/app/resources/icons/type/Setting@2x.png differ diff --git a/src/app/resources/icons/type/Shortcut.png b/src/app/resources/icons/type/Shortcut.png new file mode 100644 index 000000000..5f5dcee85 Binary files /dev/null and b/src/app/resources/icons/type/Shortcut.png differ diff --git a/src/app/resources/icons/type/Shortcut@2x.png b/src/app/resources/icons/type/Shortcut@2x.png new file mode 100644 index 000000000..6be85a650 Binary files /dev/null and b/src/app/resources/icons/type/Shortcut@2x.png differ diff --git a/src/app/resources/icons/type/Signature.png b/src/app/resources/icons/type/Signature.png new file mode 100644 index 000000000..87f6d189b Binary files /dev/null and b/src/app/resources/icons/type/Signature.png differ diff --git a/src/app/resources/icons/type/Signature@2x.png b/src/app/resources/icons/type/Signature@2x.png new file mode 100644 index 000000000..f545cfd12 Binary files /dev/null and b/src/app/resources/icons/type/Signature@2x.png differ diff --git a/src/app/resources/icons/type/Snippet.png b/src/app/resources/icons/type/Snippet.png new file mode 100644 index 000000000..ff43f3e58 Binary files /dev/null and b/src/app/resources/icons/type/Snippet.png differ diff --git a/src/app/resources/icons/type/Snippet@2x.png b/src/app/resources/icons/type/Snippet@2x.png new file mode 100644 index 000000000..7677000a1 Binary files /dev/null and b/src/app/resources/icons/type/Snippet@2x.png differ diff --git a/src/app/resources/icons/type/Special Form.png b/src/app/resources/icons/type/Special Form.png new file mode 100644 index 000000000..3938859f1 Binary files /dev/null and b/src/app/resources/icons/type/Special Form.png differ diff --git a/src/app/resources/icons/type/Special Form@2x.png b/src/app/resources/icons/type/Special Form@2x.png new file mode 100644 index 000000000..7b7d466b8 Binary files /dev/null and b/src/app/resources/icons/type/Special Form@2x.png differ diff --git a/src/app/resources/icons/type/Statement.png b/src/app/resources/icons/type/Statement.png new file mode 100644 index 000000000..87f6d189b Binary files /dev/null and b/src/app/resources/icons/type/Statement.png differ diff --git a/src/app/resources/icons/type/Statement@2x.png b/src/app/resources/icons/type/Statement@2x.png new file mode 100644 index 000000000..f545cfd12 Binary files /dev/null and b/src/app/resources/icons/type/Statement@2x.png differ diff --git a/src/app/resources/icons/type/Structure.png b/src/app/resources/icons/type/Structure.png new file mode 100644 index 000000000..31db77582 Binary files /dev/null and b/src/app/resources/icons/type/Structure.png differ diff --git a/src/app/resources/icons/type/Structure@2x.png b/src/app/resources/icons/type/Structure@2x.png new file mode 100644 index 000000000..1895f9e73 Binary files /dev/null and b/src/app/resources/icons/type/Structure@2x.png differ diff --git a/src/app/resources/icons/type/Style.png b/src/app/resources/icons/type/Style.png new file mode 100644 index 000000000..6a6460812 Binary files /dev/null and b/src/app/resources/icons/type/Style.png differ diff --git a/src/app/resources/icons/type/Style@2x.png b/src/app/resources/icons/type/Style@2x.png new file mode 100644 index 000000000..65d421626 Binary files /dev/null and b/src/app/resources/icons/type/Style@2x.png differ diff --git a/src/app/resources/icons/type/Subroutine.png b/src/app/resources/icons/type/Subroutine.png new file mode 100644 index 000000000..6a6460812 Binary files /dev/null and b/src/app/resources/icons/type/Subroutine.png differ diff --git a/src/app/resources/icons/type/Subroutine@2x.png b/src/app/resources/icons/type/Subroutine@2x.png new file mode 100644 index 000000000..65d421626 Binary files /dev/null and b/src/app/resources/icons/type/Subroutine@2x.png differ diff --git a/src/app/resources/icons/type/Syntax.png b/src/app/resources/icons/type/Syntax.png new file mode 100644 index 000000000..f58a61795 Binary files /dev/null and b/src/app/resources/icons/type/Syntax.png differ diff --git a/src/app/resources/icons/type/Syntax@2x.png b/src/app/resources/icons/type/Syntax@2x.png new file mode 100644 index 000000000..416723398 Binary files /dev/null and b/src/app/resources/icons/type/Syntax@2x.png differ diff --git a/src/resources/icons/type/Table.png b/src/app/resources/icons/type/Table.png similarity index 56% rename from src/resources/icons/type/Table.png rename to src/app/resources/icons/type/Table.png index 93e2aa2b1..19f520a7a 100644 Binary files a/src/resources/icons/type/Table.png and b/src/app/resources/icons/type/Table.png differ diff --git a/src/app/resources/icons/type/Table@2x.png b/src/app/resources/icons/type/Table@2x.png new file mode 100644 index 000000000..2790afe7e Binary files /dev/null and b/src/app/resources/icons/type/Table@2x.png differ diff --git a/src/app/resources/icons/type/Tactic.png b/src/app/resources/icons/type/Tactic.png new file mode 100644 index 000000000..572ae6718 Binary files /dev/null and b/src/app/resources/icons/type/Tactic.png differ diff --git a/src/app/resources/icons/type/Tactic@2x.png b/src/app/resources/icons/type/Tactic@2x.png new file mode 100644 index 000000000..e24f8d378 Binary files /dev/null and b/src/app/resources/icons/type/Tactic@2x.png differ diff --git a/src/resources/icons/type/Tag.png b/src/app/resources/icons/type/Tag.png similarity index 56% rename from src/resources/icons/type/Tag.png rename to src/app/resources/icons/type/Tag.png index 93e2aa2b1..19f520a7a 100644 Binary files a/src/resources/icons/type/Tag.png and b/src/app/resources/icons/type/Tag.png differ diff --git a/src/app/resources/icons/type/Tag@2x.png b/src/app/resources/icons/type/Tag@2x.png new file mode 100644 index 000000000..2790afe7e Binary files /dev/null and b/src/app/resources/icons/type/Tag@2x.png differ diff --git a/src/app/resources/icons/type/Test.png b/src/app/resources/icons/type/Test.png new file mode 100644 index 000000000..572ae6718 Binary files /dev/null and b/src/app/resources/icons/type/Test.png differ diff --git a/src/app/resources/icons/type/Test@2x.png b/src/app/resources/icons/type/Test@2x.png new file mode 100644 index 000000000..e24f8d378 Binary files /dev/null and b/src/app/resources/icons/type/Test@2x.png differ diff --git a/src/resources/icons/type/Trait.png b/src/app/resources/icons/type/Trait.png similarity index 56% rename from src/resources/icons/type/Trait.png rename to src/app/resources/icons/type/Trait.png index 93e2aa2b1..19f520a7a 100644 Binary files a/src/resources/icons/type/Trait.png and b/src/app/resources/icons/type/Trait.png differ diff --git a/src/app/resources/icons/type/Trait@2x.png b/src/app/resources/icons/type/Trait@2x.png new file mode 100644 index 000000000..2790afe7e Binary files /dev/null and b/src/app/resources/icons/type/Trait@2x.png differ diff --git a/src/app/resources/icons/type/Trigger.png b/src/app/resources/icons/type/Trigger.png new file mode 100644 index 000000000..572ae6718 Binary files /dev/null and b/src/app/resources/icons/type/Trigger.png differ diff --git a/src/app/resources/icons/type/Trigger@2x.png b/src/app/resources/icons/type/Trigger@2x.png new file mode 100644 index 000000000..e24f8d378 Binary files /dev/null and b/src/app/resources/icons/type/Trigger@2x.png differ diff --git a/src/app/resources/icons/type/Type.png b/src/app/resources/icons/type/Type.png new file mode 100644 index 000000000..70dfe4d7b Binary files /dev/null and b/src/app/resources/icons/type/Type.png differ diff --git a/src/app/resources/icons/type/Type@2x.png b/src/app/resources/icons/type/Type@2x.png new file mode 100644 index 000000000..78850a363 Binary files /dev/null and b/src/app/resources/icons/type/Type@2x.png differ diff --git a/src/app/resources/icons/type/Union.png b/src/app/resources/icons/type/Union.png new file mode 100644 index 000000000..46810c093 Binary files /dev/null and b/src/app/resources/icons/type/Union.png differ diff --git a/src/app/resources/icons/type/Union@2x.png b/src/app/resources/icons/type/Union@2x.png new file mode 100644 index 000000000..c09361058 Binary files /dev/null and b/src/app/resources/icons/type/Union@2x.png differ diff --git a/src/resources/icons/type/Unknown.png b/src/app/resources/icons/type/Unknown.png similarity index 60% rename from src/resources/icons/type/Unknown.png rename to src/app/resources/icons/type/Unknown.png index 0aa339922..de57ae505 100644 Binary files a/src/resources/icons/type/Unknown.png and b/src/app/resources/icons/type/Unknown.png differ diff --git a/src/app/resources/icons/type/Unknown@2x.png b/src/app/resources/icons/type/Unknown@2x.png new file mode 100644 index 000000000..295522df8 Binary files /dev/null and b/src/app/resources/icons/type/Unknown@2x.png differ diff --git a/src/app/resources/icons/type/Value.png b/src/app/resources/icons/type/Value.png new file mode 100644 index 000000000..8a1498766 Binary files /dev/null and b/src/app/resources/icons/type/Value.png differ diff --git a/src/app/resources/icons/type/Value@2x.png b/src/app/resources/icons/type/Value@2x.png new file mode 100644 index 000000000..fcabbdbf0 Binary files /dev/null and b/src/app/resources/icons/type/Value@2x.png differ diff --git a/src/app/resources/icons/type/Variable.png b/src/app/resources/icons/type/Variable.png new file mode 100644 index 000000000..84cee5014 Binary files /dev/null and b/src/app/resources/icons/type/Variable.png differ diff --git a/src/app/resources/icons/type/Variable@2x.png b/src/app/resources/icons/type/Variable@2x.png new file mode 100644 index 000000000..4be45091e Binary files /dev/null and b/src/app/resources/icons/type/Variable@2x.png differ diff --git a/src/app/resources/icons/type/Variant.png b/src/app/resources/icons/type/Variant.png new file mode 100644 index 000000000..8a1498766 Binary files /dev/null and b/src/app/resources/icons/type/Variant.png differ diff --git a/src/app/resources/icons/type/Variant@2x.png b/src/app/resources/icons/type/Variant@2x.png new file mode 100644 index 000000000..fcabbdbf0 Binary files /dev/null and b/src/app/resources/icons/type/Variant@2x.png differ diff --git a/src/app/resources/icons/type/View.png b/src/app/resources/icons/type/View.png new file mode 100644 index 000000000..6b20c6ff5 Binary files /dev/null and b/src/app/resources/icons/type/View.png differ diff --git a/src/app/resources/icons/type/View@2x.png b/src/app/resources/icons/type/View@2x.png new file mode 100644 index 000000000..8fc7158b7 Binary files /dev/null and b/src/app/resources/icons/type/View@2x.png differ diff --git a/src/resources/icons/type/Web.png b/src/app/resources/icons/type/Web.png similarity index 80% rename from src/resources/icons/type/Web.png rename to src/app/resources/icons/type/Web.png index d7a026469..942b17c83 100644 Binary files a/src/resources/icons/type/Web.png and b/src/app/resources/icons/type/Web.png differ diff --git a/src/resources/icons/type/Web@2x.png b/src/app/resources/icons/type/Web@2x.png similarity index 96% rename from src/resources/icons/type/Web@2x.png rename to src/app/resources/icons/type/Web@2x.png index 3cc37b1a5..2949c5a86 100644 Binary files a/src/resources/icons/type/Web@2x.png and b/src/app/resources/icons/type/Web@2x.png differ diff --git a/src/app/resources/icons/type/Word.png b/src/app/resources/icons/type/Word.png new file mode 100644 index 000000000..f8f1d9649 Binary files /dev/null and b/src/app/resources/icons/type/Word.png differ diff --git a/src/app/resources/icons/type/Word@2x.png b/src/app/resources/icons/type/Word@2x.png new file mode 100644 index 000000000..30a7c2563 Binary files /dev/null and b/src/app/resources/icons/type/Word@2x.png differ diff --git a/src/app/resources/icons/type/_DashAnnotations.png b/src/app/resources/icons/type/_DashAnnotations.png new file mode 100644 index 000000000..ce249000a Binary files /dev/null and b/src/app/resources/icons/type/_DashAnnotations.png differ diff --git a/src/app/resources/icons/type/_DashAnnotations@2x.png b/src/app/resources/icons/type/_DashAnnotations@2x.png new file mode 100644 index 000000000..cbf63fa8d Binary files /dev/null and b/src/app/resources/icons/type/_DashAnnotations@2x.png differ diff --git a/src/app/resources/icons/type/_Struct.png b/src/app/resources/icons/type/_Struct.png new file mode 100644 index 000000000..ac1ad436e Binary files /dev/null and b/src/app/resources/icons/type/_Struct.png differ diff --git a/src/app/resources/icons/type/_Struct@2x.png b/src/app/resources/icons/type/_Struct@2x.png new file mode 100644 index 000000000..0fce0e833 Binary files /dev/null and b/src/app/resources/icons/type/_Struct@2x.png differ diff --git a/src/resources/zeal.icns b/src/app/resources/zeal.icns similarity index 100% rename from src/resources/zeal.icns rename to src/app/resources/zeal.icns diff --git a/src/resources/zeal.ico b/src/app/resources/zeal.ico similarity index 100% rename from src/resources/zeal.ico rename to src/app/resources/zeal.ico diff --git a/src/resources/zeal.qrc b/src/app/resources/zeal.qrc similarity index 93% rename from src/resources/zeal.qrc rename to src/app/resources/zeal.qrc index 9b592bac0..b61104e47 100644 --- a/src/resources/zeal.qrc +++ b/src/app/resources/zeal.qrc @@ -30,6 +30,8 @@ icons/type/Category@2x.png icons/type/Class.png icons/type/Class@2x.png + icons/type/Collection.png + icons/type/Collection@2x.png icons/type/Column.png icons/type/Column@2x.png icons/type/Command.png @@ -44,6 +46,8 @@ icons/type/Conversion@2x.png icons/type/Database.png icons/type/Database@2x.png + icons/type/Decorator.png + icons/type/Decorator@2x.png icons/type/Define.png icons/type/Define@2x.png icons/type/Delegate.png @@ -86,6 +90,8 @@ icons/type/Global@2x.png icons/type/Guide.png icons/type/Guide@2x.png + icons/type/Helper.png + icons/type/Helper@2x.png icons/type/Hook.png icons/type/Hook@2x.png icons/type/Index.png @@ -134,6 +140,8 @@ icons/type/Package@2x.png icons/type/Parameter.png icons/type/Parameter@2x.png + icons/type/Pattern.png + icons/type/Pattern@2x.png icons/type/Plugin.png icons/type/Plugin@2x.png icons/type/Procedure.png @@ -156,6 +164,8 @@ icons/type/Relationship@2x.png icons/type/Report.png icons/type/Report@2x.png + icons/type/Request.png + icons/type/Request@2x.png icons/type/Resource.png icons/type/Resource@2x.png icons/type/Sample.png @@ -172,6 +182,8 @@ icons/type/Setting@2x.png icons/type/Shortcut.png icons/type/Shortcut@2x.png + icons/type/Signature.png + icons/type/Signature@2x.png icons/type/Snippet.png icons/type/Snippet@2x.png icons/type/Special Form.png @@ -210,6 +222,8 @@ icons/type/Variable@2x.png icons/type/Variant.png icons/type/Variant@2x.png + icons/type/View.png + icons/type/View@2x.png icons/type/Web.png icons/type/Web@2x.png icons/type/Word.png diff --git a/src/core/settings.cpp b/src/core/settings.cpp deleted file mode 100644 index 5a7342045..000000000 --- a/src/core/settings.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html -** -** This file is part of Zeal. -** -** Zeal 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 3 of the License, or -** (at your option) any later version. -** -** Zeal 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 Zeal. If not, see . -** -****************************************************************************/ - -#include "settings.h" - -#include -#include -#include -#include -#include -#include - -#ifdef USE_WEBENGINE -#include -#define QWebSettings QWebEngineSettings -#else -#include -#endif - -namespace { -// Configuration file groups -const char GroupBrowser[] = "browser"; -const char GroupDocsets[] = "docsets"; -const char GroupGlobalShortcuts[] = "global_shortcuts"; -const char GroupInternal[] = "internal"; -const char GroupState[] = "state"; -const char GroupProxy[] = "proxy"; -} - -using namespace Zeal::Core; - -Settings::Settings(QObject *parent) : - QObject(parent), - #ifndef PORTABLE_BUILD - m_settings(new QSettings(this)) - #else - m_settings(new QSettings(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"), - QSettings::IniFormat, this)) - #endif -{ - /// TODO: Move to user style sheet (related to #268) -#ifndef USE_WEBENGINE - QWebSettings::globalSettings()->setUserStyleSheetUrl(QStringLiteral("qrc:///browser/highlight.css")); -#endif - - load(); -} - -Settings::~Settings() -{ - save(); -} - -void Settings::load() -{ - /// TODO: Put everything in groups - startMinimized = m_settings->value(QStringLiteral("start_minimized"), false).toBool(); - checkForUpdate = m_settings->value(QStringLiteral("check_for_update"), true).toBool(); - - showSystrayIcon = m_settings->value(QStringLiteral("show_systray_icon"), true).toBool(); - minimizeToSystray = m_settings->value(QStringLiteral("minimize_to_systray"), false).toBool(); - hideOnClose = m_settings->value(QStringLiteral("hide_on_close"), false).toBool(); - - m_settings->beginGroup(GroupGlobalShortcuts); -#ifndef Q_OS_OSX - showShortcut = m_settings->value(QStringLiteral("show"), QStringLiteral("Meta+Z")).value(); -#else - showShortcut = m_settings->value(QStringLiteral("show"), QStringLiteral("Alt+Space")).value(); -#endif - m_settings->endGroup(); - - m_settings->beginGroup(GroupBrowser); - minimumFontSize = m_settings->value(QStringLiteral("minimum_font_size"), - QWebSettings::globalSettings()->fontSize(QWebSettings::MinimumFontSize)).toInt(); - m_settings->endGroup(); - - m_settings->beginGroup(GroupProxy); - proxyType = static_cast(m_settings->value(QStringLiteral("type"), - ProxyType::System).toUInt()); - proxyHost = m_settings->value(QStringLiteral("host")).toString(); - proxyPort = m_settings->value(QStringLiteral("port"), 0).toInt(); - proxyAuthenticate = m_settings->value(QStringLiteral("authenticate"), false).toBool(); - proxyUserName = m_settings->value(QStringLiteral("username")).toString(); - proxyPassword = m_settings->value(QStringLiteral("password")).toString(); - m_settings->endGroup(); - - m_settings->beginGroup(GroupDocsets); - if (m_settings->contains(QStringLiteral("path"))) { - docsetPath = m_settings->value(QStringLiteral("path")).toString(); - } else { -#ifndef PORTABLE_BUILD - docsetPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + QLatin1String("/docsets"); -#else - docsetPath = QCoreApplication::applicationDirPath() + QLatin1String("/docsets"); -#endif - QDir().mkpath(docsetPath); - } - m_settings->endGroup(); - - m_settings->beginGroup(GroupState); - windowGeometry = m_settings->value(QStringLiteral("window_geometry")).toByteArray(); - splitterGeometry = m_settings->value(QStringLiteral("splitter_geometry")).toByteArray(); - m_settings->endGroup(); - - m_settings->beginGroup(GroupInternal); - installId = m_settings->value(QStringLiteral("install_id"), - // Avoid curly braces (QTBUG-885) - QUuid::createUuid().toString().mid(1, 36)).toString(); - version = m_settings->value(QStringLiteral("version"), - QCoreApplication::applicationVersion()).toString(); - m_settings->endGroup(); -} - -void Settings::save() -{ - /// TODO: Put everything in groups - m_settings->setValue(QStringLiteral("start_minimized"), startMinimized); - m_settings->setValue(QStringLiteral("check_for_update"), checkForUpdate); - - m_settings->setValue(QStringLiteral("show_systray_icon"), showSystrayIcon); - m_settings->setValue(QStringLiteral("minimize_to_systray"), minimizeToSystray); - m_settings->setValue(QStringLiteral("hide_on_close"), hideOnClose); - - m_settings->beginGroup(GroupGlobalShortcuts); - m_settings->setValue(QStringLiteral("show"), showShortcut); - m_settings->endGroup(); - - m_settings->beginGroup(GroupBrowser); - m_settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize); - m_settings->endGroup(); - - m_settings->beginGroup(GroupProxy); - m_settings->setValue(QStringLiteral("type"), proxyType); - m_settings->setValue(QStringLiteral("host"), proxyHost); - m_settings->setValue(QStringLiteral("port"), proxyPort); - m_settings->setValue(QStringLiteral("authenticate"), proxyAuthenticate); - m_settings->setValue(QStringLiteral("username"), proxyUserName); - m_settings->setValue(QStringLiteral("password"), proxyPassword); - m_settings->endGroup(); - -#ifndef PORTABLE_BUILD - m_settings->beginGroup(GroupDocsets); - m_settings->setValue(QStringLiteral("path"), docsetPath); - m_settings->endGroup(); -#endif - - m_settings->beginGroup(GroupState); - m_settings->setValue(QStringLiteral("window_geometry"), windowGeometry); - m_settings->setValue(QStringLiteral("splitter_geometry"), splitterGeometry); - m_settings->endGroup(); - - m_settings->beginGroup(GroupInternal); - m_settings->setValue(QStringLiteral("install_id"), installId); - m_settings->setValue(QStringLiteral("version"), QCoreApplication::applicationVersion()); - m_settings->endGroup(); - - m_settings->sync(); - - emit updated(); -} diff --git a/src/core/application.cpp b/src/libs/core/application.cpp similarity index 79% rename from src/core/application.cpp rename to src/libs/core/application.cpp index 95c50aabc..c2f500c5c 100644 --- a/src/core/application.cpp +++ b/src/libs/core/application.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -24,21 +24,21 @@ #include "extractor.h" #include "settings.h" -#include "registry/docsetregistry.h" -#include "registry/searchquery.h" -#include "ui/mainwindow.h" -#include "util/version.h" + +#include +#include +#include +#include #include #include #include #include -#include -#include #include #include #include #include +#include #include #include @@ -46,18 +46,12 @@ using namespace Zeal; using namespace Zeal::Core; namespace { -const char LocalServerName[] = "ZealLocalServer"; const char ReleasesApiUrl[] = "http://api.zealdocs.org/v1/releases"; } Application *Application::m_instance = nullptr; Application::Application(QObject *parent) : - Application(SearchQuery(), parent) -{ -} - -Application::Application(const SearchQuery &query, QObject *parent) : QObject(parent) { // Ensure only one instance of Application @@ -65,35 +59,11 @@ Application::Application(const SearchQuery &query, QObject *parent) : m_instance = this; m_settings = new Settings(this); - m_localServer = new QLocalServer(this); m_networkManager = new QNetworkAccessManager(this); - m_extractorThread = new QThread(this); - m_extractor = new Extractor(); - - m_docsetRegistry = new DocsetRegistry(); - m_docsetRegistry->init(m_settings->docsetPath); - - m_mainWindow = new MainWindow(this); - - // Server for detecting already running instances - connect(m_localServer, &QLocalServer::newConnection, [this]() { - QLocalSocket *connection = m_localServer->nextPendingConnection(); - // Wait a little while the other side writes the bytes - connection->waitForReadyRead(); - if (connection->bytesAvailable()) { - QDataStream in(connection); - Zeal::SearchQuery query; - in >> query; - m_mainWindow->bringToFront(query); - } else { - m_mainWindow->bringToFront(); - } - }); - /// TODO: Verify if removeServer() is needed - QLocalServer::removeServer(LocalServerName); // remove in case previous instance crashed - m_localServer->listen(LocalServerName); // Extractor setup + m_extractorThread = new QThread(this); + m_extractor = new Extractor(); m_extractor->moveToThread(m_extractorThread); m_extractorThread->start(); connect(m_extractor, &Extractor::completed, this, &Application::extractionCompleted); @@ -103,10 +73,19 @@ Application::Application(const SearchQuery &query, QObject *parent) : connect(m_settings, &Settings::updated, this, &Application::applySettings); applySettings(); - if (!query.isEmpty()) - m_mainWindow->bringToFront(query); - else if (!m_settings->startMinimized) + m_docsetRegistry = new Registry::DocsetRegistry(); + m_docsetRegistry->init(m_settings->docsetPath); + + m_mainWindow = new MainWindow(this); + + if (m_settings->startMinimized) { + if (m_settings->showSystrayIcon && m_settings->minimizeToSystray) + return; + + m_mainWindow->showMinimized(); + } else { m_mainWindow->show(); + } } Application::~Application() @@ -118,9 +97,14 @@ Application::~Application() delete m_docsetRegistry; } -QString Application::localServerName() +/*! + * \internal + * \brief Returns a pointer to the Core::Application instance. + * \return A pointer or \c nullptr, if no instance has been created. + */ +Application *Application::instance() { - return LocalServerName; + return m_instance; } QNetworkAccessManager *Application::networkManager() const @@ -133,9 +117,19 @@ Settings *Application::settings() const return m_settings; } -DocsetRegistry *Application::docsetRegistry() +Registry::DocsetRegistry *Application::docsetRegistry() { - return m_instance->m_docsetRegistry; + return m_docsetRegistry; +} + +void Application::executeQuery(const Registry::SearchQuery &query, bool preventActivation) +{ + m_mainWindow->search(query); + + if (preventActivation) + return; + + m_mainWindow->bringToFront(); } void Application::extract(const QString &filePath, const QString &destination, const QString &root) @@ -148,13 +142,13 @@ void Application::extract(const QString &filePath, const QString &destination, c QNetworkReply *Application::download(const QUrl &url) { static const QString ua = userAgent(); - static const QString uaJson = userAgentJson(); + static const QByteArray uaJson = userAgentJson().toUtf8(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::UserAgentHeader, ua); if (url.host().endsWith(QLatin1String(".zealdocs.org", Qt::CaseInsensitive))) - request.setRawHeader("X-Zeal-User-Agent", uaJson.toUtf8()); + request.setRawHeader("X-Zeal-User-Agent", uaJson); return m_networkManager->get(request); } @@ -165,7 +159,7 @@ QNetworkReply *Application::download(const QUrl &url) Performs a check whether a new Zeal version is available. Setting \a quiet to true supresses error and "you are using the latest version" message boxes. */ -void Application::checkForUpdate(bool quiet) +void Application::checkForUpdates(bool quiet) { QNetworkReply *reply = download(QUrl(ReleasesApiUrl)); connect(reply, &QNetworkReply::finished, this, [this, quiet]() { @@ -222,12 +216,12 @@ void Application::applySettings() QString Application::userAgent() { - return QString("Zeal/%1").arg(QCoreApplication::applicationVersion()); + return QStringLiteral("Zeal/%1").arg(QCoreApplication::applicationVersion()); } QString Application::userAgentJson() const { - /// TODO: [Qt 5.4] Remove else branch + // TODO: [Qt 5.4] Remove else branch #if QT_VERSION >= 0x050400 QJsonObject app = { {QStringLiteral("version"), QCoreApplication::applicationVersion()}, @@ -290,5 +284,5 @@ QString Application::userAgentJson() const #endif // QT_VERSION >= 0x050400 - return QJsonDocument(ua).toJson(QJsonDocument::Compact); + return QString::fromUtf8(QJsonDocument(ua).toJson(QJsonDocument::Compact)); } diff --git a/src/core/application.h b/src/libs/core/application.h similarity index 82% rename from src/core/application.h rename to src/libs/core/application.h index 7d2fdf594..749fc8ca7 100644 --- a/src/core/application.h +++ b/src/libs/core/application.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -25,18 +25,18 @@ #include -class QLocalServer; - -class MainWindow; - class QNetworkAccessManager; class QNetworkReply; class QThread; +class MainWindow; + namespace Zeal { +namespace Registry { class DocsetRegistry; class SearchQuery; +} // namespace Registry namespace Core { @@ -48,20 +48,20 @@ class Application : public QObject Q_OBJECT public: explicit Application(QObject *parent = nullptr); - explicit Application(const SearchQuery &query, QObject *parent = nullptr); ~Application() override; - static QString localServerName(); + static Application *instance(); QNetworkAccessManager *networkManager() const; Settings *settings() const; - static DocsetRegistry *docsetRegistry(); + Registry::DocsetRegistry *docsetRegistry(); public slots: + void executeQuery(const Registry::SearchQuery &query, bool preventActivation); void extract(const QString &filePath, const QString &destination, const QString &root = QString()); QNetworkReply *download(const QUrl &url); - void checkForUpdate(bool quiet = false); + void checkForUpdates(bool quiet = false); signals: void extractionCompleted(const QString &filePath); @@ -81,13 +81,12 @@ private slots: Settings *m_settings = nullptr; - QLocalServer *m_localServer = nullptr; QNetworkAccessManager *m_networkManager = nullptr; QThread *m_extractorThread = nullptr; Extractor *m_extractor = nullptr; - DocsetRegistry *m_docsetRegistry = nullptr; + Registry::DocsetRegistry *m_docsetRegistry = nullptr; MainWindow *m_mainWindow = nullptr; }; diff --git a/src/core/core.pri b/src/libs/core/core.pri similarity index 65% rename from src/core/core.pri rename to src/libs/core/core.pri index a9823b8e2..0f7c62ea3 100644 --- a/src/core/core.pri +++ b/src/libs/core/core.pri @@ -1,5 +1,6 @@ -HEADERS += $$files($$PWD/*.h) -SOURCES += $$files($$PWD/*.cpp) +ZEAL_LIB_NAME = Core + +QT += network unix:!macx { CONFIG += link_pkgconfig diff --git a/src/libs/core/core.pro b/src/libs/core/core.pro new file mode 100644 index 000000000..1c61c1c65 --- /dev/null +++ b/src/libs/core/core.pro @@ -0,0 +1,4 @@ +include($$ZEAL_LIBRARY_PRI) + +HEADERS += $$files(*.h) +SOURCES += $$files(*.cpp) diff --git a/src/core/extractor.cpp b/src/libs/core/extractor.cpp similarity index 84% rename from src/core/extractor.cpp rename to src/libs/core/extractor.cpp index 1fd38a4ef..97c68e014 100644 --- a/src/core/extractor.cpp +++ b/src/libs/core/extractor.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -60,10 +60,15 @@ void Extractor::extract(const QString &filePath, const QString &destination, con if (!root.isEmpty()) destinationDir = destinationDir.absoluteFilePath(root); - /// TODO: Do not strip root directory in archive if it equals to 'root' + // TODO: Do not strip root directory in archive if it equals to 'root' archive_entry *entry; while (archive_read_next_header(info.archiveHandle, &entry) == ARCHIVE_OK) { - QString pathname = archive_entry_pathname(entry); +#ifndef Q_OS_WIN32 + QString pathname = QString::fromUtf8(archive_entry_pathname(entry)); +#else + // TODO: Remove once https://github.com/libarchive/libarchive/issues/587 is resolved. + QString pathname = QString::fromWCharArray(archive_entry_pathname_w(entry)); +#endif if (!root.isEmpty()) pathname.remove(0, pathname.indexOf(QLatin1String("/")) + 1); archive_entry_set_pathname(entry, qPrintable(destinationDir.absoluteFilePath(pathname))); diff --git a/src/core/extractor.h b/src/libs/core/extractor.h similarity index 91% rename from src/core/extractor.h rename to src/libs/core/extractor.h index 605384ca0..1d09191c9 100644 --- a/src/core/extractor.h +++ b/src/libs/core/extractor.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/libs/core/localserver.cpp b/src/libs/core/localserver.cpp new file mode 100644 index 000000000..efac2193e --- /dev/null +++ b/src/libs/core/localserver.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#include "localserver.h" + +#include + +#include +#include +#include +#include + +using namespace Zeal; +using namespace Zeal::Core; + +namespace { +const char LocalServerName[] = "ZealLocalServer"; +} + +LocalServer::LocalServer(QObject *parent) + : QObject(parent) + , m_localServer(new QLocalServer(this)) +{ + connect(m_localServer, &QLocalServer::newConnection, [this]() { + QScopedPointer connection(m_localServer->nextPendingConnection()); + // Wait while the other side writes the data. + if (!connection->waitForReadyRead(500)) + return; + + QDataStream in(connection.data()); + Registry::SearchQuery query; + bool preventActivation; + in >> query; + in >> preventActivation; + + emit newQuery(query, preventActivation); + }); +} + +/*! + * \internal + * \brief Returns the error message about the last occured error. + * \return Human-readable error message, or an empty string. + */ +QString LocalServer::errorString() const +{ + return m_localServer->errorString(); +} + +/*! + * \internal + * \brief Instructs server to listen for incoming connections. + * \param force If \c true, an attempt to remove socket file will be made. No effect on Windows. + * \return \c true if successful, \c false otherwise. + */ +bool LocalServer::start(bool force) +{ +#ifndef Q_OS_WIN32 + if (force && !QLocalServer::removeServer(LocalServerName)) { + return false; + } +#else + Q_UNUSED(force) +#endif + + return m_localServer->listen(LocalServerName); +} + +/*! + * \internal + * \brief Sends \a query to an already running application instance, if it exists. + * \param query A query to execute search with. + * \param preventActivation If \c true, application window will not activated. + * \return \c true if communication with another instance has been successful. + */ +bool LocalServer::sendQuery(const Registry::SearchQuery &query, bool preventActivation) +{ + QScopedPointer socket(new QLocalSocket()); + socket->connectToServer(LocalServerName); + + if (!socket->waitForConnected(500)) + return false; + + QDataStream out(socket.data()); + out << query; + out << preventActivation; + socket->flush(); + return true; +} diff --git a/src/libs/core/localserver.h b/src/libs/core/localserver.h new file mode 100644 index 000000000..034dea028 --- /dev/null +++ b/src/libs/core/localserver.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_CORE_LOCALSERVER_H +#define ZEAL_CORE_LOCALSERVER_H + +#include + +class QLocalServer; + +namespace Zeal { + +namespace Registry { +class SearchQuery; +} // namespace Registry + +namespace Core { + +class LocalServer : public QObject +{ + Q_OBJECT +public: + explicit LocalServer(QObject *parent = 0); + + QString errorString() const; + + bool start(bool force = false); + + static bool sendQuery(const Registry::SearchQuery &query, bool preventActivation); + +signals: + void newQuery(const Registry::SearchQuery &query, bool preventActivation); + +private: + QLocalServer *m_localServer = nullptr; +}; + +} // namespace Core +} // namespace Zeal + +#endif // ZEAL_CORE_LOCALSERVER_H diff --git a/src/libs/core/settings.cpp b/src/libs/core/settings.cpp new file mode 100644 index 000000000..cffdc9549 --- /dev/null +++ b/src/libs/core/settings.cpp @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#include "settings.h" + +#include +#include +#include +#include +#include +#include + +#ifdef USE_WEBENGINE +#include +typedef QWebEngineSettings QWebSettings; +#else +#include +#endif + +namespace { +// Configuration file groups +const char GroupBrowser[] = "browser"; +const char GroupDocsets[] = "docsets"; +const char GroupGlobalShortcuts[] = "global_shortcuts"; +const char GroupTabs[] = "tabs"; +const char GroupInternal[] = "internal"; +const char GroupState[] = "state"; +const char GroupProxy[] = "proxy"; +} + +using namespace Zeal::Core; + +Settings::Settings(QObject *parent) : + QObject(parent) +{ + // TODO: Move to user style sheet (related to #268) +#ifndef USE_WEBENGINE + QWebSettings::globalSettings() + ->setUserStyleSheetUrl(QUrl(QStringLiteral("qrc:///browser/highlight.css"))); +#endif + + load(); +} + +Settings::~Settings() +{ + save(); +} + +void Settings::load() +{ + QScopedPointer settings(qsettings()); + migrate(settings.data()); + + // TODO: Put everything in groups + startMinimized = settings->value(QStringLiteral("start_minimized"), false).toBool(); + checkForUpdate = settings->value(QStringLiteral("check_for_update"), true).toBool(); + + showSystrayIcon = settings->value(QStringLiteral("show_systray_icon"), true).toBool(); + minimizeToSystray = settings->value(QStringLiteral("minimize_to_systray"), false).toBool(); + hideOnClose = settings->value(QStringLiteral("hide_on_close"), false).toBool(); + + settings->beginGroup(GroupGlobalShortcuts); + showShortcut = settings->value(QStringLiteral("show")).value(); + settings->endGroup(); + + settings->beginGroup(GroupTabs); + openNewTabAfterActive = settings->value(QStringLiteral("open_new_tab_after_active"), false).toBool(); + settings->endGroup(); + + settings->beginGroup(GroupBrowser); + minimumFontSize = settings->value(QStringLiteral("minimum_font_size"), + QWebSettings::globalSettings()->fontSize(QWebSettings::MinimumFontSize)).toInt(); + QWebSettings::globalSettings()->setFontSize(QWebSettings::MinimumFontSize, minimumFontSize); + settings->endGroup(); + + settings->beginGroup(GroupProxy); + proxyType = static_cast(settings->value(QStringLiteral("type"), + ProxyType::System).toUInt()); + proxyHost = settings->value(QStringLiteral("host")).toString(); + proxyPort = static_cast(settings->value(QStringLiteral("port"), 0).toUInt()); + proxyAuthenticate = settings->value(QStringLiteral("authenticate"), false).toBool(); + proxyUserName = settings->value(QStringLiteral("username")).toString(); + proxyPassword = settings->value(QStringLiteral("password")).toString(); + settings->endGroup(); + + settings->beginGroup(GroupDocsets); + if (settings->contains(QStringLiteral("path"))) { + docsetPath = settings->value(QStringLiteral("path")).toString(); + } else { +#ifndef PORTABLE_BUILD + docsetPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + QLatin1String("/docsets"); +#else + docsetPath = QCoreApplication::applicationDirPath() + QLatin1String("/docsets"); +#endif + QDir().mkpath(docsetPath); + } + settings->endGroup(); + + settings->beginGroup(GroupState); + windowGeometry = settings->value(QStringLiteral("window_geometry")).toByteArray(); + verticalSplitterGeometry = settings->value(QStringLiteral("splitter_geometry")).toByteArray(); + tocSplitterState = settings->value(QStringLiteral("toc_splitter_state")).toByteArray(); + settings->endGroup(); + + settings->beginGroup(GroupInternal); + installId = settings->value(QStringLiteral("install_id"), + // Avoid curly braces (QTBUG-885) + QUuid::createUuid().toString().mid(1, 36)).toString(); + settings->endGroup(); +} + +void Settings::save() +{ + QScopedPointer settings(qsettings()); + + // TODO: Put everything in groups + settings->setValue(QStringLiteral("start_minimized"), startMinimized); + settings->setValue(QStringLiteral("check_for_update"), checkForUpdate); + + settings->setValue(QStringLiteral("show_systray_icon"), showSystrayIcon); + settings->setValue(QStringLiteral("minimize_to_systray"), minimizeToSystray); + settings->setValue(QStringLiteral("hide_on_close"), hideOnClose); + + settings->beginGroup(GroupGlobalShortcuts); + settings->setValue(QStringLiteral("show"), showShortcut); + settings->endGroup(); + + settings->beginGroup(GroupTabs); + settings->setValue(QStringLiteral("open_new_tab_after_active"), openNewTabAfterActive); + settings->endGroup(); + + settings->beginGroup(GroupBrowser); + settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize); + settings->endGroup(); + + settings->beginGroup(GroupProxy); + settings->setValue(QStringLiteral("type"), proxyType); + settings->setValue(QStringLiteral("host"), proxyHost); + settings->setValue(QStringLiteral("port"), proxyPort); + settings->setValue(QStringLiteral("authenticate"), proxyAuthenticate); + settings->setValue(QStringLiteral("username"), proxyUserName); + settings->setValue(QStringLiteral("password"), proxyPassword); + settings->endGroup(); + +#ifndef PORTABLE_BUILD + settings->beginGroup(GroupDocsets); + settings->setValue(QStringLiteral("path"), docsetPath); + settings->endGroup(); +#endif + + settings->beginGroup(GroupState); + settings->setValue(QStringLiteral("window_geometry"), windowGeometry); + settings->setValue(QStringLiteral("splitter_geometry"), verticalSplitterGeometry); + settings->setValue(QStringLiteral("toc_splitter_state"), tocSplitterState); + settings->endGroup(); + + settings->beginGroup(GroupInternal); + settings->setValue(QStringLiteral("install_id"), installId); + // Version of configuration file format, should match Zeal version. Used for migration rules. + settings->setValue(QStringLiteral("version"), QCoreApplication::applicationVersion()); + settings->endGroup(); + + settings->sync(); + + emit updated(); +} + +/*! + * \internal + * \brief Migrates settings from older application versions. + * \param settings QSettings object to update. + * + * The settings migration process relies on 'internal/version' option, that was introduced in the + * release 0.2.0, so a missing option indicates pre-0.2 release. + */ +void Settings::migrate(QSettings *settings) const +{ + settings->beginGroup(GroupInternal); + const QString version = settings->value(QStringLiteral("version")).toString(); + settings->endGroup(); + + // + // Pre 0.3 + // + + // Unset 'state/splitter_geometry', because custom styles were removed. + if (version.isEmpty() || version.startsWith(QLatin1String("0.2"))) { + settings->beginGroup(GroupState); + settings->remove(QStringLiteral("splitter_geometry")); + settings->endGroup(); + } +} + +/*! + * \internal + * \brief Returns an initialized QSettings object. + * \param parent Optional parent object. + * \return QSettings object. + * + * QSettings is initialized according to build options, e.g. standard vs portable. + * Caller is responsible for deleting the returned object. + */ +QSettings *Settings::qsettings(QObject *parent) +{ +#ifndef PORTABLE_BUILD + return new QSettings(parent); +#else + return new QSettings(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"), + QSettings::IniFormat, parent); +#endif +} diff --git a/src/core/settings.h b/src/libs/core/settings.h similarity index 73% rename from src/core/settings.h rename to src/libs/core/settings.h index a7c36c8d5..50fc37ae5 100644 --- a/src/core/settings.h +++ b/src/libs/core/settings.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -35,13 +35,14 @@ class Settings : public QObject { Q_OBJECT public: - /// NOTE: This public members are here just for simplification and should go away - /// once a more advanced settings management come in place. + /* This public members are here just for simplification and should go away + * once a more advanced settings management come in place. + */ // Startup bool startMinimized; bool checkForUpdate; - /// TODO: bool restoreLastState; + // TODO: bool restoreLastState; // System Tray bool showSystrayIcon; @@ -50,12 +51,15 @@ class Settings : public QObject // Global Shortcuts QKeySequence showShortcut; - /// TODO: QKeySequence searchSelectedTextShortcut; + // TODO: QKeySequence searchSelectedTextShortcut; + + // Tabs Behavior + bool openNewTabAfterActive; // Browser int minimumFontSize; - /// TODO: bool askOnExternalLink; - /// TODO: QString customCss; + // TODO: bool askOnExternalLink; + // TODO: QString customCss; // Network enum ProxyType : unsigned int { @@ -63,6 +67,9 @@ class Settings : public QObject System, UserDefined }; +#if QT_VERSION >= 0x050500 + Q_ENUM(ProxyType) +#endif // Internal // -------- @@ -70,8 +77,6 @@ class Settings : public QObject // a settings wipe. It is not attached to user hardware or software, and is sent exclusevely // to *.zealdocs.org hosts. QString installId; - // Version of configuration file format, should match Zeal version. Useful for migration rules. - QString version; ProxyType proxyType = ProxyType::System; QString proxyHost; @@ -85,7 +90,8 @@ class Settings : public QObject // State QByteArray windowGeometry; - QByteArray splitterGeometry; + QByteArray verticalSplitterGeometry; + QByteArray tocSplitterState; explicit Settings(QObject *parent = nullptr); ~Settings() override; @@ -98,12 +104,16 @@ public slots: void updated(); private: - QSettings *m_settings = nullptr; + void migrate(QSettings *settings) const; + + static QSettings *qsettings(QObject *parent = nullptr); }; } // namespace Core } // namespace Zeal +#if QT_VERSION < 0x050500 Q_DECLARE_METATYPE(Zeal::Core::Settings::ProxyType) +#endif #endif // SETTINGS_H diff --git a/src/libs/libs.pro b/src/libs/libs.pro new file mode 100644 index 000000000..a323f5d48 --- /dev/null +++ b/src/libs/libs.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + core \ + registry \ + ui \ + util diff --git a/src/ui/searchitemstyle.h b/src/libs/registry/cancellationtoken.cpp similarity index 66% rename from src/ui/searchitemstyle.h rename to src/libs/registry/cancellationtoken.cpp index ef87b5e18..b767c4c52 100644 --- a/src/ui/searchitemstyle.h +++ b/src/libs/registry/cancellationtoken.cpp @@ -1,8 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015 Artur Spychaj +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -21,16 +20,21 @@ ** ****************************************************************************/ -#ifndef SEARCHITEMSTYLE_H -#define SEARCHITEMSTYLE_H +#include "cancellationtoken.h" -#include +using namespace Zeal::Registry; -class ZealSearchItemStyle : public QProxyStyle +CancellationToken::CancellationToken() { -public: - QRect subElementRect(SubElement element, const QStyleOption *option, - const QWidget *widget) const override; -}; + m_cancelled = QSharedPointer(new bool(false)); +} -#endif // SEARCHITEMSTYLE_H +void CancellationToken::cancel() +{ + *m_cancelled = true; +} + +bool CancellationToken::isCanceled() const +{ + return *m_cancelled; +} diff --git a/src/ui/networkaccessmanager.h b/src/libs/registry/cancellationtoken.h similarity index 60% rename from src/ui/networkaccessmanager.h rename to src/libs/registry/cancellationtoken.h index ac7900bda..585a87e87 100644 --- a/src/ui/networkaccessmanager.h +++ b/src/libs/registry/cancellationtoken.h @@ -1,8 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015 Artur Spychaj +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -21,23 +20,30 @@ ** ****************************************************************************/ -#ifndef NETWORKACCESSMANAGER_H -#define NETWORKACCESSMANAGER_H +#ifndef CANCELLATIONTOKEN_H +#define CANCELLATIONTOKEN_H -#include +#include namespace Zeal { +namespace Registry { -class NetworkAccessManager : public QNetworkAccessManager +/// Token that stores whether cancel was called on it. +/// In async code can be used to check if another thread called cancel. +struct CancellationToken { - Q_OBJECT public: - explicit NetworkAccessManager(QObject *parent = nullptr); + CancellationToken(); + bool isCanceled() const; + void cancel(); - QNetworkReply *createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, - QIODevice *outgoingData = nullptr) override; +private: + QSharedPointer m_cancelled; }; +} // namespace Registry } // namespace Zeal -#endif // NETWORKACCESSMANAGER_H +Q_DECLARE_METATYPE(Zeal::Registry::CancellationToken) + +#endif // CANCELLATIONTOKEN_H diff --git a/src/registry/docset.cpp b/src/libs/registry/docset.cpp similarity index 68% rename from src/registry/docset.cpp rename to src/libs/registry/docset.cpp index 6256dc5c3..c6e7395da 100644 --- a/src/registry/docset.cpp +++ b/src/libs/registry/docset.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,25 +17,28 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #include "docset.h" -#include "searchquery.h" -#include "util/plist.h" +#include "cancellationtoken.h" +#include "searchresult.h" + +#include #include #include #include #include +#include +#include #include #include -#include #include -using namespace Zeal; +using namespace Zeal::Registry; namespace { const char IndexNamePrefix[] = "__zi_name"; // zi - Zeal index @@ -43,14 +46,14 @@ const char IndexNameVersion[] = "0001"; // Current index version namespace InfoPlist { const char CFBundleName[] = "CFBundleName"; -const char CFBundleIdentifier[] = "CFBundleIdentifier"; +//const char CFBundleIdentifier[] = "CFBundleIdentifier"; const char DashDocSetFamily[] = "DashDocSetFamily"; const char DashDocSetKeyword[] = "DashDocSetKeyword"; const char DashDocSetPluginKeyword[] = "DashDocSetPluginKeyword"; const char DashIndexFilePath[] = "dashIndexFilePath"; const char DocSetPlatformFamily[] = "DocSetPlatformFamily"; -const char IsDashDocset[] = "isDashDocset"; -const char IsJavaScriptEnabled[] = "isJavaScriptEnabled"; +//const char IsDashDocset[] = "isDashDocset"; +//const char IsJavaScriptEnabled[] = "isJavaScriptEnabled"; } } @@ -70,13 +73,13 @@ Docset::Docset(const QString &path) : break; } - /// TODO: Report errors here and below + // TODO: Report errors here and below if (!dir.cd(QStringLiteral("Contents"))) return; - /// TODO: 'info.plist' is invalid according to Apple, and must alsways be 'Info.plist' - /// https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPRuntimeConfig/ - /// Articles/ConfigFiles.html + // TODO: 'info.plist' is invalid according to Apple, and must alsways be 'Info.plist' + // https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPRuntimeConfig + // /Articles/ConfigFiles.html Util::Plist plist; if (dir.exists(QStringLiteral("Info.plist"))) plist.read(dir.absoluteFilePath(QStringLiteral("Info.plist"))); @@ -92,7 +95,7 @@ Docset::Docset(const QString &path) : // Fallback if meta.json is absent if (!plist.contains(InfoPlist::CFBundleName)) { m_name = m_title = plist[InfoPlist::CFBundleName].toString(); - /// TODO: Remove when MainWindow::docsetName() will not use directory name + // TODO: Remove when MainWindow::docsetName() will not use directory name m_name.replace(QLatin1Char(' '), QLatin1Char('_')); } else { m_name = QFileInfo(m_path).fileName().remove(QStringLiteral(".docset")); @@ -104,7 +107,7 @@ Docset::Docset(const QString &path) : m_title.replace(QLatin1Char('_'), QLatin1Char(' ')); } - /// TODO: Verify if this is needed + // TODO: Verify if this is needed if (plist.contains(InfoPlist::DashDocSetFamily) && plist[InfoPlist::DashDocSetFamily].toString() == QLatin1String("cheatsheet")) { m_name = m_name + QLatin1String("cheats"); @@ -125,11 +128,12 @@ Docset::Docset(const QString &path) : createIndex(); - if (!dir.cd(QStringLiteral("Documents"))) + if (!dir.cd(QStringLiteral("Documents"))) { + m_type = Type::Invalid; return; + } - // - // Setyp keywords + // Setup keywords if (plist.contains(InfoPlist::DocSetPlatformFamily)) m_keywords << plist[InfoPlist::DocSetPlatformFamily].toString(); @@ -141,19 +145,18 @@ Docset::Docset(const QString &path) : if (plist.contains(InfoPlist::DashDocSetFamily)) { const QString kw = plist[InfoPlist::DashDocSetFamily].toString(); - if (kw != QStringLiteral("dashtoc")) + if (kw != QLatin1String("dashtoc") && kw != QLatin1String("unsorteddashtoc")) m_keywords << kw; } - /// TODO: Use 'unknown' instead of CFBundleName? (See #383) - m_keywords << plist.value(InfoPlist::CFBundleName, m_name).toString().toLower(); + m_keywords.removeDuplicates(); - // Try to find index path if metadata is missing one - if (m_indexFilePath.isEmpty()) { - if (plist.contains(InfoPlist::DashIndexFilePath)) - m_indexFilePath = plist[InfoPlist::DashIndexFilePath].toString(); - else if (dir.exists(QStringLiteral("index.html"))) - m_indexFilePath = QStringLiteral("index.html"); + // Prefer index path provided by the docset over metadata. + if (plist.contains(InfoPlist::DashIndexFilePath)) { + m_indexFileUrl = createPageUrl(plist[InfoPlist::DashIndexFilePath].toString()); + } else if (m_indexFileUrl.isEmpty()) { + if (dir.exists(QStringLiteral("index.html"))) + m_indexFileUrl = createPageUrl(QStringLiteral("index.html")); else qWarning("Cannot determine index file for docset %s", qPrintable(m_name)); } @@ -211,9 +214,17 @@ QIcon Docset::icon() const return m_icon; } -QString Docset::indexFilePath() const +QIcon Docset::symbolTypeIcon(const QString &symbolType) const { - return QDir(documentPath()).absoluteFilePath(m_indexFilePath); + static const QIcon unknownIcon(QStringLiteral("typeIcon:Unknown.png")); + + const QIcon icon(QStringLiteral("typeIcon:%1.png").arg(symbolType)); + return icon.availableSizes().isEmpty() ? unknownIcon : icon; +} + +QUrl Docset::indexFileUrl() const +{ + return m_indexFileUrl; } QMap Docset::symbolCounts() const @@ -226,83 +237,54 @@ int Docset::symbolCount(const QString &symbolType) const return m_symbolCounts.value(symbolType); } -const QMap &Docset::symbols(const QString &symbolType) const +const QMap &Docset::symbols(const QString &symbolType) const { if (!m_symbols.contains(symbolType)) loadSymbols(symbolType); return m_symbols[symbolType]; } -QList Docset::search(const QString &query) const +QList Docset::search(const QString &query, const CancellationToken &token) const { - QList results; - - const SearchQuery searchQuery = SearchQuery::fromString(query); - const QString sanitizedQuery = searchQuery.sanitizedQuery(); - - if (searchQuery.hasKeywords() && !searchQuery.hasKeywords(m_keywords)) - return results; + // Make it safe to use in a SQL query. + QString sanitizedQuery = query; + sanitizedQuery.replace(QLatin1String("\\"), QLatin1String("\\\\")); + sanitizedQuery.replace(QLatin1String("_"), QLatin1String("\\_")); + sanitizedQuery.replace(QLatin1String("%"), QLatin1String("\\%")); + sanitizedQuery.replace(QLatin1String("'"), QLatin1String("''")); QString queryStr; + if (m_type == Docset::Type::Dash) { + queryStr = QStringLiteral("SELECT name, type, path " + " FROM searchIndex " + "WHERE (name LIKE '%%1%' ESCAPE '\\') " + "ORDER BY name COLLATE NOCASE").arg(sanitizedQuery); + } else { + queryStr = QStringLiteral("SELECT ztokenname, ztypename, zpath, zanchor " + " FROM ztoken " + "LEFT JOIN ztokenmetainformation " + " ON ztoken.zmetainformation = ztokenmetainformation.z_pk " + "LEFT JOIN zfilepath " + " ON ztokenmetainformation.zfile = zfilepath.z_pk " + "LEFT JOIN ztokentype " + " ON ztoken.ztokentype = ztokentype.z_pk " + "WHERE (ztokenname LIKE '%%1%' ESCAPE '\\') " + "ORDER BY ztokenname COLLATE NOCASE").arg(sanitizedQuery); + } - bool withSubStrings = false; - // %.%1% for long Django docset values like django.utils.http - // %::%1% for long C++ docset values like std::set - // %/%1% for long Go docset values like archive/tar - QString subNames = QStringLiteral(" OR %1 LIKE '%.%2%' ESCAPE '\\'"); - subNames += QLatin1String(" OR %1 LIKE '%::%2%' ESCAPE '\\'"); - subNames += QLatin1String(" OR %1 LIKE '%/%2%' ESCAPE '\\'"); - while (results.size() < 100) { - QString curQuery = sanitizedQuery; - QString notQuery; // don't return the same result twice - if (withSubStrings) { - // if less than 100 found starting with query, search all substrings - curQuery = QLatin1Char('%') + sanitizedQuery; - // don't return 'starting with' results twice - if (m_type == Docset::Type::Dash) - notQuery = QString(" AND NOT (name LIKE '%1%' ESCAPE '\\' %2) ").arg(sanitizedQuery, subNames.arg("name", sanitizedQuery)); - else - notQuery = QString(" AND NOT (ztokenname LIKE '%1%' ESCAPE '\\' %2) ").arg(sanitizedQuery, subNames.arg("ztokenname", sanitizedQuery)); - } - if (m_type == Docset::Type::Dash) { - queryStr = QString("SELECT name, type, path " - " FROM searchIndex " - "WHERE (name LIKE '%1%' ESCAPE '\\' %3) %2 " - "ORDER BY name COLLATE NOCASE LIMIT 100") - .arg(curQuery, notQuery, subNames.arg("name", curQuery)); - } else { - queryStr = QString("SELECT ztokenname, ztypename, zpath, zanchor " - " FROM ztoken " - "JOIN ztokenmetainformation " - " ON ztoken.zmetainformation = ztokenmetainformation.z_pk " - "JOIN zfilepath " - " ON ztokenmetainformation.zfile = zfilepath.z_pk " - "JOIN ztokentype " - " ON ztoken.ztokentype = ztokentype.z_pk " - "WHERE (ztokenname LIKE '%1%' ESCAPE '\\' %3) %2 " - "ORDER BY ztokenname COLLATE NOCASE LIMIT 100") - .arg(curQuery, notQuery, subNames.arg("ztokenname", curQuery)); - } + // Limit for very short queries. + // TODO: Show a notification about the reduced result set. + if (query.size() < 3) + queryStr += QLatin1String(" LIMIT 1000"); - QSqlQuery query(queryStr, database()); - while (query.next()) { - const QString itemName = query.value(0).toString(); - QString path = query.value(2).toString(); - if (m_type == Docset::Type::ZDash) { - const QString anchor = query.value(3).toString(); - if (!anchor.isEmpty()) - path += QLatin1Char('#') + anchor; - } - - /// TODO: Third should be type - results.append(SearchResult{itemName, QString(), - parseSymbolType(query.value(1).toString()), - const_cast(this), path, sanitizedQuery}); - } + QList results; - if (withSubStrings) - break; - withSubStrings = true; // try again searching for substrings + QSqlQuery sqlQuery(queryStr, database()); + while (sqlQuery.next() && !token.isCanceled()) { + results.append({sqlQuery.value(0).toString(), + parseSymbolType(sqlQuery.value(1).toString()), + const_cast(this), + createPageUrl(sqlQuery.value(2).toString(), sqlQuery.value(3).toString())}); } return results; @@ -330,25 +312,18 @@ QList Docset::relatedLinks(const QUrl &url) const } else if (m_type == Docset::Type::ZDash) { queryStr = QStringLiteral("SELECT ztoken.ztokenname, ztokentype.ztypename, zfilepath.zpath, ztokenmetainformation.zanchor " "FROM ztoken " - "JOIN ztokenmetainformation ON ztoken.zmetainformation = ztokenmetainformation.z_pk " - "JOIN zfilepath ON ztokenmetainformation.zfile = zfilepath.z_pk " - "JOIN ztokentype ON ztoken.ztokentype = ztokentype.z_pk " + "LEFT JOIN ztokenmetainformation ON ztoken.zmetainformation = ztokenmetainformation.z_pk " + "LEFT JOIN zfilepath ON ztokenmetainformation.zfile = zfilepath.z_pk " + "LEFT JOIN ztokentype ON ztoken.ztokentype = ztokentype.z_pk " "WHERE zfilepath.zpath = \"%1\" AND ztokenmetainformation.zanchor IS NOT NULL"); } - QSqlQuery query(queryStr.arg(cleanUrl.toString()), database()); - - while (query.next()) { - const QString sectionName = query.value(0).toString(); - QString sectionPath = query.value(2).toString(); - if (m_type == Docset::Type::ZDash) { - sectionPath += QLatin1Char('#'); - sectionPath += query.value(3).toString(); - } - - results.append(SearchResult{sectionName, QString(), - parseSymbolType(query.value(1).toString()), - const_cast(this), sectionPath, QString()}); + QSqlQuery sqlQuery(queryStr.arg(cleanUrl.toString()), database()); + while (sqlQuery.next()) { + results.append({sqlQuery.value(0).toString(), + parseSymbolType(sqlQuery.value(1).toString()), + const_cast(this), + createPageUrl(sqlQuery.value(2).toString(), sqlQuery.value(3).toString())}); } if (results.size() == 1) @@ -387,7 +362,7 @@ void Docset::loadMetadata() if (jsonObject.contains(QStringLiteral("extra"))) { const QJsonObject extra = jsonObject[QStringLiteral("extra")].toObject(); - m_indexFilePath = extra[QStringLiteral("indexFilePath")].toString(); + m_indexFileUrl = createPageUrl(extra[QStringLiteral("indexFilePath")].toString()); } } @@ -397,7 +372,7 @@ void Docset::countSymbols() if (m_type == Docset::Type::Dash) { queryStr = QStringLiteral("SELECT type, COUNT(*) FROM searchIndex GROUP BY type"); } else if (m_type == Docset::Type::ZDash) { - queryStr = QStringLiteral("SELECT ztypename, COUNT(*) FROM ztoken JOIN ztokentype" + queryStr = QStringLiteral("SELECT ztypename, COUNT(*) FROM ztoken LEFT JOIN ztokentype" " ON ztoken.ztokentype = ztokentype.z_pk GROUP BY ztypename"); } @@ -415,7 +390,7 @@ void Docset::countSymbols() } } -/// TODO: Fetch and cache only portions of symbols +// TODO: Fetch and cache only portions of symbols void Docset::loadSymbols(const QString &symbolType) const { for (const QString &symbol : m_symbolStrings.values(symbolType)) @@ -424,21 +399,15 @@ void Docset::loadSymbols(const QString &symbolType) const void Docset::loadSymbols(const QString &symbolType, const QString &symbolString) const { - QSqlDatabase db = database(); - if (!db.isOpen()) - return; - QString queryStr; if (m_type == Docset::Type::Dash) { queryStr = QStringLiteral("SELECT name, path FROM searchIndex WHERE type='%1' ORDER BY name ASC"); } else { - queryStr = QStringLiteral("SELECT ztokenname AS name, " - "CASE WHEN (zanchor IS NULL) THEN zpath " - "ELSE (zpath || '#' || zanchor) " - "END AS path FROM ztoken " - "JOIN ztokenmetainformation ON ztoken.zmetainformation = ztokenmetainformation.z_pk " - "JOIN zfilepath ON ztokenmetainformation.zfile = zfilepath.z_pk " - "JOIN ztokentype ON ztoken.ztokentype = ztokentype.z_pk WHERE ztypename='%1' " + queryStr = QStringLiteral("SELECT ztokenname, zpath, zanchor " + "FROM ztoken " + "LEFT JOIN ztokenmetainformation ON ztoken.zmetainformation = ztokenmetainformation.z_pk " + "LEFT JOIN zfilepath ON ztokenmetainformation.zfile = zfilepath.z_pk " + "LEFT JOIN ztokentype ON ztoken.ztokentype = ztokentype.z_pk WHERE ztypename='%1' " "ORDER BY ztokenname ASC"); } @@ -448,9 +417,10 @@ void Docset::loadSymbols(const QString &symbolType, const QString &symbolString) return; } - QMap &symbols = m_symbols[symbolType]; + QMap &symbols = m_symbols[symbolType]; while (query.next()) - symbols.insertMulti(query.value(0).toString(), QDir(documentPath()).absoluteFilePath(query.value(1).toString())); + symbols.insertMulti(query.value(0).toString(), + createPageUrl(query.value(1).toString(), query.value(2).toString())); } void Docset::createIndex() @@ -458,12 +428,14 @@ void Docset::createIndex() static const QString indexListQuery = QStringLiteral("PRAGMA INDEX_LIST('%1')"); static const QString indexDropQuery = QStringLiteral("DROP INDEX '%1'"); static const QString indexCreateQuery = QStringLiteral("CREATE INDEX IF NOT EXISTS %1%2" - " ON %3 (name COLLATE NOCASE)"); + " ON %3 (%4 COLLATE NOCASE)"); QSqlQuery query(database()); const QString tableName = m_type == Type::Dash ? QStringLiteral("searchIndex") : QStringLiteral("ztoken"); + const QString columnName = m_type == Type::Dash ? QStringLiteral("name") + : QStringLiteral("ztokenname"); query.exec(indexListQuery.arg(tableName)); @@ -481,15 +453,45 @@ void Docset::createIndex() } // Drop old indexes - for (const QString oldIndexName : oldIndexes) + for (const QString &oldIndexName : oldIndexes) query.exec(indexDropQuery.arg(oldIndexName)); - query.exec(indexCreateQuery.arg(IndexNamePrefix, IndexNameVersion, tableName)); + query.exec(indexCreateQuery.arg(IndexNamePrefix, IndexNameVersion, tableName, columnName)); +} + +QUrl Docset::createPageUrl(const QString &path, const QString &fragment) const +{ + QString realPath; + QString realFragment; + + if (fragment.isEmpty()) { + const QStringList urlParts = path.split(QLatin1Char('#')); + realPath = urlParts[0]; + if (urlParts.size() > 1) + realFragment = urlParts[1]; + } else { + realPath = path; + realFragment = fragment; + } + + static const QRegularExpression dashEntryRegExp(QLatin1String("")); + realPath.remove(dashEntryRegExp); + realFragment.remove(dashEntryRegExp); + + QUrl url = QUrl::fromLocalFile(QDir(documentPath()).absoluteFilePath(realPath)); + if (!realFragment.isEmpty()) { + if (realFragment.startsWith("//apple_ref")) + url.setFragment(realFragment, QUrl::DecodedMode); + else + url.setFragment(realFragment); + } + + return url; } QString Docset::parseSymbolType(const QString &str) { - /// Dash symbol aliases + // Dash symbol aliases const static QHash aliases = { // Attribute {QStringLiteral("Package Attributes"), QStringLiteral("Attribute")}, @@ -514,11 +516,14 @@ QString Docset::parseSymbolType(const QString &str) // Constant {QStringLiteral("data"), QStringLiteral("Constant")}, {QStringLiteral("econst"), QStringLiteral("Constant")}, + {QStringLiteral("enumdata"), QStringLiteral("Constant")}, {QStringLiteral("enumelt"), QStringLiteral("Constant")}, {QStringLiteral("clconst"), QStringLiteral("Constant")}, {QStringLiteral("structdata"), QStringLiteral("Constant")}, + {QStringLiteral("writerid"), QStringLiteral("Constant")}, {QStringLiteral("Notifications"), QStringLiteral("Constant")}, // Constructor + {QStringLiteral("structctr"), QStringLiteral("Constructor")}, {QStringLiteral("Public Constructors"), QStringLiteral("Constructor")}, // Enumeration {QStringLiteral("enum"), QStringLiteral("Enumeration")}, @@ -561,20 +566,34 @@ QString Docset::parseSymbolType(const QString &str) {QStringLiteral("macro"), QStringLiteral("Macro")}, // Method {QStringLiteral("clm"), QStringLiteral("Method")}, + {QStringLiteral("enumcm"), QStringLiteral("Method")}, + {QStringLiteral("enumctr"), QStringLiteral("Method")}, + {QStringLiteral("enumm"), QStringLiteral("Method")}, {QStringLiteral("intfctr"), QStringLiteral("Method")}, {QStringLiteral("intfcm"), QStringLiteral("Method")}, {QStringLiteral("intfm"), QStringLiteral("Method")}, + {QStringLiteral("intfsub"), QStringLiteral("Method")}, + {QStringLiteral("instsub"), QStringLiteral("Method")}, {QStringLiteral("instctr"), QStringLiteral("Method")}, {QStringLiteral("instm"), QStringLiteral("Method")}, + {QStringLiteral("structcm"), QStringLiteral("Method")}, + {QStringLiteral("structm"), QStringLiteral("Method")}, + {QStringLiteral("structsub"), QStringLiteral("Method")}, {QStringLiteral("Class Methods"), QStringLiteral("Method")}, {QStringLiteral("Inherited Methods"), QStringLiteral("Method")}, {QStringLiteral("Instance Methods"), QStringLiteral("Method")}, {QStringLiteral("Private Methods"), QStringLiteral("Method")}, {QStringLiteral("Protected Methods"), QStringLiteral("Method")}, {QStringLiteral("Public Methods"), QStringLiteral("Method")}, + // Operator + {QStringLiteral("intfopfunc"), QStringLiteral("Operator")}, + {QStringLiteral("opfunc"), QStringLiteral("Operator")}, // Property + {QStringLiteral("enump"), QStringLiteral("Property")}, + {QStringLiteral("intfdata"), QStringLiteral("Property")}, {QStringLiteral("intfp"), QStringLiteral("Property")}, {QStringLiteral("instp"), QStringLiteral("Property")}, + {QStringLiteral("structp"), QStringLiteral("Property")}, {QStringLiteral("Inherited Properties"), QStringLiteral("Property")}, {QStringLiteral("Private Properties"), QStringLiteral("Property")}, {QStringLiteral("Protected Properties"), QStringLiteral("Property")}, diff --git a/src/registry/docset.h b/src/libs/registry/docset.h similarity index 72% rename from src/registry/docset.h rename to src/libs/registry/docset.h index 76feb93b2..0f2960feb 100644 --- a/src/registry/docset.h +++ b/src/libs/registry/docset.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,28 +17,31 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #ifndef DOCSET_H #define DOCSET_H -#include "searchresult.h" - #include #include #include -#include +#include + +class QSqlDatabase; namespace Zeal { +namespace Registry { + +struct CancellationToken; +struct SearchResult; -class Docset : public QObject +class Docset { - Q_OBJECT public: explicit Docset(const QString &path); - ~Docset() override; + ~Docset(); bool isValid() const; @@ -52,17 +55,18 @@ class Docset : public QObject QString path() const; QString documentPath() const; QIcon icon() const; - QString indexFilePath() const; + QIcon symbolTypeIcon(const QString &symbolType) const; + QUrl indexFileUrl() const; QMap symbolCounts() const; int symbolCount(const QString &symbolType) const; - const QMap &symbols(const QString &symbolType) const; + const QMap &symbols(const QString &symbolType) const; - QList search(const QString &query) const; + QList search(const QString &query, const CancellationToken &token) const; QList relatedLinks(const QUrl &url) const; - /// FIXME: This is an ugly workaround before we have a proper docset sources implementation + // FIXME: This is an ugly workaround before we have a proper docset sources implementation bool hasUpdate = false; private: @@ -78,6 +82,7 @@ class Docset : public QObject void loadSymbols(const QString &symbolType) const; void loadSymbols(const QString &symbolType, const QString &symbolString) const; void createIndex(); + QUrl createPageUrl(const QString &path, const QString &fragment = QString()) const; static QString parseSymbolType(const QString &str); @@ -91,13 +96,14 @@ class Docset : public QObject QString m_path; QIcon m_icon; - QString m_indexFilePath; + QUrl m_indexFileUrl; QMap m_symbolStrings; QMap m_symbolCounts; - mutable QMap> m_symbols; + mutable QMap> m_symbols; }; +} // namespace Registry } // namespace Zeal #endif // DOCSET_H diff --git a/src/registry/docsetmetadata.cpp b/src/libs/registry/docsetmetadata.cpp similarity index 93% rename from src/registry/docsetmetadata.cpp rename to src/libs/registry/docsetmetadata.cpp index 1e4c9f0ef..42d3b392e 100644 --- a/src/registry/docsetmetadata.cpp +++ b/src/libs/registry/docsetmetadata.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -30,7 +30,7 @@ #include #include -using namespace Zeal; +using namespace Zeal::Registry; DocsetMetadata::DocsetMetadata() { @@ -48,7 +48,7 @@ DocsetMetadata::DocsetMetadata(const QJsonObject &jsonObject) m_rawIcon2x = QByteArray::fromBase64(jsonObject[QStringLiteral("icon2x")].toString() .toLocal8Bit()); - /// TODO: Check on a high-resolution screen + // TODO: Check on a high-resolution screen if (qApp->devicePixelRatio() > 1.0) { QPixmap pixmap = QPixmap::fromImage(QImage::fromData(m_rawIcon2x)); pixmap.setDevicePixelRatio(2.0); @@ -62,10 +62,10 @@ DocsetMetadata::DocsetMetadata(const QJsonObject &jsonObject) m_versions << vv.toString(); m_revision = jsonObject[QStringLiteral("revision")].toString(); - m_feedUrl = jsonObject[QStringLiteral("feed_url")].toString(); + m_feedUrl = QUrl(jsonObject[QStringLiteral("feed_url")].toString()); const QJsonArray urlArray = jsonObject[QStringLiteral("urls")].toArray(); for (const QJsonValue &url : urlArray) - m_urls.append(url.toString()); + m_urls.append(QUrl(url.toString())); m_extra = jsonObject[QStringLiteral("extra")].toObject(); } @@ -207,7 +207,7 @@ DocsetMetadata DocsetMetadata::fromDashFeed(const QUrl &feedUrl, const QByteArra } else if (xml.name() == QLatin1String("url")) { if (xml.readNext() != QXmlStreamReader::Characters) continue; - metadata.m_urls.append(xml.text().toString()); + metadata.m_urls.append(QUrl(xml.text().toString())); } } diff --git a/src/registry/docsetmetadata.h b/src/libs/registry/docsetmetadata.h similarity index 90% rename from src/registry/docsetmetadata.h rename to src/libs/registry/docsetmetadata.h index 5dda9778c..5672bf769 100644 --- a/src/registry/docsetmetadata.h +++ b/src/libs/registry/docsetmetadata.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,23 +17,20 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #ifndef DOCSETMETADATA_H #define DOCSETMETADATA_H -#include #include +#include #include #include -class QJsonObject; - namespace Zeal { - -/// TODO: Use QUrl +namespace Registry { class DocsetMetadata { @@ -78,6 +75,7 @@ class DocsetMetadata QList m_urls; }; +} // namespace Registry } // namespace Zeal #endif // DOCSETMETADATA_H diff --git a/src/registry/docsetregistry.cpp b/src/libs/registry/docsetregistry.cpp similarity index 60% rename from src/registry/docsetregistry.cpp rename to src/libs/registry/docsetregistry.cpp index d2f1ad913..adc234c94 100644 --- a/src/registry/docsetregistry.cpp +++ b/src/libs/registry/docsetregistry.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,27 +17,39 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #include "docsetregistry.h" +#include "cancellationtoken.h" +#include "searchquery.h" #include "searchresult.h" #include -#include #include -#include -#include -using namespace Zeal; +#include + +#include + +using namespace Zeal::Registry; + +void MergeQueryResults(QList &finalResult, const QList &partial) +{ + finalResult << partial; +} DocsetRegistry::DocsetRegistry(QObject *parent) : QObject(parent), m_thread(new QThread(this)) { - /// FIXME: Only search should be performed in a separate thread + // Register for use in signal connections. + qRegisterMetaType("CancellationToken"); + qRegisterMetaType>("QList"); + + // FIXME: Only search should be performed in a separate thread moveToThread(m_thread); m_thread->start(); } @@ -106,8 +118,9 @@ void DocsetRegistry::_addDocset(const QString &path) { Docset *docset = new Docset(path); - /// TODO: Emit error + // TODO: Emit error if (!docset->isValid()) { + qWarning("Could not load docset from '%s'. Please reinstall the docset.", qPrintable(path)); delete docset; return; } @@ -121,30 +134,43 @@ void DocsetRegistry::_addDocset(const QString &path) emit docsetAdded(name); } -void DocsetRegistry::search(const QString &query) +void DocsetRegistry::search(const QString &query, const CancellationToken &token) { - // Only invalidate queries - if (query.isEmpty()) - return; - - QMetaObject::invokeMethod(this, "_runQuery", Qt::QueuedConnection, Q_ARG(QString, query)); + QMetaObject::invokeMethod(this, "_runQuery", Qt::QueuedConnection, + Q_ARG(QString, query), Q_ARG(CancellationToken, token)); } -void DocsetRegistry::_runQuery(const QString &query) +void DocsetRegistry::_runQuery(const QString &query, const CancellationToken &token) { - m_queryResults.clear(); + QList enabledDocsets; - for (Docset *docset : docsets()) - m_queryResults << docset->search(query); + const SearchQuery searchQuery = SearchQuery::fromString(query); + if (searchQuery.hasKeywords()) { + for (Docset *docset : docsets()) { + if (searchQuery.hasKeywords(docset->keywords())) + enabledDocsets << docset; + } + } else { + enabledDocsets = docsets(); + } - std::sort(m_queryResults.begin(), m_queryResults.end()); + QFuture> queryResultsFuture + = QtConcurrent::mappedReduced(enabledDocsets, + std::bind(&Docset::search, + std::placeholders::_1, + searchQuery.query(), token), + &MergeQueryResults); + QList results = queryResultsFuture.result(); - emit queryCompleted(); -} + if (token.isCanceled()) + return; -const QList &DocsetRegistry::queryResults() -{ - return m_queryResults; + std::sort(results.begin(), results.end()); + + if (token.isCanceled()) + return; + + emit queryCompleted(results); } // Recursively finds and adds all docsets in a given directory. diff --git a/src/registry/docsetregistry.h b/src/libs/registry/docsetregistry.h similarity index 80% rename from src/registry/docsetregistry.h rename to src/libs/registry/docsetregistry.h index 61bf2fb0b..2b8bcadc4 100644 --- a/src/registry/docsetregistry.h +++ b/src/libs/registry/docsetregistry.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -25,13 +25,16 @@ #define DOCSETREGISTRY_H #include "docset.h" -#include "searchresult.h" #include class QThread; namespace Zeal { +namespace Registry { + +struct CancellationToken; +struct SearchResult; class DocsetRegistry : public QObject { @@ -49,11 +52,10 @@ class DocsetRegistry : public QObject Docset *docset(const QString &name) const; Docset *docset(int index) const; + QList docsets() const; - QString prepareQuery(const QString &rawQuery); - void search(const QString &query); + void search(const QString &query, const CancellationToken &token); const QList &queryResults(); - QList docsets() const; public slots: void addDocset(const QString &path); @@ -62,20 +64,20 @@ public slots: void docsetAdded(const QString &name); void docsetAboutToBeRemoved(const QString &name); void docsetRemoved(const QString &name); - void queryCompleted(); + void queryCompleted(const QList &results); private slots: void _addDocset(const QString &path); - void _runQuery(const QString &query); + void _runQuery(const QString &query, const CancellationToken &token); private: void addDocsetsFromFolder(const QString &path); QThread *m_thread = nullptr; QMap m_docsets; - QList m_queryResults; }; +} // namespace Registry } // namespace Zeal #endif // DOCSETREGISTRY_H diff --git a/src/registry/listmodel.cpp b/src/libs/registry/listmodel.cpp similarity index 92% rename from src/registry/listmodel.cpp rename to src/libs/registry/listmodel.cpp index 26c0698c8..863379994 100644 --- a/src/registry/listmodel.cpp +++ b/src/libs/registry/listmodel.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -26,7 +26,7 @@ #include "docset.h" #include "docsetregistry.h" -using namespace Zeal; +using namespace Zeal::Registry; ListModel::ListModel(DocsetRegistry *docsetRegistry, QObject *parent) : QAbstractItemModel(parent), @@ -60,11 +60,11 @@ QVariant ListModel::data(const QModelIndex &index, int role) const case Level::GroupLevel: { DocsetItem *docsetItem = reinterpret_cast(index.internalPointer()); const QString symbolType = docsetItem->groups.at(index.row())->symbolType; - return QIcon(QString("typeIcon:%1.png").arg(symbolType)); + return docsetItem->docset->symbolTypeIcon(symbolType); } case Level::SymbolLevel: { GroupItem *groupItem = reinterpret_cast(index.internalPointer()); - return QIcon(QString("typeIcon:%1.png").arg(groupItem->symbolType)); + return groupItem->docsetItem->docset->symbolTypeIcon(groupItem->symbolType); } default: return QVariant(); @@ -75,12 +75,12 @@ QVariant ListModel::data(const QModelIndex &index, int role) const if (!index.column()) return m_docsetRegistry->docset(index.row())->title(); else - return m_docsetRegistry->docset(index.row())->indexFilePath(); + return m_docsetRegistry->docset(index.row())->indexFileUrl(); case Level::GroupLevel: { DocsetItem *docsetItem = reinterpret_cast(index.internalPointer()); const QString symbolType = docsetItem->groups.at(index.row())->symbolType; - return QString(QLatin1String("%1 (%2)")).arg(pluralize(symbolType), - QString::number(docsetItem->docset->symbolCount(symbolType))); + return QStringLiteral("%1 (%2)").arg(pluralize(symbolType), + QString::number(docsetItem->docset->symbolCount(symbolType))); } case Level::SymbolLevel: { GroupItem *groupItem = reinterpret_cast(index.internalPointer()); @@ -193,7 +193,7 @@ void ListModel::addDocset(const QString &name) void ListModel::removeDocset(const QString &name) { const int index = m_docsetItems.keys().indexOf(name); - /// TODO: Investigate why this can happen (see #420) + // TODO: Investigate why this can happen (see #420) if (index == -1) return; diff --git a/src/registry/listmodel.h b/src/libs/registry/listmodel.h similarity index 87% rename from src/registry/listmodel.h rename to src/libs/registry/listmodel.h index 79283352f..2d0ed913e 100644 --- a/src/registry/listmodel.h +++ b/src/libs/registry/listmodel.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -28,6 +28,7 @@ #include namespace Zeal { +namespace Registry { class Docset; class DocsetRegistry; @@ -36,8 +37,9 @@ class ListModel : public QAbstractItemModel { Q_OBJECT public: - enum { - DocsetNameRole = Qt::UserRole, + enum ItemDataRole { + // Do not collide with SearchModel, and ProgressItemDelegate. + DocsetNameRole = Qt::UserRole + 20, UpdateAvailableRole }; @@ -83,6 +85,7 @@ private slots: QMap m_docsetItems; }; +} // namespace Registry } // namespace Zeal #endif // LISTMODEL_H diff --git a/src/libs/registry/registry.pri b/src/libs/registry/registry.pri new file mode 100644 index 000000000..e998c795b --- /dev/null +++ b/src/libs/registry/registry.pri @@ -0,0 +1 @@ +ZEAL_LIB_NAME = Registry diff --git a/src/libs/registry/registry.pro b/src/libs/registry/registry.pro new file mode 100644 index 000000000..9007b98b0 --- /dev/null +++ b/src/libs/registry/registry.pro @@ -0,0 +1,6 @@ +include($$ZEAL_LIBRARY_PRI) + +QT += sql + +HEADERS += $$files(*.h) +SOURCES += $$files(*.cpp) diff --git a/src/registry/searchmodel.cpp b/src/libs/registry/searchmodel.cpp similarity index 57% rename from src/registry/searchmodel.cpp rename to src/libs/registry/searchmodel.cpp index c7dbc67b4..ba9990dd8 100644 --- a/src/registry/searchmodel.cpp +++ b/src/libs/registry/searchmodel.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,24 +17,34 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #include "searchmodel.h" -#include "core/application.h" -#include "registry/docsetregistry.h" +#include "docset.h" #include -using namespace Zeal; +using namespace Zeal::Registry; SearchModel::SearchModel(QObject *parent) : QAbstractItemModel(parent) { } +SearchModel::SearchModel(const SearchModel &other) : + QAbstractItemModel(other.d_ptr->parent), + m_dataList(other.m_dataList) +{ +} + +bool SearchModel::isEmpty() const +{ + return m_dataList.isEmpty(); +} + QVariant SearchModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) @@ -46,23 +56,19 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: switch (index.column()) { case 0: - if (item->parentName.isEmpty()) - return item->name; - else - return QString("%1 (%2)").arg(item->name, item->parentName); + return item->name; case 1: - return QDir(item->docset->documentPath()).absoluteFilePath(item->path); + return item->url; default: return QVariant(); } - case Qt::DecorationRole: - return item->docset->icon(); + case Qt::DecorationRole: { + return item->docset->symbolTypeIcon(item->type); + } - case Roles::TypeIconRole: - if (index.column() != 0) - return QVariant(); - return QIcon(QString("typeIcon:%1.png").arg(item->type)); + case ItemDataRole::DocsetIconRole: + return item->docset->icon(); default: return QVariant(); @@ -74,7 +80,9 @@ QModelIndex SearchModel::index(int row, int column, const QModelIndex &parent) c if (parent.isValid() || m_dataList.count() <= row || column > 1) return QModelIndex(); - return createIndex(row, column, (void *)&m_dataList.at(row)); + // FIXME: const_cast + SearchResult *item = const_cast(&m_dataList.at(row)); + return createIndex(row, column, item); } QModelIndex SearchModel::parent(const QModelIndex &child) const @@ -96,6 +104,36 @@ int SearchModel::columnCount(const QModelIndex &parent) const return 2; } +bool SearchModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row + count <= m_dataList.size() && !parent.isValid()) { + beginRemoveRows(parent, row, row + count - 1); + while (count) { + m_dataList.removeAt(row); + --count; + } + endRemoveRows(); + return true; + } + return false; +} + +void SearchModel::removeSearchResultWithName(const QString &name) +{ + QMutableListIterator iterator(m_dataList); + + int rowNum = 0; + while (iterator.hasNext()) { + if (iterator.next().docset->name() == name) { + beginRemoveRows(QModelIndex(), rowNum, rowNum); + iterator.remove(); + rowNum -= 1; + endRemoveRows(); + } + rowNum += 1; + } +} + void SearchModel::setResults(const QList &results) { beginResetModel(); diff --git a/src/registry/searchmodel.h b/src/libs/registry/searchmodel.h similarity index 75% rename from src/registry/searchmodel.h rename to src/libs/registry/searchmodel.h index f7a14cd3b..d2a7dad0a 100644 --- a/src/registry/searchmodel.h +++ b/src/libs/registry/searchmodel.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -29,22 +29,29 @@ #include namespace Zeal { +namespace Registry { class SearchModel : public QAbstractItemModel { Q_OBJECT public: - enum Roles { - TypeIconRole = Qt::UserRole + // TODO: Standardise roles across app + enum ItemDataRole { + DocsetIconRole = Qt::UserRole }; explicit SearchModel(QObject *parent = nullptr); + SearchModel(const SearchModel &other); + + bool isEmpty() const; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent) const override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + void removeSearchResultWithName(const QString &name); public slots: void setResults(const QList &results = QList()); @@ -56,6 +63,7 @@ public slots: QList m_dataList; }; +} // namespace Registry } // namespace Zeal #endif // SEARCHMODEL_H diff --git a/src/registry/searchquery.cpp b/src/libs/registry/searchquery.cpp similarity index 81% rename from src/registry/searchquery.cpp rename to src/libs/registry/searchquery.cpp index 021379b46..1dedf5c01 100644 --- a/src/registry/searchquery.cpp +++ b/src/libs/registry/searchquery.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,13 +17,13 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #include "searchquery.h" -using namespace Zeal; +using namespace Zeal::Registry; namespace { const char prefixSeparator = ':'; @@ -94,7 +94,7 @@ bool SearchQuery::hasKeywords() const bool SearchQuery::hasKeyword(const QString &keyword) const { // Temporary workaround for #333 - /// TODO: Remove once #167 is implemented + // TODO: Remove once #167 is implemented for (const QString &kw : m_keywords) { if (keyword.startsWith(kw, Qt::CaseInsensitive)) return true; @@ -126,23 +126,13 @@ void SearchQuery::setQuery(const QString &str) m_query = str; } -QString SearchQuery::sanitizedQuery() const -{ - QString q = m_query; - q.replace(QLatin1String("\\"), QLatin1String("\\\\")); - q.replace(QLatin1String("_"), QLatin1String("\\_")); - q.replace(QLatin1String("%"), QLatin1String("\\%")); - q.replace(QLatin1String("'"), QLatin1String("''")); - return q; -} - -QDataStream &Zeal::operator<<(QDataStream &out, const SearchQuery &query) +QDataStream &operator<<(QDataStream &out, const Zeal::Registry::SearchQuery &query) { out << query.toString(); return out; } -QDataStream &Zeal::operator>>(QDataStream &in, SearchQuery &query) +QDataStream &operator>>(QDataStream &in, Zeal::Registry::SearchQuery &query) { QString str; in >> str; diff --git a/src/registry/searchquery.h b/src/libs/registry/searchquery.h similarity index 88% rename from src/registry/searchquery.h rename to src/libs/registry/searchquery.h index bbebb99f4..27fe9f243 100644 --- a/src/registry/searchquery.h +++ b/src/libs/registry/searchquery.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -27,6 +27,7 @@ #include namespace Zeal { +namespace Registry { /** * @short The search query model. @@ -75,18 +76,16 @@ class SearchQuery QString query() const; void setQuery(const QString &str); - /// Returns the core query, sanitized for use in SQL queries - QString sanitizedQuery() const; - private: QString m_query; QStringList m_keywords; QString m_keywordPrefix; }; -QDataStream &operator<<(QDataStream &out, const SearchQuery &query); -QDataStream &operator>>(QDataStream &in, SearchQuery &query); - +} // namespace Registry } // namespace Zeal +QDataStream &operator<<(QDataStream &out, const Zeal::Registry::SearchQuery &query); +QDataStream &operator>>(QDataStream &in, Zeal::Registry::SearchQuery &query); + #endif // SEARCHQUERY_H diff --git a/src/ui/searchitemstyle.cpp b/src/libs/registry/searchresult.cpp similarity index 59% rename from src/ui/searchitemstyle.cpp rename to src/libs/registry/searchresult.cpp index c181db14e..1679f6620 100644 --- a/src/ui/searchitemstyle.cpp +++ b/src/libs/registry/searchresult.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,19 +17,10 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ -#include "searchitemstyle.h" +#include "searchresult.h" -QRect ZealSearchItemStyle::subElementRect(SubElement element, const QStyleOption *option, - const QWidget *widget) const -{ - if (element == QStyle::SE_ItemViewItemText) { - // do not draw text - delegate does it - return QRect(); - } else { - return QProxyStyle::subElementRect(element, option, widget); - } -} +using namespace Zeal::Registry; diff --git a/src/registry/searchresult.h b/src/libs/registry/searchresult.h similarity index 73% rename from src/registry/searchresult.h rename to src/libs/registry/searchresult.h index e879daf2f..5ece8073b 100644 --- a/src/registry/searchresult.h +++ b/src/libs/registry/searchresult.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -25,27 +25,29 @@ #define SEARCHRESULT_H #include +#include namespace Zeal { +namespace Registry { class Docset; struct SearchResult { QString name; - QString parentName; QString type; Docset *docset; - QString path; + QUrl url; - /// TODO: Remove - QString query; - - bool operator<(const SearchResult &r) const; + inline bool operator<(const SearchResult &other) const + { + return QString::compare(name, other.name, Qt::CaseInsensitive) < 0; + } }; +} // namespace Registry } // namespace Zeal #endif // SEARCHRESULT_H diff --git a/src/ui/aboutdialog.cpp b/src/libs/ui/aboutdialog.cpp similarity index 77% rename from src/ui/aboutdialog.cpp rename to src/libs/ui/aboutdialog.cpp index bb49c91fa..32ebdfe40 100644 --- a/src/ui/aboutdialog.cpp +++ b/src/libs/ui/aboutdialog.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -30,10 +30,10 @@ AboutDialog::AboutDialog(QWidget *parent) : ui(new Ui::AboutDialog) { ui->setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); const QString buildInfo - = QString(tr("Version: %1
Built: %2")) - .arg(QCoreApplication::applicationVersion(), QString("%1 %2").arg(__DATE__, __TIME__)); + = tr("Version: %1
").arg(QCoreApplication::applicationVersion()); ui->buildInfoLabel->setText(buildInfo); ui->buttonBox->setFocus(Qt::OtherFocusReason); diff --git a/src/ui/aboutdialog.h b/src/libs/ui/aboutdialog.h similarity index 87% rename from src/ui/aboutdialog.h rename to src/libs/ui/aboutdialog.h index 1ee9ab0db..bf4e0ee95 100644 --- a/src/ui/aboutdialog.h +++ b/src/libs/ui/aboutdialog.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/ui/docsetlistitemdelegate.cpp b/src/libs/ui/docsetlistitemdelegate.cpp similarity index 81% rename from src/ui/docsetlistitemdelegate.cpp rename to src/libs/ui/docsetlistitemdelegate.cpp index aa32635e1..c368cf2b8 100644 --- a/src/ui/docsetlistitemdelegate.cpp +++ b/src/libs/ui/docsetlistitemdelegate.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,16 +17,18 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #include "docsetlistitemdelegate.h" -#include "registry/listmodel.h" +#include #include +using namespace Zeal; + DocsetListItemDelegate::DocsetListItemDelegate(QObject *parent) : QItemDelegate(parent) { @@ -37,7 +39,7 @@ void DocsetListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem { QItemDelegate::paint(painter, option, index); - if (!index.model()->data(index, Zeal::ListModel::UpdateAvailableRole).toBool()) + if (!index.model()->data(index, Registry::ListModel::UpdateAvailableRole).toBool()) return; const QString text = tr("Update available"); @@ -46,10 +48,9 @@ void DocsetListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem font.setItalic(true); const QFontMetrics fontMetrics(font); - const int textWidth = fontMetrics.width(text) + 2; QRect textRect = option.rect; - textRect.setLeft(textRect.right() - textWidth); + textRect.setLeft(textRect.right() - fontMetrics.width(text) - 2); painter->save(); diff --git a/src/ui/docsetlistitemdelegate.h b/src/libs/ui/docsetlistitemdelegate.h similarity index 88% rename from src/ui/docsetlistitemdelegate.h rename to src/libs/ui/docsetlistitemdelegate.h index a310b776e..0d12ce9a8 100644 --- a/src/ui/docsetlistitemdelegate.h +++ b/src/libs/ui/docsetlistitemdelegate.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/libs/ui/docsetsdialog.cpp b/src/libs/ui/docsetsdialog.cpp new file mode 100644 index 000000000..bd869e158 --- /dev/null +++ b/src/libs/ui/docsetsdialog.cpp @@ -0,0 +1,797 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#include "docsetsdialog.h" +#include "ui_docsetsdialog.h" + +#include "docsetlistitemdelegate.h" +#include "progressitemdelegate.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace Zeal; + +namespace { +const char ApiServerUrl[] = "http://api.zealdocs.org/v1"; +const char RedirectServerUrl[] = "https://go.zealdocs.org"; +// TODO: Each source plugin should have its own cache +const char DocsetListCacheFileName[] = "com.kapeli.json"; + +// TODO: Make the timeout period configurable +constexpr int CacheTimeout = 24 * 60 * 60 * 1000; // 24 hours in microseconds + +// QNetworkReply properties +const char DocsetNameProperty[] = "docsetName"; +const char DownloadTypeProperty[] = "downloadType"; +const char DownloadPreviousReceived[] = "downloadPreviousReceived"; +const char ListItemIndexProperty[] = "listItem"; +} + +DocsetsDialog::DocsetsDialog(Core::Application *app, QWidget *parent) : + QDialog(parent), + ui(new Ui::DocsetsDialog()), + m_application(app), + m_docsetRegistry(app->docsetRegistry()) +{ + using Registry::DocsetRegistry; + using Registry::ListModel; + + ui->setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + +#ifdef Q_OS_OSX + ui->availableDocsetList->setAttribute(Qt::WA_MacShowFocusRect, false); + ui->installedDocsetList->setAttribute(Qt::WA_MacShowFocusRect, false); +#endif + + ui->combinedProgressBar->hide(); + ui->cancelButton->hide(); + + // Installed docsets tab + ui->installedDocsetList->setItemDelegate(new DocsetListItemDelegate(this)); + ui->installedDocsetList->setModel(new ListModel(app->docsetRegistry(), this)); + connect(ui->installedDocsetList, &QListView::activated, this, [this](const QModelIndex &index) { + if (!index.data(ListModel::UpdateAvailableRole).toBool()) { + return; + } + + downloadDashDocset(index); + }); + + QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); + connect(selectionModel, &QItemSelectionModel::selectionChanged, + [this, selectionModel]() { + ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); + + for (const QModelIndex &index : selectionModel->selectedIndexes()) { + if (index.data(ListModel::UpdateAvailableRole).toBool()) { + ui->updateSelectedDocsetsButton->setEnabled(true); + return; + } + } + + ui->updateSelectedDocsetsButton->setEnabled(false); + }); + connect(ui->updateSelectedDocsetsButton, &QPushButton::clicked, + this, &DocsetsDialog::updateSelectedDocsets); + connect(ui->updateAllDocsetsButton, &QPushButton::clicked, + this, &DocsetsDialog::updateAllDocsets); + connect(ui->removeDocsetsButton, &QPushButton::clicked, + this, &DocsetsDialog::removeSelectedDocsets); + connect(ui->docsetFilterInput, &QLineEdit::textEdited, + this, &DocsetsDialog::updateDocsetFilter); + + // Available docsets tab + ui->availableDocsetList->setItemDelegate(new ProgressItemDelegate(this)); + connect(ui->availableDocsetList, &QListView::activated, this, [this](const QModelIndex &index) { + // TODO: Cancel download if it's already in progress. + if (index.data(ProgressItemDelegate::ShowProgressRole).toBool()) + return; + + ui->availableDocsetList->selectionModel()->select(index, QItemSelectionModel::Deselect); + + QAbstractItemModel *model = ui->availableDocsetList->model(); + model->setData(index, tr("Downloading: %p%"), ProgressItemDelegate::FormatRole); + model->setData(index, 0, ProgressItemDelegate::ValueRole); + model->setData(index, true, ProgressItemDelegate::ShowProgressRole); + + downloadDashDocset(index); + }); + + selectionModel = ui->availableDocsetList->selectionModel(); + connect(selectionModel, &QItemSelectionModel::selectionChanged, [this, selectionModel]() { + for (const QModelIndex &index : selectionModel->selectedIndexes()) { + if (!index.data(ProgressItemDelegate::ShowProgressRole).toBool()) { + ui->downloadDocsetsButton->setEnabled(true); + return; + } + } + + ui->downloadDocsetsButton->setEnabled(false); + }); + + connect(ui->downloadDocsetsButton, &QPushButton::clicked, + this, &DocsetsDialog::downloadSelectedDocsets); + + connect(m_docsetRegistry, &DocsetRegistry::docsetRemoved, this, [this](const QString name) { + QListWidgetItem *item = findDocsetListItem(name); + if (!item) + return; + + item->setHidden(false); + }); + connect(m_docsetRegistry, &DocsetRegistry::docsetAdded, this, [this](const QString name) { + QListWidgetItem *item = findDocsetListItem(name); + if (!item) + return; + + item->setHidden(true); + }); + + connect(ui->addFeedButton, &QPushButton::clicked, this, &DocsetsDialog::addDashFeed); + connect(ui->refreshButton, &QPushButton::clicked, this, &DocsetsDialog::downloadDocsetList); + + connect(m_application, &Core::Application::extractionCompleted, + this, &DocsetsDialog::extractionCompleted); + connect(m_application, &Core::Application::extractionError, + this, &DocsetsDialog::extractionError); + connect(m_application, &Core::Application::extractionProgress, + this, &DocsetsDialog::extractionProgress); + + connect(ui->cancelButton, &QPushButton::clicked, this, &DocsetsDialog::cancelDownloads); + + loadDocsetList(); +} + +DocsetsDialog::~DocsetsDialog() +{ + delete ui; +} + +void DocsetsDialog::reject() +{ + if (m_replies.isEmpty() && m_tmpFiles.isEmpty() && m_docsetsBeingDeleted.isEmpty()) { + QDialog::reject(); + return; + } + + QMessageBox::information(this, QStringLiteral("Zeal"), + tr("Please wait for all operations to finish.")); +} + +void DocsetsDialog::addDashFeed() +{ + QString clipboardText = QApplication::clipboard()->text(); + if (!clipboardText.startsWith(QLatin1String("dash-feed://"))) + clipboardText.clear(); + + QString feedUrl = QInputDialog::getText(this, QStringLiteral("Zeal"), tr("Feed URL:"), + QLineEdit::Normal, clipboardText); + if (feedUrl.isEmpty()) + return; + + if (feedUrl.startsWith(QLatin1String("dash-feed://"))) { + feedUrl = feedUrl.remove(0, 12); + feedUrl = QUrl::fromPercentEncoding(feedUrl.toUtf8()); + } + + QNetworkReply *reply = download(QUrl(feedUrl)); + reply->setProperty(DownloadTypeProperty, DownloadDashFeed); + connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); +} + +void DocsetsDialog::updateSelectedDocsets() +{ + for (const QModelIndex &index : ui->installedDocsetList->selectionModel()->selectedIndexes()) { + if (!index.data(Registry::ListModel::UpdateAvailableRole).toBool()) + continue; + + downloadDashDocset(index); + } +} + +void DocsetsDialog::updateAllDocsets() +{ + QAbstractItemModel *model = ui->installedDocsetList->model(); + for (int i = 0; i < model->rowCount(); ++i) { + const QModelIndex index = model->index(i, 0); + if (!index.data(Registry::ListModel::UpdateAvailableRole).toBool()) + continue; + + downloadDashDocset(index); + } +} + +void DocsetsDialog::removeSelectedDocsets() +{ + QItemSelectionModel *selectonModel = ui->installedDocsetList->selectionModel(); + if (!selectonModel->hasSelection()) + return; + + int ret; + + const QModelIndexList selectedIndexes = selectonModel->selectedIndexes(); + if (selectedIndexes.size() == 1) { + const QString docsetTitle = selectedIndexes.first().data().toString(); + ret = QMessageBox::question(this, QStringLiteral("Zeal"), + tr("Remove %1 docset?").arg(docsetTitle)); + } else { + ret = QMessageBox::question(this, QStringLiteral("Zeal"), + tr("Remove %n docset(s)?", nullptr, + selectedIndexes.size())); + } + + if (ret == QMessageBox::No) + return; + + // Gather names first, because model indicies become invalid when docsets are removed. + QStringList names; + for (const QModelIndex &index : selectedIndexes) + names.append(index.data(Registry::ListModel::DocsetNameRole).toString()); + + for (const QString &name : names) + removeDocset(name); +} + +void DocsetsDialog::updateDocsetFilter(const QString &filterString) +{ + const bool doSearch = !filterString.simplified().isEmpty(); + + for (int i = 0; i < ui->availableDocsetList->count(); ++i) { + QListWidgetItem *item = ui->availableDocsetList->item(i); + + // Skip installed docsets + if (m_docsetRegistry->contains(item->data(Registry::ListModel::DocsetNameRole).toString())) + continue; + + item->setHidden(doSearch && !item->text().contains(filterString, Qt::CaseInsensitive)); + } +} + +void DocsetsDialog::downloadSelectedDocsets() +{ + QItemSelectionModel *selectionModel = ui->availableDocsetList->selectionModel(); + for (const QModelIndex &index : selectionModel->selectedIndexes()) { + selectionModel->select(index, QItemSelectionModel::Deselect); + + // Do nothing if a download is already in progress. + if (index.data(ProgressItemDelegate::ShowProgressRole).toBool()) + continue; + + QAbstractItemModel *model = ui->availableDocsetList->model(); + model->setData(index, tr("Downloading: %p%"), ProgressItemDelegate::FormatRole); + model->setData(index, 0, ProgressItemDelegate::ValueRole); + model->setData(index, true, ProgressItemDelegate::ShowProgressRole); + + downloadDashDocset(index); + } +} + +/*! + \internal + Should be connected to all \l QNetworkReply::finished signals in order to process possible + HTTP-redirects correctly. +*/ +void DocsetsDialog::downloadCompleted() +{ + QScopedPointer reply( + qobject_cast(sender())); + + m_replies.removeOne(reply.data()); + + if (reply->error() != QNetworkReply::NoError) { + if (reply->error() != QNetworkReply::OperationCanceledError) { + const int ret = QMessageBox::warning(this, QStringLiteral("Zeal"), reply->errorString(), + QMessageBox::Retry | QMessageBox::Default, + QMessageBox::Cancel | QMessageBox::Escape, + QMessageBox::NoButton); + + if (ret == QMessageBox::Retry) { + QNetworkReply *newReply = download(reply->request().url()); + + // Copy properties + newReply->setProperty(DocsetNameProperty, reply->property(DocsetNameProperty)); + newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); + newReply->setProperty(ListItemIndexProperty, + reply->property(ListItemIndexProperty)); + + connect(newReply, &QNetworkReply::finished, + this, &DocsetsDialog::downloadCompleted); + return; + } + + bool ok; + QListWidgetItem *listItem = ui->availableDocsetList->item( + reply->property(ListItemIndexProperty).toInt(&ok)); + if (ok && listItem) + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + } + + resetProgress(); + return; + } + + QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if (redirectUrl.isValid()) { + if (redirectUrl.isRelative()) + redirectUrl = reply->request().url().resolved(redirectUrl); + + // TODO: Verify if scheme can be missing + if (redirectUrl.scheme().isEmpty()) + redirectUrl.setScheme(reply->request().url().scheme()); + + QNetworkReply *newReply = download(redirectUrl); + + // Copy properties + newReply->setProperty(DocsetNameProperty, reply->property(DocsetNameProperty)); + newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); + newReply->setProperty(ListItemIndexProperty, reply->property(ListItemIndexProperty)); + + connect(newReply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); + + return; + } + + switch (reply->property(DownloadTypeProperty).toUInt()) { + case DownloadDocsetList: { + const QByteArray data = reply->readAll(); + + QScopedPointer file(new QFile(cacheLocation(DocsetListCacheFileName))); + if (file->open(QIODevice::WriteOnly)) + file->write(data); + + ui->lastUpdatedLabel->setText(QFileInfo(file->fileName()) + .lastModified().toString(Qt::SystemLocaleShortDate)); + + QJsonParseError jsonError; + const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) { + QMessageBox::warning(this, QStringLiteral("Zeal"), + tr("Corrupted docset list: ") + jsonError.errorString()); + break; + } + + processDocsetList(jsonDoc.array()); + break; + } + + case DownloadDashFeed: { + Registry::DocsetMetadata metadata + = Registry::DocsetMetadata::fromDashFeed(reply->request().url(), reply->readAll()); + + if (metadata.urls().isEmpty()) { + QMessageBox::warning(this, QStringLiteral("Zeal"), tr("Invalid docset feed!")); + break; + } + + m_userFeeds[metadata.name()] = metadata; + QNetworkReply *reply = download(metadata.url()); + reply->setProperty(DocsetNameProperty, metadata.name()); + reply->setProperty(DownloadTypeProperty, DownloadDocset); + connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); + break; + } + + case DownloadDocset: { + const QString docsetName = reply->property(DocsetNameProperty).toString(); + + // TODO: Implement an explicit and verbose docset update logic + const QDir dir(m_application->settings()->docsetPath); + if (dir.exists(docsetName + QLatin1String(".docset"))) + removeDocset(docsetName); + + QTemporaryFile *tmpFile = m_tmpFiles[docsetName]; + if (!tmpFile) { + tmpFile = new QTemporaryFile(this); + tmpFile->open(); + m_tmpFiles.insert(docsetName, tmpFile); + } + + while (reply->bytesAvailable()) + tmpFile->write(reply->read(1024 * 1024)); // Use small chunks + tmpFile->close(); + + QListWidgetItem *item = findDocsetListItem(docsetName); + if (item) { + item->setData(ProgressItemDelegate::ValueRole, 0); + item->setData(ProgressItemDelegate::FormatRole, tr("Installing: %p%")); + } + + m_application->extract(tmpFile->fileName(), m_application->settings()->docsetPath, + docsetName + QLatin1String(".docset")); + break; + } + } + + // If all enqueued downloads have finished executing + if (m_replies.isEmpty()) + resetProgress(); +} + +// creates a total download progress for multiple QNetworkReplies +void DocsetsDialog::downloadProgress(qint64 received, qint64 total) +{ + // Don't show progress for non-docset pages + if (total == -1 || received < 10240) + return; + + QNetworkReply *reply = qobject_cast(sender()); + if (!reply || !reply->isOpen()) + return; + + if (reply->property(DownloadTypeProperty).toInt() == DownloadDocset) { + const QString docsetName = reply->property(DocsetNameProperty).toString(); + + QTemporaryFile *tmpFile = m_tmpFiles[docsetName]; + if (!tmpFile) { + tmpFile = new QTemporaryFile(this); + tmpFile->open(); + m_tmpFiles.insert(docsetName, tmpFile); + } + + tmpFile->write(reply->read(received)); + } + + // Try to get the item associated to the request + QListWidgetItem *item + = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); + if (item) + item->setData(ProgressItemDelegate::ValueRole, percent(received, total)); + + qint64 previousReceived = 0; + const QVariant previousReceivedVariant = reply->property(DownloadPreviousReceived); + if (!previousReceivedVariant.isValid()) + m_combinedTotal += total; + else + previousReceived = previousReceivedVariant.toLongLong(); + + m_combinedReceived += received - previousReceived; + reply->setProperty(DownloadPreviousReceived, received); + + updateCombinedProgress(); +} + +void DocsetsDialog::extractionCompleted(const QString &filePath) +{ + const QString docsetName = docsetNameForTmpFilePath(filePath); + + const QDir dataDir(m_application->settings()->docsetPath); + const QString docsetPath = dataDir.absoluteFilePath(docsetName + QLatin1String(".docset")); + + // Write metadata about docset + Registry::DocsetMetadata metadata = m_availableDocsets.contains(docsetName) + ? m_availableDocsets[docsetName] + : m_userFeeds[docsetName]; + metadata.save(docsetPath, metadata.latestVersion()); + + m_docsetRegistry->addDocset(docsetPath); + + QListWidgetItem *listItem = findDocsetListItem(docsetName); + if (listItem) { + listItem->setHidden(true); + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + } + + resetProgress(); + delete m_tmpFiles.take(docsetName); +} + +void DocsetsDialog::extractionError(const QString &filePath, const QString &errorString) +{ + const QString docsetName = docsetNameForTmpFilePath(filePath); + + QMessageBox::warning(this, QStringLiteral("Zeal"), + tr("Cannot extract docset %1: %2").arg(docsetName, errorString)); + + QListWidgetItem *listItem = findDocsetListItem(docsetName); + if (listItem) + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + + delete m_tmpFiles.take(docsetName); +} + +void DocsetsDialog::extractionProgress(const QString &filePath, qint64 extracted, qint64 total) +{ + const QString docsetName = docsetNameForTmpFilePath(filePath); + QListWidgetItem *listItem = findDocsetListItem(docsetName); + if (listItem) + listItem->setData(ProgressItemDelegate::ValueRole, percent(extracted, total)); +} + +void DocsetsDialog::loadDocsetList() +{ + const QFileInfo fi(cacheLocation(DocsetListCacheFileName)); + if (!fi.exists() || fi.lastModified().msecsTo(QDateTime::currentDateTime()) > CacheTimeout) { + downloadDocsetList(); + return; + } + + QScopedPointer file(new QFile(fi.filePath())); + if (!file->open(QIODevice::ReadOnly)) { + downloadDocsetList(); + return; + } + + QJsonParseError jsonError; + const QJsonDocument jsonDoc = QJsonDocument::fromJson(file->readAll(), &jsonError); + + if (jsonError.error != QJsonParseError::NoError) { + downloadDocsetList(); + return; + } + + // TODO: Show more user friendly labels, like "5 hours ago" + ui->lastUpdatedLabel->setText(fi.lastModified().toString(Qt::SystemLocaleShortDate)); + processDocsetList(jsonDoc.array()); +} + +QListWidgetItem *DocsetsDialog::findDocsetListItem(const QString &name) const +{ + for (int i = 0; i < ui->availableDocsetList->count(); ++i) { + QListWidgetItem *item = ui->availableDocsetList->item(i); + + if (item->data(Registry::ListModel::DocsetNameRole).toString() == name) + return item; + } + + return nullptr; +} + +bool DocsetsDialog::updatesAvailable() const +{ + for (Registry::Docset *docset : m_docsetRegistry->docsets()) { + if (docset->hasUpdate) + return true; + } + + return false; +} + +QNetworkReply *DocsetsDialog::download(const QUrl &url) +{ + QNetworkReply *reply = m_application->download(url); + connect(reply, &QNetworkReply::downloadProgress, this, &DocsetsDialog::downloadProgress); + m_replies.append(reply); + + // Installed docsets + ui->addFeedButton->setEnabled(false); + ui->updateSelectedDocsetsButton->setEnabled(false); + ui->updateAllDocsetsButton->setEnabled(false); + ui->removeDocsetsButton->setEnabled(false); + + // Available docsets + ui->refreshButton->setEnabled(false); + + updateCombinedProgress(); + + return reply; +} + +void DocsetsDialog::cancelDownloads() +{ + for (QNetworkReply *reply : m_replies) { + // Hide progress bar + QListWidgetItem *listItem + = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); + if (listItem) + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + + if (reply->property(DownloadTypeProperty).toInt() == DownloadDocset) + delete m_tmpFiles.take(reply->property(DocsetNameProperty).toString()); + + reply->abort(); + } + + resetProgress(); +} + +void DocsetsDialog::downloadDocsetList() +{ + ui->availableDocsetList->clear(); + m_availableDocsets.clear(); + + QNetworkReply *reply = download(QUrl(ApiServerUrl + QLatin1String("/docsets"))); + reply->setProperty(DownloadTypeProperty, DownloadDocsetList); + connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); +} + +void DocsetsDialog::processDocsetList(const QJsonArray &list) +{ + for (const QJsonValue &v : list) { + QJsonObject docsetJson = v.toObject(); + + Registry::DocsetMetadata metadata(docsetJson); + m_availableDocsets.insert(metadata.name(), metadata); + } + + // TODO: Move into dedicated method + for (const Registry::DocsetMetadata &metadata : m_availableDocsets) { + QListWidgetItem *listItem + = new QListWidgetItem(metadata.icon(), metadata.title(), ui->availableDocsetList); + listItem->setData(Registry::ListModel::DocsetNameRole, metadata.name()); + + if (m_docsetRegistry->contains(metadata.name())) { + listItem->setHidden(true); + + Registry::Docset *docset = m_docsetRegistry->docset(metadata.name()); + + if (metadata.latestVersion() != docset->version() + || (metadata.latestVersion() == docset->version() + && metadata.revision() > docset->revision())) { + docset->hasUpdate = true; + ui->updateAllDocsetsButton->setEnabled(true); + } + } + } + + ui->installedDocsetList->reset(); +} + +void DocsetsDialog::downloadDashDocset(const QModelIndex &index) +{ + const QString name = index.data(Registry::ListModel::DocsetNameRole).toString(); + + if (!m_availableDocsets.contains(name)) + return; + + const QString urlString = RedirectServerUrl + QStringLiteral("/d/com.kapeli/%1/latest"); + QNetworkReply *reply = download(QUrl(urlString.arg(name))); + reply->setProperty(DocsetNameProperty, name); + reply->setProperty(DownloadTypeProperty, DownloadDocset); + reply->setProperty(ListItemIndexProperty, + ui->availableDocsetList->row(findDocsetListItem(name))); + + connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); +} + +void DocsetsDialog::removeDocset(const QString &name) +{ + if (m_docsetsBeingDeleted.contains(name)) + return; + + m_docsetsBeingDeleted.append(name); + + Registry::Docset *docset = m_docsetRegistry->docset(name); + const QString title = docset->title(); + const QString docsetPath = docset->path(); + const QString tmpPath = docsetPath + QLatin1String(".deleteme.") + + QString::number(QDateTime::currentMSecsSinceEpoch()); + + // Rename first to allow simultaneous installation. + // TODO: Check for error + QDir().rename(docsetPath, tmpPath); + + m_docsetRegistry->remove(name); + + QFuture future = QtConcurrent::run([tmpPath] { + return QDir(tmpPath).removeRecursively(); + }); + + QFutureWatcher *watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, [=] { + if (!watcher->result()) { + QMessageBox::warning(this, QStringLiteral("Zeal"), + tr("Cannot delete docset %1!").arg(title)); + } + + QListWidgetItem *listItem = findDocsetListItem(name); + if (listItem) + listItem->setHidden(false); + + watcher->deleteLater(); + + m_docsetsBeingDeleted.removeOne(name); + }); +} + +void DocsetsDialog::updateCombinedProgress() +{ + if (m_replies.isEmpty()) { + resetProgress(); + return; + } + + ui->combinedProgressBar->show(); + ui->combinedProgressBar->setValue(percent(m_combinedReceived, m_combinedTotal)); + ui->cancelButton->show(); +} + +void DocsetsDialog::resetProgress() +{ + if (!m_replies.isEmpty()) + return; + + ui->cancelButton->hide(); + ui->combinedProgressBar->hide(); + ui->combinedProgressBar->setValue(0); + + m_combinedReceived = 0; + m_combinedTotal = 0; + + // Installed docsets + ui->addFeedButton->setEnabled(true); + QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); + bool hasSelectedUpdates = false; + for (const QModelIndex &index : selectionModel->selectedIndexes()) { + if (index.data(Registry::ListModel::UpdateAvailableRole).toBool()) { + hasSelectedUpdates = true; + break; + } + } + + ui->updateSelectedDocsetsButton->setEnabled(hasSelectedUpdates); + ui->updateAllDocsetsButton->setEnabled(updatesAvailable()); + ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); + + // Available docsets + ui->refreshButton->setEnabled(true); +} + +QString DocsetsDialog::docsetNameForTmpFilePath(const QString &filePath) const +{ + for (const QString &key : m_tmpFiles.keys()) { + if (m_tmpFiles[key]->fileName() == filePath) { + return key; + } + } + + return QString(); +} + +int DocsetsDialog::percent(qint64 fraction, qint64 total) +{ + if (!total) + return 0; + + return static_cast(fraction / static_cast(total) * 100); +} + +QString DocsetsDialog::cacheLocation(const QString &fileName) +{ +#ifndef PORTABLE_BUILD + const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); +#else + const QDir cacheDir(QCoreApplication::applicationDirPath() + QLatin1String("/cache")); +#endif + // TODO: Report error + QDir().mkpath(cacheDir.path()); + + return cacheDir.filePath(fileName); +} diff --git a/src/ui/settingsdialog.h b/src/libs/ui/docsetsdialog.h similarity index 62% rename from src/ui/settingsdialog.h rename to src/libs/ui/docsetsdialog.h index e951a7e15..d8a9de33a 100644 --- a/src/ui/settingsdialog.h +++ b/src/libs/ui/docsetsdialog.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,50 +17,55 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ -#ifndef SETTINGSDIALOG_H -#define SETTINGSDIALOG_H +#ifndef DOCSETSDIALOG_H +#define DOCSETSDIALOG_H -#include "registry/docsetmetadata.h" +#include #include #include #include -class QAbstractButton; class QListWidgetItem; class QNetworkReply; class QTemporaryFile; class QUrl; namespace Ui { -class SettingsDialog; +class DocsetsDialog; } namespace Zeal { +namespace Registry { class DocsetRegistry; -class ListModel; +} // namespace Registry namespace Core { class Application; } -class SettingsDialog : public QDialog +class DocsetsDialog : public QDialog { Q_OBJECT public: - explicit SettingsDialog(Core::Application *app, ListModel *listModel, QWidget *parent = nullptr); - ~SettingsDialog() override; + explicit DocsetsDialog(Core::Application *app, QWidget *parent = nullptr); + ~DocsetsDialog() override; + + void reject() override; private slots: void addDashFeed(); void updateSelectedDocsets(); void updateAllDocsets(); void removeSelectedDocsets(); + void updateDocsetFilter(const QString &filterString); + + void downloadSelectedDocsets(); void downloadCompleted(); void downloadProgress(qint64 received, qint64 total); @@ -69,9 +74,7 @@ private slots: void extractionError(const QString &filePath, const QString &errorString); void extractionProgress(const QString &filePath, qint64 extracted, qint64 total); - void on_downloadDocsetButton_clicked(); - void on_storageButton_clicked(); - void on_tabWidget_currentChanged(int current); + void loadDocsetList(); private: enum DownloadType { @@ -80,21 +83,22 @@ private slots: DownloadDocsetList }; - Ui::SettingsDialog *ui = nullptr; + Ui::DocsetsDialog *ui = nullptr; Core::Application *m_application = nullptr; - DocsetRegistry *m_docsetRegistry = nullptr; + Registry::DocsetRegistry *m_docsetRegistry = nullptr; QList m_replies; qint64 m_combinedTotal = 0; qint64 m_combinedReceived = 0; - /// TODO: Create a special model - QMap m_availableDocsets; - QMap m_userFeeds; + // TODO: Create a special model + QMap m_availableDocsets; + QMap m_userFeeds; QHash m_tmpFiles; + QStringList m_docsetsBeingDeleted; - QListWidgetItem *findDocsetListItem(const QString &title) const; + QListWidgetItem *findDocsetListItem(const QString &name) const; bool updatesAvailable() const; QNetworkReply *download(const QUrl &url); @@ -103,17 +107,20 @@ private slots: void downloadDocsetList(); void processDocsetList(const QJsonArray &list); - void downloadDashDocset(const QString &name); - void removeDocsets(const QStringList &names); + void downloadDashDocset(const QModelIndex &index); + void removeDocset(const QString &name); - void displayProgress(); + void updateCombinedProgress(); void resetProgress(); - void loadSettings(); - void saveSettings(); + // FIXME: Come up with a better approach + QString docsetNameForTmpFilePath(const QString &filePath) const; + static inline int percent(qint64 fraction, qint64 total); + + static QString cacheLocation(const QString &fileName); }; } // namespace Zeal -#endif // SETTINGSDIALOG_H +#endif // DOCSETSDIALOG_H diff --git a/src/ui/forms/aboutdialog.ui b/src/libs/ui/forms/aboutdialog.ui similarity index 93% rename from src/ui/forms/aboutdialog.ui rename to src/libs/ui/forms/aboutdialog.ui index 3b01eeb1f..46061a807 100644 --- a/src/ui/forms/aboutdialog.ui +++ b/src/libs/ui/forms/aboutdialog.ui @@ -75,13 +75,13 @@ <strong>A simple offline documentation browser</strong> <br><br> -Copyright &copy; Zeal Developers, 2013-2015. +Copyright &copy; Oleg Shparber and other conributors, 2013-2016. <br> <a href="https://zealdocs.org">zealdocs.org</a> <br> -<a href="irc://irc.freenode.net/zealdocs">#zealdocs</a> on <a href="http://www.freenode.net">Freenode</a> +<a href="irc://irc.freenode.net/zealdocs">#zealdocs</a> on <a href="https://www.freenode.net">Freenode</a> <br><br> -Zeal is an open source software available under the terms of General Public License version 3 (<a href="http://www.gnu.org/copyleft/gpl.html">GPLv3</a>). +Zeal is an open source software available under the terms of General Public License version 3 (<a href="https://www.gnu.org/copyleft/gpl.html">GPLv3</a>). <br><br> Docsets are courtesy of <a href="https://kapeli.com/dash">Dash</a>. diff --git a/src/libs/ui/forms/docsetsdialog.ui b/src/libs/ui/forms/docsetsdialog.ui new file mode 100644 index 000000000..c3ba0bbeb --- /dev/null +++ b/src/libs/ui/forms/docsetsdialog.ui @@ -0,0 +1,257 @@ + + + DocsetsDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 600 + 500 + + + + Docsets + + + + + + 0 + + + + Installed + + + + + + QAbstractItemView::ExtendedSelection + + + + 16 + 16 + + + + + + + + + + Add feed + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Update + + + + + + + false + + + Update all + + + + + + + false + + + Remove + + + + + + + + + + Available + + + + + + Filter docsets + + + true + + + + + + + QAbstractItemView::ExtendedSelection + + + + 16 + 16 + + + + + + + + + + Last updated: + + + + + + + + + + + + + + Refresh + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Download + + + + + + + + + <i>Docsets are provided by <a href="https://kapeli.com/dash">Dash</a>, the OS X Documentation Browser.</i> + + + Qt::RichText + + + true + + + + + + + + + + + + + + 100 + 0 + + + + + + + + Cancel + + + + + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + DocsetsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DocsetsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ui/forms/mainwindow.ui b/src/libs/ui/forms/mainwindow.ui similarity index 58% rename from src/ui/forms/mainwindow.ui rename to src/libs/ui/forms/mainwindow.ui index e31299056..6d26b9916 100644 --- a/src/ui/forms/mainwindow.ui +++ b/src/libs/ui/forms/mainwindow.ui @@ -6,29 +6,15 @@ 0 0 - 1050 - 579 + 900 + 600 Zeal - - -QSplitter::handle { - background: black; -} - -QFrame#searchEditFrame, QFrame#tabBarFrame { - border-top: none; - border-right: none; - border-bottom: 1px solid black; - border-left: none; -} - - - + 0 @@ -41,24 +27,18 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { 0 - + - - 5 - - - Qt::Horizontal - - - 1 + + false - + 0 - + 0 @@ -68,7 +48,7 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { - + 6 @@ -95,36 +75,32 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { - - - QFrame::NoFrame - - - QAbstractItemView::SelectItems - - - 10 - - - false - - - - - - - See also - - - 2 - - - - - - - Qt::ScrollBarAlwaysOff + + + Qt::Vertical + + + QFrame::NoFrame + + + true + + + false + + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + @@ -135,7 +111,7 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { 0 - + 0 @@ -146,42 +122,53 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { 0 - - 12 - 0 + + Go back one page + + + QToolButton::menu-indicator { image: none; } + + + + + + Go forward one page + + + QToolButton::menu-indicator { image: none; } + + + + - - - - - - 0 - 0 - - - - Open URL - - - - + + + + 0 + 0 + + + + Open URL + + @@ -207,7 +194,7 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { 0 0 - 1050 + 900 23 @@ -215,7 +202,8 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { &File - + + @@ -223,45 +211,51 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { &Help - - + + - + - &View + &Edit - - + + + - + - &Tabs + &Tools - - - - - + - - + + + + + &Quit - + + + + - &Options + Prefere&nces + + + About &Zeal @@ -272,26 +266,38 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { + + false + + + + &Back + + false + + + + &Forward - - New Tab + + - - Ctrl+T + + New &Tab - Close Tab + &Close Tab @@ -304,14 +310,27 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { Previous Tab - + + + &Submit Feedback... + + + + + &Check for updates... + + + + + + - &Report Problem... + &Find - + - &Check for Update... + &Docsets... @@ -328,6 +347,12 @@ QFrame#searchEditFrame, QFrame#tabBarFrame {
ui/widgets/searchablewebview.h
1 + + ToolBarFrame + QWidget +
ui/widgets/toolbarframe.h
+ 1 +
diff --git a/src/ui/forms/settingsdialog.ui b/src/libs/ui/forms/settingsdialog.ui similarity index 76% rename from src/ui/forms/settingsdialog.ui rename to src/libs/ui/forms/settingsdialog.ui index 711ca14cf..efc3a8965 100644 --- a/src/ui/forms/settingsdialog.ui +++ b/src/libs/ui/forms/settingsdialog.ui @@ -9,12 +9,12 @@ 0 0 - 624 - 505 + 600 + 500 - Zeal Options + Preferences @@ -156,6 +156,29 @@ + + + Tabs + + + + + + Behavior + + + + + + Open new tab after active + + + + + + + + Appearance @@ -370,163 +393,13 @@ - - - Installed docsets + + + Docsets are now available via the <b>Tools</b> menu. - - - - - - 16 - 16 - - - - - - - - - - Add feed - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Update - - - - - - - false - - - Update all - - - - - - - false - - - Remove - - - - - - - 0 - - - - - - - - - - - - Downloadable docsets + + Qt::AlignCenter - - - - - - 16 - 16 - - - - - - - - - - Last updated: - - - - - - - - - - - - - - Refresh - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - Download - - - - - - - - - <i>Docsets are provided by <a href="https://kapeli.com/dash">Dash</a>, the OS X Documentation Browser.</i> - - - Qt::RichText - - - true - - - - @@ -535,9 +408,6 @@ - - Qt::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/libs/ui/mainwindow.cpp b/src/libs/ui/mainwindow.cpp new file mode 100644 index 000000000..d7766c687 --- /dev/null +++ b/src/libs/ui/mainwindow.cpp @@ -0,0 +1,937 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "aboutdialog.h" +#include "docsetsdialog.h" +#include "searchitemdelegate.h" +#include "settingsdialog.h" +#include "qxtglobalshortcut/qxtglobalshortcut.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_WEBENGINE +#include +#include +#include + +typedef QWebEngineHistory QWebHistory; +typedef QWebEngineHistoryItem QWebHistoryItem; +typedef QWebEnginePage QWebPage; +#else +#include +#include +#include +#endif + +// TODO: [Qt 5.5] Remove in favour of native Qt support (QTBUG-31762) +#ifdef USE_APPINDICATOR +#undef signals +#include +#define signals public +#include +#endif + +using namespace Zeal; + +namespace { +const char startPageUrl[] = "qrc:///browser/start.html"; +} + +struct TabState +{ + explicit TabState() + { + searchModel = new Registry::SearchModel(); + tocModel = new Registry::SearchModel(); + + webPage = new QWebPage(); +#ifndef USE_WEBENGINE + webPage->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); + webPage->setNetworkAccessManager(Core::Application::instance()->networkManager()); +#endif + } + + TabState(const TabState &other) + : searchQuery(other.searchQuery) + , selections(other.selections) + , expansions(other.expansions) + , searchScrollPosition(other.searchScrollPosition) + , tocScrollPosition(other.tocScrollPosition) + , webViewZoomFactor(other.webViewZoomFactor) + { + searchModel = new Registry::SearchModel(*other.searchModel); + tocModel = new Registry::SearchModel(*other.tocModel); + + webPage = new QWebPage(); +#ifndef USE_WEBENGINE + webPage->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); + webPage->setNetworkAccessManager(Core::Application::instance()->networkManager()); +#endif + + restoreHistory(other.saveHistory()); + } + + ~TabState() + { + delete searchModel; + delete tocModel; + // deleteLater() prevents crashing on quit (#577) + webPage->deleteLater(); + } + + void restoreHistory(const QByteArray &array) const + { + QDataStream stream(array); + stream >> *webPage->history(); + } + + QByteArray saveHistory() const + { + QByteArray array; + QDataStream stream(&array, QIODevice::WriteOnly); + stream << *webPage->history(); + return array; + } + + QUrl url() const { +#ifdef USE_WEBENGINE + return webPage->url(); +#else + return webPage->mainFrame()->url(); +#endif + } + + void loadUrl(const QUrl &url) + { +#ifdef USE_WEBENGINE + webPage->load(url); +#else + webPage->mainFrame()->load(url); +#endif + } + + QString title() const + { +#ifdef USE_WEBENGINE + return webPage->title(); +#else + return webPage->mainFrame()->title(); +#endif + } + + QString searchQuery; + + // Content/Search results tree view state + Registry::SearchModel *searchModel = nullptr; + QModelIndexList selections; + QModelIndexList expansions; + int searchScrollPosition = 0; + + // TOC list view state + Registry::SearchModel *tocModel = nullptr; + int tocScrollPosition = 0; + + QWebPage *webPage = nullptr; + int webViewZoomFactor = 0; +}; + +MainWindow::MainWindow(Core::Application *app, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + m_application(app), + m_settings(app->settings()), + m_zealListModel(new Registry::ListModel(app->docsetRegistry(), this)), + m_globalShortcut(new QxtGlobalShortcut(m_settings->showShortcut, this)), + m_openDocsetTimer(new QTimer(this)) +{ + ui->setupUi(this); + + connect(m_settings, &Core::Settings::updated, this, &MainWindow::applySettings); + +#ifdef USE_APPINDICATOR + detectAppIndicatorSupport(); +#endif + + if (m_settings->showSystrayIcon) + createTrayIcon(); + + // initialise key grabber + connect(m_globalShortcut, &QxtGlobalShortcut::activated, this, &MainWindow::toggleWindow); + + setupTabBar(); + + QShortcut *focusSearch = new QShortcut(QStringLiteral("Ctrl+K"), this); + connect(focusSearch, &QShortcut::activated, + ui->lineEdit, static_cast(&SearchEdit::setFocus)); + + QShortcut *duplicate = new QShortcut(QStringLiteral("Ctrl+Alt+T"), this); + connect(duplicate, &QShortcut::activated, this, [this]() { duplicateTab(m_tabBar->currentIndex()); }); + + restoreGeometry(m_settings->windowGeometry); + ui->splitter->restoreState(m_settings->verticalSplitterGeometry); + + // Menu + // File + // Some platform plugins do not define QKeySequence::Quit. + if (QKeySequence(QKeySequence::Quit).isEmpty()) + ui->actionQuit->setShortcut(QStringLiteral("Ctrl+Q")); + else + ui->actionQuit->setShortcut(QKeySequence::Quit); + + connect(ui->actionQuit, &QAction::triggered, qApp, &QCoreApplication::quit); + + // Edit + ui->actionFind->setShortcut(QKeySequence::Find); + connect(ui->actionFind, &QAction::triggered, ui->webView, &SearchableWebView::showSearchBar); + + connect(ui->actionPreferences, &QAction::triggered, [this]() { + m_globalShortcut->setEnabled(false); + QScopedPointer dialog(new SettingsDialog(m_application, this)); + dialog->exec(); + m_globalShortcut->setEnabled(true); + }); + + ui->actionBack->setShortcut(QKeySequence::Back); + addAction(ui->actionBack); + ui->actionForward->setShortcut(QKeySequence::Forward); + addAction(ui->actionForward); + connect(ui->actionBack, &QAction::triggered, ui->webView, &SearchableWebView::back); + connect(ui->actionForward, &QAction::triggered, ui->webView, &SearchableWebView::forward); + + // Tools Menu + connect(ui->actionDocsets, &QAction::triggered, [this]() { + QScopedPointer dialog(new DocsetsDialog(m_application, this)); + dialog->exec(); + }); + + // Help Menu + connect(ui->actionSubmitFeedback, &QAction::triggered, [this]() { + QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/zealdocs/zeal/issues"))); + }); + connect(ui->actionCheckForUpdates, &QAction::triggered, + m_application, &Core::Application::checkForUpdates); + connect(ui->actionAboutZeal, &QAction::triggered, [this]() { + QScopedPointer dialog(new AboutDialog(this)); + dialog->exec(); + }); + connect(ui->actionAboutQt, &QAction::triggered, [this]() { + QMessageBox::aboutQt(this); + }); + + // Update check + connect(m_application, &Core::Application::updateCheckError, [this](const QString &message) { + QMessageBox::warning(this, QStringLiteral("Zeal"), message); + }); + + connect(m_application, &Core::Application::updateCheckDone, [this](const QString &version) { + if (version.isEmpty()) { + QMessageBox::information(this, QStringLiteral("Zeal"), + tr("You are using the latest version.")); + return; + } + + const int ret + = QMessageBox::information(this, QStringLiteral("Zeal"), + tr("Zeal %1 is available. Open download page?").arg(version), + QMessageBox::Yes | QMessageBox::Default, + QMessageBox::No | QMessageBox::Escape, + QMessageBox::NoButton); + if (ret == QMessageBox::Yes) + QDesktopServices::openUrl(QUrl(QStringLiteral("https://zealdocs.org/download.html"))); + }); + + m_backMenu = new QMenu(ui->backButton); + connect(m_backMenu, &QMenu::aboutToShow, this, [this]() { + m_backMenu->clear(); + QWebHistory *history = currentTabState()->webPage->history(); + QList items = history->backItems(10); + // TODO: [Qt 5.6] + //for (auto it = items.crbegin(); it != items.crend(); ++it) { + for (auto it = items.cend() - 1; it >= items.cbegin(); --it) { + const QIcon icon = docsetIcon(docsetName(it->url())); + const QWebHistoryItem item = *it; + // TODO: [Qt 5.6] + // m_backMenu->addAction(icon, it->title(), [=](bool) { history->goToItem(item); }); + QAction *action = m_backMenu->addAction(icon, it->title()); + connect(action, &QAction::triggered, [=](bool) { history->goToItem(item); }); + } + }); + ui->backButton->setDefaultAction(ui->actionBack); + ui->backButton->setMenu(m_backMenu); + + m_forwardMenu = new QMenu(ui->forwardButton); + connect(m_forwardMenu, &QMenu::aboutToShow, this, [this]() { + m_forwardMenu->clear(); + QWebHistory *history = currentTabState()->webPage->history(); + for (const QWebHistoryItem &item: history->forwardItems(10)) { + const QIcon icon = docsetIcon(docsetName(item.url())); + // TODO: [Qt 5.6] + //m_forwardMenu->addAction(icon, item.title(), [=](bool) { history->goToItem(item); }); + QAction *action = m_forwardMenu->addAction(icon, item.title()); + connect(action, &QAction::triggered, [=](bool) { history->goToItem(item); }); + } + }); + ui->forwardButton->setDefaultAction(ui->actionForward); + ui->forwardButton->setMenu(m_forwardMenu); + + // treeView and lineEdit + ui->lineEdit->setTreeView(ui->treeView); + ui->lineEdit->setFocus(); + setupSearchBoxCompletions(); + SearchItemDelegate *delegate = new SearchItemDelegate(ui->treeView); + delegate->setDecorationRoles({Registry::SearchModel::DocsetIconRole, Qt::DecorationRole}); + connect(ui->lineEdit, &QLineEdit::textChanged, [delegate](const QString &text) { + delegate->setHighlight(Registry::SearchQuery::fromString(text).query()); + }); + ui->treeView->setItemDelegate(delegate); + + ui->tocListView->setItemDelegate(new SearchItemDelegate(ui->tocListView)); + connect(ui->tocSplitter, &QSplitter::splitterMoved, this, [this]() { + m_settings->tocSplitterState = ui->tocSplitter->saveState(); + }); + + createTab(); + + connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::openDocset); + connect(ui->tocListView, &QListView::clicked, this, &MainWindow::openDocset); + connect(ui->treeView, &QTreeView::activated, this, &MainWindow::openDocset); + connect(ui->tocListView, &QListView::activated, this, &MainWindow::openDocset); + + connect(ui->webView, &SearchableWebView::urlChanged, [this](const QUrl &url) { + const QString name = docsetName(url); + m_tabBar->setTabIcon(m_tabBar->currentIndex(), docsetIcon(name)); + + Registry::Docset *docset = m_application->docsetRegistry()->docset(name); + if (docset) + currentTabState()->tocModel->setResults(docset->relatedLinks(url)); + + ui->actionBack->setEnabled(ui->webView->canGoBack()); + ui->actionForward->setEnabled(ui->webView->canGoForward()); + }); + + connect(ui->webView, &SearchableWebView::titleChanged, [this](const QString &title) { + if (title.isEmpty()) + return; + + setWindowTitle(QStringLiteral("%1 - Zeal").arg(title)); + m_tabBar->setTabText(m_tabBar->currentIndex(), title); + m_tabBar->setTabToolTip(m_tabBar->currentIndex(), title); + }); + + connect(ui->webView, &SearchableWebView::linkClicked, [this](const QUrl &url) { + const QString message = tr("Do you want to open an external link?
URL: %1"); + int ret = QMessageBox::question(this, QStringLiteral("Zeal"), message.arg(url.toString())); + if (ret == QMessageBox::Yes) + QDesktopServices::openUrl(url); + }); + + connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::queryCompleted, + this, [this](const QList &results) { + currentTabState()->searchModel->setResults(results); + }); + + connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetRemoved, + this, [this](const QString &name) { + setupSearchBoxCompletions(); + for (TabState *tabState : m_tabStates) { + if (docsetName(tabState->url()) != name) + continue; + + tabState->tocModel->setResults(); + + // optimization: disable updates temporarily because + // removeSearchResultWithName can call {begin,end}RemoveRows + // multiple times which can cause GUI updates to be suboptimal + // in case of many rows to be removed + ui->treeView->setUpdatesEnabled(false); + tabState->searchModel->removeSearchResultWithName(name); + ui->treeView->setUpdatesEnabled(true); + + tabState->loadUrl(QUrl(startPageUrl)); + + // TODO: Cleanup history + } + }); + + connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetAdded, + this, [this](const QString &) { + setupSearchBoxCompletions(); + }); + + connect(ui->lineEdit, &QLineEdit::textChanged, [this](const QString &text) { + if (text == currentTabState()->searchQuery) + return; + + currentTabState()->searchQuery = text; + m_cancelSearch.cancel(); + m_cancelSearch = Registry::CancellationToken(); + m_application->docsetRegistry()->search(text, m_cancelSearch); + if (text.isEmpty()) { + currentTabState()->tocModel->setResults(); + syncTreeView(); + } + }); + + // Setup delayed navigation to a page until user makes a pause in typing a search query. + m_openDocsetTimer->setInterval(400); + m_openDocsetTimer->setSingleShot(true); + connect(m_openDocsetTimer, &QTimer::timeout, this, [this]() { + QModelIndex index = m_openDocsetTimer->property("index").toModelIndex(); + if (!index.isValid()) + return; + + openDocset(index); + + // Get focus back. QWebPageEngine::load() always steals focus. + ui->lineEdit->setFocus(Qt::MouseFocusReason); + }); + + ui->actionNewTab->setShortcut(QKeySequence::AddTab); + connect(ui->actionNewTab, &QAction::triggered, this, [this]() { createTab(); }); + addAction(ui->actionNewTab); + + // Save expanded items + connect(ui->treeView, &QTreeView::expanded, [this](QModelIndex index) { + if (currentTabState()->expansions.indexOf(index) == -1) + currentTabState()->expansions.append(index); + }); + + connect(ui->treeView, &QTreeView::collapsed, [this](QModelIndex index) { + currentTabState()->expansions.removeOne(index); + }); + +#ifdef Q_OS_WIN32 + ui->actionCloseTab->setShortcut(QKeySequence(Qt::Key_W + Qt::CTRL)); +#else + ui->actionCloseTab->setShortcut(QKeySequence::Close); +#endif + addAction(ui->actionCloseTab); + connect(ui->actionCloseTab, &QAction::triggered, this, [this]() { closeTab(); }); + + connect(ui->openUrlButton, &QPushButton::clicked, [this]() { + const QUrl url(ui->webView->page()->history()->currentItem().url()); + if (url.scheme() != QLatin1String("qrc")) + QDesktopServices::openUrl(url); + }); + + ui->actionNextTab->setShortcut(QKeySequence::NextChild); + addAction(ui->actionNextTab); + connect(ui->actionNextTab, &QAction::triggered, [this]() { + m_tabBar->setCurrentIndex((m_tabBar->currentIndex() + 1) % m_tabBar->count()); + }); + + // TODO: Use QKeySequence::PreviousChild, when QTBUG-15746 is fixed. + ui->actionPreviousTab->setShortcut(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Tab); + addAction(ui->actionPreviousTab); + connect(ui->actionPreviousTab, &QAction::triggered, [this]() { + m_tabBar->setCurrentIndex((m_tabBar->currentIndex() - 1 + m_tabBar->count()) % m_tabBar->count()); + }); + +#ifdef Q_OS_OSX + ui->treeView->setAttribute(Qt::WA_MacShowFocusRect, false); + ui->tocListView->setAttribute(Qt::WA_MacShowFocusRect, false); +#endif + + if (m_settings->checkForUpdate) + m_application->checkForUpdates(true); +} + +MainWindow::~MainWindow() +{ + m_settings->verticalSplitterGeometry = ui->splitter->saveState(); + m_settings->windowGeometry = saveGeometry(); + + // Delete the UI first, because it depends on tab states. + delete ui; + qDeleteAll(m_tabStates); +} + +void MainWindow::search(const Registry::SearchQuery &query) +{ + if (query.isEmpty()) + return; + + ui->lineEdit->setText(query.toString()); + ui->treeView->activated(ui->treeView->currentIndex()); +} + +void MainWindow::openDocset(const QModelIndex &index) +{ + const QVariant url = index.sibling(index.row(), 1).data(); + if (url.isNull()) + return; + + ui->webView->load(url.toUrl()); + + // QWebEnginePage::load() always steals focus, so no need to do it twice. +#ifndef USE_WEBENGINE + ui->webView->focus(); +#endif +} + +QString MainWindow::docsetName(const QUrl &url) const +{ + const QRegExp docsetRegex(QStringLiteral("/([^/]+)[.]docset")); + return docsetRegex.indexIn(url.path()) != -1 ? docsetRegex.cap(1) : QString(); +} + +QIcon MainWindow::docsetIcon(const QString &docsetName) const +{ + Registry::Docset *docset = m_application->docsetRegistry()->docset(docsetName); + return docset ? docset->icon() : QIcon(QStringLiteral(":/icons/logo/icon.png")); +} + +void MainWindow::queryCompleted() +{ + m_openDocsetTimer->stop(); + + syncTreeView(); + + ui->treeView->setCurrentIndex(currentTabState()->searchModel->index(0, 0, QModelIndex())); + + m_openDocsetTimer->setProperty("index", ui->treeView->currentIndex()); + m_openDocsetTimer->start(); +} + +void MainWindow::closeTab(int index) +{ + if (index == -1) + index = m_tabBar->currentIndex(); + + if (index == -1) + return; + + delete m_tabStates.takeAt(index); + + m_tabBar->removeTab(index); + + if (m_tabStates.isEmpty()) + createTab(); +} + +void MainWindow::createTab(int index) +{ + if (m_settings->openNewTabAfterActive) + index = m_tabBar->currentIndex() + 1; + else if (index == -1) + index = m_tabStates.size(); + + using Registry::SearchModel; + TabState *newTab = new TabState(); + connect(newTab->searchModel, &SearchModel::queryCompleted, this, &MainWindow::queryCompleted); + connect(newTab->tocModel, &SearchModel::queryCompleted, this, &MainWindow::syncToc); + + newTab->loadUrl(QUrl(startPageUrl)); + + m_tabStates.insert(index, newTab); + m_tabBar->insertTab(index, tr("Loading...")); + m_tabBar->setCurrentIndex(index); +} + +void MainWindow::duplicateTab(int index) +{ + if (index < 0 || index >= m_tabStates.size()) + return; + + using Registry::SearchModel; + TabState *newTab = new TabState(*m_tabStates.at(index)); + connect(newTab->searchModel, &SearchModel::queryCompleted, this, &MainWindow::queryCompleted); + connect(newTab->tocModel, &SearchModel::queryCompleted, this, &MainWindow::syncToc); + + ++index; + m_tabStates.insert(index, newTab); + m_tabBar->insertTab(index, newTab->title()); + m_tabBar->setCurrentIndex(index); +} + +void MainWindow::syncTreeView() +{ + TabState *tabState = currentTabState(); + + if (!tabState->searchQuery.isEmpty()) { + ui->treeView->setModel(tabState->searchModel); + ui->treeView->setRootIsDecorated(false); + } else { + ui->treeView->setModel(m_zealListModel); + ui->treeView->setColumnHidden(1, true); + ui->treeView->setRootIsDecorated(true); + } + + ui->treeView->reset(); +} + +void MainWindow::syncToc() +{ + if (!currentTabState()->tocModel->isEmpty()) { + ui->tocListView->show(); + ui->tocSplitter->restoreState(m_settings->tocSplitterState); + } else { + ui->tocListView->hide(); + } + +} + +TabState *MainWindow::currentTabState() const +{ + return m_tabStates.at(m_tabBar->currentIndex()); +} + +// Sets up the search box autocompletions. +void MainWindow::setupSearchBoxCompletions() +{ + QStringList completions; + for (const Registry::Docset * const docset: m_application->docsetRegistry()->docsets()) { + if (docset->keywords().isEmpty()) + continue; + + completions << docset->keywords().first() + QLatin1Char(':'); + } + + ui->lineEdit->setCompletions(completions); +} + +void MainWindow::setupTabBar() +{ + m_tabBar = new QTabBar(this); + + m_tabBar->installEventFilter(this); + + m_tabBar->setTabsClosable(true); + m_tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); + m_tabBar->setExpanding(false); + m_tabBar->setUsesScrollButtons(true); + m_tabBar->setDrawBase(false); + m_tabBar->setDocumentMode(true); + m_tabBar->setElideMode(Qt::ElideRight); + m_tabBar->setStyleSheet(QStringLiteral("QTabBar::tab { width: 150px; }")); + + connect(m_tabBar, &QTabBar::currentChanged, this, [this](int index) { + static const char PreviousTabIndexProperty[] = "previousTabIndex"; + + if (index == -1) + return; + + // Save previous tab state + const QVariant previousTabIndex = m_tabBar->property(PreviousTabIndexProperty); + if (previousTabIndex.isValid() && previousTabIndex.toInt() < m_tabStates.size()) { + TabState *previousTabState = m_tabStates.at(previousTabIndex.toInt()); + previousTabState->selections = ui->treeView->selectionModel()->selectedIndexes(); + previousTabState->searchScrollPosition = ui->treeView->verticalScrollBar()->value(); + previousTabState->tocScrollPosition = ui->tocListView->verticalScrollBar()->value(); + previousTabState->webViewZoomFactor = ui->webView->zoomFactor(); + } + + // Load current tab state + m_tabBar->setProperty(PreviousTabIndexProperty, index); + TabState *tabState = m_tabStates.at(index); + + ui->lineEdit->setText(tabState->searchQuery); + ui->tocListView->setModel(tabState->tocModel); + + syncTreeView(); + syncToc(); + + // Bring back the selections and expansions + ui->treeView->blockSignals(true); + for (const QModelIndex &selection: tabState->selections) + ui->treeView->selectionModel()->select(selection, QItemSelectionModel::Select); + for (const QModelIndex &expandedIndex: tabState->expansions) + ui->treeView->expand(expandedIndex); + ui->treeView->blockSignals(false); + + ui->webView->setPage(tabState->webPage); + ui->webView->setZoomFactor(tabState->webViewZoomFactor); + + ui->actionBack->setEnabled(ui->webView->canGoBack()); + ui->actionForward->setEnabled(ui->webView->canGoForward()); + + ui->treeView->verticalScrollBar()->setValue(tabState->searchScrollPosition); + ui->tocListView->verticalScrollBar()->setValue(tabState->tocScrollPosition); + }); + connect(m_tabBar, &QTabBar::tabCloseRequested, this, &MainWindow::closeTab); + + for (int i = 1; i < 10; i++) { + QAction *action = new QAction(m_tabBar); +#ifdef Q_OS_LINUX + action->setShortcut(QStringLiteral("Alt+%1").arg(i)); +#else + action->setShortcut(QStringLiteral("Ctrl+%1").arg(i)); +#endif + if (i == 9) { + connect(action, &QAction::triggered, [=]() { + m_tabBar->setCurrentIndex(m_tabBar->count() - 1); + }); + } else { + connect(action, &QAction::triggered, [=]() { + m_tabBar->setCurrentIndex(i - 1); + }); + } + + addAction(action); + } + + QHBoxLayout *layout = reinterpret_cast(ui->navigationBar->layout()); + layout->insertWidget(2, m_tabBar, 0, Qt::AlignBottom); +} + +#ifdef USE_APPINDICATOR +void MainWindow::detectAppIndicatorSupport() +{ + const QByteArray xdgDesktop = qgetenv("XDG_CURRENT_DESKTOP"); + + // Unity + if (xdgDesktop == "Unity") { + m_useAppIndicator = true; + return; + } + + // Cinnamon 2.8 + // Checking specifically for 2.8 because direct AppIndicator support will be dropped soon. + if (xdgDesktop == "X-Cinnamon" && qgetenv("CINNAMON_VERSION").startsWith("2.8")) { + m_useAppIndicator = true; + return; + } +} +#endif + +#ifdef USE_APPINDICATOR +void appIndicatorToggleWindow(GtkMenu *menu, gpointer data) +{ + Q_UNUSED(menu) + static_cast(data)->toggleWindow(); +} +#endif + +void MainWindow::createTrayIcon() +{ +#ifdef USE_APPINDICATOR + if (m_trayIcon || m_appIndicator) + return; +#else + if (m_trayIcon) + return; +#endif + +#ifdef USE_APPINDICATOR + if (m_useAppIndicator) { + m_appIndicatorMenu = gtk_menu_new(); + + m_appIndicatorShowHideMenuItem = gtk_menu_item_new_with_label(qPrintable(tr("Hide"))); + gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorShowHideMenuItem); + g_signal_connect(m_appIndicatorShowHideMenuItem, "activate", + G_CALLBACK(appIndicatorToggleWindow), this); + + m_appIndicatorMenuSeparator = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorMenuSeparator); + + m_appIndicatorQuitMenuItem = gtk_menu_item_new_with_label(qPrintable(tr("Quit"))); + gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorQuitMenuItem); + g_signal_connect(m_appIndicatorQuitMenuItem, "activate", + G_CALLBACK(QCoreApplication::quit), NULL); + + gtk_widget_show_all(m_appIndicatorMenu); + + // NOTE: Zeal icon has to be installed, otherwise app indicator won't be shown + m_appIndicator = app_indicator_new("zeal", "zeal", APP_INDICATOR_CATEGORY_OTHER); + + app_indicator_set_status(m_appIndicator, APP_INDICATOR_STATUS_ACTIVE); + app_indicator_set_menu(m_appIndicator, GTK_MENU(m_appIndicatorMenu)); + } else { // others +#endif + m_trayIcon = new QSystemTrayIcon(this); + m_trayIcon->setIcon(windowIcon()); + m_trayIcon->setToolTip(QStringLiteral("Zeal")); + + connect(m_trayIcon, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) { + if (reason != QSystemTrayIcon::Trigger && reason != QSystemTrayIcon::DoubleClick) + return; + + toggleWindow(); + }); + + QMenu *trayIconMenu = new QMenu(this); + trayIconMenu->addAction(ui->actionQuit); + + m_trayIcon->setContextMenu(trayIconMenu); + + m_trayIcon->show(); +#ifdef USE_APPINDICATOR + } +#endif +} + +void MainWindow::removeTrayIcon() +{ +#ifdef USE_APPINDICATOR + if (!m_trayIcon && !m_appIndicator) + return; +#else + if (!m_trayIcon) + return; +#endif + +#ifdef USE_APPINDICATOR + if (m_useAppIndicator) { + g_clear_object(&m_appIndicator); + g_clear_object(&m_appIndicatorMenu); + g_clear_object(&m_appIndicatorShowHideMenuItem); + g_clear_object(&m_appIndicatorMenuSeparator); + g_clear_object(&m_appIndicatorQuitMenuItem); + } else { +#endif + QMenu *trayIconMenu = m_trayIcon->contextMenu(); + delete m_trayIcon; + m_trayIcon = nullptr; + delete trayIconMenu; +#ifdef USE_APPINDICATOR + } +#endif +} + +void MainWindow::bringToFront() +{ + show(); + setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); + raise(); + activateWindow(); + + ui->lineEdit->setFocus(); +} + +void MainWindow::changeEvent(QEvent *event) +{ + if (m_settings->showSystrayIcon && m_settings->minimizeToSystray + && event->type() == QEvent::WindowStateChange && isMinimized()) { + hide(); + } + QMainWindow::changeEvent(event); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + if (m_settings->showSystrayIcon && m_settings->hideOnClose) { + event->ignore(); + toggleWindow(); + } +} + +bool MainWindow::eventFilter(QObject *object, QEvent *event) +{ + if (object == m_tabBar) { + switch (event->type()) { + case QEvent::MouseButtonRelease: { + QMouseEvent *e = reinterpret_cast(event); + if (e->button() == Qt::MiddleButton) { + const int index = m_tabBar->tabAt(e->pos()); + if (index != -1) { + closeTab(index); + return true; + } + } + break; + } + case QEvent::Wheel: + // TODO: Remove in case QTBUG-8428 is fixed on all platforms + return true; + default: + break; + } + } + + return QMainWindow::eventFilter(object, event); +} + +// Captures global events in order to pass them to the search bar. +void MainWindow::keyPressEvent(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) { + case Qt::Key_Escape: + ui->lineEdit->setFocus(); + ui->lineEdit->clearQuery(); + break; + case Qt::Key_Question: + ui->lineEdit->setFocus(); + ui->lineEdit->selectQuery(); + break; + default: + QMainWindow::keyPressEvent(keyEvent); + break; + } +} + +void MainWindow::applySettings() +{ + m_globalShortcut->setShortcut(m_settings->showShortcut); + + if (m_settings->showSystrayIcon) + createTrayIcon(); + else + removeTrayIcon(); +} + +void MainWindow::toggleWindow() +{ + const bool checkActive = sender() == m_globalShortcut; + + if (!isVisible() || (checkActive && !isActiveWindow())) { +#ifdef USE_APPINDICATOR + if (m_appIndicator) { + gtk_menu_item_set_label(GTK_MENU_ITEM(m_appIndicatorShowHideMenuItem), + qPrintable(tr("Hide"))); + } +#endif + bringToFront(); + } else { +#ifdef USE_APPINDICATOR + if (m_trayIcon || m_appIndicator) { + if (m_appIndicator) { + gtk_menu_item_set_label(GTK_MENU_ITEM(m_appIndicatorShowHideMenuItem), + qPrintable(tr("Show"))); + } +#else + if (m_trayIcon) { +#endif + hide(); + } else { + showMinimized(); + } + } +} diff --git a/src/ui/mainwindow.h b/src/libs/ui/mainwindow.h similarity index 59% rename from src/ui/mainwindow.h rename to src/libs/ui/mainwindow.h index d94353ad2..7d886d777 100644 --- a/src/ui/mainwindow.h +++ b/src/libs/ui/mainwindow.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,24 +17,17 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include "registry/searchquery.h" +#include +#include -#include #include -#include - -#ifdef USE_WEBENGINE -#define QWebPage QWebEnginePage -#define QWebHistory QWebEngineHistory -#define QWebHistoryItem QWebEngineHistoryItem -#endif #ifdef USE_APPINDICATOR struct _AppIndicator; @@ -43,51 +36,29 @@ struct _GtkWidget; class QxtGlobalShortcut; +class QModelIndex; class QSystemTrayIcon; class QTabBar; -class QWebHistory; -class QWebHistoryItem; -class QWebPage; +class QTimer; namespace Ui { class MainWindow; -} +} // namespace Ui namespace Zeal { namespace Core { class Application; class Settings; -} +} // namespace Core +namespace Registry { class ListModel; -class NetworkAccessManager; -class SearchModel; -class SettingsDialog; +} //namespace Registry -} +} // namespace Zeal -// Represents per tab search state. -// needs to contain [search input, search model, section model, url] -struct SearchState -{ - QWebPage *page; - // model representing sections - Zeal::SearchModel *sectionsList; - // model representing searched for items - Zeal::SearchModel *zealSearch; - // query being searched for - QString searchQuery; - - // list of selected indices - QModelIndexList selections; - // list of expanded indices - QModelIndexList expansions; - - int scrollPosition; - int sectionsScroll; - int zoomFactor; -}; +struct TabState; class MainWindow : public QMainWindow { @@ -96,8 +67,9 @@ class MainWindow : public QMainWindow explicit MainWindow(Zeal::Core::Application *app, QWidget *parent = nullptr); ~MainWindow() override; - void bringToFront(const Zeal::SearchQuery &query = Zeal::SearchQuery()); - void createTab(); + void search(const Zeal::Registry::SearchQuery &query); + void bringToFront(); + void createTab(int index = -1); public slots: void toggleWindow(); @@ -110,42 +82,39 @@ public slots: private slots: void applySettings(); - void back(); - void forward(); - void onSearchComplete(); void openDocset(const QModelIndex &index); void queryCompleted(); - void scrollSearch(); - void saveTabState(); - void goToTab(int index); void closeTab(int index = -1); + void duplicateTab(int index); private: - void displayViewActions(); + void syncTreeView(); + void syncToc(); void setupSearchBoxCompletions(); - void reloadTabState(); - void displayTabs(); + void setupTabBar(); + + TabState *currentTabState() const; + QString docsetName(const QUrl &url) const; QIcon docsetIcon(const QString &docsetName) const; - QAction *addHistoryAction(QWebHistory *history, const QWebHistoryItem &item); + +#ifdef USE_APPINDICATOR + void detectAppIndicatorSupport(); +#endif void createTrayIcon(); void removeTrayIcon(); - QList m_tabs; - - SearchState *m_searchState = nullptr; - Zeal::NetworkAccessManager *m_zealNetworkManager = nullptr; + QList m_tabStates; Ui::MainWindow *ui = nullptr; Zeal::Core::Application *m_application = nullptr; Zeal::Core::Settings *m_settings = nullptr; - Zeal::ListModel *m_zealListModel = nullptr; - Zeal::SettingsDialog *m_settingsDialog = nullptr; + Zeal::Registry::ListModel *m_zealListModel = nullptr; QMenu *m_backMenu = nullptr; QMenu *m_forwardMenu = nullptr; - bool m_treeViewClicked = false; + Zeal::Registry::CancellationToken m_cancelSearch; QxtGlobalShortcut *m_globalShortcut = nullptr; @@ -153,7 +122,10 @@ private slots: QSystemTrayIcon *m_trayIcon = nullptr; + QTimer *m_openDocsetTimer = nullptr; + #ifdef USE_APPINDICATOR + bool m_useAppIndicator = false; _AppIndicator *m_appIndicator = nullptr; _GtkWidget *m_appIndicatorMenu = nullptr; _GtkWidget *m_appIndicatorQuitMenuItem = nullptr; diff --git a/src/ui/progressitemdelegate.cpp b/src/libs/ui/progressitemdelegate.cpp similarity index 93% rename from src/ui/progressitemdelegate.cpp rename to src/libs/ui/progressitemdelegate.cpp index 3d0b880b2..9c72c97d2 100644 --- a/src/ui/progressitemdelegate.cpp +++ b/src/libs/ui/progressitemdelegate.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/ui/progressitemdelegate.h b/src/libs/ui/progressitemdelegate.h similarity index 89% rename from src/ui/progressitemdelegate.h rename to src/libs/ui/progressitemdelegate.h index bc458f9ea..d3bc70b7e 100644 --- a/src/ui/progressitemdelegate.h +++ b/src/libs/ui/progressitemdelegate.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp similarity index 91% rename from src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp rename to src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp index 4d067b772..0790f0dcc 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ /**************************************************************************** @@ -62,10 +62,7 @@ int QxtGlobalShortcutPrivate::ref = 0; QHash, QxtGlobalShortcut *> QxtGlobalShortcutPrivate::shortcuts; QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq) : - q_ptr(qq), - enabled(true), - key(Qt::Key(0)), - mods(Qt::NoModifier) + q_ptr(qq) { #ifndef Q_OS_OSX if (ref == 0) @@ -96,24 +93,28 @@ bool QxtGlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) const quint32 nativeMods = nativeModifiers(mods); const bool res = registerShortcut(nativeKey, nativeMods); if (res) - shortcuts.insert(qMakePair(nativeKey, nativeMods), q); + shortcuts.insert({nativeKey, nativeMods}, q); else qWarning("QxtGlobalShortcut failed to register: %s", qPrintable(QKeySequence(key + mods).toString())); + return res; } bool QxtGlobalShortcutPrivate::unsetShortcut() { Q_Q(QxtGlobalShortcut); + bool res = false; const quint32 nativeKey = nativeKeycode(key); const quint32 nativeMods = nativeModifiers(mods); - if (shortcuts.value(qMakePair(nativeKey, nativeMods)) == q) + if (shortcuts.value({nativeKey, nativeMods}) == q) res = unregisterShortcut(nativeKey, nativeMods); + if (res) - shortcuts.remove(qMakePair(nativeKey, nativeMods)); + shortcuts.remove({nativeKey, nativeMods}); else qWarning("QxtGlobalShortcut failed to unregister: %s", qPrintable(QKeySequence(key + mods).toString())); + key = Qt::Key(0); mods = Qt::KeyboardModifiers(0); return res; @@ -121,7 +122,7 @@ bool QxtGlobalShortcutPrivate::unsetShortcut() bool QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) { - QxtGlobalShortcut *shortcut = shortcuts.value(qMakePair(nativeKey, nativeMods)); + QxtGlobalShortcut *shortcut = shortcuts.value({nativeKey, nativeMods}); if (!shortcut || !shortcut->isEnabled()) return false; diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h similarity index 90% rename from src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h rename to src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h index 60e9d1e0f..4ffbf60a2 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ /**************************************************************************** @@ -62,12 +62,11 @@ class QxtGlobalShortcut : public QObject { Q_OBJECT - QxtGlobalShortcutPrivate *d_ptr; + QxtGlobalShortcutPrivate *d_ptr = nullptr; Q_DECLARE_PRIVATE(QxtGlobalShortcut) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) - public: explicit QxtGlobalShortcut(QObject *parent = nullptr); explicit QxtGlobalShortcut(const QKeySequence &shortcut, QObject *parent = nullptr); @@ -78,10 +77,10 @@ class QxtGlobalShortcut : public QObject bool isEnabled() const; -public Q_SLOTS: - void setEnabled(bool enabled = true); +public slots: + void setEnabled(bool enabled); -Q_SIGNALS: +signals: void activated(); }; diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.pri b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.pri new file mode 100644 index 000000000..d4c3e3c69 --- /dev/null +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.pri @@ -0,0 +1,6 @@ +HEADERS += $$files($$PWD/*.h) +SOURCES += $$PWD/qxtglobalshortcut.cpp + +unix:!macx:SOURCES += $$PWD/qxtglobalshortcut_x11.cpp +win32:SOURCES += $$PWD/qxtglobalshortcut_win.cpp +macx:SOURCES += $$PWD/qxtglobalshortcut_mac.cpp diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp similarity index 95% rename from src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp rename to src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp index a1d1b3619..573a51231 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ /**************************************************************************** @@ -66,8 +66,8 @@ static bool qxt_mac_handler_installed = false; OSStatus qxt_mac_handle_hot_key(EventHandlerCallRef nextHandler, EventRef event, void *data) { - Q_UNUSED(nextHandler); - Q_UNUSED(data); + Q_UNUSED(nextHandler) + Q_UNUSED(data) if (GetEventClass(event) == kEventClassKeyboard && GetEventKind(event) == kEventHotKeyPressed) { EventHotKeyID keyID; GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(keyID), NULL, &keyID); diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h similarity index 90% rename from src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h rename to src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h index 274d7b170..45704e22f 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ /**************************************************************************** @@ -62,22 +62,23 @@ class QxtGlobalShortcut; class QxtGlobalShortcutPrivate : public QAbstractNativeEventFilter { - QxtGlobalShortcut *q_ptr; + QxtGlobalShortcut *q_ptr = nullptr; Q_DECLARE_PUBLIC(QxtGlobalShortcut) public: QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq); ~QxtGlobalShortcutPrivate() override; - bool enabled; - Qt::Key key; - Qt::KeyboardModifiers mods; - - bool setShortcut(const QKeySequence &shortcut); - bool unsetShortcut(); + bool enabled = true; + Qt::Key key = Qt::Key(0); + Qt::KeyboardModifiers mods = Qt::NoModifier; #ifndef Q_OS_OSX static int ref; #endif // Q_OS_OSX + + bool setShortcut(const QKeySequence &shortcut); + bool unsetShortcut(); + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp similarity index 94% rename from src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp rename to src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp index eae080c86..6f3d4a01b 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ /**************************************************************************** @@ -57,8 +57,8 @@ bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { - Q_UNUSED(eventType); - Q_UNUSED(result); + Q_UNUSED(eventType) + Q_UNUSED(result) MSG *msg = static_cast(message); if (msg->message == WM_HOTKEY) { @@ -83,7 +83,7 @@ quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifier native |= MOD_ALT; if (modifiers & Qt::MetaModifier) native |= MOD_WIN; - /// TODO: resolve these? + // TODO: resolve these? //if (modifiers & Qt::KeypadModifier) //if (modifiers & Qt::GroupSwitchModifier) return native; diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp similarity index 93% rename from src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp rename to src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp index 24649b3d3..228ff1e37 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ /**************************************************************************** @@ -70,7 +70,7 @@ const QVector maskModifiers = { bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { - Q_UNUSED(result); + Q_UNUSED(result) if (eventType != "xcb_generic_event_t") return false; @@ -126,7 +126,9 @@ quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) QScopedPointer keycodes( xcb_key_symbols_get_keycode(xcbKeySymbols, keysym)); - native = keycodes.data()[0]; // Use the first keycode + + if (!keycodes.isNull()) + native = keycodes.data()[0]; // Use the first keycode xcb_key_symbols_free(xcbKeySymbols); diff --git a/src/libs/ui/searchitemdelegate.cpp b/src/libs/ui/searchitemdelegate.cpp new file mode 100644 index 000000000..10752e066 --- /dev/null +++ b/src/libs/ui/searchitemdelegate.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#include "searchitemdelegate.h" + +#include +#include +#include +#include +#include + +SearchItemDelegate::SearchItemDelegate(QObject *parent) : + QStyledItemDelegate(parent) +{ +} + +QList SearchItemDelegate::decorationRoles() const +{ + return m_decorationRoles; +} + +void SearchItemDelegate::setDecorationRoles(const QList &roles) +{ + m_decorationRoles = roles; +} + +bool SearchItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if (event->type() != QEvent::ToolTip) + return QStyledItemDelegate::helpEvent(event, view, option, index); + + if (sizeHint(option, index).width() < view->visualRect(index).width()) { + QToolTip::hideText(); + return QStyledItemDelegate::helpEvent(event, view, option, index); + } + + QToolTip::showText(event->globalPos(), index.data().toString(), view); + return true; +} + +void SearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem opt(option); + + QStyle *style = opt.widget->style(); + + // Find decoration roles with data present. + QList roles; + for (int role : m_decorationRoles) { + if (!index.data(role).isNull()) + roles.append(role); + } + + // TODO: Implemented via initStyleOption() overload + if (!roles.isEmpty()) { + opt.features |= QStyleOptionViewItem::HasDecoration; + opt.icon = index.data(roles.first()).value(); + + const QSize actualSize = opt.icon.actualSize(opt.decorationSize); + opt.decorationSize = {std::min(opt.decorationSize.width(), actualSize.width()), + std::min(opt.decorationSize.height(), actualSize.height())}; + } + + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); + + const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &opt, opt.widget) + 1; + + if (!roles.isEmpty()) { + QIcon::Mode mode = QIcon::Normal; + if (!(opt.state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (opt.state & QStyle::State_Selected) + mode = QIcon::Selected; + const QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + // All icons are sized after the first one. + QRect iconRect = style->subElementRect(QStyle::SE_ItemViewItemDecoration, &opt, opt.widget); + const int dx = iconRect.width() + margin; + + for (int i = 1; i < roles.size(); ++i) { + opt.decorationSize.rwidth() += dx; + iconRect.translate(dx, 0); + + const QIcon icon = index.data(roles[i]).value(); + icon.paint(painter, iconRect, opt.decorationAlignment, mode, state); + } + } + + // This should not happen unless a docset is corrupted. + if (index.data().isNull()) + return; + + // Match QCommonStyle behaviour. + opt.features |= QStyleOptionViewItem::HasDisplay; + opt.text = index.data().toString(); + const QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, opt.widget) + .adjusted(margin, 0, -margin, 0); + const QFontMetrics &fm = opt.fontMetrics; + const QString elidedText = fm.elidedText(opt.text, opt.textElideMode, textRect.width()); + + if (!m_highlight.isEmpty()) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(QColor::fromRgb(255, 253, 0)); + + const QColor highlightColor = opt.state & (QStyle::State_Selected | QStyle::State_HasFocus) + ? QColor::fromRgb(255, 255, 100, 20) : QColor::fromRgb(255, 255, 100, 120); + + for (int i = 0;;) { + const int matchIndex = opt.text.indexOf(m_highlight, i, Qt::CaseInsensitive); + if (matchIndex == -1 || matchIndex >= elidedText.length() - 1) + break; + + QRect highlightRect + = textRect.adjusted(fm.width(elidedText.left(matchIndex)), 2, 0, -2); + highlightRect.setWidth(fm.width(elidedText.mid(matchIndex, m_highlight.length()))); + + QPainterPath path; + path.addRoundedRect(highlightRect, 2, 2); + + painter->fillPath(path, highlightColor); + painter->drawPath(path); + + i = matchIndex + m_highlight.length(); + } + + painter->restore(); + } + + painter->save(); + +#ifdef Q_OS_WIN32 + // QWindowsVistaStyle overrides highlight colour. + if (style->objectName() == QLatin1String("windowsvista")) { + opt.palette.setColor(QPalette::All, QPalette::HighlightedText, + opt.palette.color(QPalette::Active, QPalette::Text)); + } +#endif + + const QPalette::ColorGroup cg = opt.state & QStyle::State_Active + ? QPalette::Normal : QPalette::Inactive; + + if (opt.state & QStyle::State_Selected) + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + else + painter->setPen(opt.palette.color(cg, QPalette::Text)); + + // Vertically align the text in the middle to match QCommonStyle behaviour. + const QRect alignedRect = QStyle::alignedRect(opt.direction, opt.displayAlignment, + QSize(1, fm.height()), textRect); + painter->drawText(QPoint(alignedRect.x(), alignedRect.y() + fm.ascent()), elidedText); + painter->restore(); +} + +QSize SearchItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem opt(option); + + QStyle *style = opt.widget->style(); + + QSize size = QStyledItemDelegate::sizeHint(opt, index); + size.setWidth(0); + + const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &opt, opt.widget) + 1; + + // Find decoration roles with data present. + QList roles; + for (int role : m_decorationRoles) { + if (!index.data(role).isNull()) + roles.append(role); + } + + if (!roles.isEmpty()) { + const QIcon icon = index.data(roles.first()).value(); + const QSize actualSize = icon.actualSize(opt.decorationSize); + const int decorationWidth = std::min(opt.decorationSize.width(), actualSize.width()); + size.rwidth() = (decorationWidth + margin) * roles.size() + margin; + } + + size.rwidth() += opt.fontMetrics.width(index.data().toString()) + margin * 2; + return size; +} + +void SearchItemDelegate::setHighlight(const QString &text) +{ + m_highlight = text; +} diff --git a/src/ui/searchitemdelegate.h b/src/libs/ui/searchitemdelegate.h similarity index 69% rename from src/ui/searchitemdelegate.h rename to src/libs/ui/searchitemdelegate.h index 197ebd6aa..cf454b4a2 100644 --- a/src/ui/searchitemdelegate.h +++ b/src/libs/ui/searchitemdelegate.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -32,13 +32,20 @@ class SearchItemDelegate : public QStyledItemDelegate public: explicit SearchItemDelegate(QObject *parent = nullptr); + QList decorationRoles() const; + void setDecorationRoles(const QList &roles); + + bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, + const QModelIndex &index) override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; public slots: void setHighlight(const QString &text); private: + QList m_decorationRoles = {Qt::DecorationRole}; QString m_highlight; }; diff --git a/src/libs/ui/settingsdialog.cpp b/src/libs/ui/settingsdialog.cpp new file mode 100644 index 000000000..78ca60b6c --- /dev/null +++ b/src/libs/ui/settingsdialog.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#include "settingsdialog.h" +#include "ui_settingsdialog.h" + +#include +#include +#include + +#include +#include + +#ifdef USE_WEBENGINE +#include + +typedef QWebEngineSettings QWebSettings; +#else +#include +#endif + +using namespace Zeal; + +SettingsDialog::SettingsDialog(Core::Application *app, QWidget *parent) : + QDialog(parent), + ui(new Ui::SettingsDialog()), + m_application(app) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + // Setup signals & slots + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::saveSettings); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SettingsDialog::loadSettings); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton *button) { + if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) + saveSettings(); + }); + + connect(ui->minFontSize, static_cast(&QSpinBox::valueChanged), + this, [](int value) { + QWebSettings::globalSettings()->setFontSize(QWebSettings::MinimumFontSize, value); + }); + + loadSettings(); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +void SettingsDialog::on_storageButton_clicked() +{ + const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), + ui->storageEdit->text()); + if (!dir.isEmpty()) + ui->storageEdit->setText(QDir::toNativeSeparators(dir)); + +} + +void SettingsDialog::loadSettings() +{ + const Core::Settings * const settings = m_application->settings(); + // General Tab + ui->startMinimizedCheckBox->setChecked(settings->startMinimized); + ui->checkForUpdateCheckBox->setChecked(settings->checkForUpdate); + + ui->systrayGroupBox->setChecked(settings->showSystrayIcon); + ui->minimizeToSystrayCheckBox->setChecked(settings->minimizeToSystray); + ui->hideToSystrayCheckBox->setChecked(settings->hideOnClose); + + ui->toolButton->setKeySequence(settings->showShortcut); + + ui->openNewTabAfterActive->setChecked(settings->openNewTabAfterActive); + + // + ui->minFontSize->setValue(settings->minimumFontSize); + ui->storageEdit->setText(QDir::toNativeSeparators(settings->docsetPath)); + + // Network Tab + switch (settings->proxyType) { + case Core::Settings::ProxyType::None: + ui->noProxySettings->setChecked(true); + break; + case Core::Settings::ProxyType::System: + ui->systemProxySettings->setChecked(true); + break; + case Core::Settings::ProxyType::UserDefined: + ui->manualProxySettings->setChecked(true); + break; + } + + ui->httpProxy->setText(settings->proxyHost); + ui->httpProxyPort->setValue(settings->proxyPort); + ui->httpProxyNeedsAuth->setChecked(settings->proxyAuthenticate); + ui->httpProxyUser->setText(settings->proxyUserName); + ui->httpProxyPass->setText(settings->proxyPassword); +} + +void SettingsDialog::saveSettings() +{ + Core::Settings * const settings = m_application->settings(); + // General Tab + settings->startMinimized = ui->startMinimizedCheckBox->isChecked(); + settings->checkForUpdate = ui->checkForUpdateCheckBox->isChecked(); + + settings->showSystrayIcon = ui->systrayGroupBox->isChecked(); + settings->minimizeToSystray = ui->minimizeToSystrayCheckBox->isChecked(); + settings->hideOnClose = ui->hideToSystrayCheckBox->isChecked(); + + settings->showShortcut = ui->toolButton->keySequence(); + + settings->openNewTabAfterActive = ui->openNewTabAfterActive->isChecked(); + + // + settings->minimumFontSize = ui->minFontSize->text().toInt(); + + if (QDir::fromNativeSeparators(ui->storageEdit->text()) != settings->docsetPath) { + settings->docsetPath = QDir::fromNativeSeparators(ui->storageEdit->text()); + m_application->docsetRegistry()->init(settings->docsetPath); + } + + // Network Tab + // Proxy settings + if (ui->noProxySettings->isChecked()) + settings->proxyType = Core::Settings::ProxyType::None; + else if (ui->systemProxySettings->isChecked()) + settings->proxyType = Core::Settings::ProxyType::System; + else if (ui->manualProxySettings->isChecked()) + settings->proxyType = Core::Settings::ProxyType::UserDefined; + + settings->proxyHost = ui->httpProxy->text(); + settings->proxyPort = ui->httpProxyPort->text().toUShort(); + settings->proxyAuthenticate = ui->httpProxyNeedsAuth->isChecked(); + settings->proxyUserName = ui->httpProxyUser->text(); + settings->proxyPassword = ui->httpProxyPass->text(); + + settings->save(); +} diff --git a/src/libs/ui/settingsdialog.h b/src/libs/ui/settingsdialog.h new file mode 100644 index 000000000..a6d134911 --- /dev/null +++ b/src/libs/ui/settingsdialog.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal 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 3 of the License, or +** (at your option) any later version. +** +** Zeal 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 Zeal. If not, see . +** +****************************************************************************/ + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include + +namespace Ui { +class SettingsDialog; +} + +namespace Zeal { + +namespace Core { +class Application; +} + +class SettingsDialog : public QDialog +{ + Q_OBJECT +public: + explicit SettingsDialog(Core::Application *app, QWidget *parent = nullptr); + ~SettingsDialog() override; + +private slots: + void on_storageButton_clicked(); + +private: + Ui::SettingsDialog *ui = nullptr; + Core::Application *m_application = nullptr; + + void loadSettings(); + void saveSettings(); +}; + +} // namespace Zeal + +#endif // SETTINGSDIALOG_H diff --git a/src/libs/ui/ui.pri b/src/libs/ui/ui.pri new file mode 100644 index 000000000..e3933b077 --- /dev/null +++ b/src/libs/ui/ui.pri @@ -0,0 +1,22 @@ +ZEAL_LIB_NAME = Ui + +QT += widgets + +unix:!macx:packagesExist(appindicator-0.1) { + CONFIG += link_pkgconfig + PKGCONFIG += appindicator-0.1 gtk+-2.0 + DEFINES += USE_APPINDICATOR + message("AppIndicator support: Yes.") +} + +# QxtGlobalShortcut dependencies +unix:!macx { + QT += x11extras + + CONFIG += link_pkgconfig + PKGCONFIG += x11 xcb xcb-keysyms +} + +macx { + LIBS += -framework Carbon +} diff --git a/src/libs/ui/ui.pro b/src/libs/ui/ui.pro new file mode 100644 index 000000000..659af247d --- /dev/null +++ b/src/libs/ui/ui.pro @@ -0,0 +1,14 @@ +include($$ZEAL_LIBRARY_PRI) + +HEADERS += \ + $$files(*.h) \ + $$files(widgets/*.h) + +SOURCES += \ + $$files(*.cpp) \ + $$files(widgets/*.cpp) + +FORMS += \ + $$files(forms/*.ui) + +include(qxtglobalshortcut/qxtglobalshortcut.pri) diff --git a/src/ui/widgets/searchablewebview.cpp b/src/libs/ui/widgets/searchablewebview.cpp similarity index 89% rename from src/ui/widgets/searchablewebview.cpp rename to src/libs/ui/widgets/searchablewebview.cpp index e58cb21e2..93e919f41 100644 --- a/src/ui/widgets/searchablewebview.cpp +++ b/src/libs/ui/widgets/searchablewebview.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -26,7 +26,6 @@ #include "webview.h" #include -#include #include #include @@ -50,9 +49,6 @@ SearchableWebView::SearchableWebView(QWidget *parent) : m_searchLineEdit->installEventFilter(this); connect(m_searchLineEdit, &QLineEdit::textChanged, this, &SearchableWebView::find); - QShortcut *shortcut = new QShortcut(QKeySequence::Find, this); - connect(shortcut, &QShortcut::activated, this, &SearchableWebView::showSearch); - connect(m_webView, &QWebView::loadFinished, [&](bool ok) { Q_UNUSED(ok) moveLineEdit(); @@ -94,7 +90,7 @@ bool SearchableWebView::eventFilter(QObject *object, QEvent *event) QKeyEvent *keyEvent = reinterpret_cast(event); switch (keyEvent->key()) { case Qt::Key_Escape: - hideSearch(); + hideSearchBar(); return true; case Qt::Key_Enter: case Qt::Key_Return: @@ -138,6 +134,26 @@ void SearchableWebView::forward() m_webView->forward(); } +void SearchableWebView::showSearchBar() +{ + m_searchLineEdit->show(); + m_searchLineEdit->setFocus(); + if (!m_searchLineEdit->text().isEmpty()) { + m_searchLineEdit->selectAll(); + find(m_searchLineEdit->text()); + } +} + +void SearchableWebView::hideSearchBar() +{ + m_searchLineEdit->hide(); +#ifdef USE_WEBENGINE + m_webView->findText(QString()); +#else + m_webView->findText(QString(), QWebPage::HighlightAllOccurrences); +#endif +} + bool SearchableWebView::canGoBack() const { return m_webView->history()->canGoBack(); @@ -152,7 +168,7 @@ void SearchableWebView::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Slash: - showSearch(); + showSearchBar(); event->accept(); break; default: @@ -168,31 +184,11 @@ void SearchableWebView::resizeEvent(QResizeEvent *event) moveLineEdit(); } -void SearchableWebView::showSearch() -{ - m_searchLineEdit->show(); - m_searchLineEdit->setFocus(); - if (!m_searchLineEdit->text().isEmpty()) { - m_searchLineEdit->selectAll(); - find(m_searchLineEdit->text()); - } -} - -void SearchableWebView::hideSearch() -{ - m_searchLineEdit->hide(); -#ifdef USE_WEBENGINE - m_webView->findText(QString()); -#else - m_webView->findText(QString(), QWebPage::HighlightAllOccurrences); -#endif -} - void SearchableWebView::find(const QString &text) { #ifdef USE_WEBENGINE - /// FIXME: There's no way to just show highlight when search term is already selected. - /// So we need a workaround before switching to Qt WebEngine. + // FIXME: There's no way to just show highlight when search term is already selected. + // So we need a workaround before switching to Qt WebEngine. m_webView->findText(text); #else if (m_webView->selectedText() != text) { @@ -224,7 +220,7 @@ void SearchableWebView::moveLineEdit() { int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); #ifdef USE_WEBENGINE - /// FIXME: scrollbar width + // FIXME: scrollbar width #else frameWidth += m_webView->page()->currentFrame()->scrollBarGeometry(Qt::Vertical).width(); #endif diff --git a/src/ui/widgets/searchablewebview.h b/src/libs/ui/widgets/searchablewebview.h similarity index 88% rename from src/ui/widgets/searchablewebview.h rename to src/libs/ui/widgets/searchablewebview.h index 6384e1ce9..1b1f5bb56 100644 --- a/src/ui/widgets/searchablewebview.h +++ b/src/libs/ui/widgets/searchablewebview.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -26,12 +26,15 @@ #include -#ifdef USE_WEBENGINE - #define QWebPage QWebEnginePage -#endif class QLineEdit; + +#ifdef USE_WEBENGINE +class QWebEnginePage; +typedef QWebEnginePage QWebPage; +#else class QWebPage; +#endif class WebView; @@ -62,14 +65,14 @@ class SearchableWebView : public QWidget public slots: void back(); void forward(); + void showSearchBar(); + void hideSearchBar(); protected: void keyPressEvent(QKeyEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: - void showSearch(); - void hideSearch(); void find(const QString &text); void findNext(const QString &text, bool backward = false); void moveLineEdit(); diff --git a/src/ui/widgets/searchedit.cpp b/src/libs/ui/widgets/searchedit.cpp similarity index 84% rename from src/ui/widgets/searchedit.cpp rename to src/libs/ui/widgets/searchedit.cpp index 632096818..851a40a5a 100644 --- a/src/ui/widgets/searchedit.cpp +++ b/src/libs/ui/widgets/searchedit.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,19 +17,22 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ #include "searchedit.h" -#include "registry/searchquery.h" +#include #include +#include #include #include #include +using namespace Zeal; + SearchEdit::SearchEdit(QWidget *parent) : QLineEdit(parent) { @@ -106,20 +109,12 @@ void SearchEdit::keyPressEvent(QKeyEvent *event) event->accept(); break; case Qt::Key_Return: - emit m_treeView->activated(m_treeView->selectionModel()->currentIndex()); - event->accept(); - break; case Qt::Key_Down: - case Qt::Key_Up: { - const QModelIndex index = m_treeView->currentIndex(); - const int nextRow = index.row() + (event->key() == Qt::Key_Down ? 1 : -1); - if (nextRow >= 0 && nextRow < m_treeView->model()->rowCount()) { - const QModelIndex sibling = index.sibling(nextRow, 0); - m_treeView->setCurrentIndex(sibling); - } - event->accept(); + case Qt::Key_Up: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + QCoreApplication::sendEvent(m_treeView, event); break; - } default: QLineEdit::keyPressEvent(event); break; @@ -161,7 +156,7 @@ QString SearchEdit::currentCompletion(const QString &text) const int SearchEdit::queryStart() const { - const Zeal::SearchQuery currentQuery = Zeal::SearchQuery::fromString(text()); + const Registry::SearchQuery currentQuery = Registry::SearchQuery::fromString(text()); // Keep the filter for the first Escape press return currentQuery.query().isEmpty() ? 0 : currentQuery.keywordPrefixSize(); } diff --git a/src/ui/widgets/searchedit.h b/src/libs/ui/widgets/searchedit.h similarity index 91% rename from src/ui/widgets/searchedit.h rename to src/libs/ui/widgets/searchedit.h index a2c19a7f2..e7ba28237 100644 --- a/src/ui/widgets/searchedit.h +++ b/src/libs/ui/widgets/searchedit.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/ui/widgets/shortcutedit.cpp b/src/libs/ui/widgets/shortcutedit.cpp similarity index 94% rename from src/ui/widgets/shortcutedit.cpp rename to src/libs/ui/widgets/shortcutedit.cpp index 8dcc60ccf..c2da98bd0 100644 --- a/src/ui/widgets/shortcutedit.cpp +++ b/src/libs/ui/widgets/shortcutedit.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/ui/widgets/shortcutedit.h b/src/libs/ui/widgets/shortcutedit.h similarity index 89% rename from src/ui/widgets/shortcutedit.h rename to src/libs/ui/widgets/shortcutedit.h index 7a8dceb21..f068ca005 100644 --- a/src/ui/widgets/shortcutedit.h +++ b/src/libs/ui/widgets/shortcutedit.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ diff --git a/src/libs/ui/widgets/toolbarframe.cpp b/src/libs/ui/widgets/toolbarframe.cpp new file mode 100644 index 000000000..a6bb706bf --- /dev/null +++ b/src/libs/ui/widgets/toolbarframe.cpp @@ -0,0 +1,19 @@ +#include "toolbarframe.h" + +#include + +ToolBarFrame::ToolBarFrame(QWidget *parent) : QWidget(parent) +{ +} + +void ToolBarFrame::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + // Draw a line at the bottom. + QPainter painter(this); + painter.save(); + painter.setPen(palette().dark().color()); + painter.drawLine(0, height() - 1, width(), height() - 1); + painter.restore(); +} diff --git a/src/libs/ui/widgets/toolbarframe.h b/src/libs/ui/widgets/toolbarframe.h new file mode 100644 index 000000000..64904202b --- /dev/null +++ b/src/libs/ui/widgets/toolbarframe.h @@ -0,0 +1,16 @@ +#ifndef TOOLBARFRAME_H +#define TOOLBARFRAME_H + +#include + +class ToolBarFrame : public QWidget +{ + Q_OBJECT +public: + explicit ToolBarFrame(QWidget *parent = nullptr); + +private: + void paintEvent(QPaintEvent *event) override; +}; + +#endif // TOOLBARFRAME_H diff --git a/src/ui/widgets/webview.cpp b/src/libs/ui/widgets/webview.cpp similarity index 94% rename from src/ui/widgets/webview.cpp rename to src/libs/ui/widgets/webview.cpp index 47564ef99..bcb132af9 100644 --- a/src/ui/widgets/webview.cpp +++ b/src/libs/ui/widgets/webview.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -95,7 +95,7 @@ void WebView::mouseReleaseEvent(QMouseEvent *event) case Qt::MiddleButton: if (m_clickedLink == clickedLink(event->pos()) && m_clickedLink.isValid()) { QWebView *webView = createWindow(QWebPage::WebBrowserWindow); - webView->setUrl(m_clickedLink); + webView->load(m_clickedLink); event->accept(); } break; diff --git a/src/ui/widgets/webview.h b/src/libs/ui/widgets/webview.h similarity index 83% rename from src/ui/widgets/webview.h rename to src/libs/ui/widgets/webview.h index 508de3966..ae2d87fcc 100644 --- a/src/ui/widgets/webview.h +++ b/src/libs/ui/widgets/webview.h @@ -1,8 +1,8 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2015-2016 Oleg Shparber ** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -17,7 +17,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -25,12 +25,13 @@ #define WEBVIEW_H #ifdef USE_WEBENGINE - #include - #include - #define QWebView QWebEngineView - #define QWebPage QWebEnginePage +#include +#include + +typedef QWebEnginePage QWebPage; +typedef QWebEngineView QWebView; #else - #include +#include #endif class WebView : public QWebView diff --git a/src/util/plist.cpp b/src/libs/util/plist.cpp similarity index 89% rename from src/util/plist.cpp rename to src/libs/util/plist.cpp index c4aa408a6..6695264b1 100644 --- a/src/util/plist.cpp +++ b/src/libs/util/plist.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -36,7 +36,7 @@ bool Plist::read(const QString &fileName) { QScopedPointer file(new QFile(fileName)); if (!file->open(QIODevice::ReadOnly)) { - /// TODO: Report/log error + // TODO: Report/log error m_hasError = true; return false; } @@ -49,7 +49,7 @@ bool Plist::read(const QString &fileName) continue; if (xml.name() != QLatin1String("key")) - continue; /// TODO: Should it fail here? + continue; // TODO: Should it fail here? const QString key = xml.readElementText(); diff --git a/src/util/plist.h b/src/libs/util/plist.h similarity index 87% rename from src/util/plist.h rename to src/libs/util/plist.h index 15cf6d34d..22a98d7e1 100644 --- a/src/util/plist.h +++ b/src/libs/util/plist.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -41,7 +41,7 @@ class Plist : public QHash bool m_hasError = false; }; -} // namespace Zeal } // namespace Util +} // namespace Zeal #endif // PLIST_H diff --git a/src/libs/util/util.pri b/src/libs/util/util.pri new file mode 100644 index 000000000..3a565341a --- /dev/null +++ b/src/libs/util/util.pri @@ -0,0 +1 @@ +ZEAL_LIB_NAME = Util diff --git a/src/libs/util/util.pro b/src/libs/util/util.pro new file mode 100644 index 000000000..1c61c1c65 --- /dev/null +++ b/src/libs/util/util.pro @@ -0,0 +1,4 @@ +include($$ZEAL_LIBRARY_PRI) + +HEADERS += $$files(*.h) +SOURCES += $$files(*.cpp) diff --git a/src/util/version.cpp b/src/libs/util/version.cpp similarity index 78% rename from src/util/version.cpp rename to src/libs/util/version.cpp index dc632ee39..8dd1fc301 100644 --- a/src/util/version.cpp +++ b/src/libs/util/version.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -45,27 +45,9 @@ bool Version::isValid() const QString Version::toString() const { - return QString("%1.%2.%3").arg(m_major).arg(m_minor).arg(m_patch); + return QStringLiteral("%1.%2.%3").arg(m_major).arg(m_minor).arg(m_patch); } -/*bool Version::operator>(const Version &other) const -{ - if (m_major > other.m_major) - return true; - else if (m_major < other.m_major) - return false; - - if (m_minor > other.m_minor) - return true; - else if (m_minor < other.m_minor) - return false; - - if (m_patch > other.m_patch) - return true; - - return false; -}*/ - bool Version::fromString(const QString &str) { const QStringList parts = str.split(QLatin1Char('.')); @@ -117,4 +99,3 @@ bool operator<(const Version &lhs, const Version &rhs) } // namespace Util } // namespace Zeal - diff --git a/src/util/version.h b/src/libs/util/version.h similarity index 89% rename from src/util/version.h rename to src/libs/util/version.h index cc74760f0..be59e180d 100644 --- a/src/util/version.h +++ b/src/libs/util/version.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Oleg Shparber -** Contact: http://zealdocs.org/contact.html +** Copyright (C) 2015-2016 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact ** ** This file is part of Zeal. ** @@ -16,7 +16,7 @@ ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License -** along with Zeal. If not, see . +** along with Zeal. If not, see . ** ****************************************************************************/ @@ -29,7 +29,7 @@ namespace Zeal { namespace Util { // Based on Semantic Versioning (http://semver.org/) -/// TODO: Add support for prerelease tags and build metadata +// TODO: Add support for prerelease tags and build metadata class Version { diff --git a/src/registry/registry.pri b/src/registry/registry.pri deleted file mode 100644 index b943e5a67..000000000 --- a/src/registry/registry.pri +++ /dev/null @@ -1,5 +0,0 @@ -HEADERS += \ - $$files($$PWD/*.h) - -SOURCES += \ - $$files($$PWD/*.cpp) diff --git a/src/registry/rename.sh b/src/registry/rename.sh deleted file mode 100644 index 5220b2a06..000000000 --- a/src/registry/rename.sh +++ /dev/null @@ -1,5 +0,0 @@ -for file in zeal*; -do - mv "$file" "${file#XY zeal}" -done - diff --git a/src/registry/searchresult.cpp b/src/registry/searchresult.cpp deleted file mode 100644 index 3b7d26b7f..000000000 --- a/src/registry/searchresult.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html -** -** This file is part of Zeal. -** -** Zeal 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 3 of the License, or -** (at your option) any later version. -** -** Zeal 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 Zeal. If not, see . -** -****************************************************************************/ - -#include "searchresult.h" - -using namespace Zeal; - -bool SearchResult::operator<(const SearchResult &r) const -{ - const bool lhsStartsWithQuery = name.startsWith(query, Qt::CaseInsensitive); - const bool rhsStartsWithQuery = r.name.startsWith(query, Qt::CaseInsensitive); - - if (lhsStartsWithQuery != rhsStartsWithQuery) - return lhsStartsWithQuery > rhsStartsWithQuery; - - const int namesCmp = QString::compare(name, r.name, Qt::CaseInsensitive); - if (namesCmp) - return namesCmp < 0; - - return QString::compare(parentName, r.parentName, Qt::CaseInsensitive) < 0; -} diff --git a/src/resources/icons/logo/128x128.png b/src/resources/icons/logo/128x128.png deleted file mode 100644 index 0d391992e..000000000 Binary files a/src/resources/icons/logo/128x128.png and /dev/null differ diff --git a/src/resources/icons/logo/64x64.png b/src/resources/icons/logo/64x64.png deleted file mode 100644 index ca2b6bbe1..000000000 Binary files a/src/resources/icons/logo/64x64.png and /dev/null differ diff --git a/src/resources/icons/logo/icon.png b/src/resources/icons/logo/icon.png deleted file mode 100644 index 045096ac9..000000000 Binary files a/src/resources/icons/logo/icon.png and /dev/null differ diff --git a/src/resources/icons/logo/icon@2x.png b/src/resources/icons/logo/icon@2x.png deleted file mode 100644 index 078173c1d..000000000 Binary files a/src/resources/icons/logo/icon@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Abbreviation.png b/src/resources/icons/type/Abbreviation.png deleted file mode 100644 index 2a802d676..000000000 Binary files a/src/resources/icons/type/Abbreviation.png and /dev/null differ diff --git a/src/resources/icons/type/Alias.png b/src/resources/icons/type/Alias.png deleted file mode 100644 index 6ec492b32..000000000 Binary files a/src/resources/icons/type/Alias.png and /dev/null differ diff --git a/src/resources/icons/type/Annotation.png b/src/resources/icons/type/Annotation.png deleted file mode 100644 index 6ec492b32..000000000 Binary files a/src/resources/icons/type/Annotation.png and /dev/null differ diff --git a/src/resources/icons/type/Attribute.png b/src/resources/icons/type/Attribute.png deleted file mode 100644 index a3ce577b7..000000000 Binary files a/src/resources/icons/type/Attribute.png and /dev/null differ diff --git a/src/resources/icons/type/Axiom.png b/src/resources/icons/type/Axiom.png deleted file mode 100644 index 6ec492b32..000000000 Binary files a/src/resources/icons/type/Axiom.png and /dev/null differ diff --git a/src/resources/icons/type/Binding.png b/src/resources/icons/type/Binding.png deleted file mode 100644 index 10b590144..000000000 Binary files a/src/resources/icons/type/Binding.png and /dev/null differ diff --git a/src/resources/icons/type/Binding@2x.png b/src/resources/icons/type/Binding@2x.png deleted file mode 100644 index 9ad8740ca..000000000 Binary files a/src/resources/icons/type/Binding@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Builtin.png b/src/resources/icons/type/Builtin.png deleted file mode 100644 index 4d6f70bfc..000000000 Binary files a/src/resources/icons/type/Builtin.png and /dev/null differ diff --git a/src/resources/icons/type/Builtin@2x.png b/src/resources/icons/type/Builtin@2x.png deleted file mode 100644 index 8c78e0533..000000000 Binary files a/src/resources/icons/type/Builtin@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Callback.png b/src/resources/icons/type/Callback.png deleted file mode 100644 index 3244b4f8e..000000000 Binary files a/src/resources/icons/type/Callback.png and /dev/null differ diff --git a/src/resources/icons/type/Category.png b/src/resources/icons/type/Category.png deleted file mode 100644 index c6d11ef21..000000000 Binary files a/src/resources/icons/type/Category.png and /dev/null differ diff --git a/src/resources/icons/type/Category@2x.png b/src/resources/icons/type/Category@2x.png deleted file mode 100644 index 5767dc4d1..000000000 Binary files a/src/resources/icons/type/Category@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Class.png b/src/resources/icons/type/Class.png deleted file mode 100644 index 04366ef43..000000000 Binary files a/src/resources/icons/type/Class.png and /dev/null differ diff --git a/src/resources/icons/type/Class@2x.png b/src/resources/icons/type/Class@2x.png deleted file mode 100644 index 1b5ec178c..000000000 Binary files a/src/resources/icons/type/Class@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Column.png b/src/resources/icons/type/Column.png deleted file mode 100644 index d37ff1a2a..000000000 Binary files a/src/resources/icons/type/Column.png and /dev/null differ diff --git a/src/resources/icons/type/Column@2x.png b/src/resources/icons/type/Column@2x.png deleted file mode 100644 index 1deaf1d39..000000000 Binary files a/src/resources/icons/type/Column@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Command.png b/src/resources/icons/type/Command.png deleted file mode 100644 index a1152f0c9..000000000 Binary files a/src/resources/icons/type/Command.png and /dev/null differ diff --git a/src/resources/icons/type/Component.png b/src/resources/icons/type/Component.png deleted file mode 100644 index d37ff1a2a..000000000 Binary files a/src/resources/icons/type/Component.png and /dev/null differ diff --git a/src/resources/icons/type/Component@2x.png b/src/resources/icons/type/Component@2x.png deleted file mode 100644 index 1deaf1d39..000000000 Binary files a/src/resources/icons/type/Component@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Constant.png b/src/resources/icons/type/Constant.png deleted file mode 100644 index a59d72731..000000000 Binary files a/src/resources/icons/type/Constant.png and /dev/null differ diff --git a/src/resources/icons/type/Constant@2x.png b/src/resources/icons/type/Constant@2x.png deleted file mode 100644 index 803fa1e0b..000000000 Binary files a/src/resources/icons/type/Constant@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Constructor.png b/src/resources/icons/type/Constructor.png deleted file mode 100644 index 19a8c2824..000000000 Binary files a/src/resources/icons/type/Constructor.png and /dev/null differ diff --git a/src/resources/icons/type/Constructor@2x.png b/src/resources/icons/type/Constructor@2x.png deleted file mode 100644 index ea45258d9..000000000 Binary files a/src/resources/icons/type/Constructor@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Conversion.png b/src/resources/icons/type/Conversion.png deleted file mode 100644 index 5a203f11f..000000000 Binary files a/src/resources/icons/type/Conversion.png and /dev/null differ diff --git a/src/resources/icons/type/Database.png b/src/resources/icons/type/Database.png deleted file mode 100644 index cb036709e..000000000 Binary files a/src/resources/icons/type/Database.png and /dev/null differ diff --git a/src/resources/icons/type/Define.png b/src/resources/icons/type/Define.png deleted file mode 100644 index 2f1d30aaa..000000000 Binary files a/src/resources/icons/type/Define.png and /dev/null differ diff --git a/src/resources/icons/type/Define@2x.png b/src/resources/icons/type/Define@2x.png deleted file mode 100644 index e25617d30..000000000 Binary files a/src/resources/icons/type/Define@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Delegate.png b/src/resources/icons/type/Delegate.png deleted file mode 100644 index 24af9a7a0..000000000 Binary files a/src/resources/icons/type/Delegate.png and /dev/null differ diff --git a/src/resources/icons/type/Delegate@2x.png b/src/resources/icons/type/Delegate@2x.png deleted file mode 100644 index a1ea9252f..000000000 Binary files a/src/resources/icons/type/Delegate@2x.png and /dev/null differ diff --git a/src/resources/icons/type/DeletedSnippet.png b/src/resources/icons/type/DeletedSnippet.png deleted file mode 100644 index f37058a32..000000000 Binary files a/src/resources/icons/type/DeletedSnippet.png and /dev/null differ diff --git a/src/resources/icons/type/DeletedSnippet@2x.png b/src/resources/icons/type/DeletedSnippet@2x.png deleted file mode 100644 index aa9c07f8c..000000000 Binary files a/src/resources/icons/type/DeletedSnippet@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Diagram.png b/src/resources/icons/type/Diagram.png deleted file mode 100644 index 4b94e6385..000000000 Binary files a/src/resources/icons/type/Diagram.png and /dev/null differ diff --git a/src/resources/icons/type/Diagram@2x.png b/src/resources/icons/type/Diagram@2x.png deleted file mode 100644 index 22c581988..000000000 Binary files a/src/resources/icons/type/Diagram@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Directive.png b/src/resources/icons/type/Directive.png deleted file mode 100644 index cb036709e..000000000 Binary files a/src/resources/icons/type/Directive.png and /dev/null differ diff --git a/src/resources/icons/type/Element.png b/src/resources/icons/type/Element.png deleted file mode 100644 index a1e35dc9f..000000000 Binary files a/src/resources/icons/type/Element.png and /dev/null differ diff --git a/src/resources/icons/type/Element@2x.png b/src/resources/icons/type/Element@2x.png deleted file mode 100644 index fdceff247..000000000 Binary files a/src/resources/icons/type/Element@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Entry.png b/src/resources/icons/type/Entry.png deleted file mode 100644 index 51839c1f0..000000000 Binary files a/src/resources/icons/type/Entry.png and /dev/null differ diff --git a/src/resources/icons/type/Entry@2x.png b/src/resources/icons/type/Entry@2x.png deleted file mode 100644 index 24e0c5152..000000000 Binary files a/src/resources/icons/type/Entry@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Enumeration@2x.png b/src/resources/icons/type/Enumeration@2x.png deleted file mode 100644 index 3a4041429..000000000 Binary files a/src/resources/icons/type/Enumeration@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Environment.png b/src/resources/icons/type/Environment.png deleted file mode 100644 index 51839c1f0..000000000 Binary files a/src/resources/icons/type/Environment.png and /dev/null differ diff --git a/src/resources/icons/type/Environment@2x.png b/src/resources/icons/type/Environment@2x.png deleted file mode 100644 index 24e0c5152..000000000 Binary files a/src/resources/icons/type/Environment@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Error.png b/src/resources/icons/type/Error.png deleted file mode 100644 index 4e550328d..000000000 Binary files a/src/resources/icons/type/Error.png and /dev/null differ diff --git a/src/resources/icons/type/Event.png b/src/resources/icons/type/Event.png deleted file mode 100644 index 58be593a5..000000000 Binary files a/src/resources/icons/type/Event.png and /dev/null differ diff --git a/src/resources/icons/type/Exception.png b/src/resources/icons/type/Exception.png deleted file mode 100644 index 7049c223c..000000000 Binary files a/src/resources/icons/type/Exception.png and /dev/null differ diff --git a/src/resources/icons/type/Extension.png b/src/resources/icons/type/Extension.png deleted file mode 100644 index dad9eaee6..000000000 Binary files a/src/resources/icons/type/Extension.png and /dev/null differ diff --git a/src/resources/icons/type/Field.png b/src/resources/icons/type/Field.png deleted file mode 100644 index f97beb195..000000000 Binary files a/src/resources/icons/type/Field.png and /dev/null differ diff --git a/src/resources/icons/type/Field@2x.png b/src/resources/icons/type/Field@2x.png deleted file mode 100644 index 801ff50f6..000000000 Binary files a/src/resources/icons/type/Field@2x.png and /dev/null differ diff --git a/src/resources/icons/type/File.png b/src/resources/icons/type/File.png deleted file mode 100644 index 7e5ffaf90..000000000 Binary files a/src/resources/icons/type/File.png and /dev/null differ diff --git a/src/resources/icons/type/File@2x.png b/src/resources/icons/type/File@2x.png deleted file mode 100644 index 84021b810..000000000 Binary files a/src/resources/icons/type/File@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Filter.png b/src/resources/icons/type/Filter.png deleted file mode 100644 index f97beb195..000000000 Binary files a/src/resources/icons/type/Filter.png and /dev/null differ diff --git a/src/resources/icons/type/Filter@2x.png b/src/resources/icons/type/Filter@2x.png deleted file mode 100644 index 801ff50f6..000000000 Binary files a/src/resources/icons/type/Filter@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Foreign Key.png b/src/resources/icons/type/Foreign Key.png deleted file mode 100644 index f97beb195..000000000 Binary files a/src/resources/icons/type/Foreign Key.png and /dev/null differ diff --git a/src/resources/icons/type/Foreign Key@2x.png b/src/resources/icons/type/Foreign Key@2x.png deleted file mode 100644 index 801ff50f6..000000000 Binary files a/src/resources/icons/type/Foreign Key@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Framework.png b/src/resources/icons/type/Framework.png deleted file mode 100644 index 7e5ffaf90..000000000 Binary files a/src/resources/icons/type/Framework.png and /dev/null differ diff --git a/src/resources/icons/type/Framework@2x.png b/src/resources/icons/type/Framework@2x.png deleted file mode 100644 index 84021b810..000000000 Binary files a/src/resources/icons/type/Framework@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Function.png b/src/resources/icons/type/Function.png deleted file mode 100644 index 1ddadd2e2..000000000 Binary files a/src/resources/icons/type/Function.png and /dev/null differ diff --git a/src/resources/icons/type/Function@2x.png b/src/resources/icons/type/Function@2x.png deleted file mode 100644 index 7055fe2f3..000000000 Binary files a/src/resources/icons/type/Function@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Global.png b/src/resources/icons/type/Global.png deleted file mode 100644 index 69c471a69..000000000 Binary files a/src/resources/icons/type/Global.png and /dev/null differ diff --git a/src/resources/icons/type/Guide.png b/src/resources/icons/type/Guide.png deleted file mode 100644 index afb5b6276..000000000 Binary files a/src/resources/icons/type/Guide.png and /dev/null differ diff --git a/src/resources/icons/type/Index.png b/src/resources/icons/type/Index.png deleted file mode 100644 index 4f5ab7ef7..000000000 Binary files a/src/resources/icons/type/Index.png and /dev/null differ diff --git a/src/resources/icons/type/Indirection.png b/src/resources/icons/type/Indirection.png deleted file mode 100644 index 8eae34be9..000000000 Binary files a/src/resources/icons/type/Indirection.png and /dev/null differ diff --git a/src/resources/icons/type/Inductive.png b/src/resources/icons/type/Inductive.png deleted file mode 100644 index 8eae34be9..000000000 Binary files a/src/resources/icons/type/Inductive.png and /dev/null differ diff --git a/src/resources/icons/type/Instance.png b/src/resources/icons/type/Instance.png deleted file mode 100644 index 3554087a4..000000000 Binary files a/src/resources/icons/type/Instance.png and /dev/null differ diff --git a/src/resources/icons/type/Instruction.png b/src/resources/icons/type/Instruction.png deleted file mode 100644 index 4f5ab7ef7..000000000 Binary files a/src/resources/icons/type/Instruction.png and /dev/null differ diff --git a/src/resources/icons/type/Interface.png b/src/resources/icons/type/Interface.png deleted file mode 100644 index d6fe6154c..000000000 Binary files a/src/resources/icons/type/Interface.png and /dev/null differ diff --git a/src/resources/icons/type/Keyword.png b/src/resources/icons/type/Keyword.png deleted file mode 100644 index 75ca62e4f..000000000 Binary files a/src/resources/icons/type/Keyword.png and /dev/null differ diff --git a/src/resources/icons/type/Keyword@2x.png b/src/resources/icons/type/Keyword@2x.png deleted file mode 100644 index 515b5b30d..000000000 Binary files a/src/resources/icons/type/Keyword@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Lemma.png b/src/resources/icons/type/Lemma.png deleted file mode 100644 index 880f9ee70..000000000 Binary files a/src/resources/icons/type/Lemma.png and /dev/null differ diff --git a/src/resources/icons/type/Lemma@2x.png b/src/resources/icons/type/Lemma@2x.png deleted file mode 100644 index 784c2439d..000000000 Binary files a/src/resources/icons/type/Lemma@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Library.png b/src/resources/icons/type/Library.png deleted file mode 100644 index b3c0c8358..000000000 Binary files a/src/resources/icons/type/Library.png and /dev/null differ diff --git a/src/resources/icons/type/Library@2x.png b/src/resources/icons/type/Library@2x.png deleted file mode 100644 index 9443d03cd..000000000 Binary files a/src/resources/icons/type/Library@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Literal.png b/src/resources/icons/type/Literal.png deleted file mode 100644 index 15eadec80..000000000 Binary files a/src/resources/icons/type/Literal.png and /dev/null differ diff --git a/src/resources/icons/type/Literal@2x.png b/src/resources/icons/type/Literal@2x.png deleted file mode 100644 index 678de6175..000000000 Binary files a/src/resources/icons/type/Literal@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Macro.png b/src/resources/icons/type/Macro.png deleted file mode 100644 index 2f1d30aaa..000000000 Binary files a/src/resources/icons/type/Macro.png and /dev/null differ diff --git a/src/resources/icons/type/Macro@2x.png b/src/resources/icons/type/Macro@2x.png deleted file mode 100644 index e25617d30..000000000 Binary files a/src/resources/icons/type/Macro@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Method.png b/src/resources/icons/type/Method.png deleted file mode 100644 index 402374a81..000000000 Binary files a/src/resources/icons/type/Method.png and /dev/null differ diff --git a/src/resources/icons/type/Method@2x.png b/src/resources/icons/type/Method@2x.png deleted file mode 100644 index 1850729a0..000000000 Binary files a/src/resources/icons/type/Method@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Mixin.png b/src/resources/icons/type/Mixin.png deleted file mode 100644 index 2c5548656..000000000 Binary files a/src/resources/icons/type/Mixin.png and /dev/null differ diff --git a/src/resources/icons/type/Mixin@2x.png b/src/resources/icons/type/Mixin@2x.png deleted file mode 100644 index 081e115cd..000000000 Binary files a/src/resources/icons/type/Mixin@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Modifier.png b/src/resources/icons/type/Modifier.png deleted file mode 100644 index 48d131e06..000000000 Binary files a/src/resources/icons/type/Modifier.png and /dev/null differ diff --git a/src/resources/icons/type/Modifier@2x.png b/src/resources/icons/type/Modifier@2x.png deleted file mode 100644 index c1ce8a7bb..000000000 Binary files a/src/resources/icons/type/Modifier@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Module.png b/src/resources/icons/type/Module.png deleted file mode 100644 index fc7de5e7a..000000000 Binary files a/src/resources/icons/type/Module.png and /dev/null differ diff --git a/src/resources/icons/type/Module@2x.png b/src/resources/icons/type/Module@2x.png deleted file mode 100644 index 87abc699d..000000000 Binary files a/src/resources/icons/type/Module@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Namespace.png b/src/resources/icons/type/Namespace.png deleted file mode 100644 index 4f3535a9e..000000000 Binary files a/src/resources/icons/type/Namespace.png and /dev/null differ diff --git a/src/resources/icons/type/Namespace@2x.png b/src/resources/icons/type/Namespace@2x.png deleted file mode 100644 index 34d0a9513..000000000 Binary files a/src/resources/icons/type/Namespace@2x.png and /dev/null differ diff --git a/src/resources/icons/type/NewSnippet.png b/src/resources/icons/type/NewSnippet.png deleted file mode 100644 index 3961d07c3..000000000 Binary files a/src/resources/icons/type/NewSnippet.png and /dev/null differ diff --git a/src/resources/icons/type/NewSnippet@2x.png b/src/resources/icons/type/NewSnippet@2x.png deleted file mode 100644 index d36879176..000000000 Binary files a/src/resources/icons/type/NewSnippet@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Notation.png b/src/resources/icons/type/Notation.png deleted file mode 100644 index 399f3bf1f..000000000 Binary files a/src/resources/icons/type/Notation.png and /dev/null differ diff --git a/src/resources/icons/type/Notation@2x.png b/src/resources/icons/type/Notation@2x.png deleted file mode 100644 index 8fdfca778..000000000 Binary files a/src/resources/icons/type/Notation@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Object.png b/src/resources/icons/type/Object.png deleted file mode 100644 index 813b19fce..000000000 Binary files a/src/resources/icons/type/Object.png and /dev/null differ diff --git a/src/resources/icons/type/Object@2x.png b/src/resources/icons/type/Object@2x.png deleted file mode 100644 index 6d442b8e8..000000000 Binary files a/src/resources/icons/type/Object@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Operator.png b/src/resources/icons/type/Operator.png deleted file mode 100644 index 56d44b101..000000000 Binary files a/src/resources/icons/type/Operator.png and /dev/null differ diff --git a/src/resources/icons/type/Operator@2x.png b/src/resources/icons/type/Operator@2x.png deleted file mode 100644 index b64d56d81..000000000 Binary files a/src/resources/icons/type/Operator@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Option.png b/src/resources/icons/type/Option.png deleted file mode 100644 index 988dead12..000000000 Binary files a/src/resources/icons/type/Option.png and /dev/null differ diff --git a/src/resources/icons/type/Option@2x.png b/src/resources/icons/type/Option@2x.png deleted file mode 100644 index 2e93584ed..000000000 Binary files a/src/resources/icons/type/Option@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Package.png b/src/resources/icons/type/Package.png deleted file mode 100644 index 9f893c056..000000000 Binary files a/src/resources/icons/type/Package.png and /dev/null differ diff --git a/src/resources/icons/type/Package@2x.png b/src/resources/icons/type/Package@2x.png deleted file mode 100644 index f925cce22..000000000 Binary files a/src/resources/icons/type/Package@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Parameter.png b/src/resources/icons/type/Parameter.png deleted file mode 100644 index e3df89652..000000000 Binary files a/src/resources/icons/type/Parameter.png and /dev/null differ diff --git a/src/resources/icons/type/Parameter@2x.png b/src/resources/icons/type/Parameter@2x.png deleted file mode 100644 index 4e0e1807e..000000000 Binary files a/src/resources/icons/type/Parameter@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Plugin.png b/src/resources/icons/type/Plugin.png deleted file mode 100644 index 9f893c056..000000000 Binary files a/src/resources/icons/type/Plugin.png and /dev/null differ diff --git a/src/resources/icons/type/Plugin@2x.png b/src/resources/icons/type/Plugin@2x.png deleted file mode 100644 index f925cce22..000000000 Binary files a/src/resources/icons/type/Plugin@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Procedure.png b/src/resources/icons/type/Procedure.png deleted file mode 100644 index e3df89652..000000000 Binary files a/src/resources/icons/type/Procedure.png and /dev/null differ diff --git a/src/resources/icons/type/Procedure@2x.png b/src/resources/icons/type/Procedure@2x.png deleted file mode 100644 index 4e0e1807e..000000000 Binary files a/src/resources/icons/type/Procedure@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Projection.png b/src/resources/icons/type/Projection.png deleted file mode 100644 index e3df89652..000000000 Binary files a/src/resources/icons/type/Projection.png and /dev/null differ diff --git a/src/resources/icons/type/Projection@2x.png b/src/resources/icons/type/Projection@2x.png deleted file mode 100644 index 4e0e1807e..000000000 Binary files a/src/resources/icons/type/Projection@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Property.png b/src/resources/icons/type/Property.png deleted file mode 100644 index 87cfc52a3..000000000 Binary files a/src/resources/icons/type/Property.png and /dev/null differ diff --git a/src/resources/icons/type/Property@2x.png b/src/resources/icons/type/Property@2x.png deleted file mode 100644 index 9afcab9a7..000000000 Binary files a/src/resources/icons/type/Property@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Protocol.png b/src/resources/icons/type/Protocol.png deleted file mode 100644 index 84122fbfc..000000000 Binary files a/src/resources/icons/type/Protocol.png and /dev/null differ diff --git a/src/resources/icons/type/Protocol@2x.png b/src/resources/icons/type/Protocol@2x.png deleted file mode 100644 index 037a11626..000000000 Binary files a/src/resources/icons/type/Protocol@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Provider.png b/src/resources/icons/type/Provider.png deleted file mode 100644 index 0b7287faa..000000000 Binary files a/src/resources/icons/type/Provider.png and /dev/null differ diff --git a/src/resources/icons/type/Provider@2x.png b/src/resources/icons/type/Provider@2x.png deleted file mode 100644 index 58d214613..000000000 Binary files a/src/resources/icons/type/Provider@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Provisioner.png b/src/resources/icons/type/Provisioner.png deleted file mode 100644 index e3df89652..000000000 Binary files a/src/resources/icons/type/Provisioner.png and /dev/null differ diff --git a/src/resources/icons/type/Provisioner@2x.png b/src/resources/icons/type/Provisioner@2x.png deleted file mode 100644 index 4e0e1807e..000000000 Binary files a/src/resources/icons/type/Provisioner@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Query.png b/src/resources/icons/type/Query.png deleted file mode 100644 index 3723f8756..000000000 Binary files a/src/resources/icons/type/Query.png and /dev/null differ diff --git a/src/resources/icons/type/Query@2x.png b/src/resources/icons/type/Query@2x.png deleted file mode 100644 index fb4cff541..000000000 Binary files a/src/resources/icons/type/Query@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Sample.png b/src/resources/icons/type/Sample.png deleted file mode 100644 index d4b6bbae0..000000000 Binary files a/src/resources/icons/type/Sample.png and /dev/null differ diff --git a/src/resources/icons/type/Schema.png b/src/resources/icons/type/Schema.png deleted file mode 100644 index dae63988f..000000000 Binary files a/src/resources/icons/type/Schema.png and /dev/null differ diff --git a/src/resources/icons/type/Schema@2x.png b/src/resources/icons/type/Schema@2x.png deleted file mode 100644 index cc14ea491..000000000 Binary files a/src/resources/icons/type/Schema@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Script.png b/src/resources/icons/type/Script.png deleted file mode 100644 index 6f5a914a9..000000000 Binary files a/src/resources/icons/type/Script.png and /dev/null differ diff --git a/src/resources/icons/type/Script@2x.png b/src/resources/icons/type/Script@2x.png deleted file mode 100644 index 41ea3cb9e..000000000 Binary files a/src/resources/icons/type/Script@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Section.png b/src/resources/icons/type/Section.png deleted file mode 100644 index d57b2c0d1..000000000 Binary files a/src/resources/icons/type/Section.png and /dev/null differ diff --git a/src/resources/icons/type/Service.png b/src/resources/icons/type/Service.png deleted file mode 100644 index dae63988f..000000000 Binary files a/src/resources/icons/type/Service.png and /dev/null differ diff --git a/src/resources/icons/type/Service@2x.png b/src/resources/icons/type/Service@2x.png deleted file mode 100644 index cc14ea491..000000000 Binary files a/src/resources/icons/type/Service@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Setting.png b/src/resources/icons/type/Setting.png deleted file mode 100644 index bfc5ecb59..000000000 Binary files a/src/resources/icons/type/Setting.png and /dev/null differ diff --git a/src/resources/icons/type/Setting@2x.png b/src/resources/icons/type/Setting@2x.png deleted file mode 100644 index 5f4d8f0df..000000000 Binary files a/src/resources/icons/type/Setting@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Shortcut.png b/src/resources/icons/type/Shortcut.png deleted file mode 100644 index 6f0d5d6f5..000000000 Binary files a/src/resources/icons/type/Shortcut.png and /dev/null differ diff --git a/src/resources/icons/type/Shortcut@2x.png b/src/resources/icons/type/Shortcut@2x.png deleted file mode 100644 index c471bfdca..000000000 Binary files a/src/resources/icons/type/Shortcut@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Snippet.png b/src/resources/icons/type/Snippet.png deleted file mode 100644 index cfa2e4710..000000000 Binary files a/src/resources/icons/type/Snippet.png and /dev/null differ diff --git a/src/resources/icons/type/Snippet@2x.png b/src/resources/icons/type/Snippet@2x.png deleted file mode 100644 index f5f11e93b..000000000 Binary files a/src/resources/icons/type/Snippet@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Special Form.png b/src/resources/icons/type/Special Form.png deleted file mode 100644 index f97beb195..000000000 Binary files a/src/resources/icons/type/Special Form.png and /dev/null differ diff --git a/src/resources/icons/type/Special Form@2x.png b/src/resources/icons/type/Special Form@2x.png deleted file mode 100644 index 801ff50f6..000000000 Binary files a/src/resources/icons/type/Special Form@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Statement.png b/src/resources/icons/type/Statement.png deleted file mode 100644 index dae63988f..000000000 Binary files a/src/resources/icons/type/Statement.png and /dev/null differ diff --git a/src/resources/icons/type/Statement@2x.png b/src/resources/icons/type/Statement@2x.png deleted file mode 100644 index cc14ea491..000000000 Binary files a/src/resources/icons/type/Statement@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Structure.png b/src/resources/icons/type/Structure.png deleted file mode 100644 index 46bed4179..000000000 Binary files a/src/resources/icons/type/Structure.png and /dev/null differ diff --git a/src/resources/icons/type/Structure@2x.png b/src/resources/icons/type/Structure@2x.png deleted file mode 100644 index 7bfcbc29f..000000000 Binary files a/src/resources/icons/type/Structure@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Style.png b/src/resources/icons/type/Style.png deleted file mode 100644 index bfc5ecb59..000000000 Binary files a/src/resources/icons/type/Style.png and /dev/null differ diff --git a/src/resources/icons/type/Style@2x.png b/src/resources/icons/type/Style@2x.png deleted file mode 100644 index 5f4d8f0df..000000000 Binary files a/src/resources/icons/type/Style@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Subroutine.png b/src/resources/icons/type/Subroutine.png deleted file mode 100644 index bfc5ecb59..000000000 Binary files a/src/resources/icons/type/Subroutine.png and /dev/null differ diff --git a/src/resources/icons/type/Subroutine@2x.png b/src/resources/icons/type/Subroutine@2x.png deleted file mode 100644 index 5f4d8f0df..000000000 Binary files a/src/resources/icons/type/Subroutine@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Syntax.png b/src/resources/icons/type/Syntax.png deleted file mode 100644 index 6f5a914a9..000000000 Binary files a/src/resources/icons/type/Syntax.png and /dev/null differ diff --git a/src/resources/icons/type/Syntax@2x.png b/src/resources/icons/type/Syntax@2x.png deleted file mode 100644 index 41ea3cb9e..000000000 Binary files a/src/resources/icons/type/Syntax@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Table@2x.png b/src/resources/icons/type/Table@2x.png deleted file mode 100644 index c7d86b6a3..000000000 Binary files a/src/resources/icons/type/Table@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Tactic.png b/src/resources/icons/type/Tactic.png deleted file mode 100644 index 1a48a57b1..000000000 Binary files a/src/resources/icons/type/Tactic.png and /dev/null differ diff --git a/src/resources/icons/type/Tactic@2x.png b/src/resources/icons/type/Tactic@2x.png deleted file mode 100644 index f639580b6..000000000 Binary files a/src/resources/icons/type/Tactic@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Tag@2x.png b/src/resources/icons/type/Tag@2x.png deleted file mode 100644 index c7d86b6a3..000000000 Binary files a/src/resources/icons/type/Tag@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Test.png b/src/resources/icons/type/Test.png deleted file mode 100644 index 1a48a57b1..000000000 Binary files a/src/resources/icons/type/Test.png and /dev/null differ diff --git a/src/resources/icons/type/Test@2x.png b/src/resources/icons/type/Test@2x.png deleted file mode 100644 index f639580b6..000000000 Binary files a/src/resources/icons/type/Test@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Trait@2x.png b/src/resources/icons/type/Trait@2x.png deleted file mode 100644 index c7d86b6a3..000000000 Binary files a/src/resources/icons/type/Trait@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Trigger.png b/src/resources/icons/type/Trigger.png deleted file mode 100644 index 1a48a57b1..000000000 Binary files a/src/resources/icons/type/Trigger.png and /dev/null differ diff --git a/src/resources/icons/type/Trigger@2x.png b/src/resources/icons/type/Trigger@2x.png deleted file mode 100644 index f639580b6..000000000 Binary files a/src/resources/icons/type/Trigger@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Type.png b/src/resources/icons/type/Type.png deleted file mode 100644 index c8ca39336..000000000 Binary files a/src/resources/icons/type/Type.png and /dev/null differ diff --git a/src/resources/icons/type/Type@2x.png b/src/resources/icons/type/Type@2x.png deleted file mode 100644 index 82c148ef4..000000000 Binary files a/src/resources/icons/type/Type@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Union.png b/src/resources/icons/type/Union.png deleted file mode 100644 index 859edf9c9..000000000 Binary files a/src/resources/icons/type/Union.png and /dev/null differ diff --git a/src/resources/icons/type/Union@2x.png b/src/resources/icons/type/Union@2x.png deleted file mode 100644 index 878edfe9a..000000000 Binary files a/src/resources/icons/type/Union@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Unknown@2x.png b/src/resources/icons/type/Unknown@2x.png deleted file mode 100644 index 55646c81c..000000000 Binary files a/src/resources/icons/type/Unknown@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Value.png b/src/resources/icons/type/Value.png deleted file mode 100644 index 7b358ed87..000000000 Binary files a/src/resources/icons/type/Value.png and /dev/null differ diff --git a/src/resources/icons/type/Value@2x.png b/src/resources/icons/type/Value@2x.png deleted file mode 100644 index 9deba7f39..000000000 Binary files a/src/resources/icons/type/Value@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Variable.png b/src/resources/icons/type/Variable.png deleted file mode 100644 index 599b70d5f..000000000 Binary files a/src/resources/icons/type/Variable.png and /dev/null differ diff --git a/src/resources/icons/type/Variable@2x.png b/src/resources/icons/type/Variable@2x.png deleted file mode 100644 index 64fcb223d..000000000 Binary files a/src/resources/icons/type/Variable@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Variant.png b/src/resources/icons/type/Variant.png deleted file mode 100644 index 7b358ed87..000000000 Binary files a/src/resources/icons/type/Variant.png and /dev/null differ diff --git a/src/resources/icons/type/Variant@2x.png b/src/resources/icons/type/Variant@2x.png deleted file mode 100644 index 9deba7f39..000000000 Binary files a/src/resources/icons/type/Variant@2x.png and /dev/null differ diff --git a/src/resources/icons/type/Word.png b/src/resources/icons/type/Word.png deleted file mode 100644 index 91921bf76..000000000 Binary files a/src/resources/icons/type/Word.png and /dev/null differ diff --git a/src/resources/icons/type/Word@2x.png b/src/resources/icons/type/Word@2x.png deleted file mode 100644 index 9beea600d..000000000 Binary files a/src/resources/icons/type/Word@2x.png and /dev/null differ diff --git a/src/resources/icons/type/_DashAnnotations.png b/src/resources/icons/type/_DashAnnotations.png deleted file mode 100644 index 95d375122..000000000 Binary files a/src/resources/icons/type/_DashAnnotations.png and /dev/null differ diff --git a/src/resources/icons/type/_DashAnnotations@2x.png b/src/resources/icons/type/_DashAnnotations@2x.png deleted file mode 100644 index 75e7e4b35..000000000 Binary files a/src/resources/icons/type/_DashAnnotations@2x.png and /dev/null differ diff --git a/src/resources/icons/type/_Struct.png b/src/resources/icons/type/_Struct.png deleted file mode 100644 index 00632666a..000000000 Binary files a/src/resources/icons/type/_Struct.png and /dev/null differ diff --git a/src/resources/icons/type/_Struct@2x.png b/src/resources/icons/type/_Struct@2x.png deleted file mode 100644 index 0479034bf..000000000 Binary files a/src/resources/icons/type/_Struct@2x.png and /dev/null differ diff --git a/src/src.pro b/src/src.pro index 3dc66ac0b..9d7845825 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1,78 +1,10 @@ -TEMPLATE = app +TEMPLATE = subdirs -QT += gui widgets sql -CONFIG += c++11 silent +SUBDIRS += \ + app \ + libs -# Build features -webengine { - QT += webenginewidgets - DEFINES += USE_WEBENGINE -} else { - QT += webkitwidgets -} +app.subdir = app +libs.subdir = libs -portable { - DEFINES += PORTABLE_BUILD -} - -VERSION = 0.2.1 -DEFINES += ZEAL_VERSION=\\\"$${VERSION}\\\" - -HEADERS += \ - util/version.h \ - util/plist.h - -SOURCES += \ - main.cpp \ - util/version.cpp \ - util/plist.cpp - -include(core/core.pri) -include(registry/registry.pri) -include(ui/ui.pri) -include(3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri) - -RESOURCES += \ - resources/zeal.qrc - -DESTDIR = $$BUILD_ROOT/bin - -unix:!macx { - TARGET = zeal - isEmpty(PREFIX): PREFIX = /usr - target.path = $$PREFIX/bin - INSTALLS = target - - appicons16.files=appicons/16/* - appicons24.files=appicons/24/* - appicons32.files=appicons/32/* - appicons64.files=appicons/64/* - appicons128.files=appicons/128/* - - appicons16.path=$$PREFIX/share/icons/hicolor/16x16/apps - appicons24.path=$$PREFIX/share/icons/hicolor/24x24/apps - appicons32.path=$$PREFIX/share/icons/hicolor/32x32/apps - appicons64.path=$$PREFIX/share/icons/hicolor/64x64/apps - appicons128.path=$$PREFIX/share/icons/hicolor/128x128/apps - - desktop.files=zeal.desktop - desktop.path=$$PREFIX/share/applications - - INSTALLS += appicons16 appicons24 appicons32 appicons64 appicons128 desktop -} - -win32 { - TARGET = zeal - RC_ICONS = resources/zeal.ico -} - -macx { - TARGET = Zeal - ICON = resources/zeal.icns -} - -# Keep build directory organised -MOC_DIR = $$BUILD_ROOT/.moc -OBJECTS_DIR = $$BUILD_ROOT/.obj -RCC_DIR = $$BUILD_ROOT/.rcc -UI_DIR = $$BUILD_ROOT/.ui +app.depends = libs diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp deleted file mode 100644 index c8e4a354f..000000000 --- a/src/ui/mainwindow.cpp +++ /dev/null @@ -1,849 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html -** -** This file is part of Zeal. -** -** Zeal 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 3 of the License, or -** (at your option) any later version. -** -** Zeal 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 Zeal. If not, see . -** -****************************************************************************/ - -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include "aboutdialog.h" -#include "networkaccessmanager.h" -#include "searchitemdelegate.h" -#include "settingsdialog.h" -#include "core/application.h" -#include "core/settings.h" -#include "registry/docsetregistry.h" -#include "registry/listmodel.h" -#include "registry/searchmodel.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef USE_WEBENGINE -#include -#include -#include -#else -#include -#include -#include -#endif - -#include - -/// TODO: [Qt 5.5] Remove in favour of native Qt support (QTBUG-31762) -#ifdef USE_APPINDICATOR -#undef signals -#include -#define signals public -#include -#endif - -using namespace Zeal; - -namespace { -const char startPageUrl[] = "qrc:///browser/start.html"; -} - -MainWindow::MainWindow(Core::Application *app, QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow), - m_application(app), - m_settings(app->settings()), - m_zealListModel(new ListModel(app->docsetRegistry(), this)), - m_settingsDialog(new SettingsDialog(app, m_zealListModel, this)), - m_globalShortcut(new QxtGlobalShortcut(m_settings->showShortcut, this)) -{ - connect(m_settings, &Core::Settings::updated, this, &MainWindow::applySettings); - - m_tabBar = new QTabBar(this); - m_tabBar->installEventFilter(this); - - setWindowIcon(QIcon::fromTheme(QStringLiteral("zeal"), QIcon(QStringLiteral(":/zeal.ico")))); - - if (m_settings->showSystrayIcon) - createTrayIcon(); - - // initialise key grabber - connect(m_globalShortcut, &QxtGlobalShortcut::activated, this, &MainWindow::toggleWindow); - - // initialise ui - ui->setupUi(this); - - QShortcut *focusSearch = new QShortcut(QKeySequence(QStringLiteral("Ctrl+K")), this); - focusSearch->setContext(Qt::ApplicationShortcut); - connect(focusSearch, &QShortcut::activated, - ui->lineEdit, static_cast(&SearchEdit::setFocus)); - - restoreGeometry(m_settings->windowGeometry); - ui->splitter->restoreState(m_settings->splitterGeometry); - connect(ui->splitter, &QSplitter::splitterMoved, [=](int, int) { - m_settings->splitterGeometry = ui->splitter->saveState(); - }); - - m_zealNetworkManager = new NetworkAccessManager(this); -#ifdef USE_WEBENGINE - /// FIXME AngularJS workaround (zealnetworkaccessmanager.cpp) -#else - ui->webView->page()->setNetworkAccessManager(m_zealNetworkManager); -#endif - - // menu - if (QKeySequence(QKeySequence::Quit) != QKeySequence(QStringLiteral("Ctrl+Q"))) { - ui->actionQuit->setShortcuts(QList{QKeySequence(QStringLiteral("Ctrl+Q")), - QKeySequence::Quit}); - } else { - // Quit == Ctrl+Q - don't set the same sequence twice because it causes - // "QAction::eventFilter: Ambiguous shortcut overload: Ctrl+Q" - ui->actionQuit->setShortcuts(QList{QKeySequence::Quit}); - } - connect(ui->actionQuit, &QAction::triggered, qApp, &QCoreApplication::quit); - - connect(ui->actionOptions, &QAction::triggered, [=]() { - m_globalShortcut->setEnabled(false); - m_settingsDialog->exec(); - m_globalShortcut->setEnabled(true); - }); - - ui->actionBack->setShortcut(QKeySequence::Back); - addAction(ui->actionBack); - ui->actionForward->setShortcut(QKeySequence::Forward); - addAction(ui->actionForward); - connect(ui->actionBack, &QAction::triggered, this, &MainWindow::back); - connect(ui->actionForward, &QAction::triggered, this, &MainWindow::forward); - - // Help Menu - connect(ui->actionReportProblem, &QAction::triggered, [this]() { - QDesktopServices::openUrl(QStringLiteral("https://github.com/zealdocs/zeal/issues")); - }); - connect(ui->actionCheckForUpdate, &QAction::triggered, - m_application, &Core::Application::checkForUpdate); - connect(ui->actionAboutZeal, &QAction::triggered, [this]() { - QScopedPointer dialog(new AboutDialog(this)); - dialog->exec(); - }); - connect(ui->actionAboutQt, &QAction::triggered, [this]() { - QMessageBox::aboutQt(this); - }); - - // Update check - connect(m_application, &Core::Application::updateCheckError, [this](const QString &message) { - QMessageBox::warning(this, QStringLiteral("Zeal"), message); - }); - - connect(m_application, &Core::Application::updateCheckDone, [this](const QString &version) { - if (version.isEmpty()) { - QMessageBox::information(this, QStringLiteral("Zeal"), tr("You are using the latest Zeal version.")); - return; - } - - const int ret = QMessageBox::information(this, QStringLiteral("Zeal"), - QString(tr("A new version %1 is available. Open download page?")).arg(version), - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::Yes) - QDesktopServices::openUrl(QStringLiteral("https://zealdocs.org/download.html")); - }); - - m_backMenu = new QMenu(ui->backButton); - ui->backButton->setMenu(m_backMenu); - - m_forwardMenu = new QMenu(ui->forwardButton); - ui->forwardButton->setMenu(m_forwardMenu); - - displayViewActions(); - - // treeView and lineEdit - ui->lineEdit->setTreeView(ui->treeView); - ui->lineEdit->setFocus(); - setupSearchBoxCompletions(); - ui->treeView->setModel(m_zealListModel); - ui->treeView->setColumnHidden(1, true); - SearchItemDelegate *delegate = new SearchItemDelegate(ui->treeView); - connect(ui->lineEdit, &QLineEdit::textChanged, [delegate](const QString &text) { - delegate->setHighlight(Zeal::SearchQuery::fromString(text).query()); - }); - ui->treeView->setItemDelegate(delegate); - - createTab(); - /// FIXME: QTabBar does not emit currentChanged() after the first addTab() call - reloadTabState(); - - connect(ui->treeView, &QTreeView::clicked, [this](const QModelIndex &index) { - m_treeViewClicked = true; - ui->treeView->activated(index); - }); - connect(ui->sections, &QListView::clicked, [this](const QModelIndex &index) { - m_treeViewClicked = true; - ui->sections->activated(index); - }); - connect(ui->treeView, &QTreeView::activated, this, &MainWindow::openDocset); - connect(ui->sections, &QListView::activated, this, &MainWindow::openDocset); - connect(ui->forwardButton, &QPushButton::clicked, this, &MainWindow::forward); - connect(ui->backButton, &QPushButton::clicked, this, &MainWindow::back); - - connect(ui->webView, &SearchableWebView::urlChanged, [this](const QUrl &url) { - const QString name = docsetName(url); - m_tabBar->setTabIcon(m_tabBar->currentIndex(), docsetIcon(name)); - - Docset *docset = m_application->docsetRegistry()->docset(name); - if (docset) - m_searchState->sectionsList->setResults(docset->relatedLinks(url)); - - displayViewActions(); - }); - - connect(ui->webView, &SearchableWebView::titleChanged, [this](const QString &) { - displayViewActions(); - }); - - connect(ui->webView, &SearchableWebView::linkClicked, [this](const QUrl &url) { - const QString message = tr("Do you want to open an external link?
URL: %1"); - int ret = QMessageBox::question(this, QStringLiteral("Zeal"), message.arg(url.toString())); - if (ret == QMessageBox::Yes) - QDesktopServices::openUrl(url); - }); - - ui->sections->hide(); - ui->seeAlsoLabel->hide(); - ui->sections->setModel(m_searchState->sectionsList); - connect(m_application->docsetRegistry(), &DocsetRegistry::queryCompleted, this, &MainWindow::onSearchComplete); - - connect(m_application->docsetRegistry(), &DocsetRegistry::docsetRemoved, - this, [this](const QString &name) { - for (SearchState *searchState : m_tabs) { -#ifdef USE_WEBENGINE - if (docsetName(searchState->page->url()) != name) - continue; - - searchState->page->load(QUrl(startPageUrl)); -#else - if (docsetName(searchState->page->mainFrame()->url()) != name) - continue; - - searchState->page->mainFrame()->load(QUrl(startPageUrl)); -#endif - /// TODO: Cleanup history - } - }); - - connect(ui->lineEdit, &QLineEdit::textChanged, [this](const QString &text) { - if (!m_searchState || text == m_searchState->searchQuery) - return; - - m_searchState->searchQuery = text; - m_application->docsetRegistry()->search(text); - if (text.isEmpty()) { - m_searchState->sectionsList->setResults(); - ui->treeView->setModel(m_zealListModel); - ui->treeView->setRootIsDecorated(true); - } - }); - - ui->actionNewTab->setShortcut(QKeySequence::AddTab); - connect(ui->actionNewTab, &QAction::triggered, this, &MainWindow::createTab); - addAction(ui->actionNewTab); - - // save the expanded items: - connect(ui->treeView, &QTreeView::expanded, [this](QModelIndex index) { - if (m_searchState->expansions.indexOf(index) == -1) - m_searchState->expansions.append(index); - }); - - connect(ui->treeView, &QTreeView::collapsed, [this](QModelIndex index) { - m_searchState->expansions.removeOne(index); - }); - -#ifdef Q_OS_WIN32 - ui->actionCloseTab->setShortcut(QKeySequence(Qt::Key_W + Qt::CTRL)); -#else - ui->actionCloseTab->setShortcut(QKeySequence::Close); -#endif - addAction(ui->actionCloseTab); - connect(ui->actionCloseTab, &QAction::triggered, this, [this]() { closeTab(); }); - - m_tabBar->setTabsClosable(true); - m_tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); - m_tabBar->setExpanding(false); - m_tabBar->setUsesScrollButtons(true); - m_tabBar->setDrawBase(false); - m_tabBar->setDocumentMode(true); - m_tabBar->setElideMode(Qt::ElideRight); - m_tabBar->setStyleSheet(QStringLiteral("QTabBar::tab { width: 150px; }")); - - connect(m_tabBar, &QTabBar::currentChanged, this, &MainWindow::goToTab); - connect(m_tabBar, &QTabBar::tabCloseRequested, this, &MainWindow::closeTab); - - ((QHBoxLayout *)ui->tabBarFrame->layout())->insertWidget(2, m_tabBar, 0, Qt::AlignBottom); - - connect(ui->openUrlButton, &QPushButton::clicked, [this]() { - const QUrl url(ui->webView->page()->history()->currentItem().url()); - if (url.scheme() != QLatin1String("qrc")) - QDesktopServices::openUrl(url); - }); - - ui->actionNextTab->setShortcut(QKeySequence::NextChild); - addAction(ui->actionNextTab); - connect(ui->actionNextTab, &QAction::triggered, [this]() { - m_tabBar->setCurrentIndex((m_tabBar->currentIndex() + 1) % m_tabBar->count()); - }); - - ui->actionPreviousTab->setShortcut(QKeySequence::PreviousChild); - addAction(ui->actionPreviousTab); - connect(ui->actionPreviousTab, &QAction::triggered, [this]() { - m_tabBar->setCurrentIndex((m_tabBar->currentIndex() - 1 + m_tabBar->count()) % m_tabBar->count()); - }); - -#ifdef Q_OS_OSX - ui->treeView->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sections->setAttribute(Qt::WA_MacShowFocusRect, false); -#endif - - /// TODO: Remove in the future releases - // Check pre-0.1 docset path - QString oldDocsetDir = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - oldDocsetDir.remove(QStringLiteral("Zeal/Zeal")); - oldDocsetDir += QLatin1String("zeal/docsets"); - if (QFileInfo::exists(oldDocsetDir) && m_settings->docsetPath != oldDocsetDir) { - QMessageBox::information(this, QStringLiteral("Zeal"), - QString(tr("Old docset storage has been found in %1. " - "You can move docsets to %2 or change the docset storage path in the settings.

" - "Please note, that old docsets cannot be updated automatically, so it is better to download your docsets again.

" - "Remove or use the old docset storage to avoid this message in the future.")) - .arg(QDir::toNativeSeparators(oldDocsetDir), QDir::toNativeSeparators(m_settings->docsetPath))); - } - - if (m_settings->checkForUpdate) - m_application->checkForUpdate(true); -} - -MainWindow::~MainWindow() -{ - delete ui; - - for (SearchState *state : m_tabs) { - delete state->zealSearch; - delete state->sectionsList; - delete state; - } -} - -void MainWindow::openDocset(const QModelIndex &index) -{ - const QVariant urlStr = index.sibling(index.row(), 1).data(); - if (urlStr.isNull()) - return; - - /// TODO: Keep anchor separately from file address - QStringList urlParts = urlStr.toString().split(QLatin1Char('#')); - QUrl url = QUrl::fromLocalFile(urlParts[0]); - if (urlParts.count() > 1) - /// NOTE: QUrl::DecodedMode is a fix for #121. Let's hope it doesn't break anything. - url.setFragment(urlParts[1], QUrl::DecodedMode); - - ui->webView->load(url); - - if (!m_treeViewClicked) - ui->webView->focus(); - else - m_treeViewClicked = false; -} - -QString MainWindow::docsetName(const QUrl &url) const -{ - const QRegExp docsetRegex(QStringLiteral("/([^/]+)[.]docset")); - return docsetRegex.indexIn(url.path()) != -1 ? docsetRegex.cap(1) : QString(); -} - -QIcon MainWindow::docsetIcon(const QString &docsetName) const -{ - const Docset * const docset = m_application->docsetRegistry()->docset(docsetName); - if (!docset) - return QIcon(QStringLiteral(":/icons/logo/icon.png")); - return docset->icon(); -} - -void MainWindow::queryCompleted() -{ - m_treeViewClicked = true; - - ui->treeView->setModel(m_searchState->zealSearch); - ui->treeView->setRootIsDecorated(false); - ui->treeView->setCurrentIndex(m_searchState->zealSearch->index(0, 0, QModelIndex())); - ui->treeView->activated(ui->treeView->currentIndex()); -} - -void MainWindow::goToTab(int index) -{ - if (index == -1) - return; - - saveTabState(); - m_searchState = nullptr; - reloadTabState(); -} - -void MainWindow::closeTab(int index) -{ - if (index == -1) - index = m_tabBar->currentIndex(); - - if (index == -1) - return; - - /// TODO: proper deletion here - SearchState *state = m_tabs.takeAt(index); - - if (m_searchState == state) - m_searchState = nullptr; - - delete state->zealSearch; - delete state->sectionsList; - delete state; - - m_tabBar->removeTab(index); - - if (m_tabs.count() == 0) - createTab(); -} - -void MainWindow::createTab() -{ - saveTabState(); - - SearchState *newTab = new SearchState(); - newTab->zealSearch = new Zeal::SearchModel(); - newTab->sectionsList = new Zeal::SearchModel(); - - connect(newTab->zealSearch, &SearchModel::queryCompleted, this, &MainWindow::queryCompleted); - connect(newTab->sectionsList, &SearchModel::queryCompleted, [=]() { - const bool hasResults = newTab->sectionsList->rowCount(); - ui->sections->setVisible(hasResults); - ui->seeAlsoLabel->setVisible(hasResults); - }); - - newTab->page = new QWebPage(ui->webView); -#ifdef USE_WEBENGINE - newTab->page->load(QUrl(startPageUrl)); -#else - newTab->page->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); - newTab->page->setNetworkAccessManager(m_zealNetworkManager); - newTab->page->mainFrame()->load(QUrl(startPageUrl)); -#endif - - m_tabs.append(newTab); - - - const int index = m_tabBar->addTab(QStringLiteral("title")); - m_tabBar->setCurrentIndex(index); -} - -void MainWindow::displayTabs() -{ - ui->menuTabs->clear(); - ui->menuTabs->addAction(ui->actionNewTab); - ui->menuTabs->addAction(ui->actionCloseTab); - ui->menuTabs->addSeparator(); - ui->menuTabs->addAction(ui->actionNextTab); - ui->menuTabs->addAction(ui->actionPreviousTab); - ui->menuTabs->addSeparator(); - - ui->actionNextTab->setEnabled(m_tabBar->count() > 1); - ui->actionPreviousTab->setEnabled(m_tabBar->count() > 1); - - for (int i = 0; i < m_tabs.count(); i++) { - SearchState *state = m_tabs.at(i); -#ifdef USE_WEBENGINE - QString title = state->page->title(); -#else - QString title = state->page->history()->currentItem().title(); -#endif - QAction *action = ui->menuTabs->addAction(title); - action->setCheckable(true); - action->setChecked(i == m_tabBar->currentIndex()); - - if (i < 10) { -#ifdef Q_OS_LINUX - const QKeySequence shortcut = QString("Alt+%1").arg(QString::number((i + 1) % 10)); -#else - const QKeySequence shortcut = QString("Ctrl+%1").arg(QString::number((i + 1) % 10)); -#endif - - for (QAction *oldAction : actions()) { - if (oldAction->shortcut() == shortcut) - removeAction(oldAction); - } - - action->setShortcut(shortcut); - addAction(action); - } - - m_tabBar->setTabText(i, title); - connect(action, &QAction::triggered, [=]() { - m_tabBar->setCurrentIndex(i); - }); - } -} - -void MainWindow::reloadTabState() -{ - SearchState *searchState = m_tabs.at(m_tabBar->currentIndex()); - - ui->lineEdit->setText(searchState->searchQuery); - ui->sections->setModel(searchState->sectionsList); - - if (!searchState->searchQuery.isEmpty()) { - ui->treeView->setModel(searchState->zealSearch); - ui->treeView->setRootIsDecorated(false); - } else { - ui->treeView->setModel(m_zealListModel); - ui->treeView->setRootIsDecorated(true); - ui->treeView->reset(); - } - - // Bring back the selections and expansions - ui->treeView->blockSignals(true); - for (const QModelIndex &selection: searchState->selections) - ui->treeView->selectionModel()->select(selection, QItemSelectionModel::Select); - for (const QModelIndex &expandedIndex: searchState->expansions) - ui->treeView->expand(expandedIndex); - ui->treeView->blockSignals(false); - - ui->webView->setPage(searchState->page); - ui->webView->setZoomFactor(searchState->zoomFactor); - - int resultCount = searchState->sectionsList->rowCount(); - ui->sections->setVisible(resultCount > 1); - ui->seeAlsoLabel->setVisible(resultCount > 1); - - m_searchState = searchState; - - // scroll after the object gets loaded - /// TODO: [Qt 5.4] QTimer::singleShot(100, this, &MainWindow::scrollSearch); - QTimer::singleShot(100, this, SLOT(scrollSearch())); - - displayViewActions(); -} - -void MainWindow::scrollSearch() -{ - ui->treeView->verticalScrollBar()->setValue(m_searchState->scrollPosition); - ui->sections->verticalScrollBar()->setValue(m_searchState->sectionsScroll); -} - -void MainWindow::saveTabState() -{ - if (!m_searchState) - return; - - m_searchState->searchQuery = ui->lineEdit->text(); - m_searchState->selections = ui->treeView->selectionModel()->selectedIndexes(); - m_searchState->scrollPosition = ui->treeView->verticalScrollBar()->value(); - m_searchState->sectionsScroll = ui->sections->verticalScrollBar()->value(); - m_searchState->zoomFactor = ui->webView->zoomFactor(); -} - -void MainWindow::onSearchComplete() -{ - m_searchState->zealSearch->setResults(m_application->docsetRegistry()->queryResults()); -} - -// Sets up the search box autocompletions. -void MainWindow::setupSearchBoxCompletions() -{ - QStringList completions; - for (const Docset * const docset: m_application->docsetRegistry()->docsets()) - completions << docset->keywords().first() + QLatin1Char(':'); - ui->lineEdit->setCompletions(completions); -} - -void MainWindow::displayViewActions() -{ - ui->actionBack->setEnabled(ui->webView->canGoBack()); - ui->backButton->setEnabled(ui->webView->canGoBack()); - ui->actionForward->setEnabled(ui->webView->canGoForward()); - ui->forwardButton->setEnabled(ui->webView->canGoForward()); - - ui->menuView->clear(); - ui->menuView->addAction(ui->actionBack); - ui->menuView->addAction(ui->actionForward); - ui->menuView->addSeparator(); - - m_backMenu->clear(); - m_forwardMenu->clear(); - - QWebHistory *history = ui->webView->page()->history(); - for (const QWebHistoryItem &item: history->backItems(10)) - m_backMenu->addAction(addHistoryAction(history, item)); - if (history->count() > 0) - addHistoryAction(history, history->currentItem())->setEnabled(false); - for (const QWebHistoryItem &item: history->forwardItems(10)) - m_forwardMenu->addAction(addHistoryAction(history, item)); - - displayTabs(); -} - -void MainWindow::back() -{ - ui->webView->back(); - displayViewActions(); -} - -void MainWindow::forward() -{ - ui->webView->forward(); - displayViewActions(); -} - -QAction *MainWindow::addHistoryAction(QWebHistory *history, const QWebHistoryItem &item) -{ - const QIcon icon = docsetIcon(docsetName(item.url())); - QAction *backAction = new QAction(icon, item.title(), ui->menuView); - ui->menuView->addAction(backAction); - connect(backAction, &QAction::triggered, [=](bool) { - history->goToItem(item); - }); - - return backAction; -} - -#ifdef USE_APPINDICATOR -void appIndicatorToggleWindow(GtkMenu *menu, gpointer data) -{ - Q_UNUSED(menu); - static_cast(data)->toggleWindow(); -} -#endif - -void MainWindow::createTrayIcon() -{ -#ifdef USE_APPINDICATOR - if (m_trayIcon || m_appIndicator) - return; -#else - if (m_trayIcon) - return; -#endif - -#ifdef USE_APPINDICATOR - const QString desktop = getenv("XDG_CURRENT_DESKTOP"); - const bool isUnity = (desktop.toLower() == QLatin1String("unity")); - - if (isUnity) { // Application Indicators for Unity - m_appIndicatorMenu = gtk_menu_new(); - - m_appIndicatorShowHideMenuItem = gtk_menu_item_new_with_label(qPrintable(tr("Hide"))); - gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorShowHideMenuItem); - g_signal_connect(m_appIndicatorShowHideMenuItem, "activate", - G_CALLBACK(appIndicatorToggleWindow), this); - - m_appIndicatorMenuSeparator = gtk_separator_menu_item_new(); - gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorMenuSeparator); - - m_appIndicatorQuitMenuItem = gtk_menu_item_new_with_label(qPrintable(tr("Quit"))); - gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorQuitMenuItem); - g_signal_connect(m_appIndicatorQuitMenuItem, "activate", - G_CALLBACK(QCoreApplication::quit), NULL); - - gtk_widget_show_all(m_appIndicatorMenu); - - /// NOTE: Zeal icon has to be installed, otherwise app indicator won't be shown - m_appIndicator = app_indicator_new("zeal", "zeal", APP_INDICATOR_CATEGORY_OTHER); - - app_indicator_set_status(m_appIndicator, APP_INDICATOR_STATUS_ACTIVE); - app_indicator_set_menu(m_appIndicator, GTK_MENU(m_appIndicatorMenu)); - } else { // others -#endif - m_trayIcon = new QSystemTrayIcon(this); - m_trayIcon->setIcon(windowIcon()); - m_trayIcon->setToolTip(QStringLiteral("Zeal")); - - connect(m_trayIcon, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) { - if (reason != QSystemTrayIcon::Trigger && reason != QSystemTrayIcon::DoubleClick) - return; - - // Disable, when settings window is open - if (m_settingsDialog->isVisible()) { - m_settingsDialog->activateWindow(); - return; - } - - toggleWindow(); - }); - - QMenu *trayIconMenu = new QMenu(this); - QAction *quitAction = trayIconMenu->addAction(tr("&Quit")); - connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); - - m_trayIcon->setContextMenu(trayIconMenu); - - m_trayIcon->show(); -#ifdef USE_APPINDICATOR - } -#endif -} - -void MainWindow::removeTrayIcon() -{ -#ifdef USE_APPINDICATOR - if (!m_trayIcon && !m_appIndicator) - return; -#else - if (!m_trayIcon) - return; -#endif - -#ifdef USE_APPINDICATOR - const QString desktop = getenv("XDG_CURRENT_DESKTOP"); - const bool isUnity = (desktop.toLower() == QLatin1String("unity")); - - if (isUnity) { - g_clear_object(&m_appIndicator); - g_clear_object(&m_appIndicatorMenu); - g_clear_object(&m_appIndicatorShowHideMenuItem); - g_clear_object(&m_appIndicatorMenuSeparator); - g_clear_object(&m_appIndicatorQuitMenuItem); - } else { -#endif - QMenu *trayIconMenu = m_trayIcon->contextMenu(); - delete m_trayIcon; - m_trayIcon = nullptr; - delete trayIconMenu; -#ifdef USE_APPINDICATOR - } -#endif -} - -void MainWindow::bringToFront(const Zeal::SearchQuery &query) -{ - show(); - setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - raise(); - activateWindow(); - ui->lineEdit->setFocus(); - - if (!query.isEmpty()) { - ui->lineEdit->setText(query.toString()); - ui->treeView->setFocus(); - ui->treeView->activated(ui->treeView->currentIndex()); - } -} - -void MainWindow::changeEvent(QEvent *event) -{ - if (m_settings->showSystrayIcon && m_settings->minimizeToSystray - && event->type() == QEvent::WindowStateChange && isMinimized()) { - hide(); - } - QMainWindow::changeEvent(event); -} - -void MainWindow::closeEvent(QCloseEvent *event) -{ - m_settings->windowGeometry = saveGeometry(); - if (m_settings->showSystrayIcon && m_settings->hideOnClose) { - event->ignore(); - toggleWindow(); - } -} - -bool MainWindow::eventFilter(QObject *object, QEvent *event) -{ - if (object == m_tabBar && event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *e = reinterpret_cast(event); - if (e->button() == Qt::MiddleButton) { - const int index = m_tabBar->tabAt(e->pos()); - if (index >= 0) { - closeTab(index); - return true; - } - } - } - - return QMainWindow::eventFilter(object, event); -} - -// Captures global events in order to pass them to the search bar. -void MainWindow::keyPressEvent(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) { - case Qt::Key_Escape: - ui->lineEdit->setFocus(); - ui->lineEdit->clearQuery(); - break; - case Qt::Key_Question: - ui->lineEdit->setFocus(); - ui->lineEdit->selectQuery(); - break; - default: - QMainWindow::keyPressEvent(keyEvent); - break; - } -} - -void MainWindow::applySettings() -{ - m_globalShortcut->setShortcut(m_settings->showShortcut); - - if (m_settings->showSystrayIcon) - createTrayIcon(); - else - removeTrayIcon(); -} - -void MainWindow::toggleWindow() -{ - const bool checkActive = sender() == m_globalShortcut; - - if (!isVisible() || (checkActive && !isActiveWindow())) { -#ifdef USE_APPINDICATOR - if (m_appIndicator) { - gtk_menu_item_set_label(GTK_MENU_ITEM(m_appIndicatorShowHideMenuItem), - qPrintable(tr("Hide"))); - } -#endif - bringToFront(); - } else { -#ifdef USE_APPINDICATOR - if (m_trayIcon || m_appIndicator) { - if (m_appIndicator) { - gtk_menu_item_set_label(GTK_MENU_ITEM(m_appIndicatorShowHideMenuItem), - qPrintable(tr("Show"))); - } -#else - if (m_trayIcon) { -#endif - hide(); - } else { - showMinimized(); - } - } -} diff --git a/src/ui/networkaccessmanager.cpp b/src/ui/networkaccessmanager.cpp deleted file mode 100644 index ccecae664..000000000 --- a/src/ui/networkaccessmanager.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html -** -** This file is part of Zeal. -** -** Zeal 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 3 of the License, or -** (at your option) any later version. -** -** Zeal 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 Zeal. If not, see . -** -****************************************************************************/ - -#include "networkaccessmanager.h" - -#include - -using namespace Zeal; - -NetworkAccessManager::NetworkAccessManager(QObject *parent) : - QNetworkAccessManager(parent) -{ -} - -QNetworkReply *NetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, - const QNetworkRequest &req, - QIODevice *outgoingData) -{ - const QString scheme = req.url().scheme(); - - if (scheme == QLatin1String("qrc")) - return QNetworkAccessManager::createRequest(op, req, outgoingData); - - const bool nonFileScheme = scheme != QLatin1String("file"); - const bool nonLocalFile = !nonFileScheme && !req.url().host().isEmpty(); - - // Ignore requests which cause Zeal to hang - if (nonLocalFile || nonFileScheme) { - return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, - QNetworkRequest()); - } -#ifdef Q_OS_WIN32 - // Fix for AngularJS docset - Windows doesn't allow ':'s in filenames, - // and bsdtar.exe replaces them with '_'s, so replace all ':'s in requests - // with '_'s. - QNetworkRequest winReq(req); - QUrl winUrl(req.url()); - QString winPath = winUrl.path(); - // absolute paths are of form /C:/..., so don't replace colons occuring - // within first 3 characters - while (winPath.lastIndexOf(':') > 2) - winPath = winPath.replace(winPath.lastIndexOf(':'), 1, "_"); - - winUrl.setPath(winPath); - winReq.setUrl(winUrl); - return QNetworkAccessManager::createRequest(op, winReq, outgoingData); -#else - return QNetworkAccessManager::createRequest(op, req, outgoingData); -#endif -} diff --git a/src/ui/searchitemdelegate.cpp b/src/ui/searchitemdelegate.cpp deleted file mode 100644 index 2e68943af..000000000 --- a/src/ui/searchitemdelegate.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html -** -** This file is part of Zeal. -** -** Zeal 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 3 of the License, or -** (at your option) any later version. -** -** Zeal 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 Zeal. If not, see . -** -****************************************************************************/ - -#include "searchitemdelegate.h" - -#include "searchitemstyle.h" -#include "registry/searchmodel.h" - -#include -#include -#include - -SearchItemDelegate::SearchItemDelegate(QObject *parent) : - QStyledItemDelegate(parent) -{ -} - -void SearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option_, - const QModelIndex &index) const -{ - if (m_highlight.isEmpty()) { - QStyledItemDelegate::paint(painter, option_, index); - return; - } - - painter->save(); - - QStyleOptionViewItem option(option_); - option.text = index.data().toString(); - option.features |= QStyleOptionViewItem::HasDisplay; - - if (!index.data(Qt::DecorationRole).isNull()) { - option.features |= QStyleOptionViewItem::HasDecoration; - option.icon = index.data(Qt::DecorationRole).value(); - } - - ZealSearchItemStyle style; - style.drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget); - - if (option.state & QStyle::State_Selected) { -#ifdef Q_OS_WIN32 - option.palette.setColor(QPalette::All, QPalette::HighlightedText, - option.palette.color(QPalette::Active, QPalette::Text)); -#endif - painter->setPen(QPen(option.palette.highlightedText(), 1)); - } - - QRect rect = QApplication::style()->subElementRect(QStyle::SE_ItemViewItemText, &option, - option.widget); - const int margin = style.pixelMetric(QStyle::PM_FocusFrameHMargin, 0, option.widget); - rect.adjust(margin, 0, 2, 0); // +2px for bold text - - const QFont defaultFont(painter->font()); - QFont boldFont(defaultFont); - boldFont.setBold(true); - - const QFontMetrics metrics(defaultFont); - const QFontMetrics metricsBold(boldFont); - - const QString elided = metrics.elidedText(option.text, option.textElideMode, rect.width()); - - int from = 0; - while (from < elided.size()) { - const int to = elided.indexOf(m_highlight, from, Qt::CaseInsensitive); - - if (to == -1) { - painter->drawText(rect, elided.mid(from)); - break; - } - - QString text = elided.mid(from, to - from); - painter->drawText(rect, text); - rect.setLeft(rect.left() + metrics.width(text)); - - text = elided.mid(to, m_highlight.size()); - painter->setFont(boldFont); - painter->drawText(rect, text); - rect.setLeft(rect.left() + metricsBold.width(text)); - - painter->setFont(defaultFont); - - from = to + m_highlight.size(); - } - - painter->restore(); -} - -void SearchItemDelegate::setHighlight(const QString &text) -{ - m_highlight = text; -} diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp deleted file mode 100644 index 5c4bc04d3..000000000 --- a/src/ui/settingsdialog.cpp +++ /dev/null @@ -1,819 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Oleg Shparber -** Copyright (C) 2013-2014 Jerzy Kozera -** Contact: http://zealdocs.org/contact.html -** -** This file is part of Zeal. -** -** Zeal 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 3 of the License, or -** (at your option) any later version. -** -** Zeal 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 Zeal. If not, see . -** -****************************************************************************/ - -#include "settingsdialog.h" - -#include "docsetlistitemdelegate.h" -#include "progressitemdelegate.h" -#include "ui_settingsdialog.h" -#include "core/application.h" -#include "core/settings.h" -#include "registry/docsetregistry.h" -#include "registry/listmodel.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifdef USE_WEBENGINE -#include -#define QWebSettings QWebEngineSettings -#else -#include -#endif - -using namespace Zeal; - -namespace { -const char ApiServerUrl[] = "http://api.zealdocs.org/v1"; -const char RedirectServerUrl[] = "http://go.zealdocs.org"; -/// TODO: Each source plugin should have its own cache -const char DocsetListCacheFileName[] = "com.kapeli.json"; - -/// TODO: Make the timeout period configurable -constexpr int CacheTimeout = 24 * 60 * 60 * 1000; // 24 hours in microseconds - -// QNetworkReply properties -const char DocsetNameProperty[] = "docsetName"; -const char DownloadTypeProperty[] = "downloadType"; -const char DownloadPreviousReceived[] = "downloadPreviousReceived"; -const char ListItemIndexProperty[] = "listItem"; -} - -SettingsDialog::SettingsDialog(Core::Application *app, ListModel *listModel, QWidget *parent) : - QDialog(parent), - ui(new Ui::SettingsDialog()), - m_application(app), - m_docsetRegistry(app->docsetRegistry()) -{ - ui->setupUi(this); - -#ifdef Q_OS_OSX - ui->availableDocsetList->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->installedDocsetList->setAttribute(Qt::WA_MacShowFocusRect, false); -#endif - - ui->downloadableGroup->hide(); - ui->docsetsProgress->hide(); - - ui->installedDocsetList->setItemDelegate(new DocsetListItemDelegate(this)); - ui->installedDocsetList->setModel(listModel); - - ui->installedDocsetList->setSelectionMode(QAbstractItemView::ExtendedSelection); - QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); - connect(selectionModel, &QItemSelectionModel::selectionChanged, - [this, selectionModel]() { - if (!m_replies.isEmpty()) - return; - - ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); - - for (const QModelIndex &index : selectionModel->selectedIndexes()) { - if (index.data(Zeal::ListModel::UpdateAvailableRole).toBool()) { - ui->updateSelectedDocsetsButton->setEnabled(true); - return; - } - } - ui->updateSelectedDocsetsButton->setEnabled(false); - }); - connect(ui->updateSelectedDocsetsButton, &QPushButton::clicked, - this, &SettingsDialog::updateSelectedDocsets); - connect(ui->updateAllDocsetsButton, &QPushButton::clicked, - this, &SettingsDialog::updateAllDocsets); - connect(ui->removeDocsetsButton, &QPushButton::clicked, - this, &SettingsDialog::removeSelectedDocsets); - - ui->availableDocsetList->setItemDelegate(new ProgressItemDelegate(this)); - - // Setup signals & slots - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::saveSettings); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SettingsDialog::loadSettings); - connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton *button) { - if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) - saveSettings(); - }); - - connect(ui->minFontSize, static_cast(&QSpinBox::valueChanged), - this, [](int value) { - QWebSettings::globalSettings()->setFontSize(QWebSettings::MinimumFontSize, value); - }); - - connect(ui->addFeedButton, &QPushButton::clicked, this, &SettingsDialog::addDashFeed); - connect(ui->refreshButton, &QPushButton::clicked, this, &SettingsDialog::downloadDocsetList); - - connect(m_application, &Core::Application::extractionCompleted, - this, &SettingsDialog::extractionCompleted); - connect(m_application, &Core::Application::extractionError, - this, &SettingsDialog::extractionError); - connect(m_application, &Core::Application::extractionProgress, - this, &SettingsDialog::extractionProgress); - - loadSettings(); -} - -SettingsDialog::~SettingsDialog() -{ - delete ui; -} - -void SettingsDialog::addDashFeed() -{ - QString txt = QApplication::clipboard()->text(); - if (!txt.startsWith(QLatin1String("dash-feed://"))) - txt.clear(); - - QString feedUrl = QInputDialog::getText(this, QStringLiteral("Zeal"), tr("Feed URL:"), - QLineEdit::Normal, txt); - if (feedUrl.isEmpty()) - return; - - if (feedUrl.startsWith(QLatin1String("dash-feed://"))) { - feedUrl = feedUrl.remove(0, 12); - feedUrl = QUrl::fromPercentEncoding(feedUrl.toUtf8()); - } - - QNetworkReply *reply = download(feedUrl); - reply->setProperty(DownloadTypeProperty, DownloadDashFeed); - connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); -} - -void SettingsDialog::updateSelectedDocsets() -{ - for (const QModelIndex &index : ui->installedDocsetList->selectionModel()->selectedIndexes()) { - if (!index.data(Zeal::ListModel::UpdateAvailableRole).toBool()) - continue; - - downloadDashDocset(index.data(Zeal::ListModel::DocsetNameRole).toString()); - } -} - -void SettingsDialog::updateAllDocsets() -{ - for (const Docset * const docset : m_docsetRegistry->docsets()) { - if (!docset->hasUpdate) - continue; - - downloadDashDocset(docset->name()); - } -} - -void SettingsDialog::removeSelectedDocsets() -{ - QItemSelectionModel *selectonModel = ui->installedDocsetList->selectionModel(); - if (!selectonModel->hasSelection()) - return; - - int ret; - if (selectonModel->selectedIndexes().count() == 1) { - const QString docsetTitle = selectonModel->selectedIndexes().first().data().toString(); - ret = QMessageBox::question(this, tr("Remove Docset"), - QString(tr("Do you really want to remove %1 docset?")) - .arg(docsetTitle)); - } else { - ret = QMessageBox::question(this, tr("Remove Docsets"), - QString(tr("Do you really want to remove %1 docsets?")) - .arg(selectonModel->selectedIndexes().count())); - } - - if (ret == QMessageBox::No) - return; - - QStringList names; - for (const QModelIndex &index : selectonModel->selectedIndexes()) - names << index.data(ListModel::DocsetNameRole).toString(); - removeDocsets(names); -} - -/*! - \internal - Should be connected to all \l QNetworkReply::finished signals in order to process possible - HTTP-redirects correctly. -*/ -void SettingsDialog::downloadCompleted() -{ - QScopedPointer reply( - qobject_cast(sender())); - - m_replies.removeOne(reply.data()); - - if (reply->error() != QNetworkReply::NoError) { - if (reply->error() != QNetworkReply::OperationCanceledError) { - const int ret = QMessageBox::warning(this, tr("Network Error"), reply->errorString(), - QMessageBox::Ok | QMessageBox::Retry); - - if (ret == QMessageBox::Retry) { - QNetworkReply *newReply = download(reply->request().url()); - - // Copy properties - newReply->setProperty(DocsetNameProperty, reply->property(DocsetNameProperty)); - newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); - newReply->setProperty(ListItemIndexProperty, - reply->property(ListItemIndexProperty)); - - connect(newReply, &QNetworkReply::finished, - this, &SettingsDialog::downloadCompleted); - return; - } - - bool ok; - QListWidgetItem *listItem = ui->availableDocsetList->item( - reply->property(ListItemIndexProperty).toInt(&ok)); - if (ok && listItem) - listItem->setData(ProgressItemDelegate::ShowProgressRole, false); - } - - if (m_replies.isEmpty()) - resetProgress(); - - return; - } - - QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - if (redirectUrl.isValid()) { - if (redirectUrl.isRelative()) - redirectUrl = reply->request().url().resolved(redirectUrl); - - /// TODO: Verify if scheme can be missing - if (redirectUrl.scheme().isEmpty()) - redirectUrl.setScheme(reply->request().url().scheme()); - - QNetworkReply *newReply = download(redirectUrl); - - // Copy properties - newReply->setProperty(DocsetNameProperty, reply->property(DocsetNameProperty)); - newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); - newReply->setProperty(ListItemIndexProperty, reply->property(ListItemIndexProperty)); - - connect(newReply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); - - return; - } - - switch (reply->property(DownloadTypeProperty).toUInt()) { - case DownloadDocsetList: { - const QByteArray data = reply->readAll(); - - const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - QScopedPointer file(new QFile(cacheDir.filePath(DocsetListCacheFileName))); - if (file->open(QIODevice::WriteOnly)) - file->write(data); - - ui->lastUpdatedLabel->setText(QFileInfo(file->fileName()) - .lastModified().toString(Qt::SystemLocaleShortDate)); - - QJsonParseError jsonError; - const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) { - QMessageBox::warning(this, tr("Error"), - tr("Corrupted docset list: ") + jsonError.errorString()); - break; - } - - processDocsetList(jsonDoc.array()); - resetProgress(); - break; - } - - case DownloadDashFeed: { - DocsetMetadata metadata - = DocsetMetadata::fromDashFeed(reply->request().url(), reply->readAll()); - - if (metadata.urls().isEmpty()) { - QMessageBox::critical(this, QStringLiteral("Zeal"), tr("Invalid docset feed!")); - break; - } - - m_userFeeds[metadata.name()] = metadata; - QNetworkReply *reply = download(metadata.url()); - reply->setProperty(DocsetNameProperty, metadata.name()); - reply->setProperty(DownloadTypeProperty, DownloadDocset); - connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); - break; - } - - case DownloadDocset: { - const QString docsetName = reply->property(DocsetNameProperty).toString(); - const DocsetMetadata metadata = m_availableDocsets.contains(docsetName) - ? m_availableDocsets[docsetName] - : m_userFeeds[docsetName]; - - /// TODO: Implement an explicit and verbose docset update logic - QDir dir(m_application->settings()->docsetPath); - const QString docsetDirName = docsetName + QLatin1String(".docset"); - if (dir.exists(docsetDirName)) { - m_docsetRegistry->remove(docsetName); - const QString tmpName = QStringLiteral(".toDelete") - + QString::number(QDateTime::currentMSecsSinceEpoch()); - dir.rename(docsetDirName, tmpName); - QtConcurrent::run([=] { - QDir d(dir); - if (!d.cd(tmpName)) - return; - d.removeRecursively(); - }); - } - - QTemporaryFile *tmpFile = m_tmpFiles[docsetName]; - if (!tmpFile) { - tmpFile = new QTemporaryFile(this); - tmpFile->open(); - m_tmpFiles.insert(docsetName, tmpFile); - } - - while (reply->bytesAvailable()) - tmpFile->write(reply->read(1024 * 1024)); // Use small chunks - tmpFile->close(); - - QListWidgetItem *item = findDocsetListItem(metadata.title()); - if (item) { - item->setData(ProgressItemDelegate::ValueRole, 0); - item->setData(ProgressItemDelegate::FormatRole, tr("Installing: %p%")); - } - - m_tmpFiles.insert(metadata.name(), tmpFile); - m_application->extract(tmpFile->fileName(), m_application->settings()->docsetPath, - metadata.name() + QLatin1String(".docset")); - break; - } - } - - // If all enqueued downloads have finished executing - if (m_replies.isEmpty()) - resetProgress(); -} - -// creates a total download progress for multiple QNetworkReplies -void SettingsDialog::downloadProgress(qint64 received, qint64 total) -{ - // Don't show progress for non-docset pages - if (total == -1 || received < 10240) - return; - - QNetworkReply *reply = qobject_cast(sender()); - if (!reply || !reply->isOpen()) - return; - - if (reply->property(DownloadTypeProperty).toInt() == DownloadDocset) { - const QString docsetName = reply->property(DocsetNameProperty).toString(); - - QTemporaryFile *tmpFile = m_tmpFiles[docsetName]; - if (!tmpFile) { - tmpFile = new QTemporaryFile(this); - tmpFile->open(); - m_tmpFiles.insert(docsetName, tmpFile); - } - - tmpFile->write(reply->read(received)); - } - - // Try to get the item associated to the request - QListWidgetItem *item - = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); - if (item) - item->setData(ProgressItemDelegate::ValueRole, percent(received, total)); - - qint64 previousReceived = 0; - const QVariant previousReceivedVariant = reply->property(DownloadPreviousReceived); - if (!previousReceivedVariant.isValid()) - m_combinedTotal += total; - else - previousReceived = previousReceivedVariant.toLongLong(); - - m_combinedReceived += received - previousReceived; - reply->setProperty(DownloadPreviousReceived, received); - - displayProgress(); -} - -void SettingsDialog::extractionCompleted(const QString &filePath) -{ - QString docsetName; - - /// FIXME: Come up with a better approach - for (const QString &key : m_tmpFiles.keys()) { - if (m_tmpFiles[key]->fileName() == filePath) { - docsetName = key; - break; - } - } - - const QDir dataDir(m_application->settings()->docsetPath); - const QString docsetPath = dataDir.absoluteFilePath(docsetName + QLatin1String(".docset")); - - // Write metadata about docset - DocsetMetadata metadata = m_availableDocsets.contains(docsetName) - ? m_availableDocsets[docsetName] - : m_userFeeds[docsetName]; - metadata.save(docsetPath, metadata.latestVersion()); - - m_docsetRegistry->addDocset(docsetPath); - - QListWidgetItem *listItem = findDocsetListItem(metadata.title()); - if (listItem) { - listItem->setHidden(true); - listItem->setCheckState(Qt::Unchecked); - listItem->setData(ProgressItemDelegate::ShowProgressRole, false); - } - resetProgress(); - delete m_tmpFiles.take(docsetName); -} - -void SettingsDialog::extractionError(const QString &filePath, const QString &errorString) -{ - const QString docsetName = QFileInfo(filePath).baseName() + QLatin1String(".docset"); - QMessageBox::warning(this, tr("Extraction Error"), - QString(tr("Cannot extract docset %1: %2")).arg(docsetName, errorString)); - /// TODO: Update list item state (hide progress bar) - delete m_tmpFiles.take(docsetName); -} - -void SettingsDialog::extractionProgress(const QString &filePath, qint64 extracted, qint64 total) -{ - QString docsetName; - - /// FIXME: Come up with a better approach - for (const QString &key : m_tmpFiles.keys()) { - if (m_tmpFiles[key]->fileName() == filePath) { - docsetName = key; - break; - } - } - - DocsetMetadata metadata = m_availableDocsets.contains(docsetName) - ? m_availableDocsets[docsetName] - : m_userFeeds[docsetName]; - - QListWidgetItem *listItem = findDocsetListItem(metadata.title()); - if (listItem) - listItem->setData(ProgressItemDelegate::ValueRole, percent(extracted, total)); -} - -void SettingsDialog::on_downloadDocsetButton_clicked() -{ - if (!m_replies.isEmpty()) { - cancelDownloads(); - return; - } - - // Find each checked item, and create a NetworkRequest for it. - for (int i = 0; i < ui->availableDocsetList->count(); ++i) { - QListWidgetItem *item = ui->availableDocsetList->item(i); - if (item->checkState() != Qt::Checked) - continue; - - item->setData(ProgressItemDelegate::FormatRole, tr("Downloading: %p%")); - item->setData(ProgressItemDelegate::ValueRole, 0); - item->setData(ProgressItemDelegate::ShowProgressRole, true); - - downloadDashDocset(item->data(ListModel::DocsetNameRole).toString()); - } -} - -void SettingsDialog::on_storageButton_clicked() -{ - const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory")); - if (!dir.isEmpty()) - ui->storageEdit->setText(QDir::toNativeSeparators(dir)); - -} - -void SettingsDialog::on_tabWidget_currentChanged(int current) -{ - if (ui->tabWidget->widget(current) != ui->docsetsTab || ui->availableDocsetList->count()) - return; - - const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - if (!cacheDir.exists()) - QDir().mkpath(cacheDir.absolutePath()); - const QFileInfo fi(cacheDir.filePath(DocsetListCacheFileName)); - - if (!fi.exists() || fi.lastModified().msecsTo(QDateTime::currentDateTime()) > CacheTimeout) { - downloadDocsetList(); - return; - } - - QScopedPointer file(new QFile(fi.filePath())); - if (!file->open(QIODevice::ReadOnly)) { - downloadDocsetList(); - return; - } - - QJsonParseError jsonError; - const QJsonDocument jsonDoc = QJsonDocument::fromJson(file->readAll(), &jsonError); - - if (jsonError.error != QJsonParseError::NoError) { - downloadDocsetList(); - return; - } - - /// TODO: Show more user friendly labels, like "5 hours ago" - ui->lastUpdatedLabel->setText(fi.lastModified().toString(Qt::SystemLocaleShortDate)); - processDocsetList(jsonDoc.array()); -} - -QListWidgetItem *SettingsDialog::findDocsetListItem(const QString &title) const -{ - const QList items - = ui->availableDocsetList->findItems(title, Qt::MatchFixedString); - - if (items.isEmpty()) - return nullptr; - - return items.first(); -} - -bool SettingsDialog::updatesAvailable() const -{ - for (Docset *docset : m_docsetRegistry->docsets()) { - if (docset->hasUpdate) - return true; - } - - return false; -} - -QNetworkReply *SettingsDialog::download(const QUrl &url) -{ - displayProgress(); - - QNetworkReply *reply = m_application->download(url); - connect(reply, &QNetworkReply::downloadProgress, this, &SettingsDialog::downloadProgress); - m_replies.append(reply); - - // Installed docsets - ui->addFeedButton->setEnabled(false); - ui->updateSelectedDocsetsButton->setEnabled(false); - ui->updateAllDocsetsButton->setEnabled(false); - ui->removeDocsetsButton->setEnabled(false); - - // Available docsets - ui->availableDocsetList->setEnabled(false); - ui->refreshButton->setEnabled(false); - ui->downloadDocsetButton->setText(tr("Stop downloads")); - - return reply; -} - -void SettingsDialog::cancelDownloads() -{ - for (QNetworkReply *reply : m_replies) { - // Hide progress bar - QListWidgetItem *listItem - = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); - if (listItem) - listItem->setData(ProgressItemDelegate::ShowProgressRole, false); - - if (reply->property(DownloadTypeProperty).toInt() == DownloadDocset) - delete m_tmpFiles.take(reply->property(DocsetNameProperty).toString()); - - reply->abort(); - } - resetProgress(); -} - -void SettingsDialog::downloadDocsetList() -{ - ui->availableDocsetList->clear(); - m_availableDocsets.clear(); - - QNetworkReply *reply = download(QUrl(ApiServerUrl + QLatin1String("/docsets"))); - reply->setProperty(DownloadTypeProperty, DownloadDocsetList); - connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); -} - -void SettingsDialog::processDocsetList(const QJsonArray &list) -{ - for (const QJsonValue &v : list) { - QJsonObject docsetJson = v.toObject(); - - DocsetMetadata metadata(docsetJson); - m_availableDocsets.insert(metadata.name(), metadata); - } - - /// TODO: Move into dedicated method - for (const DocsetMetadata &metadata : m_availableDocsets) { - QListWidgetItem *listItem - = new QListWidgetItem(metadata.icon(), metadata.title(), ui->availableDocsetList); - listItem->setData(ListModel::DocsetNameRole, metadata.name()); - listItem->setCheckState(Qt::Unchecked); - - if (m_docsetRegistry->contains(metadata.name())) { - listItem->setHidden(true); - - Docset *docset = m_docsetRegistry->docset(metadata.name()); - - if (metadata.latestVersion() != docset->version() - || (metadata.latestVersion() == docset->version() - && metadata.revision() > docset->revision())) { - docset->hasUpdate = true; - ui->updateAllDocsetsButton->setEnabled(true); - } - } - } - - ui->installedDocsetList->reset(); - - if (!m_availableDocsets.isEmpty()) - ui->downloadableGroup->show(); -} - -void SettingsDialog::downloadDashDocset(const QString &name) -{ - if (!m_availableDocsets.contains(name)) - return; - - const QString urlString = RedirectServerUrl + QStringLiteral("/d/com.kapeli/%1/latest"); - QNetworkReply *reply = download(urlString.arg(name)); - reply->setProperty(DocsetNameProperty, name); - reply->setProperty(DownloadTypeProperty, DownloadDocset); - reply->setProperty(ListItemIndexProperty, - ui->availableDocsetList->row(findDocsetListItem(m_availableDocsets[name].title()))); - - connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); -} - -void SettingsDialog::removeDocsets(const QStringList &names) -{ - for (const QString &name : names) { - const QString title = m_docsetRegistry->docset(name)->title(); - m_docsetRegistry->remove(name); - - const QDir dataDir(m_application->settings()->docsetPath); - if (dataDir.exists()) { - ui->docsetsProgress->show(); - ui->removeDocsetsButton->setEnabled(false); - displayProgress(); - - QFuture future = QtConcurrent::run([=] { - QDir docsetDir(dataDir); - return docsetDir.cd(name + QLatin1String(".docset")) - && docsetDir.removeRecursively(); - }); - QFutureWatcher *watcher = new QFutureWatcher(); - watcher->setFuture(future); - connect(watcher, &QFutureWatcher::finished, [=] { - if (!watcher->result()) { - QMessageBox::warning(this, tr("Error"), - QString(tr("Cannot delete docset %1!")).arg(title)); - } - - resetProgress(); - - QListWidgetItem *listItem = findDocsetListItem(title); - if (listItem) - listItem->setHidden(false); - - watcher->deleteLater(); - }); - } - } -} - -void SettingsDialog::displayProgress() -{ - ui->docsetsProgress->setValue(percent(m_combinedReceived, m_combinedTotal)); - ui->docsetsProgress->setMaximum(100); - ui->docsetsProgress->setVisible(!m_replies.isEmpty()); -} - -void SettingsDialog::resetProgress() -{ - if (!m_replies.isEmpty()) - return; - - m_combinedReceived = 0; - m_combinedTotal = 0; - displayProgress(); - - // Installed docsets - ui->addFeedButton->setEnabled(true); - QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); - bool hasSelectedUpdates = false; - for (const QModelIndex &index : selectionModel->selectedIndexes()) { - if (index.data(Zeal::ListModel::UpdateAvailableRole).toBool()) { - hasSelectedUpdates = true; - break; - } - } - ui->updateSelectedDocsetsButton->setEnabled(hasSelectedUpdates); - ui->updateAllDocsetsButton->setEnabled(updatesAvailable()); - ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); - - // Available docsets - ui->availableDocsetList->setEnabled(true); - ui->refreshButton->setEnabled(true); - ui->downloadDocsetButton->setText(tr("Download")); -} - -void SettingsDialog::loadSettings() -{ - const Core::Settings * const settings = m_application->settings(); - // General Tab - ui->startMinimizedCheckBox->setChecked(settings->startMinimized); - ui->checkForUpdateCheckBox->setChecked(settings->checkForUpdate); - - ui->systrayGroupBox->setChecked(settings->showSystrayIcon); - ui->minimizeToSystrayCheckBox->setChecked(settings->minimizeToSystray); - ui->hideToSystrayCheckBox->setChecked(settings->hideOnClose); - - ui->toolButton->setKeySequence(settings->showShortcut); - - // - ui->minFontSize->setValue(settings->minimumFontSize); - ui->storageEdit->setText(QDir::toNativeSeparators(settings->docsetPath)); - - // Network Tab - switch (settings->proxyType) { - case Core::Settings::ProxyType::None: - ui->noProxySettings->setChecked(true); - break; - case Core::Settings::ProxyType::System: - ui->systemProxySettings->setChecked(true); - break; - case Core::Settings::ProxyType::UserDefined: - ui->manualProxySettings->setChecked(true); - ui->httpProxy->setText(settings->proxyHost); - ui->httpProxyPort->setValue(settings->proxyPort); - ui->httpProxyNeedsAuth->setChecked(settings->proxyAuthenticate); - ui->httpProxyUser->setText(settings->proxyUserName); - ui->httpProxyPass->setText(settings->proxyPassword); - break; - } -} - -void SettingsDialog::saveSettings() -{ - Core::Settings * const settings = m_application->settings(); - // General Tab - settings->startMinimized = ui->startMinimizedCheckBox->isChecked(); - settings->checkForUpdate = ui->checkForUpdateCheckBox->isChecked(); - - settings->showSystrayIcon = ui->systrayGroupBox->isChecked(); - settings->minimizeToSystray = ui->minimizeToSystrayCheckBox->isChecked(); - settings->hideOnClose = ui->hideToSystrayCheckBox->isChecked(); - - settings->showShortcut = ui->toolButton->keySequence(); - - // - settings->minimumFontSize = ui->minFontSize->text().toInt(); - - if (QDir::fromNativeSeparators(ui->storageEdit->text()) != settings->docsetPath) { - settings->docsetPath = QDir::fromNativeSeparators(ui->storageEdit->text()); - m_docsetRegistry->init(settings->docsetPath); - } - - // Network Tab - // Proxy settings - if (ui->noProxySettings->isChecked()) - settings->proxyType = Core::Settings::ProxyType::None; - else if (ui->systemProxySettings->isChecked()) - settings->proxyType = Core::Settings::ProxyType::System; - else if (ui->manualProxySettings->isChecked()) - settings->proxyType = Core::Settings::ProxyType::UserDefined; - - settings->proxyHost = ui->httpProxy->text(); - settings->proxyPort = ui->httpProxyPort->text().toUInt(); - settings->proxyAuthenticate = ui->httpProxyNeedsAuth->isChecked(); - settings->proxyUserName = ui->httpProxyUser->text(); - settings->proxyPassword = ui->httpProxyPass->text(); - - settings->save(); -} - -int SettingsDialog::percent(qint64 fraction, qint64 total) -{ - if (!total) - return 0; - - return fraction / static_cast(total) * 100; -} diff --git a/src/ui/ui.pri b/src/ui/ui.pri deleted file mode 100644 index e839d71be..000000000 --- a/src/ui/ui.pri +++ /dev/null @@ -1,19 +0,0 @@ -HEADERS += \ - $$files($$PWD/*.h) \ - $$files($$PWD/widgets/*.h) - -SOURCES += \ - $$files($$PWD/*.cpp) \ - $$files($$PWD/widgets/*.cpp) - -FORMS += \ - $$files($$PWD/forms/*.ui) - -unix:!macx { - packagesExist(appindicator-0.1) { - CONFIG += link_pkgconfig - PKGCONFIG += appindicator-0.1 gtk+-2.0 - DEFINES += USE_APPINDICATOR - message("AppIndicator support enabled") - } -} diff --git a/zeal.pro b/zeal.pro index dc7a87b65..2ba5ff08a 100644 --- a/zeal.pro +++ b/zeal.pro @@ -9,9 +9,9 @@ lessThan(QT_VERSION, "5.2.0") { } TEMPLATE = subdirs -CONFIG += ordered SUBDIRS += \ + assets \ src # Ease access to these files from Qt Creator