diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..b04d52bef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,26 @@ +# Autodetect text files +* text=auto + +# Enforce text mode +*.c text +*.cmake text +*.conf text +*.cpp text +*.css text +*.desktop text +*.h text +*.html text +*.in text +*.md text +*.pri text +*.pro text +*.qrc text +*.ui text +*.yml text +CMakeLists.txt text +COPYING + +# Binary files +*.icns binary +*.ico binary +*.png binary diff --git a/.gitignore b/.gitignore index 965f90d42..6057690ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # C++ objects and libs - *.slo *.lo *.o @@ -10,8 +9,7 @@ *.dll *.dylib -# Qt-es - +# qmake /.qmake.cache /.qmake.stash *.pro.user @@ -23,10 +21,12 @@ ui_*.h Makefile* *-build-* -# QtCreator - +# Qt Creator *.autosave -#QtCtreator Qml +# Qt Ctreator QML *.qmlproject.user *.qmlproject.user.* + +# CMake +CMakeLists.txt.user diff --git a/.shippable.yml b/.shippable.yml index 1a1ff3619..b90a8cc35 100644 --- a/.shippable.yml +++ b/.shippable.yml @@ -2,8 +2,13 @@ language: c compiler: - gcc build: + pre_ci_boot: + image_name: drydock/u16cppall + image_tag: tip 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 + - shippable_retry sudo apt-get -y -qq install --no-install-recommends cmake extra-cmake-modules qt5-default libqt5webkit5-dev libqt5x11extras5-dev libarchive-dev libxcb-keysyms1-dev + - mkdir build; cd build + - cmake .. + - cd .. + - cmake --build build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..8d4f649c0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.5.1) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +project(Zeal VERSION 0.4.0) + +# Project information. +if(APPLE) + set(PROJECT_OUTPUT_NAME "Zeal") +else() + set(PROJECT_OUTPUT_NAME "zeal") +endif() + +set(PROJECT_COMPANY_NAME "ZealDocs") +string(TIMESTAMP PROJECT_COPYRIGHT "© 2015-%Y Oleg Shparber" UTC) +set(PROJECT_DESCRIPTION "A simple documentation browser.") +set(PROJECT_URL "https://zealdocs.org") + +add_subdirectory(assets) +add_subdirectory(src) diff --git a/README.md b/README.md index 65b674714..eb89f7051 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Zeal [](https://github.com/zealdocs/zeal/releases) -[](https://kiwiirc.com/client/irc.freenode.net/#zealdocs) +[](https://gitter.im/zealdocs/zeal) [](https://telegram.me/zealdocschat) +[](https://kiwiirc.com/client/irc.freenode.net/#zealdocs) [](https://telegram.me/zealdocs) [](https://twitter.com/zealdocs) @@ -40,10 +41,9 @@ After installing Zeal, you need to download docsets. Go to *Tools->Docsets*, sel ## How to compile ### Requirements -* [Qt](https://www.qt.io/) version 5.2.0 or above. Required modules: Qt WebKit Widgets, Qt SQL plugin for SQLite, Qt X11 Extras (X11 only). +* [Qt](https://www.qt.io/) version 5.5.1 or above. Required modules: Qt WebKit Widgets, Qt X11 Extras (X11 only). * [libarchive](http://libarchive.org/). * X11 only: `xcb-util-keysyms`. -* Ubuntu Unity only: [libappindicator](https://launchpad.net/libappindicator). To compile Zeal run `qmake` and then `make`. Linux users can install Zeal with `make install` command. diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt new file mode 100644 index 000000000..24790bbf6 --- /dev/null +++ b/assets/CMakeLists.txt @@ -0,0 +1,17 @@ +if(UNIX AND NOT APPLE) + find_package(ECM REQUIRED NO_MODULE) + set(CMAKE_MODULE_PATH ${ECM_KDE_MODULE_DIR}) + include(KDEInstallDirs) + + foreach(_i 16 24 32 64 128) + install(FILES "freedesktop/appicons/${_i}/zeal.png" + DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/${_i}x${_i}/apps" + # TODO: Use `RENAME zeal.png` and get rid of subdirectories. + ) + endforeach() + + # TODO: Generate via zeal.desktop.in. + install(FILES "freedesktop/zeal.desktop" + DESTINATION ${KDE_INSTALL_APPDIR} + ) +endif() diff --git a/assets/freedesktop/appicons/128/zeal.png b/assets/freedesktop/appicons/128/zeal.png index 0d391992e..33508e861 100644 Binary files a/assets/freedesktop/appicons/128/zeal.png and b/assets/freedesktop/appicons/128/zeal.png differ diff --git a/assets/freedesktop/appicons/16/zeal.png b/assets/freedesktop/appicons/16/zeal.png index 7b184d25a..c2b18bc6a 100644 Binary files a/assets/freedesktop/appicons/16/zeal.png and b/assets/freedesktop/appicons/16/zeal.png differ diff --git a/assets/freedesktop/appicons/24/zeal.png b/assets/freedesktop/appicons/24/zeal.png index 33a9e8370..387559be5 100644 Binary files a/assets/freedesktop/appicons/24/zeal.png and b/assets/freedesktop/appicons/24/zeal.png differ diff --git a/assets/freedesktop/appicons/32/zeal.png b/assets/freedesktop/appicons/32/zeal.png index be24bcbd6..0d98110ec 100644 Binary files a/assets/freedesktop/appicons/32/zeal.png and b/assets/freedesktop/appicons/32/zeal.png differ diff --git a/assets/freedesktop/appicons/64/zeal.png b/assets/freedesktop/appicons/64/zeal.png index ca2b6bbe1..d4dd86ad7 100644 Binary files a/assets/freedesktop/appicons/64/zeal.png and b/assets/freedesktop/appicons/64/zeal.png differ diff --git a/cmake/FindSQLite.cmake b/cmake/FindSQLite.cmake new file mode 100644 index 000000000..c7a84a944 --- /dev/null +++ b/cmake/FindSQLite.cmake @@ -0,0 +1,38 @@ +# FindSQLite +# ---------- +# +# Finds SQLite headers and libraries. +# +# SQLite_FOUND - True if SQLite is found. +# SQLite_INCLUDE_DIR - Path to SQLite header files. +# SQLite_LIBRARY - SQLite library to link to. +# SQLite_VERSION_STRING - The version of SQLite found. + +# A simple fallback to pkg-config. +if(NOT WIN32) + find_package(PkgConfig QUIET) + pkg_search_module(PC_SQLITE QUIET sqlite3) +endif() + +find_path(SQLite_INCLUDE_DIR NAMES sqlite3.h + PATHS + ${PC_SQLITE_INCLUDEDIR} + ${PC_SQLITE_INCLUDE_DIRS} +) + +find_library(SQLite_LIBRARY NAMES sqlite3 + PATHS + ${PC_SQLITE_LIBDIR} + ${PC_SQLITE_LIBRARY_DIRS} +) + +# TODO: Set version when library is found not via pkg-config. +set(SQLite_VERSION_STRING ${PC_SQLITE_VERSION}) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SQLite + REQUIRED_VARS SQLite_LIBRARY SQLite_INCLUDE_DIR + VERSION_VAR SQLite_VERSION_STRING +) + +mark_as_advanced(SQLite_INCLUDE_DIR SQLite_LIBRARY) diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in new file mode 100644 index 000000000..7cab29158 --- /dev/null +++ b/cmake/MacOSXBundleInfo.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + NSHighResolutionCapable + + + diff --git a/qmake/common.pri b/qmake/common.pri index 60c5e73b2..3e0f4fa67 100644 --- a/qmake/common.pri +++ b/qmake/common.pri @@ -30,29 +30,9 @@ RCC_DIR = $$BUILD_ROOT/.rcc UI_DIR = $$BUILD_ROOT/.ui # Application version -VERSION = 0.3.1 +VERSION = 0.4.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.") @@ -63,10 +43,3 @@ CONFIG(zeal_portable) { unix:!macx { isEmpty(PREFIX): PREFIX = /usr } - -unix:!macx:packagesExist(appindicator-0.1) { - CONFIG += link_pkgconfig - PKGCONFIG += appindicator-0.1 gtk+-2.0 - DEFINES += USE_APPINDICATOR - message("AppIndicator support: Yes.") -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..59be4541d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,31 @@ +# Only C++11 +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find includes in corresponding build directories. +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +## Build options +option(ZEAL_PORTABLE_BUILD "Build portable version") +if(ZEAL_PORTABLE_BUILD) + add_definitions(-DPORTABLE_BUILD) +endif() + +## Macro +add_definitions(-DZEAL_VERSION="${Zeal_VERSION}") + +# QString options +add_definitions(-DQT_USE_QSTRINGBUILDER) +add_definitions(-DQT_RESTRICTED_CAST_FROM_ASCII) +add_definitions(-DQT_NO_CAST_TO_ASCII) +add_definitions(-DQT_NO_URL_CAST_FROM_STRING) + +## Handle moc, uic, and rcc files. +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +include_directories(libs) +add_subdirectory(libs) + +add_subdirectory(app) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt new file mode 100644 index 000000000..6ab0162d8 --- /dev/null +++ b/src/app/CMakeLists.txt @@ -0,0 +1,45 @@ +# TODO: Move zeal.qrc one level up to rely on CMAKE_AUTORCC. +find_package(Qt5Core REQUIRED) +qt5_add_resources(App_RESOURCES resources/zeal.qrc) + +if(APPLE) + list(APPEND App_RESOURCES resources/zeal.icns) +elseif(WIN32) + configure_file(versioninfo.rc.in ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc) + list(APPEND App_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc) +endif() + +add_executable(App WIN32 + main.cpp + ${App_RESOURCES} +) + +target_link_libraries(App Core Util) + +find_package(Qt5 COMPONENTS Concurrent Widgets REQUIRED) +target_link_libraries(App Qt5::Concurrent Qt5::Widgets) + +set_target_properties(App PROPERTIES + OUTPUT_NAME ${PROJECT_OUTPUT_NAME} + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +if(APPLE) + set_target_properties(App PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME} + MACOSX_BUNDLE_GUI_IDENTIFIER "org.zealdocs.zeal" + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" + MACOSX_BUNDLE_ICON_FILE "zeal.icns" + MACOSX_BUNDLE_COPYRIGHT ${PROJECT_COPYRIGHT} + RESOURCE "resources/zeal.icns" + ) +elseif(UNIX) + find_package(ECM REQUIRED NO_MODULE) + set(CMAKE_MODULE_PATH ${ECM_KDE_MODULE_DIR}) + include(KDEInstallDirs) + + install(TARGETS App DESTINATION ${KDE_INSTALL_BINDIR}) +endif() diff --git a/src/app/app.pro b/src/app/app.pro index 7dfd39a05..6b0c5ef23 100644 --- a/src/app/app.pro +++ b/src/app/app.pro @@ -2,7 +2,7 @@ include($$ZEAL_COMMON_PRI) TEMPLATE = app -QT += gui widgets sql concurrent +QT += gui widgets concurrent SOURCES += \ main.cpp @@ -35,8 +35,11 @@ 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 + exists($$lib_dir/$$basename(lib_dir).pri) { + include($$lib_dir/$$basename(lib_dir).pri) + msvc:PRE_TARGETDEPS += $$BUILD_ROOT/.lib/$${ZEAL_LIB_NAME}.$${QMAKE_EXTENSION_STATICLIB} + else:PRE_TARGETDEPS += $$BUILD_ROOT/.lib/lib$${ZEAL_LIB_NAME}.$${QMAKE_EXTENSION_STATICLIB} + # LIBS += -l$$ZEAL_LIB_NAME + } } } diff --git a/src/app/main.cpp b/src/app/main.cpp index 0ca13fd54..d06073e85 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -31,8 +31,8 @@ #include #include #include -#include #include +#include #include #ifdef Q_OS_WIN32 @@ -75,9 +75,8 @@ CommandLineParameters parseCommandLine(const QStringList &arguments) parser.addHelpOption(); parser.addVersionOption(); - // 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."))); + parser.addOption({{QStringLiteral("f"), QStringLiteral("force")}, + QObject::tr("Force the application run.")}); #ifdef Q_OS_WIN32 parser.addOption(QCommandLineOption({QStringLiteral("register")}, @@ -105,6 +104,7 @@ CommandLineParameters parseCommandLine(const QStringList &arguments) // 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:"))) { @@ -179,6 +179,11 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationDomain(QStringLiteral("zealdocs.org")); QCoreApplication::setOrganizationName(QStringLiteral("Zeal")); +#if QT_VERSION >= 0x050600 + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + QScopedPointer qapp(new QApplication(argc, argv)); const CommandLineParameters clParams = parseCommandLine(qapp->arguments()); @@ -216,16 +221,14 @@ int main(int argc, char *argv[]) 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); + msgBox->setDefaultButton(msgBox->addButton(QMessageBox::Retry)); + msgBox->addButton(QObject::tr("&Quit"), QMessageBox::DestructiveRole); switch (msgBox->exec()) { - case QMessageBox::Rejected: - return EXIT_SUCCESS; - case QMessageBox::Help: - QDesktopServices::openUrl(QUrl(contactUrl)); + case QMessageBox::Rejected: + return EXIT_SUCCESS; + case QMessageBox::Help: + QDesktopServices::openUrl(QUrl(contactUrl)); } msgBox->removeButton(msgBox->button(QMessageBox::Retry)); @@ -243,30 +246,17 @@ int main(int argc, char *argv[]) } } - // Check for SQLite plugin - // TODO: Specific to docset format and should be handled accordingly in the future - if (!QSqlDatabase::isDriverAvailable(QStringLiteral("QSQLITE"))) { - 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 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); + if (!clParams.query.isEmpty()) { + QTimer::singleShot(0, [clParams] { + Core::LocalServer::sendQuery(clParams.query, clParams.preventActivation); + }); + } return qapp->exec(); } diff --git a/src/app/resources/browser/assets/css/bulma.min.css b/src/app/resources/browser/assets/css/bulma.min.css new file mode 100644 index 000000000..fa47f1a31 --- /dev/null +++ b/src/app/resources/browser/assets/css/bulma.min.css @@ -0,0 +1 @@ +@charset "UTF-8";/*!bulma.io v0.5.0 | MIT License | github.com/jgthms/bulma*/@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}/*!minireset.css v0.0.2 | MIT License | github.com/jgthms/minireset.css*/html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*{box-sizing:inherit}*:before,*:after{box-sizing:inherit}img,embed,object,audio,video{max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0;text-align:left}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,select,textarea{font-family:BlinkMacSystemFont,-apple-system,segoe ui,roboto,oxygen,ubuntu,cantarell,fira sans,droid sans,helvetica neue,helvetica,arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1rem;font-weight:400;line-height:1.5}a{color:#469;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#ff3860;font-size:.875em;font-weight:400;padding:.25em .5em}hr{background-color:#dbdbdb;border:0;display:block;height:1px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}pre{background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;white-space:pre;word-wrap:normal}pre code{-webkit-overflow-scrolling:touch;background:0 0;color:inherit;display:block;font-size:1em;overflow-x:auto;padding:1.25rem 1.5rem}table td,table th{text-align:left;vertical-align:top}table th{color:#363636}.is-clearfix:after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-clipped{overflow:hidden!important}.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.is-size-1{font-size:3rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}}@media screen and (max-width:1007px){.is-size-1-touch{font-size:3rem!important}}@media screen and (min-width:1008px){.is-size-1-desktop{font-size:3rem!important}}@media screen and (min-width:1200px){.is-size-1-widescreen{font-size:3rem!important}}@media screen and (min-width:1392px){.is-size-1-fullhd{font-size:3rem!important}}.is-size-2{font-size:2.5rem!important}@media screen and (max-width:768px){.is-size-2-mobile{font-size:2.5rem!important}}@media screen and (min-width:769px),print{.is-size-2-tablet{font-size:2.5rem!important}}@media screen and (max-width:1007px){.is-size-2-touch{font-size:2.5rem!important}}@media screen and (min-width:1008px){.is-size-2-desktop{font-size:2.5rem!important}}@media screen and (min-width:1200px){.is-size-2-widescreen{font-size:2.5rem!important}}@media screen and (min-width:1392px){.is-size-2-fullhd{font-size:2.5rem!important}}.is-size-3{font-size:2rem!important}@media screen and (max-width:768px){.is-size-3-mobile{font-size:2rem!important}}@media screen and (min-width:769px),print{.is-size-3-tablet{font-size:2rem!important}}@media screen and (max-width:1007px){.is-size-3-touch{font-size:2rem!important}}@media screen and (min-width:1008px){.is-size-3-desktop{font-size:2rem!important}}@media screen and (min-width:1200px){.is-size-3-widescreen{font-size:2rem!important}}@media screen and (min-width:1392px){.is-size-3-fullhd{font-size:2rem!important}}.is-size-4{font-size:1.5rem!important}@media screen and (max-width:768px){.is-size-4-mobile{font-size:1.5rem!important}}@media screen and (min-width:769px),print{.is-size-4-tablet{font-size:1.5rem!important}}@media screen and (max-width:1007px){.is-size-4-touch{font-size:1.5rem!important}}@media screen and (min-width:1008px){.is-size-4-desktop{font-size:1.5rem!important}}@media screen and (min-width:1200px){.is-size-4-widescreen{font-size:1.5rem!important}}@media screen and (min-width:1392px){.is-size-4-fullhd{font-size:1.5rem!important}}.is-size-5{font-size:1.25rem!important}@media screen and (max-width:768px){.is-size-5-mobile{font-size:1.25rem!important}}@media screen and (min-width:769px),print{.is-size-5-tablet{font-size:1.25rem!important}}@media screen and (max-width:1007px){.is-size-5-touch{font-size:1.25rem!important}}@media screen and (min-width:1008px){.is-size-5-desktop{font-size:1.25rem!important}}@media screen and (min-width:1200px){.is-size-5-widescreen{font-size:1.25rem!important}}@media screen and (min-width:1392px){.is-size-5-fullhd{font-size:1.25rem!important}}.is-size-6{font-size:1rem!important}@media screen and (max-width:768px){.is-size-6-mobile{font-size:1rem!important}}@media screen and (min-width:769px),print{.is-size-6-tablet{font-size:1rem!important}}@media screen and (max-width:1007px){.is-size-6-touch{font-size:1rem!important}}@media screen and (min-width:1008px){.is-size-6-desktop{font-size:1rem!important}}@media screen and (min-width:1200px){.is-size-6-widescreen{font-size:1rem!important}}@media screen and (min-width:1392px){.is-size-6-fullhd{font-size:1rem!important}}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1007px){.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1008px){.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1200px){.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1392px){.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1007px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1007px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1008px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1008px) and (max-width:1199px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1200px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1200px) and (max-width:1391px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1392px){.has-text-centered-fullhd{text-align:center!important}}.has-text-left{text-align:left!important}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1007px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1007px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1008px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1008px) and (max-width:1199px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1200px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1200px) and (max-width:1391px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1392px){.has-text-left-fullhd{text-align:left!important}}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1007px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1007px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1008px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1008px) and (max-width:1199px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1200px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1200px) and (max-width:1391px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1392px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.has-text-white{color:#fff!important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6}.has-text-black{color:#0a0a0a!important}a.has-text-black:hover,a.has-text-black:focus{color:#000}.has-text-light{color:#f5f5f5!important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb}.has-text-dark{color:#363636!important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c}.has-text-primary{color:#469!important}a.has-text-primary:hover,a.has-text-primary:focus{color:#344e76}.has-text-info{color:#469!important}a.has-text-info:hover,a.has-text-info:focus{color:#344e76}.has-text-success{color:#23d160!important}a.has-text-success:hover,a.has-text-success:focus{color:#1ca64c}.has-text-warning{color:#ffdd57!important}a.has-text-warning:hover,a.has-text-warning:focus{color:#ffd324}.has-text-danger{color:#ff3860!important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ff0537}.has-text-black-bis{color:#121212!important}.has-text-black-ter{color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.is-block{display:block}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1007px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1007px){.is-block-touch{display:block!important}}@media screen and (min-width:1008px){.is-block-desktop{display:block!important}}@media screen and (min-width:1008px) and (max-width:1199px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1200px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1200px) and (max-width:1391px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1392px){.is-block-fullhd{display:block!important}}.is-flex{display:flex}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1007px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1007px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1008px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1008px) and (max-width:1199px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1200px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1200px) and (max-width:1391px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1392px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1007px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1007px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1008px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1008px) and (max-width:1199px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1200px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1200px) and (max-width:1391px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1392px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1007px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1007px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1008px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1008px) and (max-width:1199px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1200px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1200px) and (max-width:1391px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1392px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1007px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1007px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1008px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1008px) and (max-width:1199px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1200px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1200px) and (max-width:1391px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1392px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1007px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1007px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1008px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1008px) and (max-width:1199px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1200px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1200px) and (max-width:1391px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1392px){.is-hidden-fullhd{display:none!important}}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.box{background-color:#fff;border-radius:5px;box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);color:#4a4a4a;display:block;padding:1.25rem}.box:not(:last-child){margin-bottom:1.5rem}a.box:hover,a.box:focus{box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px #469}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #469}.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:3px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.375em - 1px);padding-left:calc(.625em - 1px);padding-right:calc(.625em - 1px);padding-top:calc(.375em - 1px);position:relative;vertical-align:top;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#fff;border-color:#dbdbdb;color:#363636;cursor:pointer;justify-content:center;padding-left:.75em;padding-right:.75em;text-align:center;white-space:nowrap}.button:focus,.button.is-focused,.button:active,.button.is-active{outline:0}.button[disabled]{cursor:not-allowed}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.375em - 1px);margin-right:.1875em}.button .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:calc(-.375em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.375em - 1px);margin-right:calc(-.375em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#469;color:#363636}.button:active,.button.is-active{border-color:#4a4a4a;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#363636}.button.is-link{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-link:hover,.button.is-link.is-hovered,.button.is-link:focus,.button.is-link.is-focused,.button.is-link:active,.button.is-link.is-active{background-color:#f5f5f5;color:#363636}.button.is-link[disabled]{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#0a0a0a}.button.is-white[disabled]{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled]{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading:after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined:focus{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading:after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined[disabled]{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined:focus{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#fff}.button.is-black[disabled]{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled]{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading:after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined:focus{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading:after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined[disabled]{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined:focus{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:#363636}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:#363636}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:#363636}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#363636}.button.is-light[disabled]{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:#363636;color:#f5f5f5}.button.is-light.is-inverted:hover{background-color:#292929}.button.is-light.is-inverted[disabled]{background-color:#363636;border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading:after{border-color:transparent transparent #363636 #363636!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined:focus{background-color:#f5f5f5;border-color:#f5f5f5;color:#363636}.button.is-light.is-outlined.is-loading:after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined[disabled]{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined:focus{background-color:#363636;color:#f5f5f5}.button.is-light.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark{background-color:#363636;border-color:transparent;color:#f5f5f5}.button.is-dark:hover,.button.is-dark.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#f5f5f5}.button.is-dark:focus,.button.is-dark.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#f5f5f5}.button.is-dark:active,.button.is-dark.is-active{background-color:#292929;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#f5f5f5}.button.is-dark[disabled]{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#f5f5f5;color:#363636}.button.is-dark.is-inverted:hover{background-color:#e8e8e8}.button.is-dark.is-inverted[disabled]{background-color:#f5f5f5;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading:after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.button.is-dark.is-outlined:focus{background-color:#363636;border-color:#363636;color:#f5f5f5}.button.is-dark.is-outlined.is-loading:after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined[disabled]{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-dark.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined:focus{background-color:#f5f5f5;color:#363636}.button.is-dark.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-primary{background-color:#469;border-color:transparent;color:#fff}.button.is-primary:hover,.button.is-primary.is-hovered{background-color:#406090;border-color:transparent;color:#fff}.button.is-primary:focus,.button.is-primary.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(68,102,153,.25);color:#fff}.button.is-primary:active,.button.is-primary.is-active{background-color:#3c5a87;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#fff}.button.is-primary[disabled]{background-color:#469;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#469}.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled]{background-color:#fff;border-color:transparent;box-shadow:none;color:#469}.button.is-primary.is-loading:after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#469;color:#469}.button.is-primary.is-outlined:hover,.button.is-primary.is-outlined:focus{background-color:#469;border-color:#469;color:#fff}.button.is-primary.is-outlined.is-loading:after{border-color:transparent transparent #469 #469!important}.button.is-primary.is-outlined[disabled]{background-color:transparent;border-color:#469;box-shadow:none;color:#469}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.button.is-primary.is-inverted.is-outlined:focus{background-color:#fff;color:#469}.button.is-primary.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info{background-color:#469;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#406090;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(68,102,153,.25);color:#fff}.button.is-info:active,.button.is-info.is-active{background-color:#3c5a87;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#fff}.button.is-info[disabled]{background-color:#469;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#469}.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled]{background-color:#fff;border-color:transparent;box-shadow:none;color:#469}.button.is-info.is-loading:after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#469;color:#469}.button.is-info.is-outlined:hover,.button.is-info.is-outlined:focus{background-color:#469;border-color:#469;color:#fff}.button.is-info.is-outlined.is-loading:after{border-color:transparent transparent #469 #469!important}.button.is-info.is-outlined[disabled]{background-color:transparent;border-color:#469;box-shadow:none;color:#469}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined:focus{background-color:#fff;color:#469}.button.is-info.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success{background-color:#23d160;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#22c65b;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(35,209,96,.25);color:#fff}.button.is-success:active,.button.is-success.is-active{background-color:#20bc56;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#fff}.button.is-success[disabled]{background-color:#23d160;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#23d160}.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled]{background-color:#fff;border-color:transparent;box-shadow:none;color:#23d160}.button.is-success.is-loading:after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#23d160;color:#23d160}.button.is-success.is-outlined:hover,.button.is-success.is-outlined:focus{background-color:#23d160;border-color:#23d160;color:#fff}.button.is-success.is-outlined.is-loading:after{border-color:transparent transparent #23d160 #23d160!important}.button.is-success.is-outlined[disabled]{background-color:transparent;border-color:#23d160;box-shadow:none;color:#23d160}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined:focus{background-color:#fff;color:#23d160}.button.is-success.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(255,221,87,.25);color:rgba(0,0,0,.7)}.button.is-warning:active,.button.is-warning.is-active{background-color:#ffd83d;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:rgba(0,0,0,.7)}.button.is-warning[disabled]{background-color:#ffdd57;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled]{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading:after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined:focus{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading:after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-outlined[disabled]{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined:focus{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-danger{background-color:#ff3860;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#ff2b56;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;box-shadow:0 0 .5em rgba(255,56,96,.25);color:#fff}.button.is-danger:active,.button.is-danger.is-active{background-color:#ff1f4b;border-color:transparent;box-shadow:inset 0 1px 2px rgba(10,10,10,.2);color:#fff}.button.is-danger[disabled]{background-color:#ff3860;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#ff3860}.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled]{background-color:#fff;border-color:transparent;box-shadow:none;color:#ff3860}.button.is-danger.is-loading:after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#ff3860;color:#ff3860}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined:focus{background-color:#ff3860;border-color:#ff3860;color:#fff}.button.is-danger.is-outlined.is-loading:after{border-color:transparent transparent #ff3860 #ff3860!important}.button.is-danger.is-outlined[disabled]{background-color:transparent;border-color:#ff3860;box-shadow:none;color:#ff3860}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined:focus{background-color:#fff;color:#ff3860}.button.is-danger.is-inverted.is-outlined[disabled]{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-small{border-radius:2px;font-size:.75rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled]{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading:after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em;position:absolute;left:calc(50% - (1em/2));top:calc(50% - (1em/2));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.container{margin:0 auto;position:relative}@media screen and (min-width:1068px){.container{max-width:960px;width:960px}.container.is-fluid{margin-left:24px;margin-right:24px;max-width:none;width:auto}}@media screen and (max-width:1259px){.container.is-widescreen{max-width:1152px;width:auto}}@media screen and (max-width:1451px){.container.is-fullhd{max-width:1344px;width:auto}}@media screen and (min-width:1260px){.container{max-width:1152px;width:1152px}}@media screen and (min-width:1452px){.container{max-width:1344px;width:1344px}}.content:not(:last-child){margin-bottom:1.5rem}.content li+li{margin-top:.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:400;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style:decimal outside;margin-left:2em;margin-top:1em}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin:2em;text-align:center}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636;text-align:left}.content table tr:hover{background-color:#f5f5f5}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content.is-small{font-size:.75rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.input,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:3px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.375em - 1px);padding-left:calc(.625em - 1px);padding-right:calc(.625em - 1px);padding-top:calc(.375em - 1px);position:relative;vertical-align:top;background-color:#fff;border-color:#dbdbdb;color:#363636;box-shadow:inset 0 1px 2px rgba(10,10,10,.1);max-width:100%;width:100%}.input:focus,.input.is-focused,.input:active,.input.is-active,.textarea:focus,.textarea.is-focused,.textarea:active,.textarea.is-active{outline:0}.input[disabled],.textarea[disabled]{cursor:not-allowed}.input:hover,.input.is-hovered,.textarea:hover,.textarea.is-hovered{border-color:#b5b5b5}.input:focus,.input.is-focused,.input:active,.input.is-active,.textarea:focus,.textarea.is-focused,.textarea:active,.textarea.is-active{border-color:#469}.input[disabled],.textarea[disabled]{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input[type=search],.textarea[type=search]{border-radius:290486px}.input.is-white,.textarea.is-white{border-color:#fff}.input.is-black,.textarea.is-black{border-color:#0a0a0a}.input.is-light,.textarea.is-light{border-color:#f5f5f5}.input.is-dark,.textarea.is-dark{border-color:#363636}.input.is-primary,.textarea.is-primary{border-color:#469}.input.is-info,.textarea.is-info{border-color:#469}.input.is-success,.textarea.is-success{border-color:#23d160}.input.is-warning,.textarea.is-warning{border-color:#ffdd57}.input.is-danger,.textarea.is-danger{border-color:#ff3860}.input.is-small,.textarea.is-small{border-radius:2px;font-size:.75rem}.input.is-medium,.textarea.is-medium{font-size:1.25rem}.input.is-large,.textarea.is-large{font-size:1.5rem}.input.is-fullwidth,.textarea.is-fullwidth{display:block;width:100%}.input.is-inline,.textarea.is-inline{display:inline;width:auto}.textarea{display:block;max-width:100%;min-width:100%;padding:.625em;resize:vertical}.textarea:not([rows]){max-height:600px;min-height:120px}.textarea[rows]{height:unset}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox[disabled],.radio[disabled]{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.25em}.select:not(.is-multiple)::after{border:1px solid #469;border-right:0;border-top:0;content:" ";display:block;height:.5em;pointer-events:none;position:absolute;transform:rotate(-45deg);width:.5em;margin-top:-.375em;right:1.125em;top:50%;z-index:4}.select select{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:3px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.375em - 1px);padding-left:calc(.625em - 1px);padding-right:calc(.625em - 1px);padding-top:calc(.375em - 1px);position:relative;vertical-align:top;background-color:#fff;border-color:#dbdbdb;color:#363636;cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select:focus,.select select.is-focused,.select select:active,.select select.is-active{outline:0}.select select[disabled]{cursor:not-allowed}.select select:hover,.select select.is-hovered{border-color:#b5b5b5}.select select:focus,.select select.is-focused,.select select:active,.select select.is-active{border-color:#469}.select select[disabled]{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.select select[disabled]::-moz-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]:-moz-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]:-ms-input-placeholder{color:rgba(122,122,122,.3)}.select select:hover{border-color:#b5b5b5}.select select:focus,.select select.is-focused,.select select:active,.select select.is-active{border-color:#469}.select select::-ms-expand{display:none}.select select[disabled]:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:unset;padding:0}.select select[multiple] option{padding:.5em 1em}.select:hover::after{border-color:#363636}.select.is-white select{border-color:#fff}.select.is-black select{border-color:#0a0a0a}.select.is-light select{border-color:#f5f5f5}.select.is-dark select{border-color:#363636}.select.is-primary select{border-color:#469}.select.is-info select{border-color:#469}.select.is-success select{border-color:#23d160}.select.is-warning select{border-color:#ffdd57}.select.is-danger select{border-color:#ff3860}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em;margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:#363636}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:#363636}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:#363636}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:#363636}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#f5f5f5}.file.is-dark:hover .file-cta,.file.is-dark.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#f5f5f5}.file.is-dark:focus .file-cta,.file.is-dark.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#f5f5f5}.file.is-dark:active .file-cta,.file.is-dark.is-active .file-cta{background-color:#292929;border-color:transparent;color:#f5f5f5}.file.is-primary .file-cta{background-color:#469;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.file.is-primary.is-hovered .file-cta{background-color:#406090;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.file.is-primary.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(68,102,153,.25);color:#fff}.file.is-primary:active .file-cta,.file.is-primary.is-active .file-cta{background-color:#3c5a87;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#469;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#406090;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(68,102,153,.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#3c5a87;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#23d160;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#22c65b;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(35,209,96,.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#20bc56;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,221,87,.25);color:rgba(0,0,0,.7)}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#ff3860;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#ff2b56;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,56,96,.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#ff1f4b;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.is-centered{justify-content:center}.file.is-centered .file-label{flex-direction:column}.file.is-centered .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-centered .file-name{border-width:0 1px 1px}.file.is-centered .file-icon{height:1.5em;width:1.5em}.file.is-centered .file-icon .fa{font-size:21px}.file.is-centered.is-small .file-icon .fa{font-size:14px}.file.is-centered.is-medium .file-icon .fa{font-size:28px}.file.is-centered.is-large .file-icon .fa{font-size:35px}.file.is-centered.has-name .file-cta{border-radius:3px 3px 0 0}.file.is-centered.has-name .file-name{border-radius:0 0 3px 3px;border-width:0 1px 1px}.file.is-right .file-cta{border-radius:0 3px 3px 0}.file.is-right .file-name{border-radius:3px 0 0 3px;border-width:1px 0 1px 1px;order:-1}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#eee}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#e8e8e8}.file-input{height:.01em;left:0;outline:0;position:absolute;top:0;width:.01em}.file-cta,.file-name{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:3px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.375em - 1px);padding-left:calc(.625em - 1px);padding-right:calc(.625em - 1px);padding-top:calc(.375em - 1px);position:relative;vertical-align:top;border-color:transparent;border-radius:3px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta:focus,.file-cta.is-focused,.file-cta:active,.file-cta.is-active,.file-name:focus,.file-name.is-focused,.file-name:active,.file-name.is-active{outline:0}.file-cta[disabled],.file-name[disabled]{cursor:not-allowed}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#f5f5f5;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:left;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#469}.help.is-info{color:#469}.help.is-success{color:#23d160}.help.is-warning{color:#ffdd57}.help.is-danger{color:#ff3860}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:first-child .button,.field.has-addons .control:first-child .input,.field.has-addons .control:first-child .select select{border-bottom-left-radius:3px;border-top-left-radius:3px}.field.has-addons .control:last-child .button,.field.has-addons .control:last-child .input,.field.has-addons .control:last-child .select select{border-bottom-right-radius:3px;border-top-right-radius:3px}.field.has-addons .control .button,.field.has-addons .control .input,.field.has-addons .control .select select{border-radius:0}.field.has-addons .control .button:hover,.field.has-addons .control .button.is-hovered,.field.has-addons .control .input:hover,.field.has-addons .control .input.is-hovered,.field.has-addons .control .select select:hover,.field.has-addons .control .select select.is-hovered{z-index:2}.field.has-addons .control .button:focus,.field.has-addons .control .button.is-focused,.field.has-addons .control .button:active,.field.has-addons .control .button.is-active,.field.has-addons .control .input:focus,.field.has-addons .control .input.is-focused,.field.has-addons .control .input:active,.field.has-addons .control .input.is-active,.field.has-addons .control .select select:focus,.field.has-addons .control .select select.is-focused,.field.has-addons .control .select select:active,.field.has-addons .control .select select.is-active{z-index:3}.field.has-addons .control .button:focus:hover,.field.has-addons .control .button.is-focused:hover,.field.has-addons .control .button:active:hover,.field.has-addons .control .button.is-active:hover,.field.has-addons .control .input:focus:hover,.field.has-addons .control .input.is-focused:hover,.field.has-addons .control .input:active:hover,.field.has-addons .control .input.is-active:hover,.field.has-addons .control .select select:focus:hover,.field.has-addons .control .select select.is-focused:hover,.field.has-addons .control .select select:active:hover,.field.has-addons .control .select select.is-active:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{font-size:1rem;position:relative;text-align:left}.control.has-icon .icon{color:#dbdbdb;height:2.25em;pointer-events:none;position:absolute;top:0;width:2.25em;z-index:4}.control.has-icon .input:focus+.icon{color:#7a7a7a}.control.has-icon .input.is-small+.icon{font-size:.75rem}.control.has-icon .input.is-medium+.icon{font-size:1.25rem}.control.has-icon .input.is-large+.icon{font-size:1.5rem}.control.has-icon:not(.has-icon-right) .icon{left:0}.control.has-icon:not(.has-icon-right) .input{padding-left:2.25em}.control.has-icon.has-icon-right .icon{right:0}.control.has-icon.has-icon-right .input{padding-right:2.25em}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#7a7a7a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.25em;pointer-events:none;position:absolute;top:0;width:2.25em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.25em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.25em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em;position:absolute!important;right:.625em;top:.625em}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon .fa{font-size:21px}.icon.is-small{height:1rem;width:1rem}.icon.is-small .fa{font-size:14px}.icon.is-medium{height:2rem;width:2rem}.icon.is-medium .fa{font-size:28px}.icon.is-large{height:3rem;width:3rem}.icon.is-large .fa{font-size:42px}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image.is-square img,.image.is-1by1 img,.image.is-4by3 img,.image.is-3by2 img,.image.is-16by9 img,.image.is-2by1 img{bottom:0;left:0;position:absolute;right:0;top:0;height:100%;width:100%}.image.is-square,.image.is-1by1{padding-top:100%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:3px;padding:1.25rem 2.5rem 1.25rem 1.5rem;position:relative}.notification:not(:last-child){margin-bottom:1.5rem}.notification a:not(.button){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{position:absolute;right:.5em;top:.5em}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:#363636}.notification.is-dark{background-color:#363636;color:#f5f5f5}.notification.is-primary{background-color:#469;color:#fff}.notification.is-info{background-color:#469;color:#fff}.notification.is-success{background-color:#23d160;color:#fff}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.notification.is-danger{background-color:#ff3860;color:#fff}.progress{-moz-appearance:none;-webkit-appearance:none;border:0;border-radius:290486px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress:not(:last-child){margin-bottom:1.5rem}.progress::-webkit-progress-bar{background-color:#dbdbdb}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-primary::-webkit-progress-value{background-color:#469}.progress.is-primary::-moz-progress-bar{background-color:#469}.progress.is-info::-webkit-progress-value{background-color:#469}.progress.is-info::-moz-progress-bar{background-color:#469}.progress.is-success::-webkit-progress-value{background-color:#23d160}.progress.is-success::-moz-progress-bar{background-color:#23d160}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-danger::-webkit-progress-value{background-color:#ff3860}.progress.is-danger::-moz-progress-bar{background-color:#ff3860}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}.table{background-color:#fff;color:#363636;margin-bottom:1.5rem}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:#363636}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#f5f5f5}.table td.is-primary,.table th.is-primary{background-color:#469;border-color:#469;color:#fff}.table td.is-info,.table th.is-info{background-color:#469;border-color:#469;color:#fff}.table td.is-success,.table th.is-success{background-color:#23d160;border-color:#23d160;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#ff3860;border-color:#ff3860;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table th{color:#363636;text-align:left}.table tr:hover{background-color:#fafafa}.table tr.is-selected{background-color:#469;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table.is-striped tbody tr:not(.is-selected):nth-child(even):hover{background-color:#f5f5f5}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.tags.has-addons .tag:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.tag{align-items:center;background-color:#f5f5f5;border-radius:3px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag .delete{margin-left:.25em;margin-right:-.375em}.tag.is-white{background-color:#fff;color:#0a0a0a}.tag.is-black{background-color:#0a0a0a;color:#fff}.tag.is-light{background-color:#f5f5f5;color:#363636}.tag.is-dark{background-color:#363636;color:#f5f5f5}.tag.is-primary{background-color:#469;color:#fff}.tag.is-info{background-color:#469;color:#fff}.tag.is-success{background-color:#23d160;color:#fff}.tag.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.tag.is-danger{background-color:#ff3860;color:#fff}.tag.is-medium{font-size:1rem}.tag.is-large{font-size:1.25rem}.tag.is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag.is-delete:before,.tag.is-delete:after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag.is-delete:before{height:1px;width:50%}.tag.is-delete:after{height:50%;width:1px}.tag.is-delete:hover,.tag.is-delete:focus{background-color:#e8e8e8}.tag.is-delete:active{background-color:#dbdbdb}.tag.is-rounded{border-radius:290486px}a.tag:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title:not(:last-child),.subtitle:not(:last-child){margin-bottom:1.5rem}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title .tag,.subtitle .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title+.highlight{margin-top:-.75rem}.title:not(.is-spaced)+.subtitle{margin-top:-1.5rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.5rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.block:not(:last-child){margin-bottom:1.5rem}.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:0;border-radius:290486px;cursor:pointer;display:inline-block;flex-grow:0;flex-shrink:0;font-size:1rem;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete:before,.delete:after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete:before{height:2px;width:50%}.delete:after{height:50%;width:2px}.delete:hover,.delete:focus{background-color:rgba(10,10,10,.3)}.delete:active{background-color:rgba(10,10,10,.4)}.delete.is-small{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.delete.is-medium{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.delete.is-large{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.fa{font-size:21px;text-align:center;vertical-align:top}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-weight:400;max-width:100%;overflow:hidden;padding:0}.highlight:not(:last-child){margin-bottom:1.5rem}.highlight pre{overflow:auto;max-width:100%}.loader{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.number{align-items:center;background-color:#f5f5f5;border-radius:290486px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.breadcrumb{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;align-items:stretch;display:flex;font-size:1rem;overflow:hidden;overflow-x:auto;white-space:nowrap}.breadcrumb:not(:last-child){margin-bottom:1.5rem}.breadcrumb a{align-items:center;color:#7a7a7a;display:flex;justify-content:center;padding:.5em .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#4a4a4a;content:"/"}.breadcrumb ul,.breadcrumb ol{align-items:center;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"→"}.breadcrumb.has-bullet-separator li+li::before{content:"•"}.breadcrumb.has-dot-separator li+li::before{content:"·"}.breadcrumb.has-succeeds-separator li+li::before{content:"≻"}.card{background-color:#fff;box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);color:#4a4a4a;max-width:100%;position:relative}.card-header{align-items:stretch;box-shadow:0 1px 2px rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem}.card-header-icon{align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem}.card-image{display:block;position:relative}.card-content{padding:1.5rem}.card-footer{border-top:1px solid #dbdbdb;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #dbdbdb}.card .media:not(:last-child){margin-bottom:.75rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:3px;box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item{padding-right:3rem;white-space:nowrap}a.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active{background-color:#469;color:#fff}.dropdown-divider{background-color:#dbdbdb;border:0;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level:not(:last-child){margin-bottom:1.5rem}.level code{border-radius:3px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:left}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:left}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#469;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:3px;font-size:1rem}.message:not(:last-child){margin-bottom:1.5rem}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff;color:#4d4d4d}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a;color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:#363636}.message.is-light .message-body{border-color:#f5f5f5;color:#4f4f4f}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#f5f5f5}.message.is-dark .message-body{border-color:#363636;color:#2a2a2a}.message.is-primary{background-color:#f8fafc}.message.is-primary .message-header{background-color:#469;color:#fff}.message.is-primary .message-body{border-color:#469;color:#344968}.message.is-info{background-color:#f8fafc}.message.is-info .message-header{background-color:#469;color:#fff}.message.is-info .message-body{border-color:#469;color:#344968}.message.is-success{background-color:#f6fef9}.message.is-success .message-header{background-color:#23d160;color:#fff}.message.is-success .message-body{border-color:#23d160;color:#0e311a}.message.is-warning{background-color:#fffdf5}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#3c3108}.message.is-danger{background-color:#fff5f7}.message.is-danger .message-header{background-color:#ff3860;color:#fff}.message.is-danger .message-body{border-color:#ff3860;color:#cd0930}.message-header{align-items:center;background-color:#4a4a4a;border-radius:3px 3px 0 0;color:#fff;display:flex;justify-content:space-between;line-height:1.25;padding:.5em .75em;position:relative}.message-header a:not(.button),.message-header strong{color:currentColor}.message-header a:not(.button){text-decoration:underline}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-top-left-radius:0;border-top-right-radius:0;border-top:0}.message-body{border:1px solid #dbdbdb;border-radius:3px;color:#4a4a4a;padding:1em 1.25em}.message-body a:not(.button),.message-body strong{color:currentColor}.message-body a:not(.button){text-decoration:underline}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{bottom:0;left:0;position:absolute;right:0;top:0;align-items:center;display:none;justify-content:center;overflow:hidden;position:fixed;z-index:20}.modal.is-active{display:flex}.modal-background{bottom:0;left:0;position:absolute;right:0;top:0;background-color:rgba(10,10,10,.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px),print{.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:0;border-radius:290486px;cursor:pointer;display:inline-block;flex-grow:0;flex-shrink:0;font-size:1rem;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px;background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-close:before,.modal-close:after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close:before{height:2px;width:50%}.modal-close:after{height:50%;width:2px}.modal-close:hover,.modal-close:focus{background-color:rgba(10,10,10,.3)}.modal-close:active{background-color:rgba(10,10,10,.4)}.modal-close.is-small{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.modal-close.is-medium{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.modal-close.is-large{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:5px;border-top-right-radius:5px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:10px}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.nav-toggle{cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem}.nav-toggle span{background-color:#4a4a4a;display:block;height:1px;left:50%;margin-left:-7px;position:absolute;top:50%;transition:none 86ms ease-out;transition-property:background,left,opacity,transform;width:15px}.nav-toggle span:nth-child(1){margin-top:-6px}.nav-toggle span:nth-child(2){margin-top:-1px}.nav-toggle span:nth-child(3){margin-top:4px}.nav-toggle:hover{background-color:#f5f5f5}.nav-toggle.is-active span{background-color:#469}.nav-toggle.is-active span:nth-child(1){margin-left:-5px;transform:rotate(45deg);transform-origin:left top}.nav-toggle.is-active span:nth-child(2){opacity:0}.nav-toggle.is-active span:nth-child(3){margin-left:-5px;transform:rotate(-45deg);transform-origin:left bottom}@media screen and (min-width:769px),print{.nav-toggle{display:none}}.nav-item{align-items:center;display:flex;flex-grow:0;flex-shrink:0;font-size:1rem;justify-content:center;line-height:1.5;padding:.5rem .75rem}.nav-item a{flex-grow:1;flex-shrink:0}.nav-item img{max-height:1.75rem}.nav-item .tag:first-child:not(:last-child){margin-right:.5rem}.nav-item .tag:last-child:not(:first-child){margin-left:.5rem}@media screen and (max-width:768px){.nav-item{justify-content:flex-start}}.nav-item a:not(.button),a.nav-item:not(.button){color:#7a7a7a}.nav-item a:not(.button):hover,a.nav-item:not(.button):hover{color:#363636}.nav-item a:not(.button).is-active,a.nav-item:not(.button).is-active{color:#363636}.nav-item a:not(.button).is-tab,a.nav-item:not(.button).is-tab{border-bottom:1px solid transparent;border-top:1px solid transparent;padding-bottom:calc(.75rem - 1px);padding-left:1rem;padding-right:1rem;padding-top:calc(.75rem - 1px)}.nav-item a:not(.button).is-tab:hover,a.nav-item:not(.button).is-tab:hover{border-bottom-color:#469;border-top-color:transparent}.nav-item a:not(.button).is-tab.is-active,a.nav-item:not(.button).is-tab.is-active{border-bottom:3px solid #469;color:#469;padding-bottom:calc(.75rem - 3px)}@media screen and (min-width:1008px){.nav-item a:not(.button).is-brand,a.nav-item:not(.button).is-brand{padding-left:0}}.nav-left,.nav-right{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;flex-grow:1;flex-shrink:0;max-width:100%;overflow:auto}@media screen and (min-width:1200px){.nav-left,.nav-right{flex-basis:0}}.nav-left{justify-content:flex-start;white-space:nowrap}.nav-right{justify-content:flex-end}.nav-center{align-items:stretch;display:flex;flex-grow:0;flex-shrink:0;justify-content:center;margin-left:auto;margin-right:auto}@media screen and (max-width:768px){.nav-menu.nav-right{background-color:#fff;box-shadow:0 4px 7px rgba(10,10,10,.1);left:0;display:none;right:0;top:100%;position:absolute}.nav-menu.nav-right .nav-item{border-top:1px solid rgba(219,219,219,.5);padding:.75rem}.nav-menu.nav-right.is-active{display:block}}.nav{align-items:stretch;background-color:#fff;display:flex;height:3.25rem;position:relative;text-align:center;z-index:10}.nav>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.nav.has-shadow{box-shadow:0 2px 3px rgba(10,10,10,.1)}.navbar{background-color:#fff;min-height:3.25rem;position:relative}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 3px rgba(10,10,10,.1)}.navbar-brand{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;flex-shrink:0;max-width:100vw;min-height:3.25rem;overflow-x:auto;overflow-y:hidden}.navbar-burger{cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:#4a4a4a;display:block;height:1px;left:50%;margin-left:-7px;position:absolute;top:50%;transition:none 86ms ease-out;transition-property:background,left,opacity,transform;width:15px}.navbar-burger span:nth-child(1){margin-top:-6px}.navbar-burger span:nth-child(2){margin-top:-1px}.navbar-burger span:nth-child(3){margin-top:4px}.navbar-burger:hover{background-color:#f5f5f5}.navbar-burger.is-active span{background-color:#469}.navbar-burger.is-active span:nth-child(1){margin-left:-5px;transform:rotate(45deg);transform-origin:left top}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){margin-left:-5px;transform:rotate(-45deg);transform-origin:left bottom}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem 1rem;position:relative}a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:hover,.navbar-link.is-active{background-color:#f5f5f5;color:#0a0a0a}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#469}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#469;border-bottom-style:solid;border-bottom-width:3px;color:#469;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link{padding-right:2.5em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#dbdbdb;border:0;display:none;height:1px;margin:.5rem 0}@media screen and (max-width:1007px){.navbar>.container{display:block}.navbar-brand .navbar-item{align-items:center;display:flex}.navbar-menu{box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}}@media screen and (min-width:1008px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent}.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#469}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-link::after{border:1px solid #469;border-right:0;border-top:0;content:" ";display:block;height:.5em;pointer-events:none;position:absolute;transform:rotate(-45deg);width:.5em;margin-top:-.375em;right:1.125em;top:50%}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:1px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#469}.navbar-dropdown.is-boxed{border-radius:5px;border-top:0;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar{margin-left:-1rem;margin-right:-1rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:hover),.navbar-link.is-active:not(:hover){background-color:transparent}.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#f5f5f5}}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:3px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.375em - 1px);padding-left:calc(.625em - 1px);padding-right:calc(.625em - 1px);padding-top:calc(.375em - 1px);position:relative;vertical-align:top;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:1em;padding-left:.5em;padding-right:.5em;justify-content:center;margin:.25rem;text-align:center}.pagination-previous:focus,.pagination-previous.is-focused,.pagination-previous:active,.pagination-previous.is-active,.pagination-next:focus,.pagination-next.is-focused,.pagination-next:active,.pagination-next.is-active,.pagination-link:focus,.pagination-link.is-focused,.pagination-link:active,.pagination-link.is-active,.pagination-ellipsis:focus,.pagination-ellipsis.is-focused,.pagination-ellipsis:active,.pagination-ellipsis.is-active{outline:0}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled]{cursor:not-allowed}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;min-width:2.25em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#469}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#469;border-color:#469;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel-heading,.panel-tabs,.panel-block{border-bottom:1px solid #dbdbdb;border-left:1px solid #dbdbdb;border-right:1px solid #dbdbdb}.panel-heading:first-child,.panel-tabs:first-child,.panel-block:first-child{border-top:1px solid #dbdbdb}.panel-heading{background-color:#f5f5f5;border-radius:3px 3px 0 0;color:#363636;font-size:1.25em;font-weight:300;line-height:1.25;padding:.5em .75em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#469}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#469;color:#363636}.panel-block.is-active .panel-icon{color:#469}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs:not(:last-child){margin-bottom:1.5rem}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#469;color:#469}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:3px 3px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-radius:3px 0 0 3px}.tabs.is-toggle li:last-child a{border-radius:0 3px 3px 0}.tabs.is-toggle li.is-active a{background-color:#469;border-color:#469;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:0}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1007px){.column.is-narrow-touch{flex:none}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1008px){.column.is-narrow-desktop{flex:none}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1200px){.column.is-narrow-widescreen{flex:none}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1392px){.column.is-narrow-fullhd{flex:none}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:.75rem}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless>.column{margin:0;padding:0}@media screen and (min-width:769px),print{.columns.is-grid{flex-wrap:wrap}.columns.is-grid>.column{max-width:33.3333%;padding:.75rem;width:33.3333%}.columns.is-grid>.column+.column{margin-left:0}}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1008px){.columns.is-desktop{display:flex}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .nav{background:0 0;box-shadow:0 1px 0 rgba(219,219,219,.3)}.hero .tabs ul{border-bottom:0}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}.hero.is-white .nav{box-shadow:0 1px 0 rgba(10,10,10,.2)}@media screen and (max-width:768px){.hero.is-white .nav-menu{background-color:#fff}}.hero.is-white a.nav-item,.hero.is-white .nav-item a:not(.button){color:rgba(10,10,10,.7)}.hero.is-white a.nav-item:hover,.hero.is-white a.nav-item.is-active,.hero.is-white .nav-item a:not(.button):hover,.hero.is-white .nav-item a:not(.button).is-active{color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e8e3e4 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .nav-menu{background-image:linear-gradient(141deg,#e8e3e4 0,#fff 71%,#fff 100%)}}@media screen and (max-width:768px){.hero.is-white .nav-toggle span{background-color:#0a0a0a}.hero.is-white .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .nav-toggle.is-active span{background-color:#0a0a0a}.hero.is-white .nav-menu .nav-item{border-top-color:rgba(10,10,10,.2)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}.hero.is-black .nav{box-shadow:0 1px 0 rgba(255,255,255,.2)}@media screen and (max-width:768px){.hero.is-black .nav-menu{background-color:#0a0a0a}}.hero.is-black a.nav-item,.hero.is-black .nav-item a:not(.button){color:rgba(255,255,255,.7)}.hero.is-black a.nav-item:hover,.hero.is-black a.nav-item.is-active,.hero.is-black .nav-item a:not(.button):hover,.hero.is-black .nav-item a:not(.button).is-active{color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .nav-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}@media screen and (max-width:768px){.hero.is-black .nav-toggle span{background-color:#fff}.hero.is-black .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .nav-toggle.is-active span{background-color:#fff}.hero.is-black .nav-menu .nav-item{border-top-color:rgba(255,255,255,.2)}}.hero.is-light{background-color:#f5f5f5;color:#363636}.hero.is-light a:not(.button),.hero.is-light strong{color:inherit}.hero.is-light .title{color:#363636}.hero.is-light .subtitle{color:rgba(54,54,54,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:#363636}.hero.is-light .nav{box-shadow:0 1px 0 rgba(54,54,54,.2)}@media screen and (max-width:768px){.hero.is-light .nav-menu{background-color:#f5f5f5}}.hero.is-light a.nav-item,.hero.is-light .nav-item a:not(.button){color:rgba(54,54,54,.7)}.hero.is-light a.nav-item:hover,.hero.is-light a.nav-item.is-active,.hero.is-light .nav-item a:not(.button):hover,.hero.is-light .nav-item a:not(.button).is-active{color:#363636}.hero.is-light .tabs a{color:#363636;opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:#363636}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:#363636;border-color:#363636;color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .nav-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}@media screen and (max-width:768px){.hero.is-light .nav-toggle span{background-color:#363636}.hero.is-light .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .nav-toggle.is-active span{background-color:#363636}.hero.is-light .nav-menu .nav-item{border-top-color:rgba(54,54,54,.2)}}.hero.is-dark{background-color:#363636;color:#f5f5f5}.hero.is-dark a:not(.button),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#f5f5f5}.hero.is-dark .subtitle{color:rgba(245,245,245,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#f5f5f5}.hero.is-dark .nav{box-shadow:0 1px 0 rgba(245,245,245,.2)}@media screen and (max-width:768px){.hero.is-dark .nav-menu{background-color:#363636}}.hero.is-dark a.nav-item,.hero.is-dark .nav-item a:not(.button){color:rgba(245,245,245,.7)}.hero.is-dark a.nav-item:hover,.hero.is-dark a.nav-item.is-active,.hero.is-dark .nav-item a:not(.button):hover,.hero.is-dark .nav-item a:not(.button).is-active{color:#f5f5f5}.hero.is-dark .tabs a{color:#f5f5f5;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#f5f5f5}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .nav-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}@media screen and (max-width:768px){.hero.is-dark .nav-toggle span{background-color:#f5f5f5}.hero.is-dark .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .nav-toggle.is-active span{background-color:#f5f5f5}.hero.is-dark .nav-menu .nav-item{border-top-color:rgba(245,245,245,.2)}}.hero.is-primary{background-color:#469;color:#fff}.hero.is-primary a:not(.button),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}.hero.is-primary .nav{box-shadow:0 1px 0 rgba(255,255,255,.2)}@media screen and (max-width:768px){.hero.is-primary .nav-menu{background-color:#469}}.hero.is-primary a.nav-item,.hero.is-primary .nav-item a:not(.button){color:rgba(255,255,255,.7)}.hero.is-primary a.nav-item:hover,.hero.is-primary a.nav-item.is-active,.hero.is-primary .nav-item a:not(.button):hover,.hero.is-primary .nav-item a:not(.button).is-active{color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#469}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#2c5a7e 0,#469 71%,#465fb1 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .nav-menu{background-image:linear-gradient(141deg,#2c5a7e 0,#469 71%,#465fb1 100%)}}@media screen and (max-width:768px){.hero.is-primary .nav-toggle span{background-color:#fff}.hero.is-primary .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .nav-toggle.is-active span{background-color:#fff}.hero.is-primary .nav-menu .nav-item{border-top-color:rgba(255,255,255,.2)}}.hero.is-info{background-color:#469;color:#fff}.hero.is-info a:not(.button),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}.hero.is-info .nav{box-shadow:0 1px 0 rgba(255,255,255,.2)}@media screen and (max-width:768px){.hero.is-info .nav-menu{background-color:#469}}.hero.is-info a.nav-item,.hero.is-info .nav-item a:not(.button){color:rgba(255,255,255,.7)}.hero.is-info a.nav-item:hover,.hero.is-info a.nav-item.is-active,.hero.is-info .nav-item a:not(.button):hover,.hero.is-info .nav-item a:not(.button).is-active{color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#469}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#2c5a7e 0,#469 71%,#465fb1 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .nav-menu{background-image:linear-gradient(141deg,#2c5a7e 0,#469 71%,#465fb1 100%)}}@media screen and (max-width:768px){.hero.is-info .nav-toggle span{background-color:#fff}.hero.is-info .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .nav-toggle.is-active span{background-color:#fff}.hero.is-info .nav-menu .nav-item{border-top-color:rgba(255,255,255,.2)}}.hero.is-success{background-color:#23d160;color:#fff}.hero.is-success a:not(.button),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}.hero.is-success .nav{box-shadow:0 1px 0 rgba(255,255,255,.2)}@media screen and (max-width:768px){.hero.is-success .nav-menu{background-color:#23d160}}.hero.is-success a.nav-item,.hero.is-success .nav-item a:not(.button){color:rgba(255,255,255,.7)}.hero.is-success a.nav-item:hover,.hero.is-success a.nav-item.is-active,.hero.is-success .nav-item a:not(.button):hover,.hero.is-success .nav-item a:not(.button).is-active{color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#23d160}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#12af2f 0,#23d160 71%,#2ce28a 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .nav-menu{background-image:linear-gradient(141deg,#12af2f 0,#23d160 71%,#2ce28a 100%)}}@media screen and (max-width:768px){.hero.is-success .nav-toggle span{background-color:#fff}.hero.is-success .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .nav-toggle.is-active span{background-color:#fff}.hero.is-success .nav-menu .nav-item{border-top-color:rgba(255,255,255,.2)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}.hero.is-warning .nav{box-shadow:0 1px 0 rgba(0,0,0,.2)}@media screen and (max-width:768px){.hero.is-warning .nav-menu{background-color:#ffdd57}}.hero.is-warning a.nav-item,.hero.is-warning .nav-item a:not(.button){color:rgba(0,0,0,.7)}.hero.is-warning a.nav-item:hover,.hero.is-warning a.nav-item.is-active,.hero.is-warning .nav-item a:not(.button):hover,.hero.is-warning .nav-item a:not(.button).is-active{color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffaf24 0,#ffdd57 71%,#fffa70 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .nav-menu{background-image:linear-gradient(141deg,#ffaf24 0,#ffdd57 71%,#fffa70 100%)}}@media screen and (max-width:768px){.hero.is-warning .nav-toggle span{background-color:rgba(0,0,0,.7)}.hero.is-warning .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .nav-toggle.is-active span{background-color:rgba(0,0,0,.7)}.hero.is-warning .nav-menu .nav-item{border-top-color:rgba(0,0,0,.2)}}.hero.is-danger{background-color:#ff3860;color:#fff}.hero.is-danger a:not(.button),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}.hero.is-danger .nav{box-shadow:0 1px 0 rgba(255,255,255,.2)}@media screen and (max-width:768px){.hero.is-danger .nav-menu{background-color:#ff3860}}.hero.is-danger a.nav-item,.hero.is-danger .nav-item a:not(.button){color:rgba(255,255,255,.7)}.hero.is-danger a.nav-item:hover,.hero.is-danger a.nav-item.is-active,.hero.is-danger .nav-item a:not(.button):hover,.hero.is-danger .nav-item a:not(.button).is-active{color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ff3860}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#ff0561 0,#ff3860 71%,#ff5257 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .nav-menu{background-image:linear-gradient(141deg,#ff0561 0,#ff3860 71%,#ff5257 100%)}}@media screen and (max-width:768px){.hero.is-danger .nav-toggle span{background-color:#fff}.hero.is-danger .nav-toggle:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .nav-toggle.is-active span{background-color:#fff}.hero.is-danger .nav-menu .nav-item{border-top-color:rgba(255,255,255,.2)}}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding-bottom:9rem;padding-top:9rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding-bottom:18rem;padding-top:18rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{bottom:0;left:0;position:absolute;right:0;top:0;overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}.section{padding:3rem 1.5rem}@media screen and (min-width:1008px){.section.is-medium{padding:9rem 1.5rem}.section.is-large{padding:18rem 1.5rem}}.footer{background-color:#f5f5f5;padding:3rem 1.5rem 6rem}#carbon{margin-left:auto;margin-right:auto;max-width:340px;min-height:150px;padding:0;position:relative}#carbon:hover{box-shadow:0 2px 3px rgba(10,10,10,.1),0 0 0 1px #469}@media screen and (min-width:769px),print{#carbon{width:340px}}#carbonads{font-size:14px;text-align:left}#carbonads a,#carbonads span{display:block}#carbonads .carbon-wrap{position:relative}#carbonads .carbon-img{background:#f5f5f5;float:left;height:100px;margin:15px 0 15px 15px;width:130px}#carbonads .carbon-img img{display:block;height:100px;width:130px}#carbonads .carbon-text{display:block;color:#363636;line-height:20px;padding:15px 15px 35px 160px}#carbonads .carbon-poweredby{bottom:0;color:#7a7a7a;font-size:.75rem;left:160px;line-height:20px;padding:0 15px 10px 0;position:absolute;right:0} \ No newline at end of file diff --git a/src/app/resources/browser/assets/css/darkmode.css b/src/app/resources/browser/assets/css/darkmode.css new file mode 100644 index 000000000..97a64f072 --- /dev/null +++ b/src/app/resources/browser/assets/css/darkmode.css @@ -0,0 +1,11 @@ +/* Dark mode (see #466). */ +/* TODO: Check if we need -webkit-filter for old Qt WebKit. */ +html { + -webkit-filter: invert() hue-rotate(180deg) contrast(70%); + filter: invert() hue-rotate(180deg) contrast(70%); +} + +html img { + -webkit-filter: invert() hue-rotate(180deg) contrast(120%); + filter: invert() hue-rotate(180deg) contrast(120%); +} diff --git a/src/app/resources/browser/assets/css/font-awesome.min.css b/src/app/resources/browser/assets/css/font-awesome.min.css new file mode 100644 index 000000000..8619afd3a --- /dev/null +++ b/src/app/resources/browser/assets/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/src/app/resources/browser/highlight.css b/src/app/resources/browser/assets/css/highlight.css similarity index 70% rename from src/app/resources/browser/highlight.css rename to src/app/resources/browser/assets/css/highlight.css index 883c96c75..78a7b1c8d 100644 --- a/src/app/resources/browser/highlight.css +++ b/src/app/resources/browser/assets/css/highlight.css @@ -1,6 +1,7 @@ +/* Highlight on navigation to an anchor. */ @-webkit-keyframes targetNavigatedAnimation { from { background: #ffffff; } - 50% { background: #ffffdd; } + 50% { background: #ffff00; } to { background: #ffffff; } } diff --git a/src/app/resources/browser/main.css b/src/app/resources/browser/assets/css/main.css similarity index 100% rename from src/app/resources/browser/main.css rename to src/app/resources/browser/assets/css/main.css diff --git a/src/app/resources/browser/assets/fonts/fontawesome-webfont.woff b/src/app/resources/browser/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/src/app/resources/browser/assets/fonts/fontawesome-webfont.woff differ diff --git a/src/app/resources/browser/start.html b/src/app/resources/browser/start.html deleted file mode 100644 index 2a53b774c..000000000 --- a/src/app/resources/browser/start.html +++ /dev/null @@ -1,18 +0,0 @@ - - - Start Page - - - - - - - Zeal - - - - - diff --git a/src/app/resources/browser/welcome-noad.html b/src/app/resources/browser/welcome-noad.html new file mode 100644 index 000000000..490a0fb22 --- /dev/null +++ b/src/app/resources/browser/welcome-noad.html @@ -0,0 +1,54 @@ + + + + + Welcome + + + + + + + + + + + zealdocs.org + + + + + + + + + + Zeal + + + Docs for everyone + + + + + + + + + + + + + + + + + Get Dash for OS X or iOS + + + + + + + + diff --git a/src/app/resources/browser/welcome.html b/src/app/resources/browser/welcome.html new file mode 100644 index 000000000..afe9e2ad6 --- /dev/null +++ b/src/app/resources/browser/welcome.html @@ -0,0 +1,57 @@ + + + + + Welcome + + + + + + + + + + + zealdocs.org + + + + + + + + + + Zeal + + + Docs for everyone + + + + + + + + + + + + + + + + + + + + Get Dash for OS X or iOS + + + + + + + + diff --git a/src/app/resources/icons/logo/128x128.png b/src/app/resources/icons/logo/128x128.png index a7d7dd556..33508e861 100644 Binary files a/src/app/resources/icons/logo/128x128.png and b/src/app/resources/icons/logo/128x128.png differ diff --git a/src/app/resources/icons/logo/icon.png b/src/app/resources/icons/logo/icon.png index 4bd3ce273..bb50f9a24 100644 Binary files a/src/app/resources/icons/logo/icon.png and b/src/app/resources/icons/logo/icon.png differ diff --git a/src/app/resources/icons/type/Abbreviation.png b/src/app/resources/icons/type/Abbreviation.png index 9f72492f8..473bf87a5 100644 Binary files a/src/app/resources/icons/type/Abbreviation.png and b/src/app/resources/icons/type/Abbreviation.png differ diff --git a/src/app/resources/icons/type/Abbreviation@2x.png b/src/app/resources/icons/type/Abbreviation@2x.png index 51e78a500..69940a550 100644 Binary files a/src/app/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 index 25444691f..716f3f269 100644 Binary files a/src/app/resources/icons/type/Alias.png and b/src/app/resources/icons/type/Alias.png differ diff --git a/src/app/resources/icons/type/Alias@2x.png b/src/app/resources/icons/type/Alias@2x.png index ce1a37dbe..7c3fefb51 100644 Binary files a/src/app/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 index 25444691f..716f3f269 100644 Binary files a/src/app/resources/icons/type/Annotation.png and b/src/app/resources/icons/type/Annotation.png differ diff --git a/src/app/resources/icons/type/Annotation@2x.png b/src/app/resources/icons/type/Annotation@2x.png index ce1a37dbe..7c3fefb51 100644 Binary files a/src/app/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 index 89ce4af5c..b9323eb36 100644 Binary files a/src/app/resources/icons/type/Attribute.png and b/src/app/resources/icons/type/Attribute.png differ diff --git a/src/app/resources/icons/type/Attribute@2x.png b/src/app/resources/icons/type/Attribute@2x.png index 2e4aaa7ee..8b3011ec8 100644 Binary files a/src/app/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 index 25444691f..716f3f269 100644 Binary files a/src/app/resources/icons/type/Axiom.png and b/src/app/resources/icons/type/Axiom.png differ diff --git a/src/app/resources/icons/type/Axiom@2x.png b/src/app/resources/icons/type/Axiom@2x.png index ce1a37dbe..7c3fefb51 100644 Binary files a/src/app/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 index b18b85bda..b4d44ba36 100644 Binary files a/src/app/resources/icons/type/Binding.png and b/src/app/resources/icons/type/Binding.png differ diff --git a/src/app/resources/icons/type/Bookmark.png b/src/app/resources/icons/type/Bookmark.png index 9be3830e5..f1fc7eee7 100644 Binary files a/src/app/resources/icons/type/Bookmark.png and b/src/app/resources/icons/type/Bookmark.png differ diff --git a/src/app/resources/icons/type/Builtin.png b/src/app/resources/icons/type/Builtin.png index 8f52dc09a..2ce616e37 100644 Binary files a/src/app/resources/icons/type/Builtin.png and b/src/app/resources/icons/type/Builtin.png differ diff --git a/src/app/resources/icons/type/Category.png b/src/app/resources/icons/type/Category.png index f12144ea5..2a4c057a6 100644 Binary files a/src/app/resources/icons/type/Category.png and b/src/app/resources/icons/type/Category.png differ diff --git a/src/app/resources/icons/type/Class.png b/src/app/resources/icons/type/Class.png index 46e4bf7e7..e105ab0eb 100644 Binary files a/src/app/resources/icons/type/Class.png and b/src/app/resources/icons/type/Class.png differ diff --git a/src/app/resources/icons/type/Collection.png b/src/app/resources/icons/type/Collection.png index 428fd1eb1..c2f905eed 100644 Binary files a/src/app/resources/icons/type/Collection.png and b/src/app/resources/icons/type/Collection.png differ diff --git a/src/app/resources/icons/type/Column.png b/src/app/resources/icons/type/Column.png index 9cc37d618..3d310d293 100644 Binary files a/src/app/resources/icons/type/Column.png and b/src/app/resources/icons/type/Column.png differ diff --git a/src/app/resources/icons/type/Component.png b/src/app/resources/icons/type/Component.png index 9cc37d618..3d310d293 100644 Binary files a/src/app/resources/icons/type/Component.png and b/src/app/resources/icons/type/Component.png differ diff --git a/src/app/resources/icons/type/Constant.png b/src/app/resources/icons/type/Constant.png index 1d56e2cfd..9d4ed3470 100644 Binary files a/src/app/resources/icons/type/Constant.png and b/src/app/resources/icons/type/Constant.png differ diff --git a/src/app/resources/icons/type/Constructor.png b/src/app/resources/icons/type/Constructor.png index cb08c47da..52511de31 100644 Binary files a/src/app/resources/icons/type/Constructor.png and b/src/app/resources/icons/type/Constructor.png differ diff --git a/src/app/resources/icons/type/Data Source.png b/src/app/resources/icons/type/Data Source.png new file mode 100644 index 000000000..74cab576c Binary files /dev/null and b/src/app/resources/icons/type/Data Source.png differ diff --git a/src/app/resources/icons/type/Data Source@2x.png b/src/app/resources/icons/type/Data Source@2x.png new file mode 100644 index 000000000..4ed1794ee Binary files /dev/null and b/src/app/resources/icons/type/Data Source@2x.png differ diff --git a/src/app/resources/icons/type/Define@2x.png b/src/app/resources/icons/type/Define@2x.png index 1bc6183ac..02fb5fbd0 100644 Binary files a/src/app/resources/icons/type/Define@2x.png and b/src/app/resources/icons/type/Define@2x.png differ diff --git a/src/app/resources/icons/type/DeletedSnippet.png b/src/app/resources/icons/type/DeletedSnippet.png deleted file mode 100644 index 91c802efe..000000000 Binary files a/src/app/resources/icons/type/DeletedSnippet.png and /dev/null differ diff --git a/src/app/resources/icons/type/DeletedSnippet@2x.png b/src/app/resources/icons/type/DeletedSnippet@2x.png deleted file mode 100644 index 7be2bc3a9..000000000 Binary files a/src/app/resources/icons/type/DeletedSnippet@2x.png and /dev/null differ diff --git a/src/app/resources/icons/type/Element.png b/src/app/resources/icons/type/Element.png index 68dc09ce2..17f84a4d7 100644 Binary files a/src/app/resources/icons/type/Element.png and b/src/app/resources/icons/type/Element.png differ diff --git a/src/app/resources/icons/type/Entry.png b/src/app/resources/icons/type/Entry.png index ffc001204..afc26c3c6 100644 Binary files a/src/app/resources/icons/type/Entry.png and b/src/app/resources/icons/type/Entry.png differ diff --git a/src/app/resources/icons/type/Enumeration.png b/src/app/resources/icons/type/Enumeration.png index e8e7f58dd..87b3d09c6 100644 Binary files a/src/app/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 index 42f71ed42..3a1cf6cb5 100644 Binary files a/src/app/resources/icons/type/Enumeration@2x.png 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 index ffc001204..afc26c3c6 100644 Binary files a/src/app/resources/icons/type/Environment.png and b/src/app/resources/icons/type/Environment.png differ diff --git a/src/app/resources/icons/type/Error.png b/src/app/resources/icons/type/Error.png index 45b9bce17..719680363 100644 Binary files a/src/app/resources/icons/type/Error.png and b/src/app/resources/icons/type/Error.png differ diff --git a/src/app/resources/icons/type/Event.png b/src/app/resources/icons/type/Event.png index 19116ccb2..21cfbc1ee 100644 Binary files a/src/app/resources/icons/type/Event.png and b/src/app/resources/icons/type/Event.png differ diff --git a/src/app/resources/icons/type/Exception.png b/src/app/resources/icons/type/Exception.png index 5bf1ea2a4..6a8780bff 100644 Binary files a/src/app/resources/icons/type/Exception.png and b/src/app/resources/icons/type/Exception.png differ diff --git a/src/app/resources/icons/type/Exception@2x.png b/src/app/resources/icons/type/Exception@2x.png index d451af706..d9ebbb615 100644 Binary files a/src/app/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 index b550fcafb..9041b946f 100644 Binary files a/src/app/resources/icons/type/Extension.png and b/src/app/resources/icons/type/Extension.png differ diff --git a/src/app/resources/icons/type/Extension@2x.png b/src/app/resources/icons/type/Extension@2x.png index 2de3c6e93..28778c9e9 100644 Binary files a/src/app/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 index 3938859f1..489bac54f 100644 Binary files a/src/app/resources/icons/type/Field.png 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 index 7b7d466b8..511664810 100644 Binary files a/src/app/resources/icons/type/Field@2x.png 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 index c1d61e7d2..3c87b412d 100644 Binary files a/src/app/resources/icons/type/File.png 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 index c977228af..fa2466fb5 100644 Binary files a/src/app/resources/icons/type/File@2x.png 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 index 3938859f1..489bac54f 100644 Binary files a/src/app/resources/icons/type/Filter.png 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 index 7b7d466b8..511664810 100644 Binary files a/src/app/resources/icons/type/Filter@2x.png and b/src/app/resources/icons/type/Filter@2x.png differ diff --git a/src/app/resources/icons/type/Flag.png b/src/app/resources/icons/type/Flag.png new file mode 100644 index 000000000..4053137b7 Binary files /dev/null and b/src/app/resources/icons/type/Flag.png differ diff --git a/src/app/resources/icons/type/Flag@2x.png b/src/app/resources/icons/type/Flag@2x.png new file mode 100644 index 000000000..edb9be055 Binary files /dev/null and b/src/app/resources/icons/type/Flag@2x.png differ diff --git a/src/app/resources/icons/type/Foreign Key.png b/src/app/resources/icons/type/Foreign Key.png index 3938859f1..489bac54f 100644 Binary files a/src/app/resources/icons/type/Foreign Key.png 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 index 7b7d466b8..511664810 100644 Binary files a/src/app/resources/icons/type/Foreign Key@2x.png 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 index c1d61e7d2..3c87b412d 100644 Binary files a/src/app/resources/icons/type/Framework.png 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 index c977228af..fa2466fb5 100644 Binary files a/src/app/resources/icons/type/Framework@2x.png 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 index d09ea3a53..be3aab178 100644 Binary files a/src/app/resources/icons/type/Function.png and b/src/app/resources/icons/type/Function.png differ diff --git a/src/app/resources/icons/type/Global.png b/src/app/resources/icons/type/Global.png index f74d2be22..5c5f029e9 100644 Binary files a/src/app/resources/icons/type/Global.png and b/src/app/resources/icons/type/Global.png differ diff --git a/src/app/resources/icons/type/Guide.png b/src/app/resources/icons/type/Guide.png index 579fa6389..370060dfb 100644 Binary files a/src/app/resources/icons/type/Guide.png and b/src/app/resources/icons/type/Guide.png differ diff --git a/src/app/resources/icons/type/Guide@2x.png b/src/app/resources/icons/type/Guide@2x.png index df2fe0c68..cd9e00f27 100644 Binary files a/src/app/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 index 4c366ab54..46e5dcaf9 100644 Binary files a/src/app/resources/icons/type/Helper.png 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 index ede52e355..3d272c241 100644 Binary files a/src/app/resources/icons/type/Helper@2x.png and b/src/app/resources/icons/type/Helper@2x.png differ diff --git a/src/app/resources/icons/type/Hook.png b/src/app/resources/icons/type/Hook.png index b088fcea9..00b764657 100644 Binary files a/src/app/resources/icons/type/Hook.png and b/src/app/resources/icons/type/Hook.png differ diff --git a/src/app/resources/icons/type/Hook@2x.png b/src/app/resources/icons/type/Hook@2x.png index b0173cb45..5ee11e1fc 100644 Binary files a/src/app/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 index 6e2fd9b4a..4327a0d9d 100644 Binary files a/src/app/resources/icons/type/Index.png and b/src/app/resources/icons/type/Index.png differ diff --git a/src/app/resources/icons/type/Index@2x.png b/src/app/resources/icons/type/Index@2x.png index 63c84035b..d3afdad6b 100644 Binary files a/src/app/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 index bbe095a22..6ac3f343f 100644 Binary files a/src/app/resources/icons/type/Indirection.png and b/src/app/resources/icons/type/Indirection.png differ diff --git a/src/app/resources/icons/type/Indirection@2x.png b/src/app/resources/icons/type/Indirection@2x.png index e65480386..a6480b0b5 100644 Binary files a/src/app/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 index bbe095a22..6ac3f343f 100644 Binary files a/src/app/resources/icons/type/Inductive.png and b/src/app/resources/icons/type/Inductive.png differ diff --git a/src/app/resources/icons/type/Inductive@2x.png b/src/app/resources/icons/type/Inductive@2x.png index e65480386..a6480b0b5 100644 Binary files a/src/app/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 index 199b1f66e..f172357be 100644 Binary files a/src/app/resources/icons/type/Instance.png and b/src/app/resources/icons/type/Instance.png differ diff --git a/src/app/resources/icons/type/Instance@2x.png b/src/app/resources/icons/type/Instance@2x.png index a2d2e9693..4d6d3c763 100644 Binary files a/src/app/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 index 6e2fd9b4a..4327a0d9d 100644 Binary files a/src/app/resources/icons/type/Instruction.png and b/src/app/resources/icons/type/Instruction.png differ diff --git a/src/app/resources/icons/type/Instruction@2x.png b/src/app/resources/icons/type/Instruction@2x.png index 63c84035b..d3afdad6b 100644 Binary files a/src/app/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 index b4843b6ef..c322a27a5 100644 Binary files a/src/app/resources/icons/type/Interface.png and b/src/app/resources/icons/type/Interface.png differ diff --git a/src/app/resources/icons/type/Interface@2x.png b/src/app/resources/icons/type/Interface@2x.png index af43e55f3..6531d5717 100644 Binary files a/src/app/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 index 2c0490116..283dacbdf 100644 Binary files a/src/app/resources/icons/type/Keyword.png 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 index 069281065..7143fb711 100644 Binary files a/src/app/resources/icons/type/Keyword@2x.png 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 index a9d3fd2fb..0159e94b9 100644 Binary files a/src/app/resources/icons/type/Lemma.png and b/src/app/resources/icons/type/Lemma.png differ diff --git a/src/app/resources/icons/type/Library.png b/src/app/resources/icons/type/Library.png index bf458ccfb..794c96678 100644 Binary files a/src/app/resources/icons/type/Library.png and b/src/app/resources/icons/type/Library.png differ diff --git a/src/app/resources/icons/type/Literal.png b/src/app/resources/icons/type/Literal.png index bd3b28ebc..6490dc0d5 100644 Binary files a/src/app/resources/icons/type/Literal.png and b/src/app/resources/icons/type/Literal.png differ diff --git a/src/app/resources/icons/type/Macro@2x.png b/src/app/resources/icons/type/Macro@2x.png index 1bc6183ac..02fb5fbd0 100644 Binary files a/src/app/resources/icons/type/Macro@2x.png and b/src/app/resources/icons/type/Macro@2x.png differ diff --git a/src/app/resources/icons/type/Member.png b/src/app/resources/icons/type/Member.png new file mode 100644 index 000000000..e456403bf Binary files /dev/null and b/src/app/resources/icons/type/Member.png differ diff --git a/src/app/resources/icons/type/Member@2x.png b/src/app/resources/icons/type/Member@2x.png new file mode 100644 index 000000000..4e245c381 Binary files /dev/null and b/src/app/resources/icons/type/Member@2x.png differ diff --git a/src/app/resources/icons/type/Message.png b/src/app/resources/icons/type/Message.png new file mode 100644 index 000000000..e6651112b Binary files /dev/null and b/src/app/resources/icons/type/Message.png differ diff --git a/src/app/resources/icons/type/Message@2x.png b/src/app/resources/icons/type/Message@2x.png new file mode 100644 index 000000000..2ffb55f88 Binary files /dev/null and b/src/app/resources/icons/type/Message@2x.png differ diff --git a/src/app/resources/icons/type/Method.png b/src/app/resources/icons/type/Method.png index 38ce04947..40ec672a3 100644 Binary files a/src/app/resources/icons/type/Method.png and b/src/app/resources/icons/type/Method.png differ diff --git a/src/app/resources/icons/type/Mixin.png b/src/app/resources/icons/type/Mixin.png index 41d520205..89c699f85 100644 Binary files a/src/app/resources/icons/type/Mixin.png and b/src/app/resources/icons/type/Mixin.png differ diff --git a/src/app/resources/icons/type/Modifier.png b/src/app/resources/icons/type/Modifier.png index 61830023a..2788279da 100644 Binary files a/src/app/resources/icons/type/Modifier.png and b/src/app/resources/icons/type/Modifier.png differ diff --git a/src/app/resources/icons/type/Module.png b/src/app/resources/icons/type/Module.png index 658fdc99b..3c9b4dea4 100644 Binary files a/src/app/resources/icons/type/Module.png and b/src/app/resources/icons/type/Module.png differ diff --git a/src/app/resources/icons/type/Namespace.png b/src/app/resources/icons/type/Namespace.png index ece329284..9cb9307b8 100644 Binary files a/src/app/resources/icons/type/Namespace.png and b/src/app/resources/icons/type/Namespace.png differ diff --git a/src/app/resources/icons/type/NewSnippet.png b/src/app/resources/icons/type/NewSnippet.png index 9148b87cc..441eb1db5 100644 Binary files a/src/app/resources/icons/type/NewSnippet.png 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 index 914695d3b..cba251475 100644 Binary files a/src/app/resources/icons/type/NewSnippet@2x.png and b/src/app/resources/icons/type/NewSnippet@2x.png differ diff --git a/src/app/resources/icons/type/Node.png b/src/app/resources/icons/type/Node.png new file mode 100644 index 000000000..3926ba9e8 Binary files /dev/null and b/src/app/resources/icons/type/Node.png differ diff --git a/src/app/resources/icons/type/Node@2x.png b/src/app/resources/icons/type/Node@2x.png new file mode 100644 index 000000000..ae0f2a8b3 Binary files /dev/null and b/src/app/resources/icons/type/Node@2x.png differ diff --git a/src/app/resources/icons/type/Notation.png b/src/app/resources/icons/type/Notation.png index d4dcf8192..3ef0d78a7 100644 Binary files a/src/app/resources/icons/type/Notation.png and b/src/app/resources/icons/type/Notation.png differ diff --git a/src/app/resources/icons/type/Object.png b/src/app/resources/icons/type/Object.png index a68029b06..87d6c3710 100644 Binary files a/src/app/resources/icons/type/Object.png and b/src/app/resources/icons/type/Object.png differ diff --git a/src/app/resources/icons/type/Operator.png b/src/app/resources/icons/type/Operator.png index a797cd887..33f20c6d8 100644 Binary files a/src/app/resources/icons/type/Operator.png and b/src/app/resources/icons/type/Operator.png differ diff --git a/src/app/resources/icons/type/Option.png b/src/app/resources/icons/type/Option.png index 6a4b57ed8..c9de06c37 100644 Binary files a/src/app/resources/icons/type/Option.png and b/src/app/resources/icons/type/Option.png differ diff --git a/src/app/resources/icons/type/Package.png b/src/app/resources/icons/type/Package.png index 9c215dc04..aae03464c 100644 Binary files a/src/app/resources/icons/type/Package.png 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 index 1dc2294db..5a48d0685 100644 Binary files a/src/app/resources/icons/type/Package@2x.png 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 index 2c8e5069e..096251e4f 100644 Binary files a/src/app/resources/icons/type/Parameter.png 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 index c3689e4e6..cadc36686 100644 Binary files a/src/app/resources/icons/type/Parameter@2x.png and b/src/app/resources/icons/type/Parameter@2x.png differ diff --git a/src/app/resources/icons/type/Pipe.png b/src/app/resources/icons/type/Pipe.png new file mode 100644 index 000000000..a0ed4e9e6 Binary files /dev/null and b/src/app/resources/icons/type/Pipe.png differ diff --git a/src/app/resources/icons/type/Pipe@2x.png b/src/app/resources/icons/type/Pipe@2x.png new file mode 100644 index 000000000..cba0b1cf7 Binary files /dev/null and b/src/app/resources/icons/type/Pipe@2x.png differ diff --git a/src/app/resources/icons/type/Plugin.png b/src/app/resources/icons/type/Plugin.png index 9c215dc04..aae03464c 100644 Binary files a/src/app/resources/icons/type/Plugin.png 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 index 1dc2294db..5a48d0685 100644 Binary files a/src/app/resources/icons/type/Plugin@2x.png 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 index e6f0525e0..8f989677a 100644 Binary files a/src/app/resources/icons/type/Procedure.png 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 index 19bdfb107..14dda5186 100644 Binary files a/src/app/resources/icons/type/Procedure@2x.png 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 index e6f0525e0..8f989677a 100644 Binary files a/src/app/resources/icons/type/Projection.png 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 index 19bdfb107..14dda5186 100644 Binary files a/src/app/resources/icons/type/Projection@2x.png 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 index 719967d92..eb7947843 100644 Binary files a/src/app/resources/icons/type/Property.png 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 index a47a3e8ce..dd89f554a 100644 Binary files a/src/app/resources/icons/type/Property@2x.png 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 index a12b9dde8..8f3ec7716 100644 Binary files a/src/app/resources/icons/type/Protocol.png and b/src/app/resources/icons/type/Protocol.png differ diff --git a/src/app/resources/icons/type/Provider.png b/src/app/resources/icons/type/Provider.png index 2c8e5069e..096251e4f 100644 Binary files a/src/app/resources/icons/type/Provider.png 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 index c3689e4e6..cadc36686 100644 Binary files a/src/app/resources/icons/type/Provider@2x.png 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 index e6f0525e0..8f989677a 100644 Binary files a/src/app/resources/icons/type/Provisioner.png 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 index 19bdfb107..14dda5186 100644 Binary files a/src/app/resources/icons/type/Provisioner@2x.png 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 index 5eff5167e..98217affe 100644 Binary files a/src/app/resources/icons/type/Query.png 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 index eef6cb644..fcb07b7ef 100644 Binary files a/src/app/resources/icons/type/Query@2x.png and b/src/app/resources/icons/type/Query@2x.png differ diff --git a/src/app/resources/icons/type/Record@2x.png b/src/app/resources/icons/type/Record@2x.png index b8ce0176d..3c61f373a 100644 Binary files a/src/app/resources/icons/type/Record@2x.png and b/src/app/resources/icons/type/Record@2x.png differ diff --git a/src/app/resources/icons/type/Reference.png b/src/app/resources/icons/type/Reference.png new file mode 100644 index 000000000..aca7a8d60 Binary files /dev/null and b/src/app/resources/icons/type/Reference.png differ diff --git a/src/app/resources/icons/type/Reference@2x.png b/src/app/resources/icons/type/Reference@2x.png new file mode 100644 index 000000000..7c901d6ab Binary files /dev/null and b/src/app/resources/icons/type/Reference@2x.png differ diff --git a/src/app/resources/icons/type/Relationship@2x.png b/src/app/resources/icons/type/Relationship@2x.png index b8ce0176d..3c61f373a 100644 Binary files a/src/app/resources/icons/type/Relationship@2x.png and b/src/app/resources/icons/type/Relationship@2x.png differ diff --git a/src/app/resources/icons/type/Report@2x.png b/src/app/resources/icons/type/Report@2x.png index b8ce0176d..3c61f373a 100644 Binary files a/src/app/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@2x.png b/src/app/resources/icons/type/Request@2x.png index b8ce0176d..3c61f373a 100644 Binary files a/src/app/resources/icons/type/Request@2x.png and b/src/app/resources/icons/type/Request@2x.png differ diff --git a/src/app/resources/icons/type/Resource@2x.png b/src/app/resources/icons/type/Resource@2x.png index b8ce0176d..3c61f373a 100644 Binary files a/src/app/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 index 8f8c54fbb..bf1021dbf 100644 Binary files a/src/app/resources/icons/type/Sample.png and b/src/app/resources/icons/type/Sample.png differ diff --git a/src/app/resources/icons/type/Sample@2x.png b/src/app/resources/icons/type/Sample@2x.png index de38d7ab7..ffa65bfcf 100644 Binary files a/src/app/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 index 87f6d189b..6b243042e 100644 Binary files a/src/app/resources/icons/type/Schema.png and b/src/app/resources/icons/type/Schema.png differ diff --git a/src/app/resources/icons/type/Script.png b/src/app/resources/icons/type/Script.png index f58a61795..7f1c8569b 100644 Binary files a/src/app/resources/icons/type/Script.png and b/src/app/resources/icons/type/Script.png differ diff --git a/src/app/resources/icons/type/Section.png b/src/app/resources/icons/type/Section.png index 77ff762bf..b4164ee71 100644 Binary files a/src/app/resources/icons/type/Section.png and b/src/app/resources/icons/type/Section.png differ diff --git a/src/app/resources/icons/type/Section@2x.png b/src/app/resources/icons/type/Section@2x.png index bcb647b9e..b4a762447 100644 Binary files a/src/app/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/Sender.png b/src/app/resources/icons/type/Sender.png new file mode 100644 index 000000000..1ec7d370e Binary files /dev/null and b/src/app/resources/icons/type/Sender.png differ diff --git a/src/app/resources/icons/type/Sender@2x.png b/src/app/resources/icons/type/Sender@2x.png new file mode 100644 index 000000000..7fc3ed4fb Binary files /dev/null and b/src/app/resources/icons/type/Sender@2x.png differ diff --git a/src/app/resources/icons/type/Service.png b/src/app/resources/icons/type/Service.png index 87f6d189b..6b243042e 100644 Binary files a/src/app/resources/icons/type/Service.png and b/src/app/resources/icons/type/Service.png differ diff --git a/src/app/resources/icons/type/Setting.png b/src/app/resources/icons/type/Setting.png index 6a6460812..8254a5c13 100644 Binary files a/src/app/resources/icons/type/Setting.png and b/src/app/resources/icons/type/Setting.png differ diff --git a/src/app/resources/icons/type/Shortcut.png b/src/app/resources/icons/type/Shortcut.png index 5f5dcee85..8c1bd2614 100644 Binary files a/src/app/resources/icons/type/Shortcut.png 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 index 6be85a650..94f6bef46 100644 Binary files a/src/app/resources/icons/type/Shortcut@2x.png 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 index 87f6d189b..6b243042e 100644 Binary files a/src/app/resources/icons/type/Signature.png and b/src/app/resources/icons/type/Signature.png differ diff --git a/src/app/resources/icons/type/Snippet.png b/src/app/resources/icons/type/Snippet.png deleted file mode 100644 index ff43f3e58..000000000 Binary files a/src/app/resources/icons/type/Snippet.png and /dev/null differ diff --git a/src/app/resources/icons/type/Snippet@2x.png b/src/app/resources/icons/type/Snippet@2x.png deleted file mode 100644 index 7677000a1..000000000 Binary files a/src/app/resources/icons/type/Snippet@2x.png and /dev/null differ diff --git a/src/app/resources/icons/type/Special Form.png b/src/app/resources/icons/type/Special Form.png index 3938859f1..489bac54f 100644 Binary files a/src/app/resources/icons/type/Special Form.png 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 index 7b7d466b8..511664810 100644 Binary files a/src/app/resources/icons/type/Special Form@2x.png 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 index 87f6d189b..6b243042e 100644 Binary files a/src/app/resources/icons/type/Statement.png and b/src/app/resources/icons/type/Statement.png differ diff --git a/src/app/resources/icons/type/Structure.png b/src/app/resources/icons/type/Structure.png index 31db77582..023f678ec 100644 Binary files a/src/app/resources/icons/type/Structure.png 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 index 1895f9e73..ca404d057 100644 Binary files a/src/app/resources/icons/type/Structure@2x.png 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 index 6a6460812..8254a5c13 100644 Binary files a/src/app/resources/icons/type/Style.png and b/src/app/resources/icons/type/Style.png differ diff --git a/src/app/resources/icons/type/Subroutine.png b/src/app/resources/icons/type/Subroutine.png index 6a6460812..8254a5c13 100644 Binary files a/src/app/resources/icons/type/Subroutine.png and b/src/app/resources/icons/type/Subroutine.png differ diff --git a/src/app/resources/icons/type/Syntax.png b/src/app/resources/icons/type/Syntax.png index f58a61795..7f1c8569b 100644 Binary files a/src/app/resources/icons/type/Syntax.png and b/src/app/resources/icons/type/Syntax.png differ diff --git a/src/app/resources/icons/type/Table.png b/src/app/resources/icons/type/Table.png index 19f520a7a..8b42b8d40 100644 Binary files a/src/app/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 index 2790afe7e..eae319626 100644 Binary files a/src/app/resources/icons/type/Table@2x.png 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 index 572ae6718..a89364b1d 100644 Binary files a/src/app/resources/icons/type/Tactic.png 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 index e24f8d378..2d480d1ff 100644 Binary files a/src/app/resources/icons/type/Tactic@2x.png and b/src/app/resources/icons/type/Tactic@2x.png differ diff --git a/src/app/resources/icons/type/Tag.png b/src/app/resources/icons/type/Tag.png index 19f520a7a..8b42b8d40 100644 Binary files a/src/app/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 index 2790afe7e..eae319626 100644 Binary files a/src/app/resources/icons/type/Tag@2x.png 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 index 572ae6718..a89364b1d 100644 Binary files a/src/app/resources/icons/type/Test.png 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 index e24f8d378..2d480d1ff 100644 Binary files a/src/app/resources/icons/type/Test@2x.png and b/src/app/resources/icons/type/Test@2x.png differ diff --git a/src/app/resources/icons/type/Trait.png b/src/app/resources/icons/type/Trait.png index 19f520a7a..8b42b8d40 100644 Binary files a/src/app/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 index 2790afe7e..eae319626 100644 Binary files a/src/app/resources/icons/type/Trait@2x.png 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 index 572ae6718..a89364b1d 100644 Binary files a/src/app/resources/icons/type/Trigger.png 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 index e24f8d378..2d480d1ff 100644 Binary files a/src/app/resources/icons/type/Trigger@2x.png 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 index 70dfe4d7b..f5c5e7187 100644 Binary files a/src/app/resources/icons/type/Type.png 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 index 78850a363..0d0e98489 100644 Binary files a/src/app/resources/icons/type/Type@2x.png and b/src/app/resources/icons/type/Type@2x.png differ diff --git a/src/app/resources/icons/type/Unknown.png b/src/app/resources/icons/type/Unknown.png index de57ae505..5af130fe4 100644 Binary files a/src/app/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 index 295522df8..8d101c85f 100644 Binary files a/src/app/resources/icons/type/Unknown@2x.png 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 index 8a1498766..29edb5e29 100644 Binary files a/src/app/resources/icons/type/Value.png and b/src/app/resources/icons/type/Value.png differ diff --git a/src/app/resources/icons/type/Variable.png b/src/app/resources/icons/type/Variable.png index 84cee5014..f6561036a 100644 Binary files a/src/app/resources/icons/type/Variable.png and b/src/app/resources/icons/type/Variable.png differ diff --git a/src/app/resources/icons/type/Variant.png b/src/app/resources/icons/type/Variant.png index 8a1498766..29edb5e29 100644 Binary files a/src/app/resources/icons/type/Variant.png and b/src/app/resources/icons/type/Variant.png differ diff --git a/src/app/resources/icons/type/View.png b/src/app/resources/icons/type/View.png index 6b20c6ff5..727689834 100644 Binary files a/src/app/resources/icons/type/View.png and b/src/app/resources/icons/type/View.png differ diff --git a/src/app/resources/icons/type/Web.png b/src/app/resources/icons/type/Web.png deleted file mode 100644 index 942b17c83..000000000 Binary files a/src/app/resources/icons/type/Web.png and /dev/null differ diff --git a/src/app/resources/icons/type/Web@2x.png b/src/app/resources/icons/type/Web@2x.png deleted file mode 100644 index 2949c5a86..000000000 Binary files a/src/app/resources/icons/type/Web@2x.png and /dev/null differ diff --git a/src/app/resources/icons/type/Word.png b/src/app/resources/icons/type/Word.png index f8f1d9649..c063b2180 100644 Binary files a/src/app/resources/icons/type/Word.png and b/src/app/resources/icons/type/Word.png differ diff --git a/src/app/resources/icons/type/_DashAnnotations.png b/src/app/resources/icons/type/_DashAnnotations.png deleted file mode 100644 index ce249000a..000000000 Binary files a/src/app/resources/icons/type/_DashAnnotations.png and /dev/null differ diff --git a/src/app/resources/icons/type/_DashAnnotations@2x.png b/src/app/resources/icons/type/_DashAnnotations@2x.png deleted file mode 100644 index cbf63fa8d..000000000 Binary files a/src/app/resources/icons/type/_DashAnnotations@2x.png and /dev/null differ diff --git a/src/app/resources/icons/type/_Struct.png b/src/app/resources/icons/type/_Struct.png deleted file mode 100644 index ac1ad436e..000000000 Binary files a/src/app/resources/icons/type/_Struct.png and /dev/null differ diff --git a/src/app/resources/icons/type/_Struct@2x.png b/src/app/resources/icons/type/_Struct@2x.png deleted file mode 100644 index 0fce0e833..000000000 Binary files a/src/app/resources/icons/type/_Struct@2x.png and /dev/null differ diff --git a/src/app/resources/zeal.qrc b/src/app/resources/zeal.qrc index b61104e47..8f3b8f363 100644 --- a/src/app/resources/zeal.qrc +++ b/src/app/resources/zeal.qrc @@ -1,13 +1,14 @@ zeal.ico - browser/highlight.css - browser/main.css - browser/start.html - icons/type/_DashAnnotations.png - icons/type/_DashAnnotations@2x.png - icons/type/_Struct.png - icons/type/_Struct@2x.png + browser/assets/css/bulma.min.css + browser/assets/css/darkmode.css + browser/assets/css/font-awesome.min.css + browser/assets/css/highlight.css + browser/assets/css/main.css + browser/assets/fonts/fontawesome-webfont.woff + browser/welcome.html + browser/welcome-noad.html icons/type/Abbreviation.png icons/type/Abbreviation@2x.png icons/type/Alias.png @@ -44,6 +45,8 @@ icons/type/Constructor@2x.png icons/type/Conversion.png icons/type/Conversion@2x.png + icons/type/Data Source.png + icons/type/Data Source@2x.png icons/type/Database.png icons/type/Database@2x.png icons/type/Decorator.png @@ -52,8 +55,6 @@ icons/type/Define@2x.png icons/type/Delegate.png icons/type/Delegate@2x.png - icons/type/DeletedSnippet.png - icons/type/DeletedSnippet@2x.png icons/type/Diagram.png icons/type/Diagram@2x.png icons/type/Directive.png @@ -80,6 +81,8 @@ icons/type/File@2x.png icons/type/Filter.png icons/type/Filter@2x.png + icons/type/Flag.png + icons/type/Flag@2x.png icons/type/Foreign Key.png icons/type/Foreign Key@2x.png icons/type/Framework.png @@ -116,6 +119,10 @@ icons/type/Literal@2x.png icons/type/Macro.png icons/type/Macro@2x.png + icons/type/Member.png + icons/type/Member@2x.png + icons/type/Message.png + icons/type/Message@2x.png icons/type/Method.png icons/type/Method@2x.png icons/type/Mixin.png @@ -128,6 +135,8 @@ icons/type/Namespace@2x.png icons/type/NewSnippet.png icons/type/NewSnippet@2x.png + icons/type/Node.png + icons/type/Node@2x.png icons/type/Notation.png icons/type/Notation@2x.png icons/type/Object.png @@ -142,6 +151,8 @@ icons/type/Parameter@2x.png icons/type/Pattern.png icons/type/Pattern@2x.png + icons/type/Pipe.png + icons/type/Pipe@2x.png icons/type/Plugin.png icons/type/Plugin@2x.png icons/type/Procedure.png @@ -158,6 +169,8 @@ icons/type/Provisioner@2x.png icons/type/Query.png icons/type/Query@2x.png + icons/type/Reference.png + icons/type/Reference@2x.png icons/type/Record.png icons/type/Record@2x.png icons/type/Relationship.png @@ -176,6 +189,8 @@ icons/type/Script@2x.png icons/type/Section.png icons/type/Section@2x.png + icons/type/Sender.png + icons/type/Sender@2x.png icons/type/Service.png icons/type/Service@2x.png icons/type/Setting.png @@ -184,8 +199,6 @@ 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 icons/type/Special Form@2x.png icons/type/Statement.png @@ -224,8 +237,6 @@ 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 icons/type/Word@2x.png icons/logo/64x64.png diff --git a/src/app/versioninfo.rc.in b/src/app/versioninfo.rc.in new file mode 100644 index 000000000..389cc764e --- /dev/null +++ b/src/app/versioninfo.rc.in @@ -0,0 +1,58 @@ +#include + +#pragma code_page(65001) + +IDI_ICON1 ICON DISCARDABLE "resources/zeal.ico" + +#define VER_COMPANYNAME_STR "${PROJECT_COMPANY_NAME}" +#define VER_FILEDESCRIPTION_STR "${PROJECT_DESCRIPTION}" +#define VER_FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH} +#define VER_FILEVERSION_STR "${PROJECT_VERSION}" +#define VER_INTERNALNAME_STR "${PROJECT_NAME}" +#define VER_LEGALCOPYRIGHT_STR "${PROJECT_COPYRIGHT}" +#define VER_LEGALTRADEMARKS1_STR "" +#define VER_LEGALTRADEMARKS2_STR "" +#define VER_ORIGINALFILENAME_STR "${PROJECT_OUTPUT_NAME}.exe" +#define VER_PRODUCTNAME_STR "${PROJECT_NAME}" +#define VER_PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH} +#define VER_PRODUCTVERSION_STR "${PROJECT_VERSION}" + +#ifndef DEBUG +#define VER_DEBUG 0 +#else +#define VER_DEBUG VS_FF_DEBUG +#endif + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +//TODO: Set file flags. +//FILEFLAGS (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG) +FILEFLAGS 0 +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", VER_FILEDESCRIPTION_STR + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", VER_INTERNALNAME_STR + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR + VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR + VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR + VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt new file mode 100644 index 000000000..d0f24cc37 --- /dev/null +++ b/src/libs/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(core) +add_subdirectory(registry) +add_subdirectory(ui) +add_subdirectory(util) diff --git a/src/libs/core/CMakeLists.txt b/src/libs/core/CMakeLists.txt new file mode 100644 index 000000000..afe6f43d9 --- /dev/null +++ b/src/libs/core/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(Core + application.cpp + extractor.cpp + localserver.cpp + networkaccessmanager.cpp + settings.cpp +) + +target_link_libraries(Core Registry Ui) + +find_package(Qt5 COMPONENTS Network WebKit Widgets REQUIRED) +target_link_libraries(Core Qt5::Network Qt5::WebKit Qt5::Widgets) + +find_package(LibArchive REQUIRED) +include_directories(${LibArchive_INCLUDE_DIRS}) +target_link_libraries(Core ${LibArchive_LIBRARIES}) diff --git a/src/libs/core/application.cpp b/src/libs/core/application.cpp index d39d90330..408359998 100644 --- a/src/libs/core/application.cpp +++ b/src/libs/core/application.cpp @@ -70,13 +70,12 @@ Application::Application(QObject *parent) : connect(m_extractor, &Extractor::error, this, &Application::extractionError); connect(m_extractor, &Extractor::progress, this, &Application::extractionProgress); + m_docsetRegistry = new Registry::DocsetRegistry(); + connect(m_settings, &Settings::updated, this, &Application::applySettings); applySettings(); - m_docsetRegistry = new Registry::DocsetRegistry(); - m_docsetRegistry->init(m_settings->docsetPath); - - m_mainWindow = new MainWindow(this); + m_mainWindow = new WidgetUi::MainWindow(this); if (m_settings->startMinimized) { if (m_settings->showSystrayIcon && m_settings->minimizeToSystray) @@ -192,6 +191,9 @@ void Application::checkForUpdates(bool quiet) void Application::applySettings() { + m_docsetRegistry->setStoragePath(m_settings->docsetPath); + m_docsetRegistry->setFuzzySearchEnabled(m_settings->fuzzySearchEnabled); + // HTTP Proxy Settings switch (m_settings->proxyType) { case Core::Settings::ProxyType::None: @@ -208,7 +210,11 @@ void Application::applySettings() proxy.setUser(m_settings->proxyUserName); proxy.setPassword(m_settings->proxyPassword); } + QNetworkProxy::setApplicationProxy(proxy); + + // Force NM to pick up changes. + m_networkManager->clearAccessCache(); break; } } @@ -221,8 +227,6 @@ QString Application::userAgent() QString Application::userAgentJson() const { - // TODO: [Qt 5.4] Remove else branch -#if QT_VERSION >= 0x050400 QJsonObject app = { {QStringLiteral("version"), QCoreApplication::applicationVersion()}, {QStringLiteral("qt_version"), qVersion()}, @@ -243,46 +247,6 @@ QString Application::userAgentJson() const {QStringLiteral("app"), app}, {QStringLiteral("os"), os} }; -#else - QJsonObject app; - app[QStringLiteral("version")] = QCoreApplication::applicationVersion(); - app[QStringLiteral("qt_version")] = QString::fromLatin1(qVersion()); - app[QStringLiteral("install_id")] = m_settings->installId; - - QJsonObject os; - -#if defined(Q_PROCESSOR_ARM) - os[QStringLiteral("arch")] = QStringLiteral("arm"); -#elif defined(Q_PROCESSOR_X86_32) - os[QStringLiteral("arch")] = QStringLiteral("i386"); -#elif defined(Q_PROCESSOR_X86_64) - os[QStringLiteral("arch")] = QStringLiteral("x86_64"); -#else - os[QStringLiteral("arch")] = QStringLiteral("unknown"); -#endif // Q_PROCESSOR_* - - os[QStringLiteral("name")] = QStringLiteral("unknown"); - os[QStringLiteral("product_type")] = QStringLiteral("unknown"); - os[QStringLiteral("product_version")] = QStringLiteral("unknown"); - -#if defined(Q_OS_LINUX) - os[QStringLiteral("kernel_type")] = QStringLiteral("linux"); -#elif defined(Q_OS_WIN) - os[QStringLiteral("kernel_type")] = QStringLiteral("windows"); -#elif defined(Q_OS_OSX) - os[QStringLiteral("kernel_type")] = QStringLiteral("osx"); -#else - os[QStringLiteral("kernel_type")] = QStringLiteral("unknown"); -#endif // Q_OS_* - - os[QStringLiteral("kernel_version")] = QStringLiteral("unknown"); - os[QStringLiteral("locale")] = QLocale::system().name(); - - QJsonObject ua; - ua[QStringLiteral("app")] = app; - ua[QStringLiteral("os")] = os; - -#endif // QT_VERSION >= 0x050400 return QString::fromUtf8(QJsonDocument(ua).toJson(QJsonDocument::Compact)); } diff --git a/src/libs/core/application.h b/src/libs/core/application.h index 749fc8ca7..1a90d590a 100644 --- a/src/libs/core/application.h +++ b/src/libs/core/application.h @@ -29,7 +29,6 @@ class QNetworkAccessManager; class QNetworkReply; class QThread; -class MainWindow; namespace Zeal { @@ -38,6 +37,10 @@ class DocsetRegistry; class SearchQuery; } // namespace Registry +namespace WidgetUi { +class MainWindow; +} // namespace WidgetUi + namespace Core { class Extractor; @@ -88,7 +91,7 @@ private slots: Registry::DocsetRegistry *m_docsetRegistry = nullptr; - MainWindow *m_mainWindow = nullptr; + WidgetUi::MainWindow *m_mainWindow = nullptr; }; } // namespace Core diff --git a/src/libs/core/core.pri b/src/libs/core/core.pri index 0f7c62ea3..41f944c82 100644 --- a/src/libs/core/core.pri +++ b/src/libs/core/core.pri @@ -1,6 +1,6 @@ ZEAL_LIB_NAME = Core -QT += network +QT += network webkit widgets unix:!macx { CONFIG += link_pkgconfig diff --git a/src/libs/core/extractor.cpp b/src/libs/core/extractor.cpp index 97c68e014..159bc76d6 100644 --- a/src/libs/core/extractor.cpp +++ b/src/libs/core/extractor.cpp @@ -58,7 +58,7 @@ void Extractor::extract(const QString &filePath, const QString &destination, con QDir destinationDir(destination); if (!root.isEmpty()) - destinationDir = destinationDir.absoluteFilePath(root); + destinationDir = destinationDir.filePath(root); // TODO: Do not strip root directory in archive if it equals to 'root' archive_entry *entry; @@ -71,7 +71,7 @@ void Extractor::extract(const QString &filePath, const QString &destination, con #endif if (!root.isEmpty()) pathname.remove(0, pathname.indexOf(QLatin1String("/")) + 1); - archive_entry_set_pathname(entry, qPrintable(destinationDir.absoluteFilePath(pathname))); + archive_entry_set_pathname(entry, qPrintable(destinationDir.filePath(pathname))); archive_read_extract(info.archiveHandle, entry, 0); } @@ -91,4 +91,3 @@ void Extractor::progressCallback(void *ptr) emit info->extractor->progress(info->filePath, extractedBytes, info->totalBytes); } - diff --git a/src/libs/core/localserver.cpp b/src/libs/core/localserver.cpp index efac2193e..3f0ac0ecb 100644 --- a/src/libs/core/localserver.cpp +++ b/src/libs/core/localserver.cpp @@ -27,7 +27,6 @@ #include #include #include -#include using namespace Zeal; using namespace Zeal::Core; @@ -40,19 +39,19 @@ 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; + connect(m_localServer, &QLocalServer::newConnection, [this] { + QLocalSocket *socket = m_localServer->nextPendingConnection(); + connect(socket, &QLocalSocket::readyRead, [this, socket] { + Registry::SearchQuery query; + bool preventActivation; - QDataStream in(connection.data()); - Registry::SearchQuery query; - bool preventActivation; - in >> query; - in >> preventActivation; + QDataStream in(socket); + in >> query; + in >> preventActivation; - emit newQuery(query, preventActivation); + emit newQuery(query, preventActivation); + socket->deleteLater(); + }); }); } @@ -94,15 +93,17 @@ bool LocalServer::start(bool force) */ bool LocalServer::sendQuery(const Registry::SearchQuery &query, bool preventActivation) { - QScopedPointer socket(new QLocalSocket()); - socket->connectToServer(LocalServerName); + QByteArray ba; + QDataStream out(&ba, QIODevice::WriteOnly); + out << query << preventActivation; - if (!socket->waitForConnected(500)) - return false; + QLocalSocket *socket = new QLocalSocket(); + connect(socket, &QLocalSocket::connected, [socket, ba] { + socket->write(ba); + socket->flush(); // Required for Linux + socket->deleteLater(); + }); - QDataStream out(socket.data()); - out << query; - out << preventActivation; - socket->flush(); - return true; + socket->connectToServer(LocalServerName); + return socket->waitForConnected(500); } diff --git a/src/libs/core/networkaccessmanager.cpp b/src/libs/core/networkaccessmanager.cpp index df1bd0a28..483485509 100644 --- a/src/libs/core/networkaccessmanager.cpp +++ b/src/libs/core/networkaccessmanager.cpp @@ -35,10 +35,22 @@ QNetworkReply *NetworkAccessManager::createRequest(QNetworkAccessManager::Operat const QNetworkRequest &request, QIODevice *outgoingData) { - // Detect URLs without schema, and prevent them from being requested on a local filesytem. + static const QStringList localSchemes = {QStringLiteral("file"), QStringLiteral("qrc")}; + const QUrl url = request.url(); - if (url.scheme() == QLatin1String("file") && !url.host().isEmpty()) - return QNetworkAccessManager::createRequest(GetOperation, QNetworkRequest(), outgoingData); + + // Forward all non-local schemaless URLs via HTTPS. + if (localSchemes.contains(url.scheme()) && !url.host().isEmpty()) { + QUrl overrideUrl(url); + overrideUrl.setScheme(QStringLiteral("https")); + + QNetworkRequest overrideRequest(request); + overrideRequest.setUrl(overrideUrl); + + return QNetworkAccessManager::createRequest(GetOperation, overrideRequest, outgoingData); + } + + return QNetworkAccessManager::createRequest(op, request, outgoingData); } diff --git a/src/libs/core/settings.cpp b/src/libs/core/settings.cpp index cffdc9549..d62bd7329 100644 --- a/src/libs/core/settings.cpp +++ b/src/libs/core/settings.cpp @@ -28,19 +28,14 @@ #include #include #include - -#ifdef USE_WEBENGINE -#include -typedef QWebEngineSettings QWebSettings; -#else #include -#endif namespace { // Configuration file groups -const char GroupBrowser[] = "browser"; +const char GroupContent[] = "content"; const char GroupDocsets[] = "docsets"; const char GroupGlobalShortcuts[] = "global_shortcuts"; +const char GroupSearch[] = "search"; const char GroupTabs[] = "tabs"; const char GroupInternal[] = "internal"; const char GroupState[] = "state"; @@ -52,12 +47,6 @@ 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(); } @@ -87,15 +76,24 @@ void Settings::load() openNewTabAfterActive = settings->value(QStringLiteral("open_new_tab_after_active"), false).toBool(); settings->endGroup(); - settings->beginGroup(GroupBrowser); + settings->beginGroup(GroupSearch); + fuzzySearchEnabled = settings->value(QStringLiteral("fuzzy_search_enabled"), false).toBool(); + settings->endGroup(); + + settings->beginGroup(GroupContent); minimumFontSize = settings->value(QStringLiteral("minimum_font_size"), - QWebSettings::globalSettings()->fontSize(QWebSettings::MinimumFontSize)).toInt(); + QWebSettings::globalSettings()->fontSize(QWebSettings::MinimumFontSize)).toInt(); QWebSettings::globalSettings()->setFontSize(QWebSettings::MinimumFontSize, minimumFontSize); + + darkModeEnabled = settings->value(QStringLiteral("dark_mode"), false).toBool(); + highlightOnNavigateEnabled = settings->value(QStringLiteral("highlight_on_navigate"), true).toBool(); + customCssFile = settings->value(QStringLiteral("custom_css_file")).toString(); + isAdDisabled = settings->value(QStringLiteral("disable_ad"), false).toBool(); settings->endGroup(); settings->beginGroup(GroupProxy); proxyType = static_cast(settings->value(QStringLiteral("type"), - ProxyType::System).toUInt()); + 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(); @@ -125,8 +123,8 @@ void Settings::load() settings->beginGroup(GroupInternal); installId = settings->value(QStringLiteral("install_id"), - // Avoid curly braces (QTBUG-885) - QUuid::createUuid().toString().mid(1, 36)).toString(); + // Avoid curly braces (QTBUG-885) + QUuid::createUuid().toString().mid(1, 36)).toString(); settings->endGroup(); } @@ -150,8 +148,16 @@ void Settings::save() settings->setValue(QStringLiteral("open_new_tab_after_active"), openNewTabAfterActive); settings->endGroup(); - settings->beginGroup(GroupBrowser); + settings->beginGroup(GroupSearch); + settings->setValue(QStringLiteral("fuzzy_search_enabled"), fuzzySearchEnabled); + settings->endGroup(); + + settings->beginGroup(GroupContent); settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize); + settings->setValue(QStringLiteral("dark_mode"), darkModeEnabled); + settings->setValue(QStringLiteral("highlight_on_navigate"), highlightOnNavigateEnabled); + settings->setValue(QStringLiteral("custom_css_file"), customCssFile); + settings->setValue(QStringLiteral("disable_ad"), isAdDisabled); settings->endGroup(); settings->beginGroup(GroupProxy); @@ -163,11 +169,9 @@ void Settings::save() 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); @@ -197,15 +201,35 @@ void Settings::save() void Settings::migrate(QSettings *settings) const { settings->beginGroup(GroupInternal); + // TODO: [Qt 5.6] Use QVersionNumber. const QString version = settings->value(QStringLiteral("version")).toString(); settings->endGroup(); + // + // Pre 0.4 + // + + // Rename 'browser' group into 'content'. + if (version < QLatin1String("0.4")) { + settings->beginGroup(QStringLiteral("browser")); + const QVariant tmpMinimumFontSize = settings->value(QStringLiteral("minimum_font_size")); + settings->endGroup(); + settings->remove(QStringLiteral("browser")); + + if (tmpMinimumFontSize.isValid()) { + settings->beginGroup(GroupContent); + settings->setValue(QStringLiteral("minimum_font_size"), tmpMinimumFontSize); + settings->endGroup(); + } + } + + // // Pre 0.3 // // Unset 'state/splitter_geometry', because custom styles were removed. - if (version.isEmpty() || version.startsWith(QLatin1String("0.2"))) { + if (version < QLatin1String("0.3")) { settings->beginGroup(GroupState); settings->remove(QStringLiteral("splitter_geometry")); settings->endGroup(); diff --git a/src/libs/core/settings.h b/src/libs/core/settings.h index 50fc37ae5..01e112a03 100644 --- a/src/libs/core/settings.h +++ b/src/libs/core/settings.h @@ -56,8 +56,16 @@ class Settings : public QObject // Tabs Behavior bool openNewTabAfterActive; - // Browser + // Search + bool fuzzySearchEnabled; + + // Content int minimumFontSize; + bool darkModeEnabled; + bool highlightOnNavigateEnabled; + QString customCssFile; + bool isAdDisabled; + // TODO: bool askOnExternalLink; // TODO: QString customCss; @@ -67,9 +75,7 @@ class Settings : public QObject System, UserDefined }; -#if QT_VERSION >= 0x050500 Q_ENUM(ProxyType) -#endif // Internal // -------- @@ -112,8 +118,4 @@ public slots: } // namespace Core } // namespace Zeal -#if QT_VERSION < 0x050500 -Q_DECLARE_METATYPE(Zeal::Core::Settings::ProxyType) -#endif - #endif // SETTINGS_H diff --git a/src/libs/registry/CMakeLists.txt b/src/libs/registry/CMakeLists.txt new file mode 100644 index 000000000..3372711f3 --- /dev/null +++ b/src/libs/registry/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(Registry + cancellationtoken.h + docset.cpp + docsetmetadata.cpp + docsetregistry.cpp + listmodel.cpp + searchmodel.cpp + searchquery.cpp + searchresult.h # Only for Qt Creator to see it. +) + +find_package(Qt5 COMPONENTS Concurrent Gui Network REQUIRED) +target_link_libraries(Registry Util Qt5::Concurrent Qt5::Gui Qt5::Network) diff --git a/src/libs/registry/cancellationtoken.cpp b/src/libs/registry/cancellationtoken.cpp deleted file mode 100644 index b767c4c52..000000000 --- a/src/libs/registry/cancellationtoken.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Artur Spychaj -** 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 "cancellationtoken.h" - -using namespace Zeal::Registry; - -CancellationToken::CancellationToken() -{ - m_cancelled = QSharedPointer(new bool(false)); -} - -void CancellationToken::cancel() -{ - *m_cancelled = true; -} - -bool CancellationToken::isCanceled() const -{ - return *m_cancelled; -} diff --git a/src/libs/registry/cancellationtoken.h b/src/libs/registry/cancellationtoken.h index 585a87e87..21f311063 100644 --- a/src/libs/registry/cancellationtoken.h +++ b/src/libs/registry/cancellationtoken.h @@ -23,27 +23,26 @@ #ifndef CANCELLATIONTOKEN_H #define CANCELLATIONTOKEN_H -#include +#include namespace Zeal { namespace Registry { /// Token that stores whether cancel was called on it. /// In async code can be used to check if another thread called cancel. -struct CancellationToken +class CancellationToken { public: - CancellationToken(); - bool isCanceled() const; - void cancel(); + inline bool isCanceled() const { return m_canceled; } + + inline void cancel() { m_canceled = true; } + inline void reset() { m_canceled = false; } private: - QSharedPointer m_cancelled; + std::atomic_bool m_canceled; }; } // namespace Registry } // namespace Zeal -Q_DECLARE_METATYPE(Zeal::Registry::CancellationToken) - #endif // CANCELLATIONTOKEN_H diff --git a/src/libs/registry/docset.cpp b/src/libs/registry/docset.cpp index c6e7395da..dc7afd14e 100644 --- a/src/libs/registry/docset.cpp +++ b/src/libs/registry/docset.cpp @@ -27,16 +27,20 @@ #include "searchresult.h" #include +#include #include #include +#include #include #include #include -#include -#include -#include #include +#include + +#include + +#include using namespace Zeal::Registry; @@ -57,6 +61,8 @@ const char DocSetPlatformFamily[] = "DocSetPlatformFamily"; } } +static void sqliteScoreFunction(sqlite3_context *context, int argc, sqlite3_value **argv); + Docset::Docset(const QString &path) : m_path(path) { @@ -68,7 +74,7 @@ Docset::Docset(const QString &path) : // Attempt to find the icon in any supported format for (const QString &iconFile : dir.entryList({QStringLiteral("icon.*")}, QDir::Files)) { - m_icon = QIcon(dir.absoluteFilePath(iconFile)); + m_icon = QIcon(dir.filePath(iconFile)); if (!m_icon.availableSizes().isEmpty()) break; } @@ -82,9 +88,9 @@ Docset::Docset(const QString &path) : // /Articles/ConfigFiles.html Util::Plist plist; if (dir.exists(QStringLiteral("Info.plist"))) - plist.read(dir.absoluteFilePath(QStringLiteral("Info.plist"))); + plist.read(dir.filePath(QStringLiteral("Info.plist"))); else if (dir.exists(QStringLiteral("info.plist"))) - plist.read(dir.absoluteFilePath(QStringLiteral("info.plist"))); + plist.read(dir.filePath(QStringLiteral("info.plist"))); else return; @@ -93,7 +99,7 @@ Docset::Docset(const QString &path) : if (m_name.isEmpty()) { // Fallback if meta.json is absent - if (!plist.contains(InfoPlist::CFBundleName)) { + if (plist.contains(InfoPlist::CFBundleName)) { m_name = m_title = plist[InfoPlist::CFBundleName].toString(); // TODO: Remove when MainWindow::docsetName() will not use directory name m_name.replace(QLatin1Char(' '), QLatin1Char('_')); @@ -116,18 +122,24 @@ Docset::Docset(const QString &path) : if (!dir.cd(QStringLiteral("Resources")) || !dir.exists(QStringLiteral("docSet.dsidx"))) return; - QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_name); - db.setDatabaseName(dir.absoluteFilePath(QStringLiteral("docSet.dsidx"))); + m_db = new Util::SQLiteDatabase(dir.filePath(QStringLiteral("docSet.dsidx"))); - if (!db.open()) { - qWarning("SQL Error: %s", qPrintable(db.lastError().text())); + if (!m_db->isOpen()) { + qWarning("SQL Error: %s", qPrintable(m_db->lastError())); return; } - m_type = db.tables().contains(QStringLiteral("searchIndex")) ? Type::Dash : Type::ZDash; + sqlite3_create_function(m_db->handle(), "zealScore", 2, SQLITE_UTF8, nullptr, + sqliteScoreFunction, nullptr, nullptr); + + m_type = m_db->tables().contains(QStringLiteral("searchIndex")) ? Type::Dash : Type::ZDash; createIndex(); + if (m_type == Docset::Type::ZDash) { + createView(); + } + if (!dir.cd(QStringLiteral("Documents"))) { m_type = Type::Invalid; return; @@ -166,7 +178,7 @@ Docset::Docset(const QString &path) : Docset::~Docset() { - QSqlDatabase::removeDatabase(m_name); + delete m_db; } bool Docset::isValid() const @@ -206,7 +218,7 @@ QString Docset::path() const QString Docset::documentPath() const { - return QDir(m_path).absoluteFilePath(QStringLiteral("Contents/Resources/Documents")); + return QDir(m_path).filePath(QStringLiteral("Contents/Resources/Documents")); } QIcon Docset::icon() const @@ -246,45 +258,46 @@ const QMap &Docset::symbols(const QString &symbolType) const QList Docset::search(const QString &query, const CancellationToken &token) const { - // 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; + QString sql; 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); + if (m_fuzzySearchEnabled) { + sql = QStringLiteral("SELECT name, type, path, '', zealScore('%1', name) as score" + " FROM searchIndex" + " WHERE score > 0"); + } else { + sql = QStringLiteral("SELECT name, type, path, ''" + " FROM searchIndex" + " WHERE (name LIKE '%%1%' ESCAPE '\\')"); + } } 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); + if (m_fuzzySearchEnabled) { + sql = QStringLiteral("SELECT name, type, path, fragment, zealScore('%1', name) as score" + " FROM searchIndex" + " WHERE score > 0"); + } else { + sql = QStringLiteral("SELECT name, type, path, fragment" + " FROM searchIndex" + " WHERE (name LIKE '%%1%' ESCAPE '\\')"); + } } // Limit for very short queries. // TODO: Show a notification about the reduced result set. - if (query.size() < 3) - queryStr += QLatin1String(" LIMIT 1000"); + if (query.size() < 3) { + sql += QLatin1String(" LIMIT 1000"); + } - QList results; + // Make it safe to use in a SQL query. + QString sanitizedQuery = query; + sanitizedQuery.replace(QLatin1Char('\''), QLatin1String("''")); + m_db->prepare(sql.arg(sanitizedQuery)); - 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())}); + QList results; + while (m_db->next() && !token.isCanceled()) { + results.append({m_db->value(0).toString(), + parseSymbolType(m_db->value(1).toString()), + m_db->value(2).toString(), m_db->value(3).toString(), + const_cast(this), m_db->value(4).toInt()}); } return results; @@ -305,25 +318,23 @@ QList Docset::relatedLinks(const QUrl &url) const cleanUrl.setFragment(QString()); // Prepare the query to look up all pages with the same url. - QString queryStr; + QString sql; if (m_type == Docset::Type::Dash) { - queryStr = QStringLiteral("SELECT name, type, path FROM searchIndex " - "WHERE path LIKE \"%1%%\" AND path <> \"%1\""); + sql = QStringLiteral("SELECT name, type, path" + " FROM searchIndex" + " WHERE path LIKE \"%1%%\" AND path <> \"%1\""); } else if (m_type == Docset::Type::ZDash) { - queryStr = QStringLiteral("SELECT ztoken.ztokenname, ztokentype.ztypename, zfilepath.zpath, ztokenmetainformation.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 zfilepath.zpath = \"%1\" AND ztokenmetainformation.zanchor IS NOT NULL"); + sql = QStringLiteral("SELECT name, type, path, fragment" + " FROM searchIndex" + " WHERE path = \"%1\" AND fragment IS NOT NULL"); } - 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())}); + m_db->prepare(sql.arg(cleanUrl.toString())); + while (m_db->next()) { + results.append({m_db->value(0).toString(), + parseSymbolType(m_db->value(1).toString()), + m_db->value(2).toString(), m_db->value(3).toString(), + const_cast(this), 0}); } if (results.size() == 1) @@ -332,9 +343,9 @@ QList Docset::relatedLinks(const QUrl &url) const return results; } -QSqlDatabase Docset::database() const +QUrl Docset::searchResultUrl(const SearchResult &result) const { - return QSqlDatabase::database(m_name, true); + return createPageUrl(result.urlPath, result.urlFragment); } void Docset::loadMetadata() @@ -362,31 +373,33 @@ void Docset::loadMetadata() if (jsonObject.contains(QStringLiteral("extra"))) { const QJsonObject extra = jsonObject[QStringLiteral("extra")].toObject(); - m_indexFileUrl = createPageUrl(extra[QStringLiteral("indexFilePath")].toString()); + + if (extra.contains(QStringLiteral("indexFilePath"))) { + m_indexFileUrl = createPageUrl(extra[QStringLiteral("indexFilePath")].toString()); + } + + if (extra.contains(QStringLiteral("keywords"))) { + for (const QJsonValueRef kw : extra[QStringLiteral("keywords")].toArray()) + m_keywords << kw.toString(); + } } } void Docset::countSymbols() { - QString queryStr; - 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 LEFT JOIN ztokentype" - " ON ztoken.ztokentype = ztokentype.z_pk GROUP BY ztypename"); - } - - QSqlQuery query(queryStr, database()); - if (query.lastError().type() != QSqlError::NoError) { - qWarning("SQL Error: %s", qPrintable(query.lastError().text())); + static const QString sql = QStringLiteral("SELECT type, COUNT(*)" + " FROM searchIndex" + " GROUP BY type"); + if (!m_db->prepare(sql)) { + qWarning("SQL Error: %s", qPrintable(m_db->lastError())); return; } - while (query.next()) { - const QString symbolTypeStr = query.value(0).toString(); + while (m_db->next()) { + const QString symbolTypeStr = m_db->value(0).toString(); const QString symbolType = parseSymbolType(symbolTypeStr); m_symbolStrings.insertMulti(symbolType, symbolTypeStr); - m_symbolCounts[symbolType] += query.value(1).toInt(); + m_symbolCounts[symbolType] += m_db->value(1).toInt(); } } @@ -399,28 +412,28 @@ void Docset::loadSymbols(const QString &symbolType) const void Docset::loadSymbols(const QString &symbolType, const QString &symbolString) const { - QString queryStr; + QString sql; if (m_type == Docset::Type::Dash) { - queryStr = QStringLiteral("SELECT name, path FROM searchIndex WHERE type='%1' ORDER BY name ASC"); + sql = QStringLiteral("SELECT name, path" + " FROM searchIndex" + " WHERE type='%1'" + " ORDER BY name"); } else { - 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"); + sql = QStringLiteral("SELECT name, path, fragment" + " FROM searchIndex" + " WHERE type='%1'" + " ORDER BY name"); } - QSqlQuery query(queryStr.arg(symbolString), database()); - if (query.lastError().type() != QSqlError::NoError) { - qWarning("SQL Error: %s", qPrintable(query.lastError().text())); + if (!m_db->prepare(sql.arg(symbolString))) { + qWarning("SQL Error: %s", qPrintable(m_db->lastError())); return; } QMap &symbols = m_symbols[symbolType]; - while (query.next()) - symbols.insertMulti(query.value(0).toString(), - createPageUrl(query.value(1).toString(), query.value(2).toString())); + while (m_db->next()) + symbols.insertMulti(m_db->value(0).toString(), + createPageUrl(m_db->value(1).toString(), m_db->value(2).toString())); } void Docset::createIndex() @@ -430,19 +443,17 @@ void Docset::createIndex() static const QString indexCreateQuery = QStringLiteral("CREATE INDEX IF NOT EXISTS %1%2" " 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)); + m_db->prepare(indexListQuery.arg(tableName)); QStringList oldIndexes; - while (query.next()) { - const QString indexName = query.value(1).toString(); + while (m_db->next()) { + const QString indexName = m_db->value(1).toString(); if (!indexName.startsWith(IndexNamePrefix)) continue; @@ -454,9 +465,29 @@ void Docset::createIndex() // Drop old indexes for (const QString &oldIndexName : oldIndexes) - query.exec(indexDropQuery.arg(oldIndexName)); + m_db->execute(indexDropQuery.arg(oldIndexName)); - query.exec(indexCreateQuery.arg(IndexNamePrefix, IndexNameVersion, tableName, columnName)); + m_db->execute(indexCreateQuery.arg(IndexNamePrefix, IndexNameVersion, tableName, columnName)); +} + +void Docset::createView() +{ + static const QString viewCreateQuery + = QStringLiteral("CREATE VIEW IF NOT EXISTS searchIndex AS" + " SELECT" + " ztokenname AS name," + " ztypename AS type," + " zpath AS path," + " zanchor AS fragment" + " FROM ztoken" + " INNER JOIN ztokenmetainformation" + " ON ztoken.zmetainformation = ztokenmetainformation.z_pk" + " INNER JOIN zfilepath" + " ON ztokenmetainformation.zfile = zfilepath.z_pk" + " INNER JOIN ztokentype" + " ON ztoken.ztokentype = ztokentype.z_pk"); + + m_db->execute(viewCreateQuery); } QUrl Docset::createPageUrl(const QString &path, const QString &fragment) const @@ -478,12 +509,14 @@ QUrl Docset::createPageUrl(const QString &path, const QString &fragment) const realPath.remove(dashEntryRegExp); realFragment.remove(dashEntryRegExp); - QUrl url = QUrl::fromLocalFile(QDir(documentPath()).absoluteFilePath(realPath)); + QUrl url = QUrl::fromLocalFile(QDir(documentPath()).filePath(realPath)); if (!realFragment.isEmpty()) { - if (realFragment.startsWith("//apple_ref")) + if (realFragment.startsWith(QLatin1String("//apple_ref")) + || realFragment.startsWith(QLatin1String("//dash_ref"))) { url.setFragment(realFragment, QUrl::DecodedMode); - else + } else { url.setFragment(realFragment); + } } return url; @@ -619,3 +652,260 @@ QString Docset::parseSymbolType(const QString &str) return aliases.value(str, str); } + +bool Docset::isFuzzySearchEnabled() const +{ + return m_fuzzySearchEnabled; +} + +void Docset::setFuzzySearchEnabled(bool enabled) +{ + m_fuzzySearchEnabled = enabled; +} + +/** + * \brief Returns score based on a substring position in a string. + * \param str Original string. + * \param index Index of the substring within \a str. + * \param length Substring length. + * \return Score value between 1 and 100. + */ +static int scoreFuzzy(const char *str, int index, int length) +{ + if (index == 0 || str[index - 1] == '.') { + // score between 66..99, if the match follows a dot, or starts the string + return qMax(66, 100 - length); + } else if (str[index + length] == 0) { + // score between 33..66, if the match is at the end of the string + return qMax(33, 67 - length); + } else { + // score between 1..33 otherwise (match in the middle of the string) + return qMax(1, 34 - length); + } +} + +// Based on https://github.com/bevacqua/fuzzysearch +static void matchFuzzy(const char *needle, int needleLength, + const char *haystack, int haystackLength, + int *start, int *length) +{ + static const int MaxDistance = 8; + static const int MaxGroupCount = 3; + + *start = -1; + + int groupCount = 0; + int bestRecursiveScore = -1; + int bestRecursiveStart = -1; + int bestRecursiveLength = -1; + + for (int i = 0, j = 0; i < needleLength; ++i) { + bool found = false; + bool first = true; + int distance = 0; + + while (j < haystackLength) { + if (needle[i] == haystack[j++]) { + if (*start == -1) { + *start = j; // first matched char + + // try starting the search later in case the first character occurs again later + int recursiveStart; + int recursiveLength; + matchFuzzy(needle, needleLength, haystack + j, + haystackLength - j, + &recursiveStart, &recursiveLength); + if (recursiveStart != -1) { + int recursiveScore = scoreFuzzy(haystack, + recursiveStart, + recursiveLength); + if (recursiveScore > bestRecursiveScore) { + bestRecursiveScore = recursiveScore; + bestRecursiveStart = recursiveStart; + bestRecursiveLength = recursiveLength; + } + } + } + + *length = j - *start + 1; + found = true; + break; + } + + // Optimizations to reduce returned number of results + // (search was returning too many irrelevant results with large docsets) + // Optimization #1: too many mismatches. + if (first) { + if (++groupCount >= MaxGroupCount) { + break; + } + + first = false; + } + + // Optimization #2: too large distance between found chars. + if (i != 0 && ++distance >= MaxDistance) { + break; + } + } + + if (!found) { + // End of haystack, char not found. + if (bestRecursiveScore != -1) { + // Can still match with the same constraints if matching started later + // (smaller distance from first char to 2nd char) + *start = bestRecursiveStart; + *length = bestRecursiveLength; + } else { + *start = -1; + } + return; + } + } + + int score = scoreFuzzy(haystack, *start, *length); + if (bestRecursiveScore > score) { + *start = bestRecursiveStart; + *length = bestRecursiveLength; + } +} + +// Ported from DevDocs (https://github.com/Thibaut/devdocs), see app/searcher.coffee. +static int scoreExact(int matchIndex, int matchLen, const char *value, int valueLen) +{ + static const char DOT = '.'; + + int score = 100; + + // Remove one point for each unmatched character. + score -= (valueLen - matchLen); + + if (matchIndex > 0) { + if (value[matchIndex - 1] == DOT) { + // If the character preceding the query is a dot, assign the same + // score as if the query was found at the beginning of the string, + // minus one. + score += matchIndex - 1; + } else if (matchLen == 1) { + // Don't match a single-character query unless it's found at the + // beginning of the string or is preceded by a dot. + return 0; + } else { + // (1) Remove one point for each unmatched character up to + // the nearest preceding dot or the beginning of the + // string. + // (2) Remove one point for each unmatched character + // following the query. + int i = matchIndex - 2; + while (i >= 0 && value[i] != DOT) + --i; + + score -= (matchIndex - i) // (1) + + (valueLen - matchLen - matchIndex); // (2) + } + + // Remove one point for each dot preceding the query, except for the + // one immediately before the query. + for (int i = matchIndex - 2; i >= 0; --i) { + if (value[i] == DOT) + --score; + } + } + + // Remove five points for each dot following the query. + for (int i = valueLen - matchLen - matchIndex - 1; i >= 0; --i) { + if (value[matchIndex + matchLen + i] == DOT) + score -= 5; + } + + return qMax(1, score); +} + +static inline int scoreFunction(const char *needleOrig, const char *haystackOrig) +{ + const int needleLength = static_cast(qstrlen(needleOrig)); + const int haystackLength = static_cast(qstrlen(haystackOrig)); + + QVarLengthArray needle(needleLength + 1); + QVarLengthArray haystack(haystackLength + 1); + + for (int i = 0, j = 0; i <= needleLength; ++i, ++j) { + const char c = needleOrig[i]; + if ((i > 0 && needleOrig[i - 1] == ':' && c == ':') // C++ (::) + || c == '/' || c == '_' || c == ' ') { // Go, some Guides + needle[j] = '.'; + } else if (c >= 'A' && c <= 'Z') { + needle[j] = c + 32; + } else { + needle[j] = c; + } + } + + for (int i = 0, j = 0; i <= haystackLength; ++i, ++j) { + const char c = haystackOrig[i]; + if ((i > 0 && haystackOrig[i - 1] == ':' && c == ':') // C++ (::) + || c == '/' || c == '_' || c == ' ') { // Go, some Guides + haystack[j] = '.'; + } else if (c >= 'A' && c <= 'Z') { + haystack[j] = c + 32; + } else { + haystack[j] = c; + } + } + + int score = 0; + int matchIndex = -1; + int matchLength = 0; + int exactIndex = -1; + const char *exactMatch = std::strstr(haystack.data(), needle.data()); + + if (exactMatch != nullptr) { + exactIndex = exactMatch - haystack.data(); + } + + if (exactIndex == -1) { + matchFuzzy(needle.data(), needleLength, + haystack.data(), haystackLength, + &matchIndex, &matchLength); + } + + if (matchIndex == -1 && exactIndex == -1) { // no match + // simply return 0 + return 0; + } else if (exactIndex != -1) { + // +100 to make sure exact matches are always on top. + score = scoreExact(exactIndex, needleLength, haystack.data(), haystackLength) + 100; + } else { + score = scoreFuzzy(haystack.data(), matchIndex, matchLength); + + int indexOfLastDot; + for (indexOfLastDot = haystackLength - 1; indexOfLastDot >= 0; --indexOfLastDot) { + if (haystack[indexOfLastDot] == '.') + break; + } + + if (indexOfLastDot != -1) { + matchIndex = -1; + matchFuzzy(needle.data(), needleLength, + haystack.data() + indexOfLastDot + 1, haystackLength - (indexOfLastDot + 1), + &matchIndex, &matchLength); + + if (matchIndex != -1) { + score = qMax(score, scoreFuzzy(haystack.data() + indexOfLastDot + 1, + matchIndex, matchLength)); + } + } + } + + return score; +} + +static void sqliteScoreFunction(sqlite3_context *context, int argc, sqlite3_value **argv) +{ + Q_UNUSED(argc); + + const char *needle = reinterpret_cast(sqlite3_value_text(argv[0])); + const char *haystack = reinterpret_cast(sqlite3_value_text(argv[1])); + + sqlite3_result_int(context, scoreFunction(needle, haystack)); +} diff --git a/src/libs/registry/docset.h b/src/libs/registry/docset.h index 0f2960feb..e599a64e9 100644 --- a/src/libs/registry/docset.h +++ b/src/libs/registry/docset.h @@ -29,12 +29,15 @@ #include #include -class QSqlDatabase; - namespace Zeal { + +namespace Util { +class SQLiteDatabase; +} + namespace Registry { -struct CancellationToken; +class CancellationToken; struct SearchResult; class Docset @@ -66,9 +69,15 @@ class Docset QList search(const QString &query, const CancellationToken &token) const; QList relatedLinks(const QUrl &url) const; + // FIXME: This a temporary solution to create URL on demand. + QUrl searchResultUrl(const SearchResult &result) const; + // FIXME: This is an ugly workaround before we have a proper docset sources implementation bool hasUpdate = false; + bool isFuzzySearchEnabled() const; + void setFuzzySearchEnabled(bool enabled); + private: enum class Type { Invalid, @@ -76,17 +85,16 @@ class Docset ZDash }; - QSqlDatabase database() const; void loadMetadata(); void countSymbols(); void loadSymbols(const QString &symbolType) const; void loadSymbols(const QString &symbolType, const QString &symbolString) const; void createIndex(); + void createView(); QUrl createPageUrl(const QString &path, const QString &fragment = QString()) const; static QString parseSymbolType(const QString &str); - QString m_sourceId; QString m_name; QString m_title; QStringList m_keywords; @@ -101,6 +109,8 @@ class Docset QMap m_symbolStrings; QMap m_symbolCounts; mutable QMap> m_symbols; + Util::SQLiteDatabase *m_db = nullptr; + bool m_fuzzySearchEnabled = false; }; } // namespace Registry diff --git a/src/libs/registry/docsetmetadata.cpp b/src/libs/registry/docsetmetadata.cpp index 42d3b392e..14864be58 100644 --- a/src/libs/registry/docsetmetadata.cpp +++ b/src/libs/registry/docsetmetadata.cpp @@ -38,8 +38,6 @@ DocsetMetadata::DocsetMetadata() DocsetMetadata::DocsetMetadata(const QJsonObject &jsonObject) { - m_sourceId = jsonObject[QStringLiteral("sourceId")].toString(); - m_name = jsonObject[QStringLiteral("name")].toString(); m_title = jsonObject[QStringLiteral("title")].toString(); @@ -48,31 +46,26 @@ DocsetMetadata::DocsetMetadata(const QJsonObject &jsonObject) m_rawIcon2x = QByteArray::fromBase64(jsonObject[QStringLiteral("icon2x")].toString() .toLocal8Bit()); - // TODO: Check on a high-resolution screen if (qApp->devicePixelRatio() > 1.0) { QPixmap pixmap = QPixmap::fromImage(QImage::fromData(m_rawIcon2x)); pixmap.setDevicePixelRatio(2.0); m_icon.addPixmap(pixmap); } - for (const QJsonValue &vv : jsonObject[QStringLiteral("aliases")].toArray()) + for (const QJsonValueRef vv : jsonObject[QStringLiteral("aliases")].toArray()) m_aliases << vv.toString(); - for (const QJsonValue &vv : jsonObject[QStringLiteral("versions")].toArray()) + for (const QJsonValueRef vv : jsonObject[QStringLiteral("versions")].toArray()) m_versions << vv.toString(); + m_revision = jsonObject[QStringLiteral("revision")].toString(); m_feedUrl = QUrl(jsonObject[QStringLiteral("feed_url")].toString()); - const QJsonArray urlArray = jsonObject[QStringLiteral("urls")].toArray(); - for (const QJsonValue &url : urlArray) - m_urls.append(QUrl(url.toString())); - m_extra = jsonObject[QStringLiteral("extra")].toObject(); -} + for (const QJsonValueRef vv : jsonObject[QStringLiteral("urls")].toArray()) + m_urls.append(QUrl(vv.toString())); -QString DocsetMetadata::sourceId() const -{ - return m_sourceId; + m_extra = jsonObject[QStringLiteral("extra")].toObject(); } /*! @@ -86,7 +79,6 @@ void DocsetMetadata::save(const QString &path, const QString &version) QJsonObject jsonObject; - jsonObject[QStringLiteral("sourceId")] = m_sourceId; jsonObject[QStringLiteral("name")] = m_name; jsonObject[QStringLiteral("title")] = m_title; @@ -182,7 +174,6 @@ QList DocsetMetadata::urls() const DocsetMetadata DocsetMetadata::fromDashFeed(const QUrl &feedUrl, const QByteArray &data) { DocsetMetadata metadata; - metadata.m_sourceId = QStringLiteral("dash-feed"); metadata.m_name = feedUrl.fileName(); metadata.m_name.chop(4); // Strip ".xml" extension @@ -196,7 +187,7 @@ DocsetMetadata DocsetMetadata::fromDashFeed(const QUrl &feedUrl, const QByteArra while (!xml.atEnd()) { const QXmlStreamReader::TokenType token = xml.readNext(); - if (token == QXmlStreamReader::StartDocument || token != QXmlStreamReader::StartElement) + if (token != QXmlStreamReader::StartElement) continue; // Try to pull out the relevant data diff --git a/src/libs/registry/docsetmetadata.h b/src/libs/registry/docsetmetadata.h index 5672bf769..13405df69 100644 --- a/src/libs/registry/docsetmetadata.h +++ b/src/libs/registry/docsetmetadata.h @@ -38,8 +38,6 @@ class DocsetMetadata explicit DocsetMetadata(); explicit DocsetMetadata(const QJsonObject &jsonObject); - QString sourceId() const; - void save(const QString &path, const QString &version); QString name() const; @@ -57,8 +55,6 @@ class DocsetMetadata static DocsetMetadata fromDashFeed(const QUrl &feedUrl, const QByteArray &data); private: - QString m_sourceId; - QString m_name; QString m_title; QStringList m_aliases; diff --git a/src/libs/registry/docsetregistry.cpp b/src/libs/registry/docsetregistry.cpp index adc234c94..b61617aec 100644 --- a/src/libs/registry/docsetregistry.cpp +++ b/src/libs/registry/docsetregistry.cpp @@ -23,7 +23,7 @@ #include "docsetregistry.h" -#include "cancellationtoken.h" +#include "docset.h" #include "searchquery.h" #include "searchresult.h" @@ -46,7 +46,6 @@ DocsetRegistry::DocsetRegistry(QObject *parent) : m_thread(new QThread(this)) { // Register for use in signal connections. - qRegisterMetaType("CancellationToken"); qRegisterMetaType>("QList"); // FIXME: Only search should be performed in a separate thread @@ -61,14 +60,44 @@ DocsetRegistry::~DocsetRegistry() qDeleteAll(m_docsets); } -void DocsetRegistry::init(const QString &path) +QString DocsetRegistry::storagePath() const { - for (const QString &name : m_docsets.keys()) + return m_storagePath; +} + +void DocsetRegistry::setStoragePath(const QString &path) +{ + if (path == m_storagePath) { + return; + } + + m_storagePath = path; + + for (const QString &name : m_docsets.keys()) { remove(name); + } addDocsetsFromFolder(path); } +bool DocsetRegistry::isFuzzySearchEnabled() const +{ + return m_fuzzySearchEnabled; +} + +void DocsetRegistry::setFuzzySearchEnabled(bool enabled) +{ + if (enabled == m_fuzzySearchEnabled) { + return; + } + + m_fuzzySearchEnabled = enabled; + + for (Docset *docset : m_docsets) { + docset->setFuzzySearchEnabled(enabled); + } +} + int DocsetRegistry::count() const { return m_docsets.count(); @@ -110,43 +139,56 @@ QList DocsetRegistry::docsets() const void DocsetRegistry::addDocset(const QString &path) { - QMetaObject::invokeMethod(this, "_addDocset", Qt::BlockingQueuedConnection, - Q_ARG(QString, path)); + QFutureWatcher *watcher = new QFutureWatcher(); + connect(watcher, &QFutureWatcher::finished, this, [this, watcher] { + QScopedPointer, QScopedPointerDeleteLater> guard(watcher); + + Docset *docset = watcher->result(); + // TODO: Emit error + if (!docset->isValid()) { + qWarning("Could not load docset from '%s'. Reinstall the docset.", + qPrintable(docset->path())); + delete docset; + return; + } + + docset->setFuzzySearchEnabled(m_fuzzySearchEnabled); + + const QString name = docset->name(); + if (m_docsets.contains(name)) { + remove(name); + } + + m_docsets[name] = docset; + emit docsetAdded(name); + }); + + watcher->setFuture(QtConcurrent::run([path] { + return new Docset(path); + })); } -void DocsetRegistry::_addDocset(const QString &path) +void DocsetRegistry::search(const QString &query) { - Docset *docset = new Docset(path); + m_cancellationToken.cancel(); - // TODO: Emit error - if (!docset->isValid()) { - qWarning("Could not load docset from '%s'. Please reinstall the docset.", qPrintable(path)); - delete docset; + if (query.isEmpty()) { + emit searchCompleted({}); return; } - const QString name = docset->name(); - - if (m_docsets.contains(name)) - remove(name); - - m_docsets[name] = docset; - emit docsetAdded(name); + QMetaObject::invokeMethod(this, "_runQuery", Qt::QueuedConnection, Q_ARG(QString, query)); } -void DocsetRegistry::search(const QString &query, const CancellationToken &token) +void DocsetRegistry::_runQuery(const QString &query) { - QMetaObject::invokeMethod(this, "_runQuery", Qt::QueuedConnection, - Q_ARG(QString, query), Q_ARG(CancellationToken, token)); -} + m_cancellationToken.reset(); -void DocsetRegistry::_runQuery(const QString &query, const CancellationToken &token) -{ QList enabledDocsets; const SearchQuery searchQuery = SearchQuery::fromString(query); if (searchQuery.hasKeywords()) { - for (Docset *docset : docsets()) { + for (Docset *docset : m_docsets) { if (searchQuery.hasKeywords(docset->keywords())) enabledDocsets << docset; } @@ -158,19 +200,20 @@ void DocsetRegistry::_runQuery(const QString &query, const CancellationToken &to = QtConcurrent::mappedReduced(enabledDocsets, std::bind(&Docset::search, std::placeholders::_1, - searchQuery.query(), token), + searchQuery.query(), + std::ref(m_cancellationToken)), &MergeQueryResults); QList results = queryResultsFuture.result(); - if (token.isCanceled()) + if (m_cancellationToken.isCanceled()) return; std::sort(results.begin(), results.end()); - if (token.isCanceled()) + if (m_cancellationToken.isCanceled()) return; - emit queryCompleted(results); + emit searchCompleted(results); } // Recursively finds and adds all docsets in a given directory. @@ -179,8 +222,8 @@ void DocsetRegistry::addDocsetsFromFolder(const QString &path) const QDir dir(path); for (const QFileInfo &subdir : dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs)) { if (subdir.suffix() == QLatin1String("docset")) - addDocset(subdir.absoluteFilePath()); + addDocset(subdir.filePath()); else - addDocsetsFromFolder(subdir.absoluteFilePath()); + addDocsetsFromFolder(subdir.filePath()); } } diff --git a/src/libs/registry/docsetregistry.h b/src/libs/registry/docsetregistry.h index 2b8bcadc4..462d7cc55 100644 --- a/src/libs/registry/docsetregistry.h +++ b/src/libs/registry/docsetregistry.h @@ -24,16 +24,17 @@ #ifndef DOCSETREGISTRY_H #define DOCSETREGISTRY_H -#include "docset.h" +#include "cancellationtoken.h" #include +#include class QThread; namespace Zeal { namespace Registry { -struct CancellationToken; +class Docset; struct SearchResult; class DocsetRegistry : public QObject @@ -43,7 +44,11 @@ class DocsetRegistry : public QObject explicit DocsetRegistry(QObject *parent = nullptr); ~DocsetRegistry() override; - void init(const QString &path); + QString storagePath() const; + void setStoragePath(const QString &path); + + bool isFuzzySearchEnabled() const; + void setFuzzySearchEnabled(bool enabled); int count() const; bool contains(const QString &name) const; @@ -54,7 +59,7 @@ class DocsetRegistry : public QObject Docset *docset(int index) const; QList docsets() const; - void search(const QString &query, const CancellationToken &token); + void search(const QString &query); const QList &queryResults(); public slots: @@ -64,17 +69,21 @@ public slots: void docsetAdded(const QString &name); void docsetAboutToBeRemoved(const QString &name); void docsetRemoved(const QString &name); - void queryCompleted(const QList &results); + void searchCompleted(const QList &results); private slots: - void _addDocset(const QString &path); - void _runQuery(const QString &query, const CancellationToken &token); + void _runQuery(const QString &query); private: void addDocsetsFromFolder(const QString &path); + QString m_storagePath; + bool m_fuzzySearchEnabled = false; + QThread *m_thread = nullptr; QMap m_docsets; + + CancellationToken m_cancellationToken; }; } // namespace Registry diff --git a/src/libs/registry/itemdatarole.h b/src/libs/registry/itemdatarole.h new file mode 100644 index 000000000..faf31a81a --- /dev/null +++ b/src/libs/registry/itemdatarole.h @@ -0,0 +1,19 @@ +#ifndef ZEAL_REGISTRY_ITEMDATAROLE_H +#define ZEAL_REGISTRY_ITEMDATAROLE_H + +#include + +namespace Zeal { +namespace Registry { + +enum ItemDataRole { + DocsetIconRole = Qt::UserRole, + DocsetNameRole, + UpdateAvailableRole, + UrlRole +}; + +} // namespace Registry +} // namespace Zeal + +#endif // ZEAL_REGISTRY_ITEMDATAROLE_H diff --git a/src/libs/registry/listmodel.cpp b/src/libs/registry/listmodel.cpp index 863379994..f456a08a8 100644 --- a/src/libs/registry/listmodel.cpp +++ b/src/libs/registry/listmodel.cpp @@ -25,6 +25,7 @@ #include "docset.h" #include "docsetregistry.h" +#include "itemdatarole.h" using namespace Zeal::Registry; @@ -72,10 +73,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: switch (indexLevel(index)) { case Level::DocsetLevel: - if (!index.column()) - return m_docsetRegistry->docset(index.row())->title(); - else - return m_docsetRegistry->docset(index.row())->indexFileUrl(); + return m_docsetRegistry->docset(index.row())->title(); case Level::GroupLevel: { DocsetItem *docsetItem = reinterpret_cast(index.internalPointer()); const QString symbolType = docsetItem->groups.at(index.row())->symbolType; @@ -85,19 +83,28 @@ QVariant ListModel::data(const QModelIndex &index, int role) const case Level::SymbolLevel: { GroupItem *groupItem = reinterpret_cast(index.internalPointer()); auto it = groupItem->docsetItem->docset->symbols(groupItem->symbolType).cbegin() + index.row(); - if (!index.column()) - return it.key(); - else - return it.value(); + return it.key(); } default: return QVariant(); } - case DocsetNameRole: + case ItemDataRole::UrlRole: + switch (indexLevel(index)) { + case Level::DocsetLevel: + return m_docsetRegistry->docset(index.row())->indexFileUrl(); + case Level::SymbolLevel: { + GroupItem *groupItem = reinterpret_cast(index.internalPointer()); + auto it = groupItem->docsetItem->docset->symbols(groupItem->symbolType).cbegin() + index.row(); + return it.value(); + } + default: + return QVariant(); + } + case ItemDataRole::DocsetNameRole: if (index.parent().isValid()) return QVariant(); return m_docsetRegistry->docset(index.row())->name(); - case UpdateAvailableRole: + case ItemDataRole::UpdateAvailableRole: if (index.parent().isValid()) return QVariant(); return m_docsetRegistry->docset(index.row())->hasUpdate; @@ -145,10 +152,8 @@ QModelIndex ListModel::parent(const QModelIndex &child) const int ListModel::columnCount(const QModelIndex &parent) const { - if (indexLevel(parent) == Level::DocsetLevel) - return 1; - else - return 2; + Q_UNUSED(parent); + return 1; } int ListModel::rowCount(const QModelIndex &parent) const diff --git a/src/libs/registry/listmodel.h b/src/libs/registry/listmodel.h index 2d0ed913e..9eae37150 100644 --- a/src/libs/registry/listmodel.h +++ b/src/libs/registry/listmodel.h @@ -24,7 +24,7 @@ #ifndef LISTMODEL_H #define LISTMODEL_H -#include +#include #include namespace Zeal { @@ -37,12 +37,6 @@ class ListModel : public QAbstractItemModel { Q_OBJECT public: - enum ItemDataRole { - // Do not collide with SearchModel, and ProgressItemDelegate. - DocsetNameRole = Qt::UserRole + 20, - UpdateAvailableRole - }; - explicit ListModel(DocsetRegistry *docsetRegistry, QObject *parent = nullptr); ~ListModel() override; diff --git a/src/libs/registry/registry.pro b/src/libs/registry/registry.pro index 9007b98b0..1c61c1c65 100644 --- a/src/libs/registry/registry.pro +++ b/src/libs/registry/registry.pro @@ -1,6 +1,4 @@ include($$ZEAL_LIBRARY_PRI) -QT += sql - HEADERS += $$files(*.h) SOURCES += $$files(*.cpp) diff --git a/src/libs/registry/searchmodel.cpp b/src/libs/registry/searchmodel.cpp index ba9990dd8..3f70b32ce 100644 --- a/src/libs/registry/searchmodel.cpp +++ b/src/libs/registry/searchmodel.cpp @@ -24,18 +24,17 @@ #include "searchmodel.h" #include "docset.h" - -#include +#include "itemdatarole.h" using namespace Zeal::Registry; SearchModel::SearchModel(QObject *parent) : - QAbstractItemModel(parent) + QAbstractListModel(parent) { } SearchModel::SearchModel(const SearchModel &other) : - QAbstractItemModel(other.d_ptr->parent), + QAbstractListModel(other.d_ptr->parent), m_dataList(other.m_dataList) { } @@ -54,22 +53,17 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::DisplayRole: - switch (index.column()) { - case 0: - return item->name; - case 1: - return item->url; - default: - return QVariant(); - } + return item->name; - case Qt::DecorationRole: { + case Qt::DecorationRole: return item->docset->symbolTypeIcon(item->type); - } case ItemDataRole::DocsetIconRole: return item->docset->icon(); + case ItemDataRole::UrlRole: + return item->docset->searchResultUrl(*item); + default: return QVariant(); } @@ -85,12 +79,6 @@ QModelIndex SearchModel::index(int row, int column, const QModelIndex &parent) c return createIndex(row, column, item); } -QModelIndex SearchModel::parent(const QModelIndex &child) const -{ - Q_UNUSED(child) - return QModelIndex(); -} - int SearchModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) @@ -98,12 +86,6 @@ int SearchModel::rowCount(const QModelIndex &parent) const return 0; } -int SearchModel::columnCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return 2; -} - bool SearchModel::removeRows(int row, int count, const QModelIndex &parent) { if (row + count <= m_dataList.size() && !parent.isValid()) { @@ -139,5 +121,5 @@ void SearchModel::setResults(const QList &results) beginResetModel(); m_dataList = results; endResetModel(); - emit queryCompleted(); + emit updated(); } diff --git a/src/libs/registry/searchmodel.h b/src/libs/registry/searchmodel.h index d2a7dad0a..32446ce4c 100644 --- a/src/libs/registry/searchmodel.h +++ b/src/libs/registry/searchmodel.h @@ -26,20 +26,15 @@ #include "searchresult.h" -#include +#include namespace Zeal { namespace Registry { -class SearchModel : public QAbstractItemModel +class SearchModel : public QAbstractListModel { Q_OBJECT public: - // TODO: Standardise roles across app - enum ItemDataRole { - DocsetIconRole = Qt::UserRole - }; - explicit SearchModel(QObject *parent = nullptr); SearchModel(const SearchModel &other); @@ -47,9 +42,7 @@ class SearchModel : public QAbstractItemModel 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); @@ -57,7 +50,7 @@ public slots: void setResults(const QList &results = QList()); signals: - void queryCompleted(); + void updated(); private: QList m_dataList; diff --git a/src/libs/registry/searchresult.h b/src/libs/registry/searchresult.h index 5ece8073b..f6e9a2fcc 100644 --- a/src/libs/registry/searchresult.h +++ b/src/libs/registry/searchresult.h @@ -37,13 +37,18 @@ struct SearchResult QString name; QString type; + QString urlPath; + QString urlFragment; + Docset *docset; - QUrl url; + int score; inline bool operator<(const SearchResult &other) const { - return QString::compare(name, other.name, Qt::CaseInsensitive) < 0; + if (score == other.score) + return QString::compare(name, other.name, Qt::CaseInsensitive) < 0; + return score > other.score; } }; diff --git a/src/libs/ui/CMakeLists.txt b/src/libs/ui/CMakeLists.txt new file mode 100644 index 000000000..bb473772b --- /dev/null +++ b/src/libs/ui/CMakeLists.txt @@ -0,0 +1,33 @@ +add_subdirectory(qxtglobalshortcut) + +list(APPEND Widgets_SOURCES + widgets/searchablewebview.cpp + widgets/searchedit.cpp + widgets/shortcutedit.cpp + widgets/toolbarframe.cpp + widgets/webview.cpp +) + +set(Ui_FORMS + aboutdialog.ui + docsetsdialog.ui + mainwindow.ui + settingsdialog.ui +) + +add_library(Ui + aboutdialog.cpp + docsetlistitemdelegate.cpp + docsetsdialog.cpp + mainwindow.cpp + progressitemdelegate.cpp + searchitemdelegate.cpp + settingsdialog.cpp + ${Widgets_SOURCES} + ${Ui_FORMS} # For Qt Creator. +) + +target_link_libraries(Ui QxtGlobalShortcut Registry) + +find_package(Qt5 COMPONENTS WebKitWidgets REQUIRED) +target_link_libraries(Ui Qt5::WebKitWidgets) diff --git a/src/libs/ui/aboutdialog.cpp b/src/libs/ui/aboutdialog.cpp index 32ebdfe40..5388ee1b5 100644 --- a/src/libs/ui/aboutdialog.cpp +++ b/src/libs/ui/aboutdialog.cpp @@ -25,6 +25,8 @@ #include +using namespace Zeal::WidgetUi; + AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) diff --git a/src/libs/ui/aboutdialog.h b/src/libs/ui/aboutdialog.h index bf4e0ee95..4c6e38871 100644 --- a/src/libs/ui/aboutdialog.h +++ b/src/libs/ui/aboutdialog.h @@ -20,14 +20,17 @@ ** ****************************************************************************/ -#ifndef ABOUTDIALOG_H -#define ABOUTDIALOG_H +#ifndef ZEAL_WIDGETUI_ABOUTDIALOG_H +#define ZEAL_WIDGETUI_ABOUTDIALOG_H #include +namespace Zeal { +namespace WidgetUi { + namespace Ui { class AboutDialog; -} +} // namespace Ui class AboutDialog : public QDialog { @@ -40,4 +43,7 @@ class AboutDialog : public QDialog Ui::AboutDialog *ui; }; -#endif // ABOUTDIALOG_H +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_ABOUTDIALOG_H diff --git a/src/libs/ui/forms/aboutdialog.ui b/src/libs/ui/aboutdialog.ui similarity index 92% rename from src/libs/ui/forms/aboutdialog.ui rename to src/libs/ui/aboutdialog.ui index 46061a807..9a5de5f64 100644 --- a/src/libs/ui/forms/aboutdialog.ui +++ b/src/libs/ui/aboutdialog.ui @@ -1,7 +1,7 @@ - AboutDialog - + Zeal::WidgetUi::AboutDialog + 0 @@ -32,7 +32,7 @@ - :/icons/logo/64x64.png + :/icons/logo/64x64.png @@ -110,13 +110,13 @@ Docsets are courtesy of <a href="https://kapeli.com/dash">Dash&l - + buttonBox clicked(QAbstractButton*) - AboutDialog + Zeal::WidgetUi::AboutDialog close() diff --git a/src/libs/ui/docsetlistitemdelegate.cpp b/src/libs/ui/docsetlistitemdelegate.cpp index c368cf2b8..9c80f6158 100644 --- a/src/libs/ui/docsetlistitemdelegate.cpp +++ b/src/libs/ui/docsetlistitemdelegate.cpp @@ -23,11 +23,12 @@ #include "docsetlistitemdelegate.h" -#include +#include #include using namespace Zeal; +using namespace Zeal::WidgetUi; DocsetListItemDelegate::DocsetListItemDelegate(QObject *parent) : QItemDelegate(parent) @@ -39,7 +40,7 @@ void DocsetListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem { QItemDelegate::paint(painter, option, index); - if (!index.model()->data(index, Registry::ListModel::UpdateAvailableRole).toBool()) + if (!index.model()->data(index, Registry::ItemDataRole::UpdateAvailableRole).toBool()) return; const QString text = tr("Update available"); diff --git a/src/libs/ui/docsetlistitemdelegate.h b/src/libs/ui/docsetlistitemdelegate.h index 0d12ce9a8..9e09003f2 100644 --- a/src/libs/ui/docsetlistitemdelegate.h +++ b/src/libs/ui/docsetlistitemdelegate.h @@ -21,11 +21,14 @@ ** ****************************************************************************/ -#ifndef DOCSETLISTITEMDELEGATE_H -#define DOCSETLISTITEMDELEGATE_H +#ifndef ZEAL_WIDGETUI_DOCSETLISTITEMDELEGATE_H +#define ZEAL_WIDGETUI_DOCSETLISTITEMDELEGATE_H #include +namespace Zeal { +namespace WidgetUi { + class DocsetListItemDelegate : public QItemDelegate { Q_OBJECT @@ -36,4 +39,7 @@ class DocsetListItemDelegate : public QItemDelegate const QModelIndex &index) const override; }; -#endif // DOCSETLISTITEMDELEGATE_H +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_DOCSETLISTITEMDELEGATE_H diff --git a/src/libs/ui/docsetsdialog.cpp b/src/libs/ui/docsetsdialog.cpp index a0a8a0e6b..af8100061 100644 --- a/src/libs/ui/docsetsdialog.cpp +++ b/src/libs/ui/docsetsdialog.cpp @@ -29,7 +29,9 @@ #include #include +#include #include +#include #include #include @@ -45,6 +47,7 @@ #include using namespace Zeal; +using namespace Zeal::WidgetUi; namespace { const char ApiServerUrl[] = "http://api.zealdocs.org/v1"; @@ -86,7 +89,7 @@ DocsetsDialog::DocsetsDialog(Core::Application *app, QWidget *parent) : 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()) { + if (!index.data(Registry::ItemDataRole::UpdateAvailableRole).toBool()) { return; } @@ -99,7 +102,7 @@ DocsetsDialog::DocsetsDialog(Core::Application *app, QWidget *parent) : ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); for (const QModelIndex &index : selectionModel->selectedRows()) { - if (index.data(ListModel::UpdateAvailableRole).toBool()) { + if (index.data(Registry::ItemDataRole::UpdateAvailableRole).toBool()) { ui->updateSelectedDocsetsButton->setEnabled(true); return; } @@ -212,13 +215,12 @@ void DocsetsDialog::addDashFeed() 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()->selectedRows()) { - if (!index.data(Registry::ListModel::UpdateAvailableRole).toBool()) + if (!index.data(Registry::ItemDataRole::UpdateAvailableRole).toBool()) continue; downloadDashDocset(index); @@ -230,7 +232,7 @@ 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()) + if (!index.data(Registry::ItemDataRole::UpdateAvailableRole).toBool()) continue; downloadDashDocset(index); @@ -262,7 +264,7 @@ void DocsetsDialog::removeSelectedDocsets() // 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()); + names.append(index.data(Registry::ItemDataRole::DocsetNameRole).toString()); for (const QString &name : names) removeDocset(name); @@ -276,7 +278,7 @@ void DocsetsDialog::updateDocsetFilter(const QString &filterString) QListWidgetItem *item = ui->availableDocsetList->item(i); // Skip installed docsets - if (m_docsetRegistry->contains(item->data(Registry::ListModel::DocsetNameRole).toString())) + if (m_docsetRegistry->contains(item->data(Registry::ItemDataRole::DocsetNameRole).toString())) continue; item->setHidden(doSearch && !item->text().contains(filterString, Qt::CaseInsensitive)); @@ -329,9 +331,6 @@ void DocsetsDialog::downloadCompleted() newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); newReply->setProperty(ListItemIndexProperty, reply->property(ListItemIndexProperty)); - - connect(newReply, &QNetworkReply::finished, - this, &DocsetsDialog::downloadCompleted); return; } @@ -362,8 +361,6 @@ void DocsetsDialog::downloadCompleted() newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); newReply->setProperty(ListItemIndexProperty, reply->property(ListItemIndexProperty)); - connect(newReply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); - return; } @@ -382,8 +379,16 @@ void DocsetsDialog::downloadCompleted() const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error != QJsonParseError::NoError) { - QMessageBox::warning(this, QStringLiteral("Zeal"), - tr("Corrupted docset list: ") + jsonError.errorString()); + // TODO: Log QJsonParseError. + const QMessageBox::StandardButton rc + = QMessageBox::warning(this, QStringLiteral("Zeal"), + tr("Server returned a corrupted docset list."), + QMessageBox::Retry | QMessageBox::Cancel); + + if (rc == QMessageBox::Retry) { + downloadDocsetList(); + } + break; } @@ -404,7 +409,7 @@ void DocsetsDialog::downloadCompleted() QNetworkReply *reply = download(metadata.url()); reply->setProperty(DocsetNameProperty, metadata.name()); reply->setProperty(DownloadTypeProperty, DownloadDocset); - connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); + break; } @@ -492,7 +497,7 @@ 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")); + const QString docsetPath = dataDir.filePath(docsetName + QLatin1String(".docset")); // Write metadata about docset Registry::DocsetMetadata metadata = m_availableDocsets.contains(docsetName) @@ -566,7 +571,7 @@ 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) + if (item->data(Registry::ItemDataRole::DocsetNameRole).toString() == name) return item; } @@ -587,6 +592,7 @@ QNetworkReply *DocsetsDialog::download(const QUrl &url) { QNetworkReply *reply = m_application->download(url); connect(reply, &QNetworkReply::downloadProgress, this, &DocsetsDialog::downloadProgress); + connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); m_replies.append(reply); // Installed docsets @@ -628,7 +634,6 @@ void DocsetsDialog::downloadDocsetList() QNetworkReply *reply = download(QUrl(ApiServerUrl + QLatin1String("/docsets"))); reply->setProperty(DownloadTypeProperty, DownloadDocsetList); - connect(reply, &QNetworkReply::finished, this, &DocsetsDialog::downloadCompleted); } void DocsetsDialog::processDocsetList(const QJsonArray &list) @@ -644,7 +649,7 @@ void DocsetsDialog::processDocsetList(const QJsonArray &list) for (const Registry::DocsetMetadata &metadata : m_availableDocsets) { QListWidgetItem *listItem = new QListWidgetItem(metadata.icon(), metadata.title(), ui->availableDocsetList); - listItem->setData(Registry::ListModel::DocsetNameRole, metadata.name()); + listItem->setData(Registry::ItemDataRole::DocsetNameRole, metadata.name()); if (m_docsetRegistry->contains(metadata.name())) { listItem->setHidden(true); @@ -652,8 +657,7 @@ void DocsetsDialog::processDocsetList(const QJsonArray &list) Registry::Docset *docset = m_docsetRegistry->docset(metadata.name()); if (metadata.latestVersion() != docset->version() - || (metadata.latestVersion() == docset->version() - && metadata.revision() > docset->revision())) { + || metadata.revision() > docset->revision()) { docset->hasUpdate = true; ui->updateAllDocsetsButton->setEnabled(true); } @@ -665,7 +669,7 @@ void DocsetsDialog::processDocsetList(const QJsonArray &list) void DocsetsDialog::downloadDashDocset(const QModelIndex &index) { - const QString name = index.data(Registry::ListModel::DocsetNameRole).toString(); + const QString name = index.data(Registry::ItemDataRole::DocsetNameRole).toString(); if (!m_availableDocsets.contains(name)) return; @@ -676,8 +680,6 @@ void DocsetsDialog::downloadDashDocset(const QModelIndex &index) 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) @@ -757,7 +759,7 @@ void DocsetsDialog::resetProgress() QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); bool hasSelectedUpdates = false; for (const QModelIndex &index : selectionModel->selectedRows()) { - if (index.data(Registry::ListModel::UpdateAvailableRole).toBool()) { + if (index.data(Registry::ItemDataRole::UpdateAvailableRole).toBool()) { hasSelectedUpdates = true; break; } @@ -773,9 +775,9 @@ void DocsetsDialog::resetProgress() QString DocsetsDialog::docsetNameForTmpFilePath(const QString &filePath) const { - for (const QString &key : m_tmpFiles.keys()) { - if (m_tmpFiles[key]->fileName() == filePath) { - return key; + for (auto it = m_tmpFiles.cbegin(), end = m_tmpFiles.cend(); it != end; ++it) { + if (it.value()->fileName() == filePath) { + return it.key(); } } diff --git a/src/libs/ui/docsetsdialog.h b/src/libs/ui/docsetsdialog.h index d8a9de33a..21ad35f2a 100644 --- a/src/libs/ui/docsetsdialog.h +++ b/src/libs/ui/docsetsdialog.h @@ -21,8 +21,8 @@ ** ****************************************************************************/ -#ifndef DOCSETSDIALOG_H -#define DOCSETSDIALOG_H +#ifndef ZEAL_WIDGETUI_DOCSETSDIALOG_H +#define ZEAL_WIDGETUI_DOCSETSDIALOG_H #include @@ -35,9 +35,6 @@ class QNetworkReply; class QTemporaryFile; class QUrl; -namespace Ui { -class DocsetsDialog; -} namespace Zeal { @@ -49,6 +46,12 @@ namespace Core { class Application; } +namespace WidgetUi { + +namespace Ui { +class DocsetsDialog; +} // namespace Ui + class DocsetsDialog : public QDialog { Q_OBJECT @@ -121,6 +124,7 @@ private slots: static QString cacheLocation(const QString &fileName); }; +} // namespace WidgetUi } // namespace Zeal -#endif // DOCSETSDIALOG_H +#endif // ZEAL_WIDGETUI_DOCSETSDIALOG_H diff --git a/src/libs/ui/forms/docsetsdialog.ui b/src/libs/ui/docsetsdialog.ui similarity index 97% rename from src/libs/ui/forms/docsetsdialog.ui rename to src/libs/ui/docsetsdialog.ui index cd32e279f..bd71185f8 100644 --- a/src/libs/ui/forms/docsetsdialog.ui +++ b/src/libs/ui/docsetsdialog.ui @@ -1,7 +1,7 @@ - DocsetsDialog - + Zeal::WidgetUi::DocsetsDialog + Qt::ApplicationModal @@ -230,7 +230,7 @@ buttonBox accepted() - DocsetsDialog + Zeal::WidgetUi::DocsetsDialog accept() @@ -246,7 +246,7 @@ buttonBox rejected() - DocsetsDialog + Zeal::WidgetUi::DocsetsDialog reject() diff --git a/src/libs/ui/mainwindow.cpp b/src/libs/ui/mainwindow.cpp index d7766c687..b970a0fd7 100644 --- a/src/libs/ui/mainwindow.cpp +++ b/src/libs/ui/mainwindow.cpp @@ -32,12 +32,15 @@ #include #include +#include #include +#include #include #include #include #include +#include #include #include #include @@ -46,35 +49,23 @@ #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; +using namespace Zeal::WidgetUi; namespace { -const char startPageUrl[] = "qrc:///browser/start.html"; +const char WelcomePageUrl[] = "qrc:///browser/welcome.html"; +const char WelcomePageNoAdUrl[] = "qrc:///browser/welcome-noad.html"; +const char DarkModeCssUrl[] = ":/browser/assets/css/darkmode.css"; +const char HighlightOnNavigateCssUrl[] = ":/browser/assets/css/highlight.css"; } +namespace Zeal { +namespace WidgetUi { + struct TabState { explicit TabState() @@ -83,10 +74,8 @@ struct TabState 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) @@ -101,10 +90,8 @@ struct TabState 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()); } @@ -132,29 +119,17 @@ struct TabState } 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; @@ -173,6 +148,9 @@ struct TabState int webViewZoomFactor = 0; }; +} // namespace WidgetUi +} // namespace Zeal + MainWindow::MainWindow(Core::Application *app, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), @@ -184,15 +162,6 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : { 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); @@ -317,7 +286,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : ui->lineEdit->setFocus(); setupSearchBoxCompletions(); SearchItemDelegate *delegate = new SearchItemDelegate(ui->treeView); - delegate->setDecorationRoles({Registry::SearchModel::DocsetIconRole, Qt::DecorationRole}); + delegate->setDecorationRoles({Registry::ItemDataRole::DocsetIconRole, Qt::DecorationRole}); connect(ui->lineEdit, &QLineEdit::textChanged, [delegate](const QString &text) { delegate->setHighlight(Registry::SearchQuery::fromString(text).query()); }); @@ -363,32 +332,39 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : QDesktopServices::openUrl(url); }); - connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::queryCompleted, + connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::searchCompleted, this, [this](const QList &results) { currentTabState()->searchModel->setResults(results); }); - connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetRemoved, + connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetAboutToBeRemoved, 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); + if (tabState == currentTabState()) { + // Disable updates because removeSearchResultWithName can + // call {begin,end}RemoveRows multiple times, and cause + // degradation of UI responsiveness. + ui->treeView->setUpdatesEnabled(false); + tabState->searchModel->removeSearchResultWithName(name); + ui->treeView->setUpdatesEnabled(true); + } else { + tabState->searchModel->removeSearchResultWithName(name); + } - tabState->loadUrl(QUrl(startPageUrl)); + if (docsetName(tabState->url()) == name) { + tabState->tocModel->setResults(); + // TODO: Add custom 'Page has been removed' page. + if (m_settings->isAdDisabled) { + tabState->loadUrl(QUrl(WelcomePageNoAdUrl)); + } else { + tabState->loadUrl(QUrl(WelcomePageUrl)); + } + } // TODO: Cleanup history } + + setupSearchBoxCompletions(); }); connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetAdded, @@ -401,13 +377,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : 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(); - } + m_application->docsetRegistry()->search(text); }); // Setup delayed navigation to a page until user makes a pause in typing a search query. @@ -420,7 +390,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : openDocset(index); - // Get focus back. QWebPageEngine::load() always steals focus. + // Get focus back. ui->lineEdit->setFocus(Qt::MouseFocusReason); }); @@ -452,14 +422,16 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : QDesktopServices::openUrl(url); }); - ui->actionNextTab->setShortcut(QKeySequence::NextChild); + ui->actionNextTab->setShortcuts({QKeySequence::NextChild, + QKeySequence(Qt::ControlModifier| Qt::Key_PageDown)}); 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); + ui->actionPreviousTab->setShortcuts({QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Tab), + QKeySequence(Qt::ControlModifier| Qt::Key_PageUp)}); addAction(ui->actionPreviousTab); connect(ui->actionPreviousTab, &QAction::triggered, [this]() { m_tabBar->setCurrentIndex((m_tabBar->currentIndex() - 1 + m_tabBar->count()) % m_tabBar->count()); @@ -470,6 +442,9 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : ui->tocListView->setAttribute(Qt::WA_MacShowFocusRect, false); #endif + connect(m_settings, &Core::Settings::updated, this, &MainWindow::applySettings); + applySettings(); + if (m_settings->checkForUpdate) m_application->checkForUpdates(true); } @@ -490,21 +465,17 @@ void MainWindow::search(const Registry::SearchQuery &query) return; ui->lineEdit->setText(query.toString()); - ui->treeView->activated(ui->treeView->currentIndex()); + emit ui->treeView->activated(ui->treeView->currentIndex()); } void MainWindow::openDocset(const QModelIndex &index) { - const QVariant url = index.sibling(index.row(), 1).data(); + const QVariant url = index.data(Registry::ItemDataRole::UrlRole); 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 @@ -556,14 +527,20 @@ void MainWindow::createTab(int index) using Registry::SearchModel; TabState *newTab = new TabState(); - connect(newTab->searchModel, &SearchModel::queryCompleted, this, &MainWindow::queryCompleted); - connect(newTab->tocModel, &SearchModel::queryCompleted, this, &MainWindow::syncToc); + connect(newTab->searchModel, &SearchModel::updated, this, &MainWindow::queryCompleted); + connect(newTab->tocModel, &SearchModel::updated, this, &MainWindow::syncToc); - newTab->loadUrl(QUrl(startPageUrl)); + if (m_settings->isAdDisabled) { + newTab->loadUrl(QUrl(WelcomePageNoAdUrl)); + } else { + newTab->loadUrl(QUrl(WelcomePageUrl)); + } m_tabStates.insert(index, newTab); m_tabBar->insertTab(index, tr("Loading...")); m_tabBar->setCurrentIndex(index); + + ui->lineEdit->setFocus(); } void MainWindow::duplicateTab(int index) @@ -573,8 +550,8 @@ void MainWindow::duplicateTab(int index) 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); + connect(newTab->searchModel, &SearchModel::updated, this, &MainWindow::queryCompleted); + connect(newTab->tocModel, &SearchModel::updated, this, &MainWindow::syncToc); ++index; m_tabStates.insert(index, newTab); @@ -712,118 +689,39 @@ void MainWindow::setupTabBar() 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_trayIcon = new QSystemTrayIcon(this); + m_trayIcon->setIcon(windowIcon()); + m_trayIcon->setToolTip(QStringLiteral("Zeal")); - 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; + connect(m_trayIcon, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) { + if (reason != QSystemTrayIcon::Trigger && reason != QSystemTrayIcon::DoubleClick) + return; - toggleWindow(); - }); + toggleWindow(); + }); - QMenu *trayIconMenu = new QMenu(this); - trayIconMenu->addAction(ui->actionQuit); + QMenu *trayIconMenu = new QMenu(this); + trayIconMenu->addAction(ui->actionQuit); - m_trayIcon->setContextMenu(trayIconMenu); + m_trayIcon->setContextMenu(trayIconMenu); - m_trayIcon->show(); -#ifdef USE_APPINDICATOR - } -#endif + m_trayIcon->show(); } 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 + QMenu *trayIconMenu = m_trayIcon->contextMenu(); + delete m_trayIcon; + m_trayIcon = nullptr; + delete trayIconMenu; } void MainWindow::bringToFront() @@ -905,6 +803,32 @@ void MainWindow::applySettings() createTrayIcon(); else removeTrayIcon(); + + // Content + QByteArray ba; + if (m_settings->darkModeEnabled) { + QScopedPointer file(new QFile(DarkModeCssUrl)); + if (file->open(QIODevice::ReadOnly)) { + ba += file->readAll(); + } + } + + if (m_settings->highlightOnNavigateEnabled) { + QScopedPointer file(new QFile(HighlightOnNavigateCssUrl)); + if (file->open(QIODevice::ReadOnly)) { + ba += file->readAll(); + } + } + + if (QFileInfo::exists(m_settings->customCssFile)) { + QScopedPointer file(new QFile(m_settings->customCssFile)); + if (file->open(QIODevice::ReadOnly)) { + ba += file->readAll(); + } + } + + const QString cssUrl = QLatin1String("data:text/css;charset=utf-8;base64,") + ba.toBase64(); + QWebSettings::globalSettings()->setUserStyleSheetUrl(QUrl(cssUrl)); } void MainWindow::toggleWindow() @@ -912,23 +836,9 @@ 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/libs/ui/mainwindow.h b/src/libs/ui/mainwindow.h index 7d886d777..13d453f79 100644 --- a/src/libs/ui/mainwindow.h +++ b/src/libs/ui/mainwindow.h @@ -21,19 +21,13 @@ ** ****************************************************************************/ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H +#ifndef ZEAL_WIDGETUI_MAINWINDOW_H +#define ZEAL_WIDGETUI_MAINWINDOW_H #include -#include #include -#ifdef USE_APPINDICATOR -struct _AppIndicator; -struct _GtkWidget; -#endif - class QxtGlobalShortcut; class QModelIndex; @@ -41,10 +35,6 @@ class QSystemTrayIcon; class QTabBar; class QTimer; -namespace Ui { -class MainWindow; -} // namespace Ui - namespace Zeal { namespace Core { @@ -56,7 +46,11 @@ namespace Registry { class ListModel; } //namespace Registry -} // namespace Zeal +namespace WidgetUi { + +namespace Ui { +class MainWindow; +} // namespace Ui struct TabState; @@ -98,9 +92,6 @@ private slots: QString docsetName(const QUrl &url) const; QIcon docsetIcon(const QString &docsetName) const; -#ifdef USE_APPINDICATOR - void detectAppIndicatorSupport(); -#endif void createTrayIcon(); void removeTrayIcon(); @@ -114,8 +105,6 @@ private slots: QMenu *m_backMenu = nullptr; QMenu *m_forwardMenu = nullptr; - Zeal::Registry::CancellationToken m_cancelSearch; - QxtGlobalShortcut *m_globalShortcut = nullptr; QTabBar *m_tabBar = nullptr; @@ -123,15 +112,9 @@ 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; - _GtkWidget *m_appIndicatorShowHideMenuItem = nullptr; - _GtkWidget *m_appIndicatorMenuSeparator = nullptr; -#endif }; -#endif // MAINWINDOW_H +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_MAINWINDOW_H diff --git a/src/libs/ui/forms/mainwindow.ui b/src/libs/ui/mainwindow.ui similarity index 93% rename from src/libs/ui/forms/mainwindow.ui rename to src/libs/ui/mainwindow.ui index 6d26b9916..5aa6c8d69 100644 --- a/src/libs/ui/forms/mainwindow.ui +++ b/src/libs/ui/mainwindow.ui @@ -1,7 +1,7 @@ - MainWindow - + Zeal::WidgetUi::MainWindow + 0 @@ -29,6 +29,9 @@ + + Qt::Horizontal + false @@ -39,27 +42,15 @@ - - - 0 - 40 - - - - 6 - - 6 - - - 6 + 0 - 6 + 0 @@ -112,12 +103,6 @@ - - - 0 - 40 - - 0 @@ -195,7 +180,7 @@ 0 0 900 - 23 + 22 diff --git a/src/libs/ui/progressitemdelegate.cpp b/src/libs/ui/progressitemdelegate.cpp index 9c72c97d2..e69acb52e 100644 --- a/src/libs/ui/progressitemdelegate.cpp +++ b/src/libs/ui/progressitemdelegate.cpp @@ -26,6 +26,8 @@ #include #include +using namespace Zeal::WidgetUi; + ProgressItemDelegate::ProgressItemDelegate(QObject *parent) : QItemDelegate(parent) { diff --git a/src/libs/ui/progressitemdelegate.h b/src/libs/ui/progressitemdelegate.h index d3bc70b7e..e77a73580 100644 --- a/src/libs/ui/progressitemdelegate.h +++ b/src/libs/ui/progressitemdelegate.h @@ -21,11 +21,14 @@ ** ****************************************************************************/ -#ifndef PROGRESSITEMDELEGATE_H -#define PROGRESSITEMDELEGATE_H +#ifndef ZEAL_WIDGETUI_PROGRESSITEMDELEGATE_H +#define ZEAL_WIDGETUI_PROGRESSITEMDELEGATE_H #include +namespace Zeal { +namespace WidgetUi { + class ProgressItemDelegate : public QItemDelegate { Q_OBJECT @@ -45,4 +48,7 @@ class ProgressItemDelegate : public QItemDelegate static const int progressBarWidth = 150; }; -#endif // PROGRESSITEMDELEGATE_H +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_PROGRESSITEMDELEGATE_H diff --git a/src/libs/ui/qxtglobalshortcut/CMakeLists.txt b/src/libs/ui/qxtglobalshortcut/CMakeLists.txt new file mode 100644 index 000000000..8b1b0ef46 --- /dev/null +++ b/src/libs/ui/qxtglobalshortcut/CMakeLists.txt @@ -0,0 +1,41 @@ +list(APPEND QxtGlobalShortcut_SOURCES + qxtglobalshortcut.cpp +) + +if(APPLE) + list(APPEND QxtGlobalShortcut_SOURCES + qxtglobalshortcut_mac.cpp + ) +elseif(UNIX) + find_package(X11) + if(X11_FOUND) + list(APPEND QxtGlobalShortcut_SOURCES + qxtglobalshortcut_x11.cpp + ) + endif() +elseif(WIN32) + list(APPEND QxtGlobalShortcut_SOURCES + qxtglobalshortcut_win.cpp + ) +endif() + +add_library(QxtGlobalShortcut ${QxtGlobalShortcut_SOURCES}) + +find_package(Qt5Gui REQUIRED) +target_link_libraries(QxtGlobalShortcut Qt5::Gui) + +if(APPLE) + find_library(CARBON_LIBRARY Carbon) + target_link_libraries(QxtGlobalShortcut ${CARBON_LIBRARY}) +elseif(UNIX AND X11_FOUND) + target_link_libraries(QxtGlobalShortcut ${X11_LIBRARIES}) + + find_package(Qt5 COMPONENTS X11Extras REQUIRED) + target_link_libraries(QxtGlobalShortcut Qt5::X11Extras) + + find_package(ECM REQUIRED NO_MODULE) + set(CMAKE_MODULE_PATH ${ECM_FIND_MODULE_DIR}) + + find_package(XCB COMPONENTS XCB KEYSYMS REQUIRED) + target_link_libraries(QxtGlobalShortcut XCB::XCB XCB::KEYSYMS) +endif() diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp index 0790f0dcc..b641f5248 100644 --- a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.cpp @@ -1,241 +1,241 @@ -/**************************************************************************** -** -** 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 . -** -****************************************************************************/ -/**************************************************************************** -** Copyright (c) 2006 - 2011, the LibQxt project. -** See the Qxt AUTHORS file for a list of authors and copyright holders. -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the LibQxt project nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** -*****************************************************************************/ - -#include "qxtglobalshortcut.h" -#include "qxtglobalshortcut_p.h" - -#include - -#ifndef Q_OS_OSX -int QxtGlobalShortcutPrivate::ref = 0; -#endif // Q_OS_OSX - -QHash, QxtGlobalShortcut *> QxtGlobalShortcutPrivate::shortcuts; - -QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq) : - q_ptr(qq) -{ -#ifndef Q_OS_OSX - if (ref == 0) - QAbstractEventDispatcher::instance()->installNativeEventFilter(this); - ++ref; -#endif // Q_OS_OSX -} - -QxtGlobalShortcutPrivate::~QxtGlobalShortcutPrivate() -{ -#ifndef Q_OS_OSX - --ref; - if (ref == 0) { - QAbstractEventDispatcher *ed = QAbstractEventDispatcher::instance(); - if (ed != 0) - ed->removeNativeEventFilter(this); - } -#endif // Q_OS_OSX -} - -bool QxtGlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) -{ - Q_Q(QxtGlobalShortcut); - Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; - key = shortcut.isEmpty() ? Qt::Key(0) : Qt::Key((shortcut[0] ^ allMods) & shortcut[0]); - mods = shortcut.isEmpty() ? Qt::KeyboardModifiers(0) : Qt::KeyboardModifiers(shortcut[0] & allMods); - const quint32 nativeKey = nativeKeycode(key); - const quint32 nativeMods = nativeModifiers(mods); - const bool res = registerShortcut(nativeKey, nativeMods); - if (res) - 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({nativeKey, nativeMods}) == q) - res = unregisterShortcut(nativeKey, nativeMods); - - if (res) - 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; -} - -bool QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) -{ - QxtGlobalShortcut *shortcut = shortcuts.value({nativeKey, nativeMods}); - if (!shortcut || !shortcut->isEnabled()) - return false; - - emit shortcut->activated(); - return true; -} - -/*! - \class QxtGlobalShortcut - \inmodule QxtWidgets - \brief The QxtGlobalShortcut class provides a global shortcut aka "hotkey". - - A global shortcut triggers even if the application is not active. This - makes it easy to implement applications that react to certain shortcuts - still if some other application is active or if the application is for - example minimized to the system tray. - - Example usage: - \code - QxtGlobalShortcut* shortcut = new QxtGlobalShortcut(window); - connect(shortcut, SIGNAL(activated()), window, SLOT(toggleVisibility())); - shortcut->setShortcut(QKeySequence("Ctrl+Shift+F12")); - \endcode - - \bold {Note:} Since Qxt 0.6 QxtGlobalShortcut no more requires QxtApplication. - */ - -/*! - \fn QxtGlobalShortcut::activated() - - This signal is emitted when the user types the shortcut's key sequence. - - \sa shortcut - */ - -/*! - Constructs a new QxtGlobalShortcut with \a parent. - */ -QxtGlobalShortcut::QxtGlobalShortcut(QObject *parent) : - QObject(parent), - d_ptr(new QxtGlobalShortcutPrivate(this)) -{ -} - -/*! - Constructs a new QxtGlobalShortcut with \a shortcut and \a parent. - */ -QxtGlobalShortcut::QxtGlobalShortcut(const QKeySequence &shortcut, QObject *parent) : - QObject(parent), - d_ptr(new QxtGlobalShortcutPrivate(this)) -{ - setShortcut(shortcut); -} - -/*! - Destructs the QxtGlobalShortcut. - */ -QxtGlobalShortcut::~QxtGlobalShortcut() -{ - Q_D(QxtGlobalShortcut); - if (d->key != 0) - d->unsetShortcut(); - delete d; -} - -/*! - \property QxtGlobalShortcut::shortcut - \brief the shortcut key sequence - - \bold {Note:} Notice that corresponding key press and release events are not - delivered for registered global shortcuts even if they are disabled. - Also, comma separated key sequences are not supported. - Only the first part is used: - - \code - qxtShortcut->setShortcut(QKeySequence("Ctrl+Alt+A,Ctrl+Alt+B")); - Q_ASSERT(qxtShortcut->shortcut() == QKeySequence("Ctrl+Alt+A")); - \endcode - */ -QKeySequence QxtGlobalShortcut::shortcut() const -{ - Q_D(const QxtGlobalShortcut); - return QKeySequence(d->key | d->mods); -} - -bool QxtGlobalShortcut::setShortcut(const QKeySequence &shortcut) -{ - Q_D(QxtGlobalShortcut); - if (d->key != 0 && !d->unsetShortcut()) - return false; - if (shortcut.isEmpty()) - return true; - return d->setShortcut(shortcut); -} - -/*! - \property QxtGlobalShortcut::enabled - \brief whether the shortcut is enabled - - A disabled shortcut does not get activated. - - The default value is \c true. - - \sa setDisabled() - */ -bool QxtGlobalShortcut::isEnabled() const -{ - Q_D(const QxtGlobalShortcut); - return d->enabled; -} - -void QxtGlobalShortcut::setEnabled(bool enabled) -{ - Q_D(QxtGlobalShortcut); - d->enabled = enabled; -} +/**************************************************************************** +** +** 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 . +** +****************************************************************************/ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#include "qxtglobalshortcut.h" +#include "qxtglobalshortcut_p.h" + +#include + +#ifndef Q_OS_OSX +int QxtGlobalShortcutPrivate::ref = 0; +#endif // Q_OS_OSX + +QHash, QxtGlobalShortcut *> QxtGlobalShortcutPrivate::shortcuts; + +QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq) : + q_ptr(qq) +{ +#ifndef Q_OS_OSX + if (ref == 0) + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + ++ref; +#endif // Q_OS_OSX +} + +QxtGlobalShortcutPrivate::~QxtGlobalShortcutPrivate() +{ +#ifndef Q_OS_OSX + --ref; + if (ref == 0) { + QAbstractEventDispatcher *ed = QAbstractEventDispatcher::instance(); + if (ed != 0) + ed->removeNativeEventFilter(this); + } +#endif // Q_OS_OSX +} + +bool QxtGlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) +{ + Q_Q(QxtGlobalShortcut); + Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; + key = shortcut.isEmpty() ? Qt::Key(0) : Qt::Key((shortcut[0] ^ allMods) & shortcut[0]); + mods = shortcut.isEmpty() ? Qt::KeyboardModifiers(0) : Qt::KeyboardModifiers(shortcut[0] & allMods); + const quint32 nativeKey = nativeKeycode(key); + const quint32 nativeMods = nativeModifiers(mods); + const bool res = registerShortcut(nativeKey, nativeMods); + if (res) + 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({nativeKey, nativeMods}) == q) + res = unregisterShortcut(nativeKey, nativeMods); + + if (res) + 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; +} + +bool QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) +{ + QxtGlobalShortcut *shortcut = shortcuts.value({nativeKey, nativeMods}); + if (!shortcut || !shortcut->isEnabled()) + return false; + + emit shortcut->activated(); + return true; +} + +/*! + \class QxtGlobalShortcut + \inmodule QxtWidgets + \brief The QxtGlobalShortcut class provides a global shortcut aka "hotkey". + + A global shortcut triggers even if the application is not active. This + makes it easy to implement applications that react to certain shortcuts + still if some other application is active or if the application is for + example minimized to the system tray. + + Example usage: + \code + QxtGlobalShortcut* shortcut = new QxtGlobalShortcut(window); + connect(shortcut, SIGNAL(activated()), window, SLOT(toggleVisibility())); + shortcut->setShortcut(QKeySequence("Ctrl+Shift+F12")); + \endcode + + \bold {Note:} Since Qxt 0.6 QxtGlobalShortcut no more requires QxtApplication. + */ + +/*! + \fn QxtGlobalShortcut::activated() + + This signal is emitted when the user types the shortcut's key sequence. + + \sa shortcut + */ + +/*! + Constructs a new QxtGlobalShortcut with \a parent. + */ +QxtGlobalShortcut::QxtGlobalShortcut(QObject *parent) : + QObject(parent), + d_ptr(new QxtGlobalShortcutPrivate(this)) +{ +} + +/*! + Constructs a new QxtGlobalShortcut with \a shortcut and \a parent. + */ +QxtGlobalShortcut::QxtGlobalShortcut(const QKeySequence &shortcut, QObject *parent) : + QObject(parent), + d_ptr(new QxtGlobalShortcutPrivate(this)) +{ + setShortcut(shortcut); +} + +/*! + Destructs the QxtGlobalShortcut. + */ +QxtGlobalShortcut::~QxtGlobalShortcut() +{ + Q_D(QxtGlobalShortcut); + if (d->key != 0) + d->unsetShortcut(); + delete d; +} + +/*! + \property QxtGlobalShortcut::shortcut + \brief the shortcut key sequence + + \bold {Note:} Notice that corresponding key press and release events are not + delivered for registered global shortcuts even if they are disabled. + Also, comma separated key sequences are not supported. + Only the first part is used: + + \code + qxtShortcut->setShortcut(QKeySequence("Ctrl+Alt+A,Ctrl+Alt+B")); + Q_ASSERT(qxtShortcut->shortcut() == QKeySequence("Ctrl+Alt+A")); + \endcode + */ +QKeySequence QxtGlobalShortcut::shortcut() const +{ + Q_D(const QxtGlobalShortcut); + return QKeySequence(d->key | d->mods); +} + +bool QxtGlobalShortcut::setShortcut(const QKeySequence &shortcut) +{ + Q_D(QxtGlobalShortcut); + if (d->key != 0 && !d->unsetShortcut()) + return false; + if (shortcut.isEmpty()) + return true; + return d->setShortcut(shortcut); +} + +/*! + \property QxtGlobalShortcut::enabled + \brief whether the shortcut is enabled + + A disabled shortcut does not get activated. + + The default value is \c true. + + \sa setDisabled() + */ +bool QxtGlobalShortcut::isEnabled() const +{ + Q_D(const QxtGlobalShortcut); + return d->enabled; +} + +void QxtGlobalShortcut::setEnabled(bool enabled) +{ + Q_D(QxtGlobalShortcut); + d->enabled = enabled; +} diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h index 4ffbf60a2..05b52a0bd 100644 --- a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut.h @@ -1,87 +1,87 @@ -/**************************************************************************** -** -** 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 . -** -****************************************************************************/ -/**************************************************************************** -** Copyright (c) 2006 - 2011, the LibQxt project. -** See the Qxt AUTHORS file for a list of authors and copyright holders. -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the LibQxt project nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** -*****************************************************************************/ - -#ifndef QXTGLOBALSHORTCUT_H -#define QXTGLOBALSHORTCUT_H - -#include -#include - -class QxtGlobalShortcutPrivate; - -class QxtGlobalShortcut : public QObject -{ - Q_OBJECT - - 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); - ~QxtGlobalShortcut() override; - - QKeySequence shortcut() const; - bool setShortcut(const QKeySequence &shortcut); - - bool isEnabled() const; - -public slots: - void setEnabled(bool enabled); - -signals: - void activated(); -}; - -#endif // QXTGLOBALSHORTCUT_H +/**************************************************************************** +** +** 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 . +** +****************************************************************************/ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#ifndef QXTGLOBALSHORTCUT_H +#define QXTGLOBALSHORTCUT_H + +#include +#include + +class QxtGlobalShortcutPrivate; + +class QxtGlobalShortcut : public QObject +{ + Q_OBJECT + + 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); + ~QxtGlobalShortcut() override; + + QKeySequence shortcut() const; + bool setShortcut(const QKeySequence &shortcut); + + bool isEnabled() const; + +public slots: + void setEnabled(bool enabled); + +signals: + void activated(); +}; + +#endif // QXTGLOBALSHORTCUT_H diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp index 573a51231..0901d741c 100644 --- a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_mac.cpp @@ -1,286 +1,285 @@ -/**************************************************************************** -** -** 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 . -** -****************************************************************************/ -/**************************************************************************** -** Copyright (c) 2006 - 2011, the LibQxt project. -** See the Qxt AUTHORS file for a list of authors and copyright holders. -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the LibQxt project nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** -*****************************************************************************/ - -#include "qxtglobalshortcut_p.h" - -#include -#include -#include - -#include - -typedef QPair Identifier; -static QMap keyRefs; -static QHash keyIDs; -static quint32 hotKeySerial = 0; -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) - if (GetEventClass(event) == kEventClassKeyboard && GetEventKind(event) == kEventHotKeyPressed) { - EventHotKeyID keyID; - GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(keyID), NULL, &keyID); - Identifier id = keyIDs.key(keyID.id); - QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first); - } - return noErr; -} - -bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, - void *message, long *result) -{ - Q_UNUSED(eventType) - Q_UNUSED(message) - Q_UNUSED(result) - return false; -} - -quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) -{ - quint32 native = 0; - if (modifiers & Qt::ShiftModifier) - native |= shiftKey; - if (modifiers & Qt::ControlModifier) - native |= cmdKey; - if (modifiers & Qt::AltModifier) - native |= optionKey; - if (modifiers & Qt::MetaModifier) - native |= controlKey; - if (modifiers & Qt::KeypadModifier) - native |= kEventKeyModifierNumLockMask; - return native; -} - -quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) -{ - UTF16Char ch; - // Constants found in NSEvent.h from AppKit.framework - switch (key) { - case Qt::Key_Return: - return kVK_Return; - case Qt::Key_Enter: - return kVK_ANSI_KeypadEnter; - case Qt::Key_Tab: - return kVK_Tab; - case Qt::Key_Space: - return kVK_Space; - case Qt::Key_Backspace: - return kVK_Delete; - case Qt::Key_Control: - return kVK_Command; - case Qt::Key_Shift: - return kVK_Shift; - case Qt::Key_CapsLock: - return kVK_CapsLock; - case Qt::Key_Option: - return kVK_Option; - case Qt::Key_Meta: - return kVK_Control; - case Qt::Key_F17: - return kVK_F17; - case Qt::Key_VolumeUp: - return kVK_VolumeUp; - case Qt::Key_VolumeDown: - return kVK_VolumeDown; - case Qt::Key_F18: - return kVK_F18; - case Qt::Key_F19: - return kVK_F19; - case Qt::Key_F20: - return kVK_F20; - case Qt::Key_F5: - return kVK_F5; - case Qt::Key_F6: - return kVK_F6; - case Qt::Key_F7: - return kVK_F7; - case Qt::Key_F3: - return kVK_F3; - case Qt::Key_F8: - return kVK_F8; - case Qt::Key_F9: - return kVK_F9; - case Qt::Key_F11: - return kVK_F11; - case Qt::Key_F13: - return kVK_F13; - case Qt::Key_F16: - return kVK_F16; - case Qt::Key_F14: - return kVK_F14; - case Qt::Key_F10: - return kVK_F10; - case Qt::Key_F12: - return kVK_F12; - case Qt::Key_F15: - return kVK_F15; - case Qt::Key_Help: - return kVK_Help; - case Qt::Key_Home: - return kVK_Home; - case Qt::Key_PageUp: - return kVK_PageUp; - case Qt::Key_Delete: - return kVK_ForwardDelete; - case Qt::Key_F4: - return kVK_F4; - case Qt::Key_End: - return kVK_End; - case Qt::Key_F2: - return kVK_F2; - case Qt::Key_PageDown: - return kVK_PageDown; - case Qt::Key_F1: - return kVK_F1; - case Qt::Key_Left: - return kVK_LeftArrow; - case Qt::Key_Right: - return kVK_RightArrow; - case Qt::Key_Down: - return kVK_DownArrow; - case Qt::Key_Up: - return kVK_UpArrow; - default: - ; - } - - if (key == Qt::Key_Escape) - ch = 27; - else if (key == Qt::Key_Return) - ch = 13; - else if (key == Qt::Key_Enter) - ch = 3; - else if (key == Qt::Key_Tab) - ch = 9; - else - ch = key; - - CFDataRef currentLayoutData; - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - - if (currentKeyboard == NULL) - return 0; - - currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - CFRelease(currentKeyboard); - if (currentLayoutData == NULL) - return 0; - - UCKeyboardLayout *header = (UCKeyboardLayout *)CFDataGetBytePtr(currentLayoutData); - UCKeyboardTypeHeader *table = header->keyboardTypeList; - - uint8_t *data = (uint8_t*)header; - // God, would a little documentation for this shit kill you... - for (quint32 i = 0; i < header->keyboardTypeCount; ++i) { - UCKeyStateRecordsIndex *stateRec = 0; - if (table[i].keyStateRecordsIndexOffset != 0) { - stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); - if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; - } - - UCKeyToCharTableIndex *charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); - if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) - continue; - - for (quint32 j=0; j < charTable->keyToCharTableCount; ++j) { - UCKeyOutput *keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); - for (quint32 k=0; k < charTable->keyToCharTableSize; ++k) { - if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { - long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; - if (stateRec && idx < stateRec->keyStateRecordCount) { - UCKeyStateRecord *rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); - if (rec->stateZeroCharData == ch) return k; - } - } else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { - if (keyToChar[k] == ch) - return k; - } - } // for k - } // for j - } // for i - return 0; -} - -bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) -{ - if (!qxt_mac_handler_installed) { - EventTypeSpec t; - t.eventClass = kEventClassKeyboard; - t.eventKind = kEventHotKeyPressed; - InstallApplicationEventHandler(&qxt_mac_handle_hot_key, 1, &t, NULL, NULL); - } - - EventHotKeyID keyID; - keyID.signature = 'cute'; - keyID.id = ++hotKeySerial; - - EventHotKeyRef ref = 0; - bool rv = !RegisterEventHotKey(nativeKey, nativeMods, keyID, GetApplicationEventTarget(), 0, &ref); - if (rv) { - keyIDs.insert(Identifier(nativeMods, nativeKey), keyID.id); - keyRefs.insert(keyID.id, ref); - } - return rv; -} - -bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) -{ - Identifier id(nativeMods, nativeKey); - if (!keyIDs.contains(id)) - return false; - - EventHotKeyRef ref = keyRefs.take(keyIDs[id]); - keyIDs.remove(id); - return !UnregisterEventHotKey(ref); -} +/**************************************************************************** +** +** 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 . +** +****************************************************************************/ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#include "qxtglobalshortcut_p.h" + +#include +#include + +#include + +typedef QPair Identifier; +static QMap keyRefs; +static QHash keyIDs; +static quint32 hotKeySerial = 0; +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) + if (GetEventClass(event) == kEventClassKeyboard && GetEventKind(event) == kEventHotKeyPressed) { + EventHotKeyID keyID; + GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(keyID), NULL, &keyID); + Identifier id = keyIDs.key(keyID.id); + QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first); + } + return noErr; +} + +bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, + void *message, long *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(message) + Q_UNUSED(result) + return false; +} + +quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) +{ + quint32 native = 0; + if (modifiers & Qt::ShiftModifier) + native |= shiftKey; + if (modifiers & Qt::ControlModifier) + native |= cmdKey; + if (modifiers & Qt::AltModifier) + native |= optionKey; + if (modifiers & Qt::MetaModifier) + native |= controlKey; + if (modifiers & Qt::KeypadModifier) + native |= kEventKeyModifierNumLockMask; + return native; +} + +quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) +{ + UTF16Char ch; + // Constants found in NSEvent.h from AppKit.framework + switch (key) { + case Qt::Key_Return: + return kVK_Return; + case Qt::Key_Enter: + return kVK_ANSI_KeypadEnter; + case Qt::Key_Tab: + return kVK_Tab; + case Qt::Key_Space: + return kVK_Space; + case Qt::Key_Backspace: + return kVK_Delete; + case Qt::Key_Control: + return kVK_Command; + case Qt::Key_Shift: + return kVK_Shift; + case Qt::Key_CapsLock: + return kVK_CapsLock; + case Qt::Key_Option: + return kVK_Option; + case Qt::Key_Meta: + return kVK_Control; + case Qt::Key_F17: + return kVK_F17; + case Qt::Key_VolumeUp: + return kVK_VolumeUp; + case Qt::Key_VolumeDown: + return kVK_VolumeDown; + case Qt::Key_F18: + return kVK_F18; + case Qt::Key_F19: + return kVK_F19; + case Qt::Key_F20: + return kVK_F20; + case Qt::Key_F5: + return kVK_F5; + case Qt::Key_F6: + return kVK_F6; + case Qt::Key_F7: + return kVK_F7; + case Qt::Key_F3: + return kVK_F3; + case Qt::Key_F8: + return kVK_F8; + case Qt::Key_F9: + return kVK_F9; + case Qt::Key_F11: + return kVK_F11; + case Qt::Key_F13: + return kVK_F13; + case Qt::Key_F16: + return kVK_F16; + case Qt::Key_F14: + return kVK_F14; + case Qt::Key_F10: + return kVK_F10; + case Qt::Key_F12: + return kVK_F12; + case Qt::Key_F15: + return kVK_F15; + case Qt::Key_Help: + return kVK_Help; + case Qt::Key_Home: + return kVK_Home; + case Qt::Key_PageUp: + return kVK_PageUp; + case Qt::Key_Delete: + return kVK_ForwardDelete; + case Qt::Key_F4: + return kVK_F4; + case Qt::Key_End: + return kVK_End; + case Qt::Key_F2: + return kVK_F2; + case Qt::Key_PageDown: + return kVK_PageDown; + case Qt::Key_F1: + return kVK_F1; + case Qt::Key_Left: + return kVK_LeftArrow; + case Qt::Key_Right: + return kVK_RightArrow; + case Qt::Key_Down: + return kVK_DownArrow; + case Qt::Key_Up: + return kVK_UpArrow; + default: + ; + } + + if (key == Qt::Key_Escape) + ch = 27; + else if (key == Qt::Key_Return) + ch = 13; + else if (key == Qt::Key_Enter) + ch = 3; + else if (key == Qt::Key_Tab) + ch = 9; + else + ch = key; + + CFDataRef currentLayoutData; + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + + if (currentKeyboard == NULL) + return 0; + + currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + CFRelease(currentKeyboard); + if (currentLayoutData == NULL) + return 0; + + UCKeyboardLayout *header = (UCKeyboardLayout *)CFDataGetBytePtr(currentLayoutData); + UCKeyboardTypeHeader *table = header->keyboardTypeList; + + uint8_t *data = (uint8_t*)header; + // God, would a little documentation for this shit kill you... + for (quint32 i = 0; i < header->keyboardTypeCount; ++i) { + UCKeyStateRecordsIndex *stateRec = 0; + if (table[i].keyStateRecordsIndexOffset != 0) { + stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); + if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; + } + + UCKeyToCharTableIndex *charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); + if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) + continue; + + for (quint32 j=0; j < charTable->keyToCharTableCount; ++j) { + UCKeyOutput *keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); + for (quint32 k=0; k < charTable->keyToCharTableSize; ++k) { + if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { + long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; + if (stateRec && idx < stateRec->keyStateRecordCount) { + UCKeyStateRecord *rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); + if (rec->stateZeroCharData == ch) return k; + } + } else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { + if (keyToChar[k] == ch) + return k; + } + } // for k + } // for j + } // for i + return 0; +} + +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) +{ + if (!qxt_mac_handler_installed) { + EventTypeSpec t; + t.eventClass = kEventClassKeyboard; + t.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(&qxt_mac_handle_hot_key, 1, &t, NULL, NULL); + } + + EventHotKeyID keyID; + keyID.signature = 'cute'; + keyID.id = ++hotKeySerial; + + EventHotKeyRef ref = 0; + bool rv = !RegisterEventHotKey(nativeKey, nativeMods, keyID, GetApplicationEventTarget(), 0, &ref); + if (rv) { + keyIDs.insert(Identifier(nativeMods, nativeKey), keyID.id); + keyRefs.insert(keyID.id, ref); + } + return rv; +} + +bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) +{ + Identifier id(nativeMods, nativeKey); + if (!keyIDs.contains(id)) + return false; + + EventHotKeyRef ref = keyRefs.take(keyIDs[id]); + keyIDs.remove(id); + return !UnregisterEventHotKey(ref); +} diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h index 45704e22f..f37f266e7 100644 --- a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_p.h @@ -1,97 +1,97 @@ -/**************************************************************************** -** -** 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 . -** -****************************************************************************/ -/**************************************************************************** -** Copyright (c) 2006 - 2011, the LibQxt project. -** See the Qxt AUTHORS file for a list of authors and copyright holders. -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the LibQxt project nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** -*****************************************************************************/ - -#ifndef QXTGLOBALSHORTCUT_P_H -#define QXTGLOBALSHORTCUT_P_H - -#include -#include - -class QKeySequence; - -class QxtGlobalShortcut; - -class QxtGlobalShortcutPrivate : public QAbstractNativeEventFilter -{ - QxtGlobalShortcut *q_ptr = nullptr; - Q_DECLARE_PUBLIC(QxtGlobalShortcut) -public: - QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq); - ~QxtGlobalShortcutPrivate() override; - - 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; - - static bool activateShortcut(quint32 nativeKey, quint32 nativeMods); - -private: - static quint32 nativeKeycode(Qt::Key keycode); - static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); - - static bool registerShortcut(quint32 nativeKey, quint32 nativeMods); - static bool unregisterShortcut(quint32 nativeKey, quint32 nativeMods); - - static QHash, QxtGlobalShortcut *> shortcuts; -}; - -#endif // QXTGLOBALSHORTCUT_P_H +/**************************************************************************** +** +** 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 . +** +****************************************************************************/ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#ifndef QXTGLOBALSHORTCUT_P_H +#define QXTGLOBALSHORTCUT_P_H + +#include +#include + +class QKeySequence; + +class QxtGlobalShortcut; + +class QxtGlobalShortcutPrivate : public QAbstractNativeEventFilter +{ + QxtGlobalShortcut *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QxtGlobalShortcut) +public: + QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq); + ~QxtGlobalShortcutPrivate() override; + + 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; + + static bool activateShortcut(quint32 nativeKey, quint32 nativeMods); + +private: + static quint32 nativeKeycode(Qt::Key keycode); + static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); + + static bool registerShortcut(quint32 nativeKey, quint32 nativeMods); + static bool unregisterShortcut(quint32 nativeKey, quint32 nativeMods); + + static QHash, QxtGlobalShortcut *> shortcuts; +}; + +#endif // QXTGLOBALSHORTCUT_P_H diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp index 6f3d4a01b..177a2f8c6 100644 --- a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_win.cpp @@ -1,264 +1,264 @@ -/**************************************************************************** -** -** 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 . -** -****************************************************************************/ -/**************************************************************************** -** Copyright (c) 2006 - 2011, the LibQxt project. -** See the Qxt AUTHORS file for a list of authors and copyright holders. -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the LibQxt project nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** -*****************************************************************************/ - -#include "qxtglobalshortcut_p.h" - -#include - -bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray &eventType, - void *message, long *result) -{ - Q_UNUSED(eventType) - Q_UNUSED(result) - - MSG *msg = static_cast(message); - if (msg->message == WM_HOTKEY) { - const quint32 keycode = HIWORD(msg->lParam); - const quint32 modifiers = LOWORD(msg->lParam); - activateShortcut(keycode, modifiers); - } - - return false; -} - - -quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) -{ - // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN - quint32 native = 0; - if (modifiers & Qt::ShiftModifier) - native |= MOD_SHIFT; - if (modifiers & Qt::ControlModifier) - native |= MOD_CONTROL; - if (modifiers & Qt::AltModifier) - native |= MOD_ALT; - if (modifiers & Qt::MetaModifier) - native |= MOD_WIN; - // TODO: resolve these? - //if (modifiers & Qt::KeypadModifier) - //if (modifiers & Qt::GroupSwitchModifier) - return native; -} - -quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) -{ - switch (key) { - case Qt::Key_Escape: - return VK_ESCAPE; - case Qt::Key_Tab: - case Qt::Key_Backtab: - return VK_TAB; - case Qt::Key_Backspace: - return VK_BACK; - case Qt::Key_Return: - case Qt::Key_Enter: - return VK_RETURN; - case Qt::Key_Insert: - return VK_INSERT; - case Qt::Key_Delete: - return VK_DELETE; - case Qt::Key_Pause: - return VK_PAUSE; - case Qt::Key_Print: - return VK_PRINT; - case Qt::Key_Clear: - return VK_CLEAR; - case Qt::Key_Home: - return VK_HOME; - case Qt::Key_End: - return VK_END; - case Qt::Key_Left: - return VK_LEFT; - case Qt::Key_Up: - return VK_UP; - case Qt::Key_Right: - return VK_RIGHT; - case Qt::Key_Down: - return VK_DOWN; - case Qt::Key_PageUp: - return VK_PRIOR; - case Qt::Key_PageDown: - return VK_NEXT; - case Qt::Key_F1: - return VK_F1; - case Qt::Key_F2: - return VK_F2; - case Qt::Key_F3: - return VK_F3; - case Qt::Key_F4: - return VK_F4; - case Qt::Key_F5: - return VK_F5; - case Qt::Key_F6: - return VK_F6; - case Qt::Key_F7: - return VK_F7; - case Qt::Key_F8: - return VK_F8; - case Qt::Key_F9: - return VK_F9; - case Qt::Key_F10: - return VK_F10; - case Qt::Key_F11: - return VK_F11; - case Qt::Key_F12: - return VK_F12; - case Qt::Key_F13: - return VK_F13; - case Qt::Key_F14: - return VK_F14; - case Qt::Key_F15: - return VK_F15; - case Qt::Key_F16: - return VK_F16; - case Qt::Key_F17: - return VK_F17; - case Qt::Key_F18: - return VK_F18; - case Qt::Key_F19: - return VK_F19; - case Qt::Key_F20: - return VK_F20; - case Qt::Key_F21: - return VK_F21; - case Qt::Key_F22: - return VK_F22; - case Qt::Key_F23: - return VK_F23; - case Qt::Key_F24: - return VK_F24; - case Qt::Key_Space: - return VK_SPACE; - case Qt::Key_Asterisk: - return VK_MULTIPLY; - case Qt::Key_Plus: - return VK_ADD; - case Qt::Key_Comma: - return VK_SEPARATOR; - case Qt::Key_Minus: - return VK_SUBTRACT; - case Qt::Key_Slash: - return VK_DIVIDE; - case Qt::Key_MediaNext: - return VK_MEDIA_NEXT_TRACK; - case Qt::Key_MediaPrevious: - return VK_MEDIA_PREV_TRACK; - case Qt::Key_MediaPlay: - return VK_MEDIA_PLAY_PAUSE; - case Qt::Key_MediaStop: - return VK_MEDIA_STOP; - // couldn't find those in VK_* - //case Qt::Key_MediaLast: - //case Qt::Key_MediaRecord: - case Qt::Key_VolumeDown: - return VK_VOLUME_DOWN; - case Qt::Key_VolumeUp: - return VK_VOLUME_UP; - case Qt::Key_VolumeMute: - return VK_VOLUME_MUTE; - - // numbers - case Qt::Key_0: - case Qt::Key_1: - case Qt::Key_2: - case Qt::Key_3: - case Qt::Key_4: - case Qt::Key_5: - case Qt::Key_6: - case Qt::Key_7: - case Qt::Key_8: - case Qt::Key_9: - return key; - - // letters - case Qt::Key_A: - case Qt::Key_B: - case Qt::Key_C: - case Qt::Key_D: - case Qt::Key_E: - case Qt::Key_F: - case Qt::Key_G: - case Qt::Key_H: - case Qt::Key_I: - case Qt::Key_J: - case Qt::Key_K: - case Qt::Key_L: - case Qt::Key_M: - case Qt::Key_N: - case Qt::Key_O: - case Qt::Key_P: - case Qt::Key_Q: - case Qt::Key_R: - case Qt::Key_S: - case Qt::Key_T: - case Qt::Key_U: - case Qt::Key_V: - case Qt::Key_W: - case Qt::Key_X: - case Qt::Key_Y: - case Qt::Key_Z: - return key; - - default: - return 0; - } -} - -bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) -{ - return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey); -} - -bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) -{ - return UnregisterHotKey(0, nativeMods ^ nativeKey); -} +/**************************************************************************** +** +** 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 . +** +****************************************************************************/ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#include "qxtglobalshortcut_p.h" + +#include + +bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray &eventType, + void *message, long *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + MSG *msg = static_cast(message); + if (msg->message == WM_HOTKEY) { + const quint32 keycode = HIWORD(msg->lParam); + const quint32 modifiers = LOWORD(msg->lParam); + activateShortcut(keycode, modifiers); + } + + return false; +} + + +quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) +{ + // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN + quint32 native = 0; + if (modifiers & Qt::ShiftModifier) + native |= MOD_SHIFT; + if (modifiers & Qt::ControlModifier) + native |= MOD_CONTROL; + if (modifiers & Qt::AltModifier) + native |= MOD_ALT; + if (modifiers & Qt::MetaModifier) + native |= MOD_WIN; + // TODO: resolve these? + //if (modifiers & Qt::KeypadModifier) + //if (modifiers & Qt::GroupSwitchModifier) + return native; +} + +quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) +{ + switch (key) { + case Qt::Key_Escape: + return VK_ESCAPE; + case Qt::Key_Tab: + case Qt::Key_Backtab: + return VK_TAB; + case Qt::Key_Backspace: + return VK_BACK; + case Qt::Key_Return: + case Qt::Key_Enter: + return VK_RETURN; + case Qt::Key_Insert: + return VK_INSERT; + case Qt::Key_Delete: + return VK_DELETE; + case Qt::Key_Pause: + return VK_PAUSE; + case Qt::Key_Print: + return VK_PRINT; + case Qt::Key_Clear: + return VK_CLEAR; + case Qt::Key_Home: + return VK_HOME; + case Qt::Key_End: + return VK_END; + case Qt::Key_Left: + return VK_LEFT; + case Qt::Key_Up: + return VK_UP; + case Qt::Key_Right: + return VK_RIGHT; + case Qt::Key_Down: + return VK_DOWN; + case Qt::Key_PageUp: + return VK_PRIOR; + case Qt::Key_PageDown: + return VK_NEXT; + case Qt::Key_F1: + return VK_F1; + case Qt::Key_F2: + return VK_F2; + case Qt::Key_F3: + return VK_F3; + case Qt::Key_F4: + return VK_F4; + case Qt::Key_F5: + return VK_F5; + case Qt::Key_F6: + return VK_F6; + case Qt::Key_F7: + return VK_F7; + case Qt::Key_F8: + return VK_F8; + case Qt::Key_F9: + return VK_F9; + case Qt::Key_F10: + return VK_F10; + case Qt::Key_F11: + return VK_F11; + case Qt::Key_F12: + return VK_F12; + case Qt::Key_F13: + return VK_F13; + case Qt::Key_F14: + return VK_F14; + case Qt::Key_F15: + return VK_F15; + case Qt::Key_F16: + return VK_F16; + case Qt::Key_F17: + return VK_F17; + case Qt::Key_F18: + return VK_F18; + case Qt::Key_F19: + return VK_F19; + case Qt::Key_F20: + return VK_F20; + case Qt::Key_F21: + return VK_F21; + case Qt::Key_F22: + return VK_F22; + case Qt::Key_F23: + return VK_F23; + case Qt::Key_F24: + return VK_F24; + case Qt::Key_Space: + return VK_SPACE; + case Qt::Key_Asterisk: + return VK_MULTIPLY; + case Qt::Key_Plus: + return VK_ADD; + case Qt::Key_Comma: + return VK_SEPARATOR; + case Qt::Key_Minus: + return VK_SUBTRACT; + case Qt::Key_Slash: + return VK_DIVIDE; + case Qt::Key_MediaNext: + return VK_MEDIA_NEXT_TRACK; + case Qt::Key_MediaPrevious: + return VK_MEDIA_PREV_TRACK; + case Qt::Key_MediaPlay: + return VK_MEDIA_PLAY_PAUSE; + case Qt::Key_MediaStop: + return VK_MEDIA_STOP; + // couldn't find those in VK_* + //case Qt::Key_MediaLast: + //case Qt::Key_MediaRecord: + case Qt::Key_VolumeDown: + return VK_VOLUME_DOWN; + case Qt::Key_VolumeUp: + return VK_VOLUME_UP; + case Qt::Key_VolumeMute: + return VK_VOLUME_MUTE; + + // numbers + case Qt::Key_0: + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + case Qt::Key_9: + return key; + + // letters + case Qt::Key_A: + case Qt::Key_B: + case Qt::Key_C: + case Qt::Key_D: + case Qt::Key_E: + case Qt::Key_F: + case Qt::Key_G: + case Qt::Key_H: + case Qt::Key_I: + case Qt::Key_J: + case Qt::Key_K: + case Qt::Key_L: + case Qt::Key_M: + case Qt::Key_N: + case Qt::Key_O: + case Qt::Key_P: + case Qt::Key_Q: + case Qt::Key_R: + case Qt::Key_S: + case Qt::Key_T: + case Qt::Key_U: + case Qt::Key_V: + case Qt::Key_W: + case Qt::Key_X: + case Qt::Key_Y: + case Qt::Key_Z: + return key; + + default: + return 0; + } +} + +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) +{ + return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey); +} + +bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) +{ + return UnregisterHotKey(0, nativeMods ^ nativeKey); +} diff --git a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp index 228ff1e37..38d7b20fc 100644 --- a/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp +++ b/src/libs/ui/qxtglobalshortcut/qxtglobalshortcut_x11.cpp @@ -1,178 +1,178 @@ -/**************************************************************************** -** -** 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 . -** -****************************************************************************/ -/**************************************************************************** -** Copyright (c) 2006 - 2011, the LibQxt project. -** See the Qxt AUTHORS file for a list of authors and copyright holders. -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the LibQxt project nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** -*****************************************************************************/ - -#include "qxtglobalshortcut_p.h" - -#include -#include -#include -#include
+ + + + + + +
+ Get Dash for OS X or iOS +