diff --git a/.shippable.yml b/.shippable.yml index df90b25fd..eca162338 100644 --- a/.shippable.yml +++ b/.shippable.yml @@ -1,64 +1,7 @@ -build_image: shippableimages/ubuntu1404_base -cache: true before_install: - - dpkg --add-architecture i386 - - shippable_retry sudo apt-get -y -q update - - export QT_DIR=$HOME/qt/5.4/mingw491_32 + - shippable_retry sudo apt-get -y -qq update install: - - shippable_retry sudo apt-get -y -q install --no-install-recommends wine p7zip-full ca-certificates curl - # Install Qt - - bash -c '[ -d $HOME/qt/Tools ] || mkdir -p $HOME/qt/OUT' - - bash -c '[ -d $HOME/qt/Tools ] || curl -Lso $HOME/qt/qt.exe http://download.qt.io/official_releases/qt/5.4/5.4.0/qt-opensource-windows-x86-mingw491_opengl-5.4.0.exe' - - bash -c '[ -d $HOME/qt/Tools ] || wine $HOME/qt/qt.exe --dump-binary-data -o $HOME/qt/OUT' - - bash -c '[ -d $HOME/qt/Tools ] || 7z x $HOME/qt/OUT/qt.54.win32_mingw491/5.4.0-1i686-4.9.1-release-posix-dwarf-rt_v3-rev2-runtime.7z -o$HOME/qt' - - bash -c '[ -d $HOME/qt/Tools ] || 7z x $HOME/qt/OUT/qt.54.win32_mingw491/5.4.0-1icu_53_1_mingw_builds_4_9_1_posix_dwarf_32.7z -o$HOME/qt' - - bash -c '[ -d $HOME/qt/Tools ] || 7z x $HOME/qt/OUT/qt.54.win32_mingw491/5.4.0-1qt5_addons.7z -o$HOME/qt' - - bash -c '[ -d $HOME/qt/Tools ] || 7z x $HOME/qt/OUT/qt.54.win32_mingw491/5.4.0-1qt5_essentials.7z -o$HOME/qt' - - bash -c '[ -d $HOME/qt/Tools ] || 7z x $HOME/qt/OUT/qt.tools.win32_mingw491/4.9.1-2i686-4.9.1-release-posix-dwarf-rt_v3-rev2.7z -o$HOME/qt' - - rm -rf $HOME/qt/OUT - - rm -f $HOME/qt/qt.exe - # Install libarchive headers and library - - bash -c '[ -f $HOME/qt/Tools/mingw491_32/i686-w64-mingw32/include/archive.h ] || curl -LOs https://bitbucket.org/zealdocs/zeal-win32-binary-downloads/downloads/libarchive-3.1.2-win32-mingw491.7z' - - bash -c '[ -f $HOME/qt/Tools/mingw491_32/i686-w64-mingw32/include/archive.h ] || 7z x libarchive-3.1.2-win32-mingw491.7z -o$HOME/qt/Tools/mingw491_32/i686-w64-mingw32' - - rm -f libarchive-3.1.2-win32-mingw491.7z - # Setup environment - - echo '[Paths]' > $QT_DIR/bin/qt.conf - - echo 'Prefix=..' >> $QT_DIR/bin/qt.conf - - echo '[HKEY_CURRENT_USER\Environment]' > $HOME/qt/temp.reg - - echo '"QMAKESPEC"="BASEDIR\\5.4\\mingw491_32\\mkspecs\\win32-g++"' >> $HOME/qt/temp.reg - - echo '"QTDIR"="BASEDIR\\5.4\\mingw491_32"' >> $HOME/qt/temp.reg - - echo '"PATH"="BASEDIR\\Tools\\mingw491_32\\libexec\\gcc\\i686-w64-mingw32;BASEDIR\\Tools\\mingw491_32\\bin;BASEDIR\\5.4\\mingw491_32\\bin"' >> $HOME/qt/temp.reg - - sed -i "s.BASEDIR.Z:$HOME/qt.g" $HOME/qt/temp.reg - - sed -i 's./.\\\\.g' $HOME/qt/temp.reg - - wine regedit $HOME/qt/temp.reg -before_script: - - export ZEAL_VERSION=`date +%Y%m%d` - - export ZEAL_DIR=zeal-$ZEAL_VERSION + - shippable_retry sudo apt-get -y -qq install --no-install-recommends qt5-default libqt5webkit5-dev libqt5x11extras5-dev libarchive-dev libxcb-keysyms1-dev script: - - wine qmake - - wine mingw32-make -after_success: - # Prepare ZIP package - - mkdir $ZEAL_DIR - - cp bin/zeal.exe $ZEAL_DIR - - for dir in imageformats platforms sqldrivers; do mkdir $ZEAL_DIR/$dir; done - - cp $HOME/qt/Tools/mingw491_32/opt/bin/ssleay32.dll $ZEAL_DIR - - cp $HOME/qt/Tools/mingw491_32/opt/bin/libeay32.dll $ZEAL_DIR - - for l in gif ico jpeg tiff; do cp $QT_DIR/plugins/imageformats/q$l.dll $ZEAL_DIR/imageformats; done - - cp $QT_DIR/plugins/platforms/qwindows.dll $ZEAL_DIR/platforms - - cp $QT_DIR/plugins/sqldrivers/qsqlite.dll $ZEAL_DIR/sqldrivers - - cp $QT_DIR/bin/libstdc++-6.dll $ZEAL_DIR - - cp $QT_DIR/bin/libgcc_s_dw2-1.dll $ZEAL_DIR - - cp $QT_DIR/bin/libwinpthread-1.dll $ZEAL_DIR - - cp $QT_DIR/bin/icu*.dll $ZEAL_DIR - - for l in Core Gui Multimedia MultimediaWidgets Network OpenGL Qml Quick Sql WebKit WebKitWidgets WebChannel Widgets Positioning PrintSupport Sensors; do cp $QT_DIR/bin/Qt5$l.dll $ZEAL_DIR; done - # Pack everything into ZIP - - 7z a -mx=9 $ZEAL_DIR.zip $ZEAL_DIR - # Upload to BitBucket - - bash -c 'curl -H "Content-Type:application/octet-stream" -H "Auth-Token:$AUTH_TOKEN" --data-binary @$ZEAL_DIR.zip ci2bb.herokuapp.com || true' - # Cleanup - - rm -rf $ZEAL_DIR* - -env: - global: - - secure: LRSLJQbkpc6kkWDVbmvCARm20edC+1RCCgSdEzK0GfG66V+dyGYi6ubFBv1M+kV9YEVY1FkkZctiKlneJLpi/YS3pSJF7bEhYggtxNxRD+9bAVk4GZwVxT7TUw1ZPtLUkWc7L7PqgIO+OOiI8THwsONBNcn5q4fLsByhlt2XOyGPOrQYmfmqjWpgErgI0K4aYoJfoJGTgV2v5sZ+owSkMniyJY5WU9iZkRLO63ddmk72d9eq8ECwgQqxDyhR9/WtHkMPCrwVh06RFd9jG4DfCNDDZRdQLdGxCPXTNwP7QHqG9mKFRQeM91gs/tKlhFFlfPHMckWiBK4hAo+WLC+Ekg== + - qmake + - make diff --git a/README.md b/README.md index 222de864b..611f2feb5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # Zeal -[![Build Status](https://api.shippable.com/projects/54ac2ce4d46935d5fbc19b84/badge?branchName=master)](https://app.shippable.com/projects/54ac2ce4d46935d5fbc19b84/builds/latest) -[![AppVeyor](https://img.shields.io/appveyor/ci/trollixx/zeal.svg?style=flat-square)](https://ci.appveyor.com/project/trollixx/zeal) -[![Coverity Scan](https://img.shields.io/coverity/scan/4271.svg?style=flat-square)](https://scan.coverity.com/projects/4271) [![IRC](https://img.shields.io/badge/irc-%23zealdocs-blue.svg?style=flat-square)](https://kiwiirc.com/client/irc.freenode.net/#zealdocs) > **zeal** *noun* @@ -13,15 +10,24 @@ > > (from WordNet 3.0) -Zeal is a simple offline documentation browser inspired by [Dash](http://kapeli.com/dash/). +Zeal is a simple offline documentation browser inspired by [Dash](https://kapeli.com/dash). + +![Screenshot](https://i.imgur.com/SiLvpz8.png) + +[More screenshots](https://imgur.com/a/eVi97) -![Screenshot](http://i.imgur.com/SiLvpz8.png) +## Build Status + +[![Coverity Scan](https://img.shields.io/coverity/scan/4271.svg?style=flat-square)](https://scan.coverity.com/projects/4271) -[More screenshots](http://imgur.com/a/eVi97) +OS \ Branch | master | stable +------------|--------|------- +Linux | [![Shippable](https://img.shields.io/shippable/54ac2ce4d46935d5fbc19b84/master.svg?style=flat-square)](https://app.shippable.com/builds/54ac2ce4d46935d5fbc19b84) | [![Shippable](https://img.shields.io/shippable/54ac2ce4d46935d5fbc19b84/stable.svg?style=flat-square)](https://app.shippable.com/builds/54ac2ce4d46935d5fbc19b84) +Windows | [![AppVeyor](https://img.shields.io/appveyor/ci/trollixx/zeal/master.svg?style=flat-square)](https://ci.appveyor.com/project/trollixx/zeal) | [![AppVeyor](https://img.shields.io/appveyor/ci/trollixx/zeal/stable.svg?style=flat-square)](https://ci.appveyor.com/project/trollixx/zeal) ## Download -For details about binary packages (currently available for Windows and Ubuntu) see [downloads page](http://zealdocs.org/download.html). Also, the latest unstable builds are available [here]( https://bitbucket.org/zealdocs/zeal-win32-binary-downloads/downloads). +Get binary builds for Windows and Linux from the [download page](https://zealdocs.org/download.html). ## How to use @@ -29,18 +35,17 @@ After installing Zeal, you need to download docsets. Go to *File->Options->Docse ## How to compile -If you prefer to compile Zeal manually. - ### Requirements -* [Qt](https://www.qt.io/) (version 5.2.0 or above is required) -* [libarchive](http://libarchive.org/) -* Optionally [libappindicator](https://launchpad.net/libappindicator) for Unity users +* [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). +* [libarchive](http://libarchive.org/). +* X11 only: `xcb-util-keysyms`. +* Ubuntu Unity only: [libappindicator](https://launchpad.net/libappindicator). -To compile it, run `qmake` and `make`. +To compile Zeal run `qmake` and then `make`. Linux users can install Zeal with `make install` command. ## Query & Filter docsets -You can limit the search scope by using ':' to indicate the desired docsets. +You can limit the search scope by using ':' to indicate the desired docsets: `java:BaseDAO` @@ -56,4 +61,17 @@ If you prefer, you can start Zeal with a query from command line: ## Creating your own docsets -You can use [Dash's instructions for generating docsets](http://kapeli.com/docsets). +You can use [Dash's instructions for generating docsets](https://kapeli.com/docsets). + +## Contact and Support + +We want your feedback! Here's a list of different ways to contact developers and request help: +* Report bugs and submit feature requests to [GitHub issues](https://github.com/zealdocs/zeal/issues). +* Ask any questions in our [Google Group](https://groups.google.com/d/forum/zealdocs). You can simply send an email to zealdocs@googlegroups.com. +* For a quick chat with developers and other Zeal users use our IRC channel: #zealdocs on [Freenode](https://freenode.net/). Also available through Kiwi IRC [web interface](https://kiwiirc.com/client/irc.freenode.net/#zealdocs). Please, take into consideration possible time zone differences. +* Finally, for a private communications send us [email](mailto:zeal@zealdocs.org). +* And do not forget to follow [@zealdocs](https://twitter.com/zealdocs) on Twitter! + +## License + +This software is licensed under the terms of the GNU General Public License version 3 (GPLv3). Full text of the license is available in the [COPYING](https://github.com/zealdocs/zeal/blob/master/COPYING) file and [online](http://opensource.org/licenses/gpl-3.0.html). diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobal.h b/src/3rdparty/qxtglobalshortcut/qxtglobal.h deleted file mode 100644 index f34144830..000000000 --- a/src/3rdparty/qxtglobalshortcut/qxtglobal.h +++ /dev/null @@ -1,48 +0,0 @@ -/**************************************************************************** -** 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 QXTGLOBAL_H -#define QXTGLOBAL_H - -#include - -//--------------------------export macros------------------------------ - -#if !defined(QXT_STATIC) -# if defined(BUILD_QXT_GUI) -# define QXT_GUI_EXPORT Q_DECL_EXPORT -# else -# define QXT_GUI_EXPORT Q_DECL_IMPORT -# endif -#else -# define QXT_GUI_EXPORT -#endif // BUILD_QXT_GUI - -#endif // QXT_GLOBAL diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp index c20a56b8a..4d067b772 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.cpp @@ -1,4 +1,25 @@ -#include "qxtglobalshortcut.h" +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. @@ -29,6 +50,7 @@ ** *****************************************************************************/ +#include "qxtglobalshortcut.h" #include "qxtglobalshortcut_p.h" #include @@ -36,6 +58,7 @@ #ifndef Q_OS_OSX int QxtGlobalShortcutPrivate::ref = 0; #endif // Q_OS_OSX + QHash, QxtGlobalShortcut *> QxtGlobalShortcutPrivate::shortcuts; QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate(QxtGlobalShortcut *qq) : @@ -96,11 +119,14 @@ bool QxtGlobalShortcutPrivate::unsetShortcut() return res; } -void QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) +bool QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) { - QxtGlobalShortcut* shortcut = shortcuts.value(qMakePair(nativeKey, nativeMods)); - if (shortcut && shortcut->isEnabled()) - emit shortcut->activated(); + QxtGlobalShortcut *shortcut = shortcuts.value(qMakePair(nativeKey, nativeMods)); + if (!shortcut || !shortcut->isEnabled()) + return false; + + emit shortcut->activated(); + return true; } /*! diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h index ed18f3cd4..60e9d1e0f 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.h @@ -1,4 +1,25 @@ -#ifndef QXTGLOBALSHORTCUT_H +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. @@ -29,16 +50,15 @@ ** *****************************************************************************/ +#ifndef QXTGLOBALSHORTCUT_H #define QXTGLOBALSHORTCUT_H -#include "qxtglobal.h" - #include #include class QxtGlobalShortcutPrivate; -class QXT_GUI_EXPORT QxtGlobalShortcut : public QObject +class QxtGlobalShortcut : public QObject { Q_OBJECT diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri index c30b751c9..44257cfd9 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut.pri @@ -1,12 +1,20 @@ -DEFINES += BUILD_QXT_GUI - INCLUDEPATH += $$PWD HEADERS += $$files($$PWD/*.h) - SOURCES += $$PWD/qxtglobalshortcut.cpp -macx:SOURCES += $$PWD/qxtglobalshortcut_mac.cpp -unix:!macx:SOURCES += $$PWD/qxtglobalshortcut_x11.cpp + +unix:!macx { + QT += x11extras + + CONFIG += link_pkgconfig + PKGCONFIG += x11 xcb xcb-keysyms + + SOURCES += $$PWD/qxtglobalshortcut_x11.cpp +} + win32:SOURCES += $$PWD/qxtglobalshortcut_win.cpp -macx:LIBS += -framework Carbon +macx { + SOURCES += $$PWD/qxtglobalshortcut_mac.cpp + LIBS += -framework Carbon +} diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp index 37793a9ee..a1d1b3619 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_mac.cpp @@ -1,4 +1,25 @@ -#include +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. @@ -30,10 +51,12 @@ *****************************************************************************/ #include "qxtglobalshortcut_p.h" -#include -#include -#include + #include +#include +#include + +#include typedef QPair Identifier; static QMap keyRefs; diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h index 2ee338a35..274d7b170 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_p.h @@ -1,4 +1,25 @@ -#ifndef QXTGLOBALSHORTCUT_P_H +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. @@ -29,6 +50,7 @@ ** *****************************************************************************/ +#ifndef QXTGLOBALSHORTCUT_P_H #define QXTGLOBALSHORTCUT_P_H #include @@ -53,14 +75,13 @@ class QxtGlobalShortcutPrivate : public QAbstractNativeEventFilter bool setShortcut(const QKeySequence &shortcut); bool unsetShortcut(); - static bool error; #ifndef Q_OS_OSX static int ref; #endif // Q_OS_OSX virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; - static void activateShortcut(quint32 nativeKey, quint32 nativeMods); + static bool activateShortcut(quint32 nativeKey, quint32 nativeMods); private: static quint32 nativeKeycode(Qt::Key keycode); diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp index 5e4785bad..eae080c86 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_win.cpp @@ -1,4 +1,25 @@ -#include "qxtglobalshortcut_p.h" +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. @@ -29,8 +50,9 @@ ** *****************************************************************************/ -#include +#include "qxtglobalshortcut_p.h" +#include bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray &eventType, void *message, long *result) diff --git a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp index eb1cafb4d..24649b3d3 100644 --- a/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp +++ b/src/3rdparty/qxtglobalshortcut/qxtglobalshortcut_x11.cpp @@ -1,4 +1,25 @@ -#include "qxtglobalshortcut_p.h" +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. @@ -29,194 +50,127 @@ ** *****************************************************************************/ +#include "qxtglobalshortcut_p.h" -#include #include +#include #include - -#include +#include #include +#include #include namespace { - -const QVector maskModifiers = QVector() - << 0 << Mod2Mask << LockMask << (Mod2Mask | LockMask); - -typedef int (*X11ErrorHandler)(Display *display, XErrorEvent *event); - -class QxtX11ErrorHandler { -public: - static bool error; - - static int qxtX11ErrorHandler(Display *display, XErrorEvent *event) - { - Q_UNUSED(display); - switch (event->error_code) - { - case BadAccess: - case BadValue: - case BadWindow: - if (event->request_code == 33 /* X_GrabKey */ || - event->request_code == 34 /* X_UngrabKey */) { - error = true; - //TODO: - //char errstr[256]; - //XGetErrorText(dpy, err->error_code, errstr, 256); - } - } - return 0; - } - - QxtX11ErrorHandler() - { - error = false; - m_previousErrorHandler = XSetErrorHandler(qxtX11ErrorHandler); - } - - ~QxtX11ErrorHandler() - { - XSetErrorHandler(m_previousErrorHandler); - } - -private: - X11ErrorHandler m_previousErrorHandler; +const QVector maskModifiers = { + 0, XCB_MOD_MASK_2, XCB_MOD_MASK_LOCK, (XCB_MOD_MASK_2 | XCB_MOD_MASK_LOCK) }; - -bool QxtX11ErrorHandler::error = false; - -class QxtX11Data { -public: - QxtX11Data() - { - QPlatformNativeInterface *native = qApp->platformNativeInterface(); - void *display = native->nativeResourceForScreen(QByteArray("display"), - QGuiApplication::primaryScreen()); - m_display = reinterpret_cast(display); - } - - bool isValid() - { - return m_display != 0; - } - - Display *display() - { - Q_ASSERT(isValid()); - return m_display; - } - - Window rootWindow() - { - return DefaultRootWindow(display()); - } - - bool grabKey(quint32 keycode, quint32 modifiers, Window window) - { - QxtX11ErrorHandler errorHandler; - - for (int i = 0; !errorHandler.error && i < maskModifiers.size(); ++i) { - XGrabKey(display(), keycode, modifiers | maskModifiers[i], window, True, - GrabModeAsync, GrabModeAsync); - } - - if (errorHandler.error) { - ungrabKey(keycode, modifiers, window); - return false; - } - - return true; - } - - bool ungrabKey(quint32 keycode, quint32 modifiers, Window window) - { - QxtX11ErrorHandler errorHandler; - - for (quint32 maskMods : maskModifiers) - XUngrabKey(display(), keycode, modifiers | maskMods, window); - - return !errorHandler.error; - } - -private: - Display *m_display; -}; - } // namespace bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); - - xcb_key_press_event_t *kev = 0; - if (eventType == "xcb_generic_event_t") { - xcb_generic_event_t *ev = static_cast(message); - if ((ev->response_type & 127) == XCB_KEY_PRESS) - kev = static_cast(message); - } - - if (kev != 0) { - unsigned int keycode = kev->detail; - unsigned int keystate = 0; - if(kev->state & XCB_MOD_MASK_1) - keystate |= Mod1Mask; - if(kev->state & XCB_MOD_MASK_CONTROL) - keystate |= ControlMask; - if(kev->state & XCB_MOD_MASK_4) - keystate |= Mod4Mask; - if(kev->state & XCB_MOD_MASK_SHIFT) - keystate |= ShiftMask; - - activateShortcut(keycode, - // Mod1Mask == Alt, Mod4Mask == Meta - keystate & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask)); - } - return false; + if (eventType != "xcb_generic_event_t") + return false; + + xcb_generic_event_t *event = reinterpret_cast(message); + if ((event->response_type & ~0x80) != XCB_KEY_PRESS) + return false; + + xcb_key_press_event_t *keyPressEvent = reinterpret_cast(event); + + // Avoid keyboard freeze + xcb_connection_t *xcbConnection = QX11Info::connection(); + xcb_allow_events(xcbConnection, XCB_ALLOW_REPLAY_KEYBOARD, keyPressEvent->time); + xcb_flush(xcbConnection); + + unsigned int keycode = keyPressEvent->detail; + unsigned int keystate = 0; + if (keyPressEvent->state & XCB_MOD_MASK_1) + keystate |= XCB_MOD_MASK_1; + if (keyPressEvent->state & XCB_MOD_MASK_CONTROL) + keystate |= XCB_MOD_MASK_CONTROL; + if (keyPressEvent->state & XCB_MOD_MASK_4) + keystate |= XCB_MOD_MASK_4; + if (keyPressEvent->state & XCB_MOD_MASK_SHIFT) + keystate |= XCB_MOD_MASK_SHIFT; + + return activateShortcut(keycode, keystate); } quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) { - // ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, and Mod5Mask quint32 native = 0; if (modifiers & Qt::ShiftModifier) - native |= ShiftMask; + native |= XCB_MOD_MASK_SHIFT; if (modifiers & Qt::ControlModifier) - native |= ControlMask; + native |= XCB_MOD_MASK_CONTROL; if (modifiers & Qt::AltModifier) - native |= Mod1Mask; + native |= XCB_MOD_MASK_1; if (modifiers & Qt::MetaModifier) - native |= Mod4Mask; + native |= XCB_MOD_MASK_4; - /// TODO: resolve these? - //if (modifiers & Qt::MetaModifier) - //if (modifiers & Qt::KeypadModifier) - //if (modifiers & Qt::GroupSwitchModifier) return native; } quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) { - QxtX11Data x11; - if (!x11.isValid()) - return 0; + quint32 native = 0; KeySym keysym = XStringToKeysym(QKeySequence(key).toString().toLatin1().data()); - if (keysym == NoSymbol) + if (keysym == XCB_NO_SYMBOL) keysym = static_cast(key); - return XKeysymToKeycode(x11.display(), keysym); + xcb_key_symbols_t *xcbKeySymbols = xcb_key_symbols_alloc(QX11Info::connection()); + + QScopedPointer keycodes( + xcb_key_symbols_get_keycode(xcbKeySymbols, keysym)); + native = keycodes.data()[0]; // Use the first keycode + + xcb_key_symbols_free(xcbKeySymbols); + + return native; } bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) { - QxtX11Data x11; - return x11.isValid() && x11.grabKey(nativeKey, nativeMods, x11.rootWindow()); + xcb_connection_t *xcbConnection = QX11Info::connection(); + + QList xcbCookies; + for (quint32 maskMods : maskModifiers) { + xcbCookies << xcb_grab_key_checked(xcbConnection, 1, QX11Info::appRootWindow(), + nativeMods | maskMods, nativeKey, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + } + + bool failed = false; + for (xcb_void_cookie_t cookie : xcbCookies) { + QScopedPointer error(xcb_request_check(xcbConnection, cookie)); + failed = !error.isNull(); + } + + if (failed) + unregisterShortcut(nativeKey, nativeMods); + + return !failed; } bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) { - QxtX11Data x11; - return x11.isValid() && x11.ungrabKey(nativeKey, nativeMods, x11.rootWindow()); + xcb_connection_t *xcbConnection = QX11Info::connection(); + + QList xcbCookies; + for (quint32 maskMods : maskModifiers) { + xcb_ungrab_key(xcbConnection, nativeKey, QX11Info::appRootWindow(), nativeMods | maskMods); + + } + + bool failed = false; + for (xcb_void_cookie_t cookie : xcbCookies) { + QScopedPointer error(xcb_request_check(xcbConnection, cookie)); + failed = !error.isNull(); + } + + return !failed; } diff --git a/src/appicons/128/zeal.png b/src/appicons/128/zeal.png index ddd765dc3..0d391992e 100644 Binary files a/src/appicons/128/zeal.png and b/src/appicons/128/zeal.png differ diff --git a/src/appicons/16/zeal.png b/src/appicons/16/zeal.png index 045096ac9..7b184d25a 100644 Binary files a/src/appicons/16/zeal.png and b/src/appicons/16/zeal.png differ diff --git a/src/appicons/24/zeal.png b/src/appicons/24/zeal.png index a4cde584a..33a9e8370 100644 Binary files a/src/appicons/24/zeal.png and b/src/appicons/24/zeal.png differ diff --git a/src/appicons/32/zeal.png b/src/appicons/32/zeal.png index 078173c1d..be24bcbd6 100644 Binary files a/src/appicons/32/zeal.png and b/src/appicons/32/zeal.png differ diff --git a/src/appicons/64/zeal.png b/src/appicons/64/zeal.png index 538878320..ca2b6bbe1 100644 Binary files a/src/appicons/64/zeal.png and b/src/appicons/64/zeal.png differ diff --git a/src/core/application.cpp b/src/core/application.cpp index 4712b71c1..f05b4b29b 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "application.h" #include "extractor.h" @@ -47,7 +69,10 @@ Application::Application(const SearchQuery &query, QObject *parent) : m_networkManager = new QNetworkAccessManager(this); m_extractorThread = new QThread(this); m_extractor = new Extractor(); + m_docsetRegistry = new DocsetRegistry(); + m_docsetRegistry->init(m_settings->docsetPath); + m_mainWindow = new MainWindow(this); // Server for detecting already running instances @@ -122,50 +147,32 @@ void Application::extract(const QString &filePath, const QString &destination, c QNetworkReply *Application::download(const QUrl &url) { - const static QString userAgent = QString("Zeal/%1 (%2 %3; Qt/%4)") - .arg(QCoreApplication::applicationVersion()) - /// TODO: [Qt 5.4] Remove #else block -#if QT_VERSION >= 0x050400 - .arg(QSysInfo::prettyProductName()) - .arg(QSysInfo::currentCpuArchitecture()) -#else -#if defined(Q_OS_LINUX) - .arg(QStringLiteral("Linux")) -#elif defined(Q_OS_WIN32) - .arg(QStringLiteral("Windows")) -#elif defined(Q_OS_OSX) - .arg(QStringLiteral("OS X")) -#else - .arg(QStringLiteral("unknown")) -#endif // Q_OS_* + static const QString userAgent = userAgentJson(); -#if defined(Q_PROCESSOR_ARM) - .arg(QStringLiteral("arm")) -#elif defined(Q_PROCESSOR_X86_32) - .arg(QStringLiteral("i386")) -#elif defined(Q_PROCESSOR_X86_64) - .arg(QStringLiteral("x86_64")) -#else - .arg(QStringLiteral("unknown")) -#endif // Q_PROCESSOR_* + QNetworkRequest request(url); -#endif - .arg(qVersion()); + if (url.host().endsWith(QLatin1String(".zealdocs.org", Qt::CaseInsensitive))) + request.setRawHeader("X-Zeal-User-Agent", userAgent.toUtf8()); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::UserAgentHeader, userAgent); return m_networkManager->get(request); } -void Application::checkUpdate() +/*! + \internal + + Performs a check whether a new Zeal version is available. Setting \a quiet to true supresses + error and "you are using the latest version" message boxes. +*/ +void Application::checkForUpdate(bool quiet) { QNetworkReply *reply = download(QUrl(ReleasesApiUrl)); - connect(reply, &QNetworkReply::finished, this, [this]() { + connect(reply, &QNetworkReply::finished, this, [this, quiet]() { QScopedPointer reply( qobject_cast(sender())); if (reply->error() != QNetworkReply::NoError) { - emit updateCheckError(reply->errorString()); + if (!quiet) + emit updateCheckError(reply->errorString()); return; } @@ -173,7 +180,8 @@ void Application::checkUpdate() const QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError); if (jsonError.error != QJsonParseError::NoError) { - emit updateCheckError(jsonError.errorString()); + if (!quiet) + emit updateCheckError(jsonError.errorString()); return; } @@ -181,7 +189,7 @@ void Application::checkUpdate() const Util::Version latestVersion = latestVersionInfo[QStringLiteral("version")].toString(); if (latestVersion > Util::Version(QCoreApplication::applicationVersion())) emit updateCheckDone(latestVersion.toString()); - else + else if (!quiet) emit updateCheckDone(); }); } @@ -209,3 +217,72 @@ void Application::applySettings() } } } + + +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()}, + {QStringLiteral("install_id"), m_settings->installId} + }; + + QJsonObject os = { + {QStringLiteral("arch"), QSysInfo::currentCpuArchitecture()}, + {QStringLiteral("name"), QSysInfo::prettyProductName()}, + {QStringLiteral("product_type"), QSysInfo::productType()}, + {QStringLiteral("product_version"), QSysInfo::productVersion()}, + {QStringLiteral("kernel_type"), QSysInfo::kernelType()}, + {QStringLiteral("kernel_version"), QSysInfo::kernelVersion()}, + {QStringLiteral("locale"), QLocale::system().name()} + }; + + QJsonObject ua = { + {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 QJsonDocument(ua).toJson(QJsonDocument::Compact); +} diff --git a/src/core/application.h b/src/core/application.h index 3bcaed401..3d444ef66 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef APPLICATION_H #define APPLICATION_H @@ -39,7 +61,7 @@ class Application : public QObject public slots: void extract(const QString &filePath, const QString &destination, const QString &root = QString()); QNetworkReply *download(const QUrl &url); - void checkUpdate(); + void checkForUpdate(bool quiet = false); signals: void extractionCompleted(const QString &filePath); @@ -52,6 +74,8 @@ private slots: void applySettings(); private: + QString userAgentJson() const; + static Application *m_instance; Settings *m_settings = nullptr; diff --git a/src/core/extractor.cpp b/src/core/extractor.cpp index f126e4efc..1fd38a4ef 100644 --- a/src/core/extractor.cpp +++ b/src/core/extractor.cpp @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "extractor.h" #include @@ -15,11 +37,11 @@ Extractor::Extractor(QObject *parent) : void Extractor::extract(const QString &filePath, const QString &destination, const QString &root) { ExtractInfo info = { - .extractor = this, - .archiveHandle = archive_read_new(), - .filePath = filePath, - .totalBytes = QFileInfo(filePath).size(), - .extractedBytes = 0 + this, // extractor + archive_read_new(), // archiveHandle + filePath, // filePath + QFileInfo(filePath).size(), // totalBytes + 0 // extractedBytes }; archive_read_support_filter_all(info.archiveHandle); diff --git a/src/core/extractor.h b/src/core/extractor.h index 27c5fecea..605384ca0 100644 --- a/src/core/extractor.h +++ b/src/core/extractor.h @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef EXTRACTOR_H #define EXTRACTOR_H diff --git a/src/core/settings.cpp b/src/core/settings.cpp index cb6fcde9e..f1f6ec4d7 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "settings.h" #include @@ -5,24 +27,35 @@ #include #include #include +#include #ifdef USE_WEBENGINE - #include - #define QWebSettings QWebEngineSettings +#include +#define QWebSettings QWebEngineSettings #else - #include +#include #endif +namespace { +// Configuration file groups +const char GroupBrowser[] = "browser"; +const char GroupDocsets[] = "docsets"; +const char GroupGlobalShortcuts[] = "global_shortcuts"; +const char GroupInternal[] = "internal"; +const char GroupState[] = "state"; +const char GroupProxy[] = "proxy"; +} + using namespace Zeal::Core; Settings::Settings(QObject *parent) : QObject(parent), -#ifndef PORTABLE_BUILD + #ifndef PORTABLE_BUILD m_settings(new QSettings(this)) -#else + #else m_settings(new QSettings(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"), QSettings::IniFormat, this)) -#endif + #endif { /// TODO: Move to user style sheet (related to #268) #ifndef USE_WEBENGINE @@ -41,12 +74,13 @@ void Settings::load() { /// TODO: Put everything in groups startMinimized = m_settings->value(QStringLiteral("start_minimized"), false).toBool(); + checkForUpdate = m_settings->value(QStringLiteral("check_for_update"), true).toBool(); showSystrayIcon = m_settings->value(QStringLiteral("show_systray_icon"), true).toBool(); minimizeToSystray = m_settings->value(QStringLiteral("minimize_to_systray"), false).toBool(); hideOnClose = m_settings->value(QStringLiteral("hide_on_close"), false).toBool(); - m_settings->beginGroup(QStringLiteral("global_shortcuts")); + m_settings->beginGroup(GroupGlobalShortcuts); #ifndef Q_OS_OSX showShortcut = m_settings->value(QStringLiteral("show"), QStringLiteral("Meta+Z")).value(); #else @@ -54,13 +88,13 @@ void Settings::load() #endif m_settings->endGroup(); - m_settings->beginGroup(QStringLiteral("browser")); + m_settings->beginGroup(GroupBrowser); minimumFontSize = m_settings->value(QStringLiteral("minimum_font_size"), QWebSettings::globalSettings()->fontSize(QWebSettings::MinimumFontSize)).toInt(); m_settings->endGroup(); - m_settings->beginGroup(QStringLiteral("proxy")); - proxyType = static_cast(m_settings->value(QStringLiteral("type"), ProxyType::System).toUInt()); + m_settings->beginGroup(GroupProxy); + proxyType = m_settings->value(QStringLiteral("type"), ProxyType::System).value(); proxyHost = m_settings->value(QStringLiteral("host")).toString(); proxyPort = m_settings->value(QStringLiteral("port"), 0).toInt(); proxyAuthenticate = m_settings->value(QStringLiteral("authenticate"), false).toBool(); @@ -68,7 +102,7 @@ void Settings::load() proxyPassword = m_settings->value(QStringLiteral("password")).toString(); m_settings->endGroup(); - m_settings->beginGroup(QStringLiteral("docsets")); + m_settings->beginGroup(GroupDocsets); if (m_settings->contains(QStringLiteral("path"))) { docsetPath = m_settings->value(QStringLiteral("path")).toString(); } else { @@ -82,30 +116,39 @@ void Settings::load() } m_settings->endGroup(); - m_settings->beginGroup(QStringLiteral("state")); + m_settings->beginGroup(GroupState); windowGeometry = m_settings->value(QStringLiteral("window_geometry")).toByteArray(); splitterGeometry = m_settings->value(QStringLiteral("splitter_geometry")).toByteArray(); m_settings->endGroup(); + + m_settings->beginGroup(GroupInternal); + installId = m_settings->value(QStringLiteral("install_id"), + // Avoid curly braces (QTBUG-885) + QUuid::createUuid().toString().mid(1, 36)).toString(); + version = m_settings->value(QStringLiteral("version"), + QCoreApplication::applicationVersion()).toString(); + m_settings->endGroup(); } void Settings::save() { /// TODO: Put everything in groups m_settings->setValue(QStringLiteral("start_minimized"), startMinimized); + m_settings->setValue(QStringLiteral("check_for_update"), checkForUpdate); m_settings->setValue(QStringLiteral("show_systray_icon"), showSystrayIcon); m_settings->setValue(QStringLiteral("minimize_to_systray"), minimizeToSystray); m_settings->setValue(QStringLiteral("hide_on_close"), hideOnClose); - m_settings->beginGroup(QStringLiteral("global_shortcuts")); + m_settings->beginGroup(GroupGlobalShortcuts); m_settings->setValue(QStringLiteral("show"), showShortcut); m_settings->endGroup(); - m_settings->beginGroup(QStringLiteral("browser")); + m_settings->beginGroup(GroupBrowser); m_settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize); m_settings->endGroup(); - m_settings->beginGroup(QStringLiteral("proxy")); + m_settings->beginGroup(GroupProxy); m_settings->setValue(QStringLiteral("type"), proxyType); m_settings->setValue(QStringLiteral("host"), proxyHost); m_settings->setValue(QStringLiteral("port"), proxyPort); @@ -115,16 +158,21 @@ void Settings::save() m_settings->endGroup(); #ifndef PORTABLE_BUILD - m_settings->beginGroup(QStringLiteral("docsets")); + m_settings->beginGroup(GroupDocsets); m_settings->setValue(QStringLiteral("path"), docsetPath); m_settings->endGroup(); #endif - m_settings->beginGroup(QStringLiteral("state")); + m_settings->beginGroup(GroupState); m_settings->setValue(QStringLiteral("window_geometry"), windowGeometry); m_settings->setValue(QStringLiteral("splitter_geometry"), splitterGeometry); m_settings->endGroup(); + m_settings->beginGroup(GroupInternal); + m_settings->setValue(QStringLiteral("install_id"), installId); + m_settings->setValue(QStringLiteral("version"), version); + m_settings->endGroup(); + m_settings->sync(); emit updated(); diff --git a/src/core/settings.h b/src/core/settings.h index 1b0eaaa77..f24af513f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SETTINGS_H #define SETTINGS_H @@ -18,6 +40,7 @@ class Settings : public QObject // Startup bool startMinimized; + bool checkForUpdate; /// TODO: bool restoreLastState; // System Tray @@ -41,6 +64,15 @@ class Settings : public QObject UserDefined }; + // Internal + // -------- + // InstallId is a UUID used to indentify a Zeal installation. Created on first start or after + // a settings wipe. It is not attached to user hardware or software, and is sent exclusevely + // to *.zealdocs.org hosts. + QString installId; + // Version of configuration file format, should match Zeal version. Useful for migration rules. + QString version; + ProxyType proxyType = ProxyType::System; QString proxyHost; quint16 proxyPort; @@ -72,4 +104,6 @@ public slots: } // namespace Core } // namespace Zeal +Q_DECLARE_METATYPE(Zeal::Core::Settings::ProxyType) + #endif // SETTINGS_H diff --git a/src/main.cpp b/src/main.cpp index 2160cd945..917ce66df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "core/application.h" #include "registry/searchquery.h" #include #include +#include +#include #include #include +#include +#include #include #include #include @@ -45,7 +72,7 @@ CommandLineParameters parseCommandLine(const QStringList &arguments) /// TODO: [Qt 5.4] parser.addOption({{"f", "force"}, "Force the application run."}); parser.addOption(QCommandLineOption({QStringLiteral("f"), QStringLiteral("force")}, QObject::tr("Force the application run."))); - /// TODO: [0.2.0] Remove --query support + /// TODO: [0.3.0] Remove --query support parser.addOption(QCommandLineOption({QStringLiteral("q"), QStringLiteral("query")}, QObject::tr("[DEPRECATED] Query ."), QStringLiteral("term"))); @@ -197,6 +224,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"))) { + const int ret = QMessageBox::critical(nullptr, QStringLiteral("Zeal"), + QObject::tr("Qt SQLite driver is not available."), + QMessageBox::Close, QMessageBox::Help); + if (ret == QMessageBox::Help) + QDesktopServices::openUrl(QStringLiteral("https://zealdocs.org/contact.html")); + return 0; + } + QDir::setSearchPaths(QStringLiteral("typeIcon"), {QStringLiteral(":/icons/type")}); QScopedPointer app(new Zeal::Core::Application(clParams.query)); diff --git a/src/registry/docset.cpp b/src/registry/docset.cpp index 7c150f4cf..c9ec8b84a 100644 --- a/src/registry/docset.cpp +++ b/src/registry/docset.cpp @@ -1,7 +1,30 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "docset.h" -#include "docsetinfo.h" #include "searchquery.h" +#include "util/plist.h" #include #include @@ -14,6 +37,23 @@ using namespace Zeal; +namespace { +const char IndexNamePrefix[] = "__zi_name"; // zi - Zeal index +const char IndexNameVersion[] = "0001"; // Current index version + +namespace InfoPlist { +const char CFBundleName[] = "CFBundleName"; +const char CFBundleIdentifier[] = "CFBundleIdentifier"; +const char DashDocSetFamily[] = "DashDocSetFamily"; +const char DashDocSetKeyword[] = "DashDocSetKeyword"; +const char DashDocSetPluginKeyword[] = "DashDocSetPluginKeyword"; +const char DashIndexFilePath[] = "dashIndexFilePath"; +const char DocSetPlatformFamily[] = "DocSetPlatformFamily"; +const char IsDashDocset[] = "isDashDocset"; +const char IsJavaScriptEnabled[] = "isJavaScriptEnabled"; +} +} + Docset::Docset(const QString &path) : m_path(path) { @@ -34,18 +74,24 @@ Docset::Docset(const QString &path) : if (!dir.cd(QStringLiteral("Contents"))) return; - DocsetInfo info; + /// TODO: 'info.plist' is invalid according to Apple, and must alsways be 'Info.plist' + /// https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPRuntimeConfig/ + /// Articles/ConfigFiles.html + Util::Plist plist; if (dir.exists(QStringLiteral("Info.plist"))) - info = DocsetInfo::fromPlist(dir.absoluteFilePath(QStringLiteral("Info.plist"))); + plist.read(dir.absoluteFilePath(QStringLiteral("Info.plist"))); else if (dir.exists(QStringLiteral("info.plist"))) - info = DocsetInfo::fromPlist(dir.absoluteFilePath(QStringLiteral("info.plist"))); + plist.read(dir.absoluteFilePath(QStringLiteral("info.plist"))); else return; + if (plist.hasError()) + return; + if (m_name.isEmpty()) { // Fallback if meta.json is absent - if (!info.bundleName.isEmpty()) { - m_name = m_title = info.bundleName; + 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('_')); } else { @@ -59,7 +105,7 @@ Docset::Docset(const QString &path) : } /// TODO: Verify if this is needed - if (info.family == QLatin1String("cheatsheet")) + if (plist[InfoPlist::DashDocSetFamily].toString() == QLatin1String("cheatsheet")) m_name = m_name + QLatin1String("cheats"); if (!dir.cd(QStringLiteral("Resources")) || !dir.exists(QStringLiteral("docSet.dsidx"))) @@ -75,15 +121,35 @@ Docset::Docset(const QString &path) : m_type = db.tables().contains(QStringLiteral("searchIndex")) ? Type::Dash : Type::ZDash; + createIndex(); + if (!dir.cd(QStringLiteral("Documents"))) return; - m_keyword = (info.bundleName.isEmpty() ? m_name : info.bundleName).toLower(); + // + // Setyp keywords + if (plist.contains(InfoPlist::DocSetPlatformFamily)) + m_keywords << plist[InfoPlist::DocSetPlatformFamily].toString(); + + if (plist.contains(InfoPlist::DashDocSetPluginKeyword)) + m_keywords << plist[InfoPlist::DashDocSetPluginKeyword].toString(); + + if (plist.contains(InfoPlist::DashDocSetKeyword)) + m_keywords << plist[InfoPlist::DashDocSetKeyword].toString(); + + if (plist.contains(InfoPlist::DashDocSetFamily)) { + const QString kw = plist[InfoPlist::DashDocSetFamily].toString(); + if (kw != QStringLiteral("dashtoc")) + m_keywords << kw; + } + + /// TODO: Use 'unknown' instead of CFBundleName? (See #383) + m_keywords << plist.value(InfoPlist::CFBundleName, m_name).toString().toLower(); // Try to find index path if metadata is missing one if (m_indexFilePath.isEmpty()) { - if (!info.indexFilePath.isEmpty() && dir.exists(info.indexFilePath)) - m_indexFilePath = info.indexFilePath; + if (plist.contains(InfoPlist::DashIndexFilePath)) + m_indexFilePath = plist[InfoPlist::DashIndexFilePath].toString(); else if (dir.exists(QStringLiteral("index.html"))) m_indexFilePath = QStringLiteral("index.html"); else @@ -113,9 +179,9 @@ QString Docset::title() const return m_title; } -QString Docset::keyword() const +QStringList Docset::keywords() const { - return m_keyword; + return m_keywords; } QString Docset::version() const @@ -172,7 +238,7 @@ QList Docset::search(const QString &query) const const SearchQuery searchQuery = SearchQuery::fromString(query); const QString sanitizedQuery = searchQuery.sanitizedQuery(); - if (searchQuery.hasKeywords() && !searchQuery.hasKeyword(m_keyword)) + if (searchQuery.hasKeywords() && !searchQuery.hasKeywords(m_keywords)) return results; QString queryStr; @@ -197,32 +263,38 @@ QList Docset::search(const QString &query) const notQuery = QString(" AND NOT (ztokenname LIKE '%1%' ESCAPE '\\' %2) ").arg(sanitizedQuery, subNames.arg("ztokenname", sanitizedQuery)); } if (m_type == Docset::Type::Dash) { - queryStr = QString("SELECT name, path " + queryStr = QString("SELECT name, type, path " " FROM searchIndex " "WHERE (name LIKE '%1%' ESCAPE '\\' %3) %2 " - "LIMIT 100") + "ORDER BY name COLLATE NOCASE LIMIT 100") .arg(curQuery, notQuery, subNames.arg("name", curQuery)); } else { - queryStr = QString("SELECT ztokenname, zpath, zanchor " + queryStr = QString("SELECT ztokenname, ztypename, zpath, zanchor " " FROM ztoken " "JOIN ztokenmetainformation " " ON ztoken.zmetainformation = ztokenmetainformation.z_pk " "JOIN zfilepath " " ON ztokenmetainformation.zfile = zfilepath.z_pk " + "JOIN ztokentype " + " ON ztoken.ztokentype = ztokentype.z_pk " "WHERE (ztokenname LIKE '%1%' ESCAPE '\\' %3) %2 " - "LIMIT 100").arg(curQuery, notQuery, - subNames.arg("ztokenname", curQuery)); + "ORDER BY ztokenname COLLATE NOCASE LIMIT 100") + .arg(curQuery, notQuery, subNames.arg("ztokenname", curQuery)); } QSqlQuery query(queryStr, database()); while (query.next()) { const QString itemName = query.value(0).toString(); - QString path = query.value(1).toString(); - if (m_type == Docset::Type::ZDash) - path += QLatin1Char('#') + query.value(1).toString(); + QString path = query.value(2).toString(); + if (m_type == Docset::Type::ZDash) { + const QString anchor = query.value(3).toString(); + if (!anchor.isEmpty()) + path += QLatin1Char('#') + anchor; + } /// TODO: Third should be type - results.append(SearchResult{itemName, QString(), QString(), + results.append(SearchResult{itemName, QString(), + parseSymbolType(query.value(1).toString()), const_cast(this), path, sanitizedQuery}); } @@ -259,27 +331,27 @@ QList Docset::relatedLinks(const QUrl &url) const "JOIN ztokenmetainformation ON ztoken.zmetainformation = ztokenmetainformation.z_pk " "JOIN zfilepath ON ztokenmetainformation.zfile = zfilepath.z_pk " "JOIN ztokentype ON ztoken.ztokentype = ztokentype.z_pk " - "WHERE zfilepath.zpath = \"%1\""); + "WHERE zfilepath.zpath = \"%1\" AND ztokenmetainformation.zanchor IS NOT NULL"); } QSqlQuery query(queryStr.arg(cleanUrl.toString()), database()); while (query.next()) { - QString sectionName = query.value(0).toString(); + const QString sectionName = query.value(0).toString(); QString sectionPath = query.value(2).toString(); if (m_type == Docset::Type::ZDash) { sectionPath += QLatin1Char('#'); sectionPath += query.value(3).toString(); } - //QString parentName; - //normalizeName(sectionName, parentName); - results.append(SearchResult{sectionName, QString(), parseSymbolType(query.value(1).toString()), const_cast(this), sectionPath, QString()}); } + if (results.size() == 1) + results.clear(); + return results; } @@ -288,22 +360,6 @@ QSqlDatabase Docset::database() const return QSqlDatabase::database(m_name, true); } -void Docset::normalizeName(QString &name, QString &parentName) -{ - QRegExp matchMethodName(QStringLiteral("^([^\\(]+)(?:\\(.*\\))?$")); - if (matchMethodName.indexIn(name) != -1) - name = matchMethodName.cap(1); - - const QStringList separators = {QStringLiteral("."), QStringLiteral("::"), QStringLiteral("/")}; - for (const QString &sep : separators) { - if (name.indexOf(sep) != -1 && name.indexOf(sep) != 0) { - const QStringList splitted = name.split(sep); - name = splitted.at(splitted.size()-1); - parentName = splitted.at(splitted.size()-2); - } - } -} - void Docset::loadMetadata() { const QDir dir(m_path); @@ -395,6 +451,40 @@ void Docset::loadSymbols(const QString &symbolType, const QString &symbolString) symbols.insertMulti(query.value(0).toString(), QDir(documentPath()).absoluteFilePath(query.value(1).toString())); } +void Docset::createIndex() +{ + static const QString indexListQuery = QStringLiteral("PRAGMA INDEX_LIST('%1')"); + static const QString indexDropQuery = QStringLiteral("DROP INDEX '%1'"); + static const QString indexCreateQuery = QStringLiteral("CREATE INDEX IF NOT EXISTS %1%2" + " ON %3 (name COLLATE NOCASE)"); + + QSqlQuery query(database()); + + const QString tableName = m_type == Type::Dash ? QStringLiteral("searchIndex") + : QStringLiteral("ztoken"); + + query.exec(indexListQuery.arg(tableName)); + + QStringList oldIndexes; + + while (query.next()) { + const QString indexName = query.value(1).toString(); + if (!indexName.startsWith(IndexNamePrefix)) + continue; + + if (indexName.endsWith(IndexNameVersion)) + return; + + oldIndexes << indexName; + } + + // Drop old indexes + for (const QString oldIndexName : oldIndexes) + query.exec(indexDropQuery.arg(oldIndexName)); + + query.exec(indexCreateQuery.arg(IndexNamePrefix, IndexNameVersion, tableName)); +} + QString Docset::parseSymbolType(const QString &str) { /// Dash symbol aliases diff --git a/src/registry/docset.h b/src/registry/docset.h index e82d1cbb3..76feb93b2 100644 --- a/src/registry/docset.h +++ b/src/registry/docset.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef DOCSET_H #define DOCSET_H @@ -21,7 +44,7 @@ class Docset : public QObject QString name() const; QString title() const; - QString keyword() const; + QStringList keywords() const; QString version() const; QString revision() const; @@ -39,14 +62,9 @@ class Docset : public QObject QList search(const QString &query) const; QList relatedLinks(const QUrl &url) const; - QSqlDatabase database() const; - /// FIXME: This is an ugly workaround before we have a proper docset sources implementation bool hasUpdate = false; - /// FIXME: Get rid of it - static void normalizeName(QString &name, QString &parentName); - private: enum class Type { Invalid, @@ -54,17 +72,19 @@ class Docset : public QObject 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(); static QString parseSymbolType(const QString &str); QString m_sourceId; QString m_name; QString m_title; - QString m_keyword; + QStringList m_keywords; QString m_version; QString m_revision; Docset::Type m_type = Type::Invalid; diff --git a/src/registry/docsetinfo.cpp b/src/registry/docsetinfo.cpp deleted file mode 100644 index 3242e6810..000000000 --- a/src/registry/docsetinfo.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "docsetinfo.h" - -#include -#include -#include - -using namespace Zeal; - -DocsetInfo DocsetInfo::fromPlist(const QString &filePath) -{ - DocsetInfo docsetInfo; - - QScopedPointer file(new QFile(filePath)); - if (!file->open(QIODevice::ReadOnly)) - return docsetInfo; - - QXmlStreamReader xml(file.data()); - - while (!xml.atEnd()) { - const QXmlStreamReader::TokenType token = xml.readNext(); - if (token == QXmlStreamReader::StartDocument || token != QXmlStreamReader::StartElement) - continue; - - if (xml.name() != QLatin1String("key")) - continue; - - const QString key = xml.readElementText(); - - // Skip whitespaces between tags - while (xml.readNext() == QXmlStreamReader::Characters); - - if (xml.tokenType() != QXmlStreamReader::StartElement) - continue; - - QVariant value; - if (xml.name() == QLatin1String("string")) - value = xml.readElementText(); - else if (xml.name() == QLatin1String("true")) - value = true; - else - continue; // Skip unknown types - - if (key == QLatin1String("dashIndexFilePath")) - docsetInfo.indexFilePath = value.toString(); - else if (key == QLatin1String("DashDocSetKeyword")) - docsetInfo.keyword = value.toString(); - else if (key == QLatin1String("DashDocSetFamily")) - docsetInfo.family = value.toString(); - else if (key == QLatin1String("CFBundleName")) - docsetInfo.bundleName = value.toString(); - else if (key == QLatin1String("CFBundleIdentifier")) - docsetInfo.bundleIdentifier = value.toString(); - else if (key == QLatin1String("isDashDocset")) - docsetInfo.isDashDocset = value.toBool(); - else if (key == QLatin1String("isJavaScriptEnabled")) - docsetInfo.isJavaScriptEnabled = value.toBool(); - } - - /// TODO: Check xml.hasError() - - return docsetInfo; -} diff --git a/src/registry/docsetinfo.h b/src/registry/docsetinfo.h deleted file mode 100644 index 308f0f6a1..000000000 --- a/src/registry/docsetinfo.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef DOCSETINFO_H -#define DOCSETINFO_H - -#include - -namespace Zeal { - -struct DocsetInfo -{ - static DocsetInfo fromPlist(const QString &filePath); - - QString bundleName; - QString bundleIdentifier; - QString indexFilePath; - QString family; - QString keyword; - bool isDashDocset = false; - bool isJavaScriptEnabled = false; -}; - -} // namespace Zeal - -#endif // DOCSETINFO_H diff --git a/src/registry/docsetmetadata.cpp b/src/registry/docsetmetadata.cpp index 4c021168d..1e4c9f0ef 100644 --- a/src/registry/docsetmetadata.cpp +++ b/src/registry/docsetmetadata.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "docsetmetadata.h" #include diff --git a/src/registry/docsetmetadata.h b/src/registry/docsetmetadata.h index da5b51123..5dda9778c 100644 --- a/src/registry/docsetmetadata.h +++ b/src/registry/docsetmetadata.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef DOCSETMETADATA_H #define DOCSETMETADATA_H diff --git a/src/registry/docsetregistry.cpp b/src/registry/docsetregistry.cpp index 865a7f0bf..d2f1ad913 100644 --- a/src/registry/docsetregistry.cpp +++ b/src/registry/docsetregistry.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "docsetregistry.h" #include "searchresult.h" diff --git a/src/registry/docsetregistry.h b/src/registry/docsetregistry.h index b7ea63017..61bf2fb0b 100644 --- a/src/registry/docsetregistry.h +++ b/src/registry/docsetregistry.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef DOCSETREGISTRY_H #define DOCSETREGISTRY_H diff --git a/src/registry/listmodel.cpp b/src/registry/listmodel.cpp index 3c3df0983..26c0698c8 100644 --- a/src/registry/listmodel.cpp +++ b/src/registry/listmodel.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "listmodel.h" #include "docset.h" @@ -170,6 +193,10 @@ void ListModel::addDocset(const QString &name) void ListModel::removeDocset(const QString &name) { const int index = m_docsetItems.keys().indexOf(name); + /// TODO: Investigate why this can happen (see #420) + if (index == -1) + return; + beginRemoveRows(QModelIndex(), index, index); DocsetItem *docsetItem = m_docsetItems.take(name); diff --git a/src/registry/listmodel.h b/src/registry/listmodel.h index 3a6e4ddb9..79283352f 100644 --- a/src/registry/listmodel.h +++ b/src/registry/listmodel.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef LISTMODEL_H #define LISTMODEL_H diff --git a/src/registry/searchmodel.cpp b/src/registry/searchmodel.cpp index eaefb2a72..c7dbc67b4 100644 --- a/src/registry/searchmodel.cpp +++ b/src/registry/searchmodel.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchmodel.h" #include "core/application.h" @@ -14,32 +37,36 @@ SearchModel::SearchModel(QObject *parent) : QVariant SearchModel::data(const QModelIndex &index, int role) const { - if ((role != Qt::DisplayRole && role != Qt::DecorationRole) || !index.isValid()) + if (!index.isValid()) return QVariant(); SearchResult *item = static_cast(index.internalPointer()); - if (role == Qt::DecorationRole) { - if (index.column() != 0) + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case 0: + if (item->parentName.isEmpty()) + return item->name; + else + return QString("%1 (%2)").arg(item->name, item->parentName); + case 1: + return QDir(item->docset->documentPath()).absoluteFilePath(item->path); + default: return QVariant(); + } - /// TODO: Provide two icons (docset & symbol) once search item delegate supports that - if (item->type.isEmpty()) - return item->docset->icon(); - else - return QIcon(QString("typeIcon:%1.png").arg(item->type));; - } + case Qt::DecorationRole: + return item->docset->icon(); - if (index.column() == 0) { - if (!item->parentName.isEmpty()) - return QString("%1 (%2)").arg(item->name, item->parentName); - else - return item->name; + case Roles::TypeIconRole: + if (index.column() != 0) + return QVariant(); + return QIcon(QString("typeIcon:%1.png").arg(item->type)); - } else if (index.column() == 1) { - return QDir(item->docset->documentPath()).absoluteFilePath(item->path); + default: + return QVariant(); } - return QVariant(); } QModelIndex SearchModel::index(int row, int column, const QModelIndex &parent) const diff --git a/src/registry/searchmodel.h b/src/registry/searchmodel.h index 4bca47bcc..f7a14cd3b 100644 --- a/src/registry/searchmodel.h +++ b/src/registry/searchmodel.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHMODEL_H #define SEARCHMODEL_H @@ -11,12 +34,16 @@ class SearchModel : public QAbstractItemModel { Q_OBJECT public: + enum Roles { + TypeIconRole = Qt::UserRole + }; + explicit SearchModel(QObject *parent = nullptr); 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) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent) const override; public slots: diff --git a/src/registry/searchquery.cpp b/src/registry/searchquery.cpp index 8a9649b8a..021379b46 100644 --- a/src/registry/searchquery.cpp +++ b/src/registry/searchquery.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchquery.h" using namespace Zeal; @@ -12,10 +35,9 @@ SearchQuery::SearchQuery() } SearchQuery::SearchQuery(const QString &query, const QStringList &keywords) : - m_query(query), - m_keywords(keywords), - m_keywordPrefix(keywords.join(keywordSeparator)) + m_query(query) { + setKeywords(keywords); } SearchQuery SearchQuery::fromString(const QString &str) @@ -25,10 +47,10 @@ SearchQuery SearchQuery::fromString(const QString &str) QString query; QStringList keywords; - if (sepAt >= 1 && (next >= str.size() || str.at(next) != prefixSeparator)) { - query = str.midRef(next).toString().trimmed(); + if (sepAt > 0 && (next >= str.size() || str.at(next) != prefixSeparator)) { + query = str.mid(next).trimmed(); - const QString keywordStr = str.leftRef(sepAt).toString().trimmed(); + const QString keywordStr = str.left(sepAt).trimmed(); keywords = keywordStr.split(keywordSeparator); } else { query = str.trimmed(); @@ -42,7 +64,7 @@ QString SearchQuery::toString() const if (m_keywords.isEmpty()) return m_query; else - return m_keywords.join(keywordSeparator) + prefixSeparator + m_query; + return m_keywordPrefix + m_query; } bool SearchQuery::isEmpty() const @@ -57,7 +79,11 @@ QStringList SearchQuery::keywords() const void SearchQuery::setKeywords(const QStringList &list) { + if (list.isEmpty()) + return; + m_keywords = list; + m_keywordPrefix = list.join(keywordSeparator) + prefixSeparator; } bool SearchQuery::hasKeywords() const @@ -67,7 +93,22 @@ bool SearchQuery::hasKeywords() const bool SearchQuery::hasKeyword(const QString &keyword) const { - return m_keywords.contains(keyword); + // Temporary workaround for #333 + /// TODO: Remove once #167 is implemented + for (const QString &kw : m_keywords) { + if (keyword.startsWith(kw, Qt::CaseInsensitive)) + return true; + } + return false; +} + +bool SearchQuery::hasKeywords(const QStringList &keywords) const +{ + for (const QString &keyword : keywords) { + if (m_keywords.contains(keyword)) + return true; + } + return false; } int SearchQuery::keywordPrefixSize() const diff --git a/src/registry/searchquery.h b/src/registry/searchquery.h index 972e1a9e0..bbebb99f4 100644 --- a/src/registry/searchquery.h +++ b/src/registry/searchquery.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHQUERY_H #define SEARCHQUERY_H @@ -44,6 +67,7 @@ class SearchQuery /// Returns true if the docset prefix match the ones given on query bool hasKeyword(const QString &keyword) const; + bool hasKeywords(const QStringList &keywords) const; /// Returns the docset filter raw size for the given query int keywordPrefixSize() const; diff --git a/src/registry/searchresult.cpp b/src/registry/searchresult.cpp index fc0760fb2..3b7d26b7f 100644 --- a/src/registry/searchresult.cpp +++ b/src/registry/searchresult.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchresult.h" using namespace Zeal; diff --git a/src/registry/searchresult.h b/src/registry/searchresult.h index 97c14ef74..e879daf2f 100644 --- a/src/registry/searchresult.h +++ b/src/registry/searchresult.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHRESULT_H #define SEARCHRESULT_H diff --git a/src/resources/browser/start.html b/src/resources/browser/start.html index ccda10541..326ec2b0f 100644 --- a/src/resources/browser/start.html +++ b/src/resources/browser/start.html @@ -11,8 +11,8 @@

Zeal

diff --git a/src/resources/icons/logo/128x128.png b/src/resources/icons/logo/128x128.png index ddd765dc3..0d391992e 100644 Binary files a/src/resources/icons/logo/128x128.png and b/src/resources/icons/logo/128x128.png differ diff --git a/src/resources/icons/logo/64x64.png b/src/resources/icons/logo/64x64.png index 538878320..ca2b6bbe1 100644 Binary files a/src/resources/icons/logo/64x64.png and b/src/resources/icons/logo/64x64.png differ diff --git a/src/resources/icons/type/Event.png b/src/resources/icons/type/Event.png index 5546a10c2..58be593a5 100644 Binary files a/src/resources/icons/type/Event.png and b/src/resources/icons/type/Event.png differ diff --git a/src/resources/icons/type/Field.png b/src/resources/icons/type/Field.png index bfe812472..f97beb195 100644 Binary files a/src/resources/icons/type/Field.png and b/src/resources/icons/type/Field.png differ diff --git a/src/resources/icons/type/File.png b/src/resources/icons/type/File.png index c0c3f37a7..7e5ffaf90 100644 Binary files a/src/resources/icons/type/File.png and b/src/resources/icons/type/File.png differ diff --git a/src/resources/icons/type/Filter.png b/src/resources/icons/type/Filter.png index bfe812472..f97beb195 100644 Binary files a/src/resources/icons/type/Filter.png and b/src/resources/icons/type/Filter.png differ diff --git a/src/resources/icons/type/Foreign Key.png b/src/resources/icons/type/Foreign Key.png index bfe812472..f97beb195 100644 Binary files a/src/resources/icons/type/Foreign Key.png and b/src/resources/icons/type/Foreign Key.png differ diff --git a/src/resources/icons/type/Framework.png b/src/resources/icons/type/Framework.png index c0c3f37a7..7e5ffaf90 100644 Binary files a/src/resources/icons/type/Framework.png and b/src/resources/icons/type/Framework.png differ diff --git a/src/resources/icons/type/Index.png b/src/resources/icons/type/Index.png index e0d2e5445..4f5ab7ef7 100644 Binary files a/src/resources/icons/type/Index.png and b/src/resources/icons/type/Index.png differ diff --git a/src/resources/icons/type/Indirection.png b/src/resources/icons/type/Indirection.png index 24fd7fb30..8eae34be9 100644 Binary files a/src/resources/icons/type/Indirection.png and b/src/resources/icons/type/Indirection.png differ diff --git a/src/resources/icons/type/Inductive.png b/src/resources/icons/type/Inductive.png index 24fd7fb30..8eae34be9 100644 Binary files a/src/resources/icons/type/Inductive.png and b/src/resources/icons/type/Inductive.png differ diff --git a/src/resources/icons/type/Instance.png b/src/resources/icons/type/Instance.png index b4a8b5567..3554087a4 100644 Binary files a/src/resources/icons/type/Instance.png and b/src/resources/icons/type/Instance.png differ diff --git a/src/resources/icons/type/Instruction.png b/src/resources/icons/type/Instruction.png index e0d2e5445..4f5ab7ef7 100644 Binary files a/src/resources/icons/type/Instruction.png and b/src/resources/icons/type/Instruction.png differ diff --git a/src/resources/icons/type/Interface.png b/src/resources/icons/type/Interface.png index 91fb59868..d6fe6154c 100644 Binary files a/src/resources/icons/type/Interface.png and b/src/resources/icons/type/Interface.png differ diff --git a/src/resources/icons/type/Lemma.png b/src/resources/icons/type/Lemma.png index 064c36017..880f9ee70 100644 Binary files a/src/resources/icons/type/Lemma.png and b/src/resources/icons/type/Lemma.png differ diff --git a/src/resources/icons/type/Library.png b/src/resources/icons/type/Library.png index 15605d7b9..b3c0c8358 100644 Binary files a/src/resources/icons/type/Library.png and b/src/resources/icons/type/Library.png differ diff --git a/src/resources/icons/type/Literal.png b/src/resources/icons/type/Literal.png index b0a51d1d4..15eadec80 100644 Binary files a/src/resources/icons/type/Literal.png and b/src/resources/icons/type/Literal.png differ diff --git a/src/resources/icons/type/Special Form.png b/src/resources/icons/type/Special Form.png new file mode 100644 index 000000000..f97beb195 Binary files /dev/null and b/src/resources/icons/type/Special Form.png differ diff --git a/src/resources/icons/type/Special Form@2x.png b/src/resources/icons/type/Special Form@2x.png new file mode 100644 index 000000000..801ff50f6 Binary files /dev/null and b/src/resources/icons/type/Special Form@2x.png differ diff --git a/src/resources/icons/type/Syntax.png b/src/resources/icons/type/Syntax.png new file mode 100644 index 000000000..6f5a914a9 Binary files /dev/null and b/src/resources/icons/type/Syntax.png differ diff --git a/src/resources/icons/type/Syntax@2x.png b/src/resources/icons/type/Syntax@2x.png new file mode 100644 index 000000000..41ea3cb9e Binary files /dev/null and b/src/resources/icons/type/Syntax@2x.png differ diff --git a/src/resources/icons/type/Table.png b/src/resources/icons/type/Table.png index b5bbf62cc..93e2aa2b1 100644 Binary files a/src/resources/icons/type/Table.png and b/src/resources/icons/type/Table.png differ diff --git a/src/resources/icons/type/Table@2x.png b/src/resources/icons/type/Table@2x.png index 6ecb7c6f4..c7d86b6a3 100644 Binary files a/src/resources/icons/type/Table@2x.png and b/src/resources/icons/type/Table@2x.png differ diff --git a/src/resources/icons/type/Tactic@2x.png b/src/resources/icons/type/Tactic@2x.png index 766bcb1fb..f639580b6 100644 Binary files a/src/resources/icons/type/Tactic@2x.png and b/src/resources/icons/type/Tactic@2x.png differ diff --git a/src/resources/icons/type/Tag.png b/src/resources/icons/type/Tag.png index b5bbf62cc..93e2aa2b1 100644 Binary files a/src/resources/icons/type/Tag.png and b/src/resources/icons/type/Tag.png differ diff --git a/src/resources/icons/type/Tag@2x.png b/src/resources/icons/type/Tag@2x.png index 6ecb7c6f4..c7d86b6a3 100644 Binary files a/src/resources/icons/type/Tag@2x.png and b/src/resources/icons/type/Tag@2x.png differ diff --git a/src/resources/icons/type/Test@2x.png b/src/resources/icons/type/Test@2x.png index 766bcb1fb..f639580b6 100644 Binary files a/src/resources/icons/type/Test@2x.png and b/src/resources/icons/type/Test@2x.png differ diff --git a/src/resources/icons/type/Trait.png b/src/resources/icons/type/Trait.png index b5bbf62cc..93e2aa2b1 100644 Binary files a/src/resources/icons/type/Trait.png and b/src/resources/icons/type/Trait.png differ diff --git a/src/resources/icons/type/Trait@2x.png b/src/resources/icons/type/Trait@2x.png index 6ecb7c6f4..c7d86b6a3 100644 Binary files a/src/resources/icons/type/Trait@2x.png and b/src/resources/icons/type/Trait@2x.png differ diff --git a/src/resources/icons/type/Trigger@2x.png b/src/resources/icons/type/Trigger@2x.png index 766bcb1fb..f639580b6 100644 Binary files a/src/resources/icons/type/Trigger@2x.png and b/src/resources/icons/type/Trigger@2x.png differ diff --git a/src/resources/icons/type/Type@2x.png b/src/resources/icons/type/Type@2x.png index 25e6e860a..82c148ef4 100644 Binary files a/src/resources/icons/type/Type@2x.png and b/src/resources/icons/type/Type@2x.png differ diff --git a/src/resources/zeal.ico b/src/resources/zeal.ico index 8977ed969..9e772aa35 100644 Binary files a/src/resources/zeal.ico and b/src/resources/zeal.ico differ diff --git a/src/resources/zeal.qrc b/src/resources/zeal.qrc index 4fbec442e..9b592bac0 100644 --- a/src/resources/zeal.qrc +++ b/src/resources/zeal.qrc @@ -174,12 +174,16 @@ icons/type/Shortcut@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 icons/type/Statement@2x.png icons/type/Structure.png icons/type/Structure@2x.png icons/type/Style.png icons/type/Style@2x.png + icons/type/Syntax.png + icons/type/Syntax@2x.png icons/type/Subroutine.png icons/type/Subroutine@2x.png icons/type/Table.png diff --git a/src/src.pro b/src/src.pro index 8f2c72198..f4627a3b0 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1,7 +1,7 @@ TEMPLATE = app -QT += gui gui-private widgets sql -CONFIG += c++11 +QT += gui widgets sql +CONFIG += c++11 silent # Build features webengine { @@ -15,19 +15,17 @@ portable { DEFINES += PORTABLE_BUILD } -# TODO: Obtain version number from Git tags -VERSION = $$(ZEAL_VERSION) -isEmpty(VERSION) { - VERSION = 0.0.0 -} +VERSION = 0.2.0 DEFINES += ZEAL_VERSION=\\\"$${VERSION}\\\" HEADERS += \ - util/version.h + util/version.h \ + util/plist.h SOURCES += \ main.cpp \ - util/version.cpp + util/version.cpp \ + util/plist.cpp include(core/core.pri) include(registry/registry.pri) diff --git a/src/ui/aboutdialog.cpp b/src/ui/aboutdialog.cpp index 304d9624a..bb49c91fa 100644 --- a/src/ui/aboutdialog.cpp +++ b/src/ui/aboutdialog.cpp @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "aboutdialog.h" #include "ui_aboutdialog.h" diff --git a/src/ui/aboutdialog.h b/src/ui/aboutdialog.h index 85ba07d83..1ee9ab0db 100644 --- a/src/ui/aboutdialog.h +++ b/src/ui/aboutdialog.h @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef ABOUTDIALOG_H #define ABOUTDIALOG_H diff --git a/src/ui/docsetlistitemdelegate.cpp b/src/ui/docsetlistitemdelegate.cpp index c1c2e8aff..aa32635e1 100644 --- a/src/ui/docsetlistitemdelegate.cpp +++ b/src/ui/docsetlistitemdelegate.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "docsetlistitemdelegate.h" #include "registry/listmodel.h" diff --git a/src/ui/docsetlistitemdelegate.h b/src/ui/docsetlistitemdelegate.h index a2bd45be4..a310b776e 100644 --- a/src/ui/docsetlistitemdelegate.h +++ b/src/ui/docsetlistitemdelegate.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef DOCSETLISTITEMDELEGATE_H #define DOCSETLISTITEMDELEGATE_H diff --git a/src/ui/forms/aboutdialog.ui b/src/ui/forms/aboutdialog.ui index 4f84b9dce..3b01eeb1f 100644 --- a/src/ui/forms/aboutdialog.ui +++ b/src/ui/forms/aboutdialog.ui @@ -77,13 +77,13 @@ <br><br> Copyright &copy; Zeal Developers, 2013-2015. <br> -<a href="http://zealdocs.org">http://zealdocs.org</a> +<a href="https://zealdocs.org">zealdocs.org</a> <br> <a href="irc://irc.freenode.net/zealdocs">#zealdocs</a> on <a href="http://www.freenode.net">Freenode</a> <br><br> Zeal is an open source software available under the terms of General Public License version 3 (<a href="http://www.gnu.org/copyleft/gpl.html">GPLv3</a>). <br><br> -Docsets are courtesy of <a href="http://kapeli.com/dash">Dash</a>. +Docsets are courtesy of <a href="https://kapeli.com/dash">Dash</a>. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop diff --git a/src/ui/forms/mainwindow.ui b/src/ui/forms/mainwindow.ui index 6fb65e9d8..e31299056 100644 --- a/src/ui/forms/mainwindow.ui +++ b/src/ui/forms/mainwindow.ui @@ -86,6 +86,9 @@ QFrame#searchEditFrame, QFrame#tabBarFrame { Enter your query + + true + diff --git a/src/ui/forms/settingsdialog.ui b/src/ui/forms/settingsdialog.ui index ac9aaece5..711ca14cf 100644 --- a/src/ui/forms/settingsdialog.ui +++ b/src/ui/forms/settingsdialog.ui @@ -41,12 +41,12 @@ - - - false - + - Restore last state (not implemented) + Check for update + + + true @@ -370,58 +370,84 @@ - - - - - - - - false - - - Update docsets - - - - - - - Add feed - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Delete - - - - - - - 0 - - - - + + + Installed docsets + + + + + + + 16 + 16 + + + + + + + + + + Add feed + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Update + + + + + + + false + + + Update all + + + + + + + false + + + Remove + + + + + + + 0 + + + + + + + @@ -430,7 +456,14 @@ - + + + + 16 + 16 + + + @@ -483,7 +516,7 @@ - <i>Docsets are provided by <a href="http://kapeli.com/dash">Dash</a>, the OS X Documentation Browser.</i> + <i>Docsets are provided by <a href="https://kapeli.com/dash">Dash</a>, the OS X Documentation Browser.</i> Qt::RichText diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index b8fa24716..c8e4a354f 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "mainwindow.h" #include "ui_mainwindow.h" @@ -14,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -25,18 +49,22 @@ #include #ifdef USE_WEBENGINE - #include - #include - #include +#include +#include +#include #else - #include - #include - #include +#include +#include +#include #endif #include -#ifdef USE_LIBAPPINDICATOR +/// TODO: [Qt 5.5] Remove in favour of native Qt support (QTBUG-31762) +#ifdef USE_APPINDICATOR +#undef signals +#include +#define signals public #include #endif @@ -55,7 +83,10 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : m_settingsDialog(new SettingsDialog(app, m_zealListModel, this)), m_globalShortcut(new QxtGlobalShortcut(m_settings->showShortcut, this)) { + connect(m_settings, &Core::Settings::updated, this, &MainWindow::applySettings); + m_tabBar = new QTabBar(this); + m_tabBar->installEventFilter(this); setWindowIcon(QIcon::fromTheme(QStringLiteral("zeal"), QIcon(QStringLiteral(":/zeal.ico")))); @@ -63,28 +94,15 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : createTrayIcon(); // initialise key grabber - connect(m_globalShortcut, &QxtGlobalShortcut::activated, [this]() { - if (!isVisible() || !isActiveWindow()) { - bringToFront(); - } else { -#ifdef USE_LIBAPPINDICATOR - if (m_trayIcon || m_indicator) { -#else - if (m_trayIcon) { -#endif - hide(); - } else { - showMinimized(); - } - } - }); - - m_application->docsetRegistry()->init(m_settings->docsetPath); + connect(m_globalShortcut, &QxtGlobalShortcut::activated, this, &MainWindow::toggleWindow); // initialise ui ui->setupUi(this); - setupShortcuts(); + QShortcut *focusSearch = new QShortcut(QKeySequence(QStringLiteral("Ctrl+K")), this); + focusSearch->setContext(Qt::ApplicationShortcut); + connect(focusSearch, &QShortcut::activated, + ui->lineEdit, static_cast(&SearchEdit::setFocus)); restoreGeometry(m_settings->windowGeometry); ui->splitter->restoreState(m_settings->splitterGeometry); @@ -108,28 +126,11 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : // "QAction::eventFilter: Ambiguous shortcut overload: Ctrl+Q" ui->actionQuit->setShortcuts(QList{QKeySequence::Quit}); } - addAction(ui->actionQuit); - connect(ui->actionQuit, &QAction::triggered, [=]() { - m_settings->windowGeometry = saveGeometry(); - }); connect(ui->actionQuit, &QAction::triggered, qApp, &QCoreApplication::quit); connect(ui->actionOptions, &QAction::triggered, [=]() { m_globalShortcut->setEnabled(false); - - if (m_settingsDialog->exec()) { - m_globalShortcut->setShortcut(m_settings->showShortcut); - - if (m_settings->showSystrayIcon) { - createTrayIcon(); - } else if (m_trayIcon) { - QMenu *trayIconMenu = m_trayIcon->contextMenu(); - delete m_trayIcon; - m_trayIcon = nullptr; - delete trayIconMenu; - } - } - + m_settingsDialog->exec(); m_globalShortcut->setEnabled(true); }); @@ -145,7 +146,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : QDesktopServices::openUrl(QStringLiteral("https://github.com/zealdocs/zeal/issues")); }); connect(ui->actionCheckForUpdate, &QAction::triggered, - m_application, &Core::Application::checkUpdate); + m_application, &Core::Application::checkForUpdate); connect(ui->actionAboutZeal, &QAction::triggered, [this]() { QScopedPointer dialog(new AboutDialog(this)); dialog->exec(); @@ -169,7 +170,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : QString(tr("A new version %1 is available. Open download page?")).arg(version), QMessageBox::Yes, QMessageBox::No); if (ret == QMessageBox::Yes) - QDesktopServices::openUrl(QStringLiteral("http://zealdocs.org/download.html")); + QDesktopServices::openUrl(QStringLiteral("https://zealdocs.org/download.html")); }); m_backMenu = new QMenu(ui->backButton); @@ -186,9 +187,15 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : setupSearchBoxCompletions(); ui->treeView->setModel(m_zealListModel); ui->treeView->setColumnHidden(1, true); - ui->treeView->setItemDelegate(new SearchItemDelegate(ui->lineEdit, ui->treeView)); + SearchItemDelegate *delegate = new SearchItemDelegate(ui->treeView); + connect(ui->lineEdit, &QLineEdit::textChanged, [delegate](const QString &text) { + delegate->setHighlight(Zeal::SearchQuery::fromString(text).query()); + }); + ui->treeView->setItemDelegate(delegate); createTab(); + /// FIXME: QTabBar does not emit currentChanged() after the first addTab() call + reloadTabState(); connect(ui->treeView, &QTreeView::clicked, [this](const QModelIndex &index) { m_treeViewClicked = true; @@ -231,7 +238,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : connect(m_application->docsetRegistry(), &DocsetRegistry::queryCompleted, this, &MainWindow::onSearchComplete); connect(m_application->docsetRegistry(), &DocsetRegistry::docsetRemoved, - [this](const QString &name) { + this, [this](const QString &name) { for (SearchState *searchState : m_tabs) { #ifdef USE_WEBENGINE if (docsetName(searchState->page->url()) != name) @@ -257,15 +264,13 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : if (text.isEmpty()) { m_searchState->sectionsList->setResults(); ui->treeView->setModel(m_zealListModel); + ui->treeView->setRootIsDecorated(true); } }); ui->actionNewTab->setShortcut(QKeySequence::AddTab); + connect(ui->actionNewTab, &QAction::triggered, this, &MainWindow::createTab); addAction(ui->actionNewTab); - connect(ui->actionNewTab, &QAction::triggered, [this]() { - saveTabState(); - createTab(); - }); // save the expanded items: connect(ui->treeView, &QTreeView::expanded, [this](QModelIndex index) { @@ -283,9 +288,10 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : ui->actionCloseTab->setShortcut(QKeySequence::Close); #endif addAction(ui->actionCloseTab); - connect(ui->actionCloseTab, &QAction::triggered, this, &MainWindow::closeTab); + connect(ui->actionCloseTab, &QAction::triggered, this, [this]() { closeTab(); }); m_tabBar->setTabsClosable(true); + m_tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); m_tabBar->setExpanding(false); m_tabBar->setUsesScrollButtons(true); m_tabBar->setDrawBase(false); @@ -332,8 +338,11 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : "You can move docsets to %2 or change the docset storage path in the settings.

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

" "Remove or use the old docset storage to avoid this message in the future.")) - .arg(oldDocsetDir, m_settings->docsetPath)); + .arg(QDir::toNativeSeparators(oldDocsetDir), QDir::toNativeSeparators(m_settings->docsetPath))); } + + if (m_settings->checkForUpdate) + m_application->checkForUpdate(true); } MainWindow::~MainWindow() @@ -387,19 +396,18 @@ void MainWindow::queryCompleted() m_treeViewClicked = true; ui->treeView->setModel(m_searchState->zealSearch); - ui->treeView->setColumnHidden(1, true); + ui->treeView->setRootIsDecorated(false); ui->treeView->setCurrentIndex(m_searchState->zealSearch->index(0, 0, QModelIndex())); ui->treeView->activated(ui->treeView->currentIndex()); } void MainWindow::goToTab(int index) { - saveTabState(); - - if (m_tabs.isEmpty()) + if (index == -1) return; - m_searchState = m_tabs.at(index); + saveTabState(); + m_searchState = nullptr; reloadTabState(); } @@ -408,15 +416,18 @@ void MainWindow::closeTab(int index) if (index == -1) index = m_tabBar->currentIndex(); + if (index == -1) + return; + /// TODO: proper deletion here - SearchState *tab = m_tabs.takeAt(index); + SearchState *state = m_tabs.takeAt(index); - if (m_searchState == tab) + if (m_searchState == state) m_searchState = nullptr; - delete tab->zealSearch; - delete tab->sectionsList; - delete tab; + delete state->zealSearch; + delete state->sectionsList; + delete state; m_tabBar->removeTab(index); @@ -426,41 +437,33 @@ void MainWindow::closeTab(int index) void MainWindow::createTab() { + saveTabState(); + SearchState *newTab = new SearchState(); newTab->zealSearch = new Zeal::SearchModel(); newTab->sectionsList = new Zeal::SearchModel(); connect(newTab->zealSearch, &SearchModel::queryCompleted, this, &MainWindow::queryCompleted); connect(newTab->sectionsList, &SearchModel::queryCompleted, [=]() { - const bool hasResults = newTab->sectionsList->rowCount(QModelIndex()); + const bool hasResults = newTab->sectionsList->rowCount(); ui->sections->setVisible(hasResults); ui->seeAlsoLabel->setVisible(hasResults); }); - ui->lineEdit->clear(); - newTab->page = new QWebPage(ui->webView); -#ifndef USE_WEBENGINE +#ifdef USE_WEBENGINE + newTab->page->load(QUrl(startPageUrl)); +#else newTab->page->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); newTab->page->setNetworkAccessManager(m_zealNetworkManager); + newTab->page->mainFrame()->load(QUrl(startPageUrl)); #endif - ui->treeView->setModel(NULL); - ui->treeView->setModel(m_zealListModel); - ui->treeView->setColumnHidden(1, true); - m_tabs.append(newTab); - m_searchState = newTab; - m_tabBar->addTab(QStringLiteral("title")); - m_tabBar->setCurrentIndex(m_tabs.size() - 1); - reloadTabState(); -#ifdef USE_WEBENGINE - newTab->page->load(QUrl(startPageUrl)); -#else - newTab->page->mainFrame()->load(QUrl(startPageUrl)); -#endif + const int index = m_tabBar->addTab(QStringLiteral("title")); + m_tabBar->setCurrentIndex(index); } void MainWindow::displayTabs() @@ -512,29 +515,37 @@ void MainWindow::displayTabs() void MainWindow::reloadTabState() { - ui->lineEdit->setText(m_searchState->searchQuery); - ui->sections->setModel(m_searchState->sectionsList); + SearchState *searchState = m_tabs.at(m_tabBar->currentIndex()); + + ui->lineEdit->setText(searchState->searchQuery); + ui->sections->setModel(searchState->sectionsList); - if (!m_searchState->searchQuery.isEmpty()) { - ui->treeView->setModel(m_searchState->zealSearch); + if (!searchState->searchQuery.isEmpty()) { + ui->treeView->setModel(searchState->zealSearch); + ui->treeView->setRootIsDecorated(false); } else { ui->treeView->setModel(m_zealListModel); - ui->treeView->setColumnHidden(1, true); + ui->treeView->setRootIsDecorated(true); + ui->treeView->reset(); } - // Bring back the selections and expansions. - for (const QModelIndex &selection: m_searchState->selections) + // Bring back the selections and expansions + ui->treeView->blockSignals(true); + for (const QModelIndex &selection: searchState->selections) ui->treeView->selectionModel()->select(selection, QItemSelectionModel::Select); - for (const QModelIndex &expandedIndex: m_searchState->expansions) + for (const QModelIndex &expandedIndex: searchState->expansions) ui->treeView->expand(expandedIndex); + ui->treeView->blockSignals(false); - ui->webView->setPage(m_searchState->page); - ui->webView->setZealZoomFactor(m_searchState->zoomFactor); + ui->webView->setPage(searchState->page); + ui->webView->setZoomFactor(searchState->zoomFactor); - int resultCount = m_searchState->sectionsList->rowCount(QModelIndex()); + int resultCount = searchState->sectionsList->rowCount(); ui->sections->setVisible(resultCount > 1); ui->seeAlsoLabel->setVisible(resultCount > 1); + m_searchState = searchState; + // scroll after the object gets loaded /// TODO: [Qt 5.4] QTimer::singleShot(100, this, &MainWindow::scrollSearch); QTimer::singleShot(100, this, SLOT(scrollSearch())); @@ -552,11 +563,12 @@ void MainWindow::saveTabState() { if (!m_searchState) return; + m_searchState->searchQuery = ui->lineEdit->text(); m_searchState->selections = ui->treeView->selectionModel()->selectedIndexes(); m_searchState->scrollPosition = ui->treeView->verticalScrollBar()->value(); m_searchState->sectionsScroll = ui->sections->verticalScrollBar()->value(); - m_searchState->zoomFactor = ui->webView->zealZoomFactor(); + m_searchState->zoomFactor = ui->webView->zoomFactor(); } void MainWindow::onSearchComplete() @@ -569,7 +581,7 @@ void MainWindow::setupSearchBoxCompletions() { QStringList completions; for (const Docset * const docset: m_application->docsetRegistry()->docsets()) - completions << docset->keyword() + QLatin1Char(':'); + completions << docset->keywords().first() + QLatin1Char(':'); ui->lineEdit->setCompletions(completions); } @@ -623,44 +635,51 @@ QAction *MainWindow::addHistoryAction(QWebHistory *history, const QWebHistoryIte return backAction; } -#ifdef USE_LIBAPPINDICATOR -void onQuit(GtkMenu *menu, gpointer data) +#ifdef USE_APPINDICATOR +void appIndicatorToggleWindow(GtkMenu *menu, gpointer data) { Q_UNUSED(menu); - QApplication *self = static_cast(data); - self->quit(); + static_cast(data)->toggleWindow(); } - #endif void MainWindow::createTrayIcon() { -#ifdef USE_LIBAPPINDICATOR - if (m_trayIcon || m_indicator) return; +#ifdef USE_APPINDICATOR + if (m_trayIcon || m_appIndicator) + return; #else - if (m_trayIcon) return; + if (m_trayIcon) + return; #endif -#ifdef USE_LIBAPPINDICATOR +#ifdef USE_APPINDICATOR const QString desktop = getenv("XDG_CURRENT_DESKTOP"); const bool isUnity = (desktop.toLower() == QLatin1String("unity")); if (isUnity) { // Application Indicators for Unity - GtkWidget *menu; - GtkWidget *quitItem; + 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); - menu = gtk_menunew(); + m_appIndicatorMenuSeparator = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(m_appIndicatorMenu), m_appIndicatorMenuSeparator); - quitItem = gtk_menuitem_new_with_label("Quit"); - gtk_menushell_append(GTK_menuSHELL(menu), quitItem); - g_signal_connect(quitItem, "activate", G_CALLBACK(onQuit), qApp); - gtk_widget_show(quitItem); + 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); - m_indicator = app_indicator_new("zeal", - icon.name().toLatin1().data(), APP_INDICATOR_CATEGORY_OTHER); + gtk_widget_show_all(m_appIndicatorMenu); - app_indicator_set_status(m_indicator, APP_INDICATOR_STATUS_ACTIVE); - app_indicator_set_menu(m_indicator, GTK_MENU(menu)); + /// 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); @@ -677,10 +696,7 @@ void MainWindow::createTrayIcon() return; } - if (isVisible()) - hide(); - else - bringToFront(); + toggleWindow(); }); QMenu *trayIconMenu = new QMenu(this); @@ -690,7 +706,38 @@ void MainWindow::createTrayIcon() m_trayIcon->setContextMenu(trayIconMenu); m_trayIcon->show(); -#ifdef USE_LIBAPPINDICATOR +#ifdef USE_APPINDICATOR + } +#endif +} + +void MainWindow::removeTrayIcon() +{ +#ifdef USE_APPINDICATOR + if (!m_trayIcon && !m_appIndicator) + return; +#else + if (!m_trayIcon) + return; +#endif + +#ifdef USE_APPINDICATOR + const QString desktop = getenv("XDG_CURRENT_DESKTOP"); + const bool isUnity = (desktop.toLower() == QLatin1String("unity")); + + if (isUnity) { + g_clear_object(&m_appIndicator); + g_clear_object(&m_appIndicatorMenu); + g_clear_object(&m_appIndicatorShowHideMenuItem); + g_clear_object(&m_appIndicatorMenuSeparator); + g_clear_object(&m_appIndicatorQuitMenuItem); + } else { +#endif + QMenu *trayIconMenu = m_trayIcon->contextMenu(); + delete m_trayIcon; + m_trayIcon = nullptr; + delete trayIconMenu; +#ifdef USE_APPINDICATOR } #endif } @@ -710,22 +757,38 @@ void MainWindow::bringToFront(const Zeal::SearchQuery &query) } } +void MainWindow::changeEvent(QEvent *event) +{ + if (m_settings->showSystrayIcon && m_settings->minimizeToSystray + && event->type() == QEvent::WindowStateChange && isMinimized()) { + hide(); + } + QMainWindow::changeEvent(event); +} + void MainWindow::closeEvent(QCloseEvent *event) { m_settings->windowGeometry = saveGeometry(); if (m_settings->showSystrayIcon && m_settings->hideOnClose) { event->ignore(); - hide(); + toggleWindow(); } } -void MainWindow::setupShortcuts() +bool MainWindow::eventFilter(QObject *object, QEvent *event) { - QShortcut *focusSearch = new QShortcut(QKeySequence(QStringLiteral("Ctrl+K")), this); - focusSearch->setContext(Qt::ApplicationShortcut); - connect(focusSearch, &QShortcut::activated, [=]() { - ui->lineEdit->setFocus(); - }); + if (object == m_tabBar && event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *e = reinterpret_cast(event); + if (e->button() == Qt::MiddleButton) { + const int index = m_tabBar->tabAt(e->pos()); + if (index >= 0) { + closeTab(index); + return true; + } + } + } + + return QMainWindow::eventFilter(object, event); } // Captures global events in order to pass them to the search bar. @@ -745,3 +808,42 @@ void MainWindow::keyPressEvent(QKeyEvent *keyEvent) break; } } + +void MainWindow::applySettings() +{ + m_globalShortcut->setShortcut(m_settings->showShortcut); + + if (m_settings->showSystrayIcon) + createTrayIcon(); + else + removeTrayIcon(); +} + +void MainWindow::toggleWindow() +{ + const bool checkActive = sender() == m_globalShortcut; + + if (!isVisible() || (checkActive && !isActiveWindow())) { +#ifdef USE_APPINDICATOR + if (m_appIndicator) { + gtk_menu_item_set_label(GTK_MENU_ITEM(m_appIndicatorShowHideMenuItem), + qPrintable(tr("Hide"))); + } +#endif + bringToFront(); + } else { +#ifdef USE_APPINDICATOR + if (m_trayIcon || m_appIndicator) { + if (m_appIndicator) { + gtk_menu_item_set_label(GTK_MENU_ITEM(m_appIndicatorShowHideMenuItem), + qPrintable(tr("Show"))); + } +#else + if (m_trayIcon) { +#endif + hide(); + } else { + showMinimized(); + } + } +} diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index e5fde9e37..d94353ad2 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -7,16 +30,15 @@ #include #include -#ifdef USE_LIBAPPINDICATOR -#undef signals -#include -#define signals public +#ifdef USE_WEBENGINE +#define QWebPage QWebEnginePage +#define QWebHistory QWebEngineHistory +#define QWebHistoryItem QWebEngineHistoryItem #endif -#ifdef USE_WEBENGINE - #define QWebPage QWebEnginePage - #define QWebHistory QWebEngineHistory - #define QWebHistoryItem QWebEngineHistoryItem +#ifdef USE_APPINDICATOR +struct _AppIndicator; +struct _GtkWidget; #endif class QxtGlobalShortcut; @@ -77,12 +99,17 @@ class MainWindow : public QMainWindow void bringToFront(const Zeal::SearchQuery &query = Zeal::SearchQuery()); void createTab(); +public slots: + void toggleWindow(); + protected: + void changeEvent(QEvent *event) override; void closeEvent(QCloseEvent *event) override; - void setupShortcuts(); + bool eventFilter(QObject *object, QEvent *event) override; void keyPressEvent(QKeyEvent *keyEvent) override; private slots: + void applySettings(); void back(); void forward(); void onSearchComplete(); @@ -102,6 +129,7 @@ private slots: QIcon docsetIcon(const QString &docsetName) const; QAction *addHistoryAction(QWebHistory *history, const QWebHistoryItem &item); void createTrayIcon(); + void removeTrayIcon(); QList m_tabs; @@ -125,8 +153,12 @@ private slots: QSystemTrayIcon *m_trayIcon = nullptr; -#ifdef USE_LIBAPPINDICATOR - AppIndicator *m_indicator = nullptr; // for Unity +#ifdef USE_APPINDICATOR + _AppIndicator *m_appIndicator = nullptr; + _GtkWidget *m_appIndicatorMenu = nullptr; + _GtkWidget *m_appIndicatorQuitMenuItem = nullptr; + _GtkWidget *m_appIndicatorShowHideMenuItem = nullptr; + _GtkWidget *m_appIndicatorMenuSeparator = nullptr; #endif }; diff --git a/src/ui/networkaccessmanager.cpp b/src/ui/networkaccessmanager.cpp index da8d93af5..ccecae664 100644 --- a/src/ui/networkaccessmanager.cpp +++ b/src/ui/networkaccessmanager.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "networkaccessmanager.h" #include diff --git a/src/ui/networkaccessmanager.h b/src/ui/networkaccessmanager.h index f3b78ec40..ac7900bda 100644 --- a/src/ui/networkaccessmanager.h +++ b/src/ui/networkaccessmanager.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef NETWORKACCESSMANAGER_H #define NETWORKACCESSMANAGER_H diff --git a/src/ui/progressitemdelegate.cpp b/src/ui/progressitemdelegate.cpp index f3a6eac80..3d0b880b2 100644 --- a/src/ui/progressitemdelegate.cpp +++ b/src/ui/progressitemdelegate.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "progressitemdelegate.h" #include diff --git a/src/ui/progressitemdelegate.h b/src/ui/progressitemdelegate.h index eb3bd351d..bc458f9ea 100644 --- a/src/ui/progressitemdelegate.h +++ b/src/ui/progressitemdelegate.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef PROGRESSITEMDELEGATE_H #define PROGRESSITEMDELEGATE_H diff --git a/src/ui/searchitemdelegate.cpp b/src/ui/searchitemdelegate.cpp index e14bdcca4..2e68943af 100644 --- a/src/ui/searchitemdelegate.cpp +++ b/src/ui/searchitemdelegate.cpp @@ -1,22 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchitemdelegate.h" #include "searchitemstyle.h" -#include "registry/searchquery.h" +#include "registry/searchmodel.h" #include #include #include -SearchItemDelegate::SearchItemDelegate(QLineEdit *lineEdit, QWidget *view) : - QStyledItemDelegate(view), - m_lineEdit(lineEdit), - m_view(view) +SearchItemDelegate::SearchItemDelegate(QObject *parent) : + QStyledItemDelegate(parent) { } void SearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option_, - const QModelIndex &index) const + const QModelIndex &index) const { + if (m_highlight.isEmpty()) { + QStyledItemDelegate::paint(painter, option_, index); + return; + } + painter->save(); QStyleOptionViewItem option(option_); @@ -29,7 +55,7 @@ void SearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } ZealSearchItemStyle style; - style.drawControl(QStyle::CE_ItemViewItem, &option, painter, m_view); + style.drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget); if (option.state & QStyle::State_Selected) { #ifdef Q_OS_WIN32 @@ -39,39 +65,47 @@ void SearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->setPen(QPen(option.palette.highlightedText(), 1)); } - QRect rect = qApp->style()->subElementRect(QStyle::SE_ItemViewItemText, &option, m_view); - const int margin = style.pixelMetric(QStyle::PM_FocusFrameHMargin, 0, m_view); + QRect rect = QApplication::style()->subElementRect(QStyle::SE_ItemViewItemText, &option, + option.widget); + const int margin = style.pixelMetric(QStyle::PM_FocusFrameHMargin, 0, option.widget); rect.adjust(margin, 0, 2, 0); // +2px for bold text - QFont bold(painter->font()); - bold.setBold(true); - const QFontMetrics metricsBold(bold); + const QFont defaultFont(painter->font()); + QFont boldFont(defaultFont); + boldFont.setBold(true); - const QFontMetrics metrics(painter->font()); - QString elided = metrics.elidedText(index.data().toString(), option.textElideMode, rect.width()); + const QFontMetrics metrics(defaultFont); + const QFontMetrics metricsBold(boldFont); - QString highlight; - if (m_lineEdit) - highlight = Zeal::SearchQuery::fromString(m_lineEdit->text()).query(); + const QString elided = metrics.elidedText(option.text, option.textElideMode, rect.width()); int from = 0; while (from < elided.size()) { - const int until = highlight.isEmpty() ? -1 : elided.toLower().indexOf(highlight.toLower(), from); + const int to = elided.indexOf(m_highlight, from, Qt::CaseInsensitive); - if (until == -1) { + if (to == -1) { painter->drawText(rect, elided.mid(from)); - from = elided.size(); - } else { - painter->drawText(rect, elided.mid(from, until - from)); - rect.setLeft(rect.left() + metrics.width(elided.mid(from, until - from))); - QFont old(painter->font()); - painter->setFont(bold); - painter->drawText(rect, elided.mid(until, highlight.size())); - painter->setFont(old); - rect.setLeft(rect.left() + metricsBold.width(elided.mid(until, highlight.size()))); - from = until + highlight.size(); + break; } + + QString text = elided.mid(from, to - from); + painter->drawText(rect, text); + rect.setLeft(rect.left() + metrics.width(text)); + + text = elided.mid(to, m_highlight.size()); + painter->setFont(boldFont); + painter->drawText(rect, text); + rect.setLeft(rect.left() + metricsBold.width(text)); + + painter->setFont(defaultFont); + + from = to + m_highlight.size(); } painter->restore(); } + +void SearchItemDelegate::setHighlight(const QString &text) +{ + m_highlight = text; +} diff --git a/src/ui/searchitemdelegate.h b/src/ui/searchitemdelegate.h index 892e482dc..197ebd6aa 100644 --- a/src/ui/searchitemdelegate.h +++ b/src/ui/searchitemdelegate.h @@ -1,21 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHITEMDELEGATE_H #define SEARCHITEMDELEGATE_H -#include #include class SearchItemDelegate : public QStyledItemDelegate { Q_OBJECT public: - explicit SearchItemDelegate(QLineEdit *lineEdit_ = nullptr, QWidget *view = nullptr); + explicit SearchItemDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +public slots: + void setHighlight(const QString &text); + private: - QLineEdit *m_lineEdit = nullptr; - QWidget *m_view = nullptr; + QString m_highlight; }; #endif // SEARCHITEMDELEGATE_H diff --git a/src/ui/searchitemstyle.cpp b/src/ui/searchitemstyle.cpp index 7b98876d3..c181db14e 100644 --- a/src/ui/searchitemstyle.cpp +++ b/src/ui/searchitemstyle.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchitemstyle.h" QRect ZealSearchItemStyle::subElementRect(SubElement element, const QStyleOption *option, diff --git a/src/ui/searchitemstyle.h b/src/ui/searchitemstyle.h index 91cc75ae0..ef87b5e18 100644 --- a/src/ui/searchitemstyle.h +++ b/src/ui/searchitemstyle.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHITEMSTYLE_H #define SEARCHITEMSTYLE_H diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index 6c2d5d0dc..5c4bc04d3 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "settingsdialog.h" #include "docsetlistitemdelegate.h" @@ -22,10 +45,10 @@ #include #ifdef USE_WEBENGINE - #include - #define QWebSettings QWebEngineSettings +#include +#define QWebSettings QWebEngineSettings #else - #include +#include #endif using namespace Zeal; @@ -65,6 +88,30 @@ SettingsDialog::SettingsDialog(Core::Application *app, ListModel *listModel, QWi ui->installedDocsetList->setItemDelegate(new DocsetListItemDelegate(this)); ui->installedDocsetList->setModel(listModel); + ui->installedDocsetList->setSelectionMode(QAbstractItemView::ExtendedSelection); + QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); + connect(selectionModel, &QItemSelectionModel::selectionChanged, + [this, selectionModel]() { + if (!m_replies.isEmpty()) + return; + + ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); + + for (const QModelIndex &index : selectionModel->selectedIndexes()) { + if (index.data(Zeal::ListModel::UpdateAvailableRole).toBool()) { + ui->updateSelectedDocsetsButton->setEnabled(true); + return; + } + } + ui->updateSelectedDocsetsButton->setEnabled(false); + }); + connect(ui->updateSelectedDocsetsButton, &QPushButton::clicked, + this, &SettingsDialog::updateSelectedDocsets); + connect(ui->updateAllDocsetsButton, &QPushButton::clicked, + this, &SettingsDialog::updateAllDocsets); + connect(ui->removeDocsetsButton, &QPushButton::clicked, + this, &SettingsDialog::removeSelectedDocsets); + ui->availableDocsetList->setItemDelegate(new ProgressItemDelegate(this)); // Setup signals & slots @@ -81,7 +128,6 @@ SettingsDialog::SettingsDialog(Core::Application *app, ListModel *listModel, QWi }); connect(ui->addFeedButton, &QPushButton::clicked, this, &SettingsDialog::addDashFeed); - connect(ui->updateButton, &QPushButton::clicked, this, &SettingsDialog::updateDocsets); connect(ui->refreshButton, &QPushButton::clicked, this, &SettingsDialog::downloadDocsetList); connect(m_application, &Core::Application::extractionCompleted, @@ -99,67 +145,72 @@ SettingsDialog::~SettingsDialog() delete ui; } -void SettingsDialog::extractionCompleted(const QString &filePath) +void SettingsDialog::addDashFeed() { - QString docsetName; + QString txt = QApplication::clipboard()->text(); + if (!txt.startsWith(QLatin1String("dash-feed://"))) + txt.clear(); - /// FIXME: Come up with a better approach - for (const QString &key : m_tmpFiles.keys()) { - if (m_tmpFiles[key]->fileName() == filePath) { - docsetName = key; - break; - } - } + QString feedUrl = QInputDialog::getText(this, QStringLiteral("Zeal"), tr("Feed URL:"), + QLineEdit::Normal, txt); + if (feedUrl.isEmpty()) + return; - const QDir dataDir(m_application->settings()->docsetPath); - const QString docsetPath = dataDir.absoluteFilePath(docsetName + QLatin1String(".docset")); + if (feedUrl.startsWith(QLatin1String("dash-feed://"))) { + feedUrl = feedUrl.remove(0, 12); + feedUrl = QUrl::fromPercentEncoding(feedUrl.toUtf8()); + } - // Write metadata about docset - DocsetMetadata metadata = m_availableDocsets.contains(docsetName) - ? m_availableDocsets[docsetName] - : m_userFeeds[docsetName]; - metadata.save(docsetPath, metadata.latestVersion()); + QNetworkReply *reply = download(feedUrl); + reply->setProperty(DownloadTypeProperty, DownloadDashFeed); + connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); +} - m_docsetRegistry->addDocset(docsetPath); +void SettingsDialog::updateSelectedDocsets() +{ + for (const QModelIndex &index : ui->installedDocsetList->selectionModel()->selectedIndexes()) { + if (!index.data(Zeal::ListModel::UpdateAvailableRole).toBool()) + continue; - QListWidgetItem *listItem = findDocsetListItem(metadata.title()); - if (listItem) { - listItem->setHidden(true); - listItem->setCheckState(Qt::Unchecked); - listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + downloadDashDocset(index.data(Zeal::ListModel::DocsetNameRole).toString()); } - resetProgress(); - delete m_tmpFiles.take(docsetName); } -void SettingsDialog::extractionError(const QString &filePath, const QString &errorString) +void SettingsDialog::updateAllDocsets() { - const QString docsetName = QFileInfo(filePath).baseName() + QLatin1String(".docset"); - QMessageBox::warning(this, tr("Extraction Error"), - QString(tr("Cannot extract docset %1: %2")).arg(docsetName, errorString)); - /// TODO: Update list item state (hide progress bar) - delete m_tmpFiles.take(docsetName); + for (const Docset * const docset : m_docsetRegistry->docsets()) { + if (!docset->hasUpdate) + continue; + + downloadDashDocset(docset->name()); + } } -void SettingsDialog::extractionProgress(const QString &filePath, qint64 extracted, qint64 total) +void SettingsDialog::removeSelectedDocsets() { - QString docsetName; + QItemSelectionModel *selectonModel = ui->installedDocsetList->selectionModel(); + if (!selectonModel->hasSelection()) + return; - /// FIXME: Come up with a better approach - for (const QString &key : m_tmpFiles.keys()) { - if (m_tmpFiles[key]->fileName() == filePath) { - docsetName = key; - break; - } + int ret; + if (selectonModel->selectedIndexes().count() == 1) { + const QString docsetTitle = selectonModel->selectedIndexes().first().data().toString(); + ret = QMessageBox::question(this, tr("Remove Docset"), + QString(tr("Do you really want to remove %1 docset?")) + .arg(docsetTitle)); + } else { + ret = QMessageBox::question(this, tr("Remove Docsets"), + QString(tr("Do you really want to remove %1 docsets?")) + .arg(selectonModel->selectedIndexes().count())); } - DocsetMetadata metadata = m_availableDocsets.contains(docsetName) - ? m_availableDocsets[docsetName] - : m_userFeeds[docsetName]; + if (ret == QMessageBox::No) + return; - QListWidgetItem *listItem = findDocsetListItem(metadata.title()); - if (listItem) - listItem->setData(ProgressItemDelegate::ValueRole, percent(extracted, total)); + QStringList names; + for (const QModelIndex &index : selectonModel->selectedIndexes()) + names << index.data(ListModel::DocsetNameRole).toString(); + removeDocsets(names); } /*! @@ -175,8 +226,30 @@ void SettingsDialog::downloadCompleted() m_replies.removeOne(reply.data()); if (reply->error() != QNetworkReply::NoError) { - if (reply->error() != QNetworkReply::OperationCanceledError) - QMessageBox::warning(this, tr("Network Error"), reply->errorString()); + if (reply->error() != QNetworkReply::OperationCanceledError) { + const int ret = QMessageBox::warning(this, tr("Network Error"), reply->errorString(), + QMessageBox::Ok | QMessageBox::Retry); + + if (ret == QMessageBox::Retry) { + QNetworkReply *newReply = download(reply->request().url()); + + // Copy properties + newReply->setProperty(DocsetNameProperty, reply->property(DocsetNameProperty)); + newReply->setProperty(DownloadTypeProperty, reply->property(DownloadTypeProperty)); + newReply->setProperty(ListItemIndexProperty, + reply->property(ListItemIndexProperty)); + + connect(newReply, &QNetworkReply::finished, + this, &SettingsDialog::downloadCompleted); + return; + } + + bool ok; + QListWidgetItem *listItem = ui->availableDocsetList->item( + reply->property(ListItemIndexProperty).toInt(&ok)); + if (ok && listItem) + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + } if (m_replies.isEmpty()) resetProgress(); @@ -193,7 +266,7 @@ void SettingsDialog::downloadCompleted() if (redirectUrl.scheme().isEmpty()) redirectUrl.setScheme(reply->request().url().scheme()); - QNetworkReply *newReply = startDownload(redirectUrl); + QNetworkReply *newReply = download(redirectUrl); // Copy properties newReply->setProperty(DocsetNameProperty, reply->property(DocsetNameProperty)); @@ -205,7 +278,7 @@ void SettingsDialog::downloadCompleted() return; } - switch (static_cast(reply->property(DownloadTypeProperty).toUInt())) { + switch (reply->property(DownloadTypeProperty).toUInt()) { case DownloadDocsetList: { const QByteArray data = reply->readAll(); @@ -232,7 +305,8 @@ void SettingsDialog::downloadCompleted() } case DownloadDashFeed: { - DocsetMetadata metadata = DocsetMetadata::fromDashFeed(reply->request().url(), reply->readAll()); + DocsetMetadata metadata + = DocsetMetadata::fromDashFeed(reply->request().url(), reply->readAll()); if (metadata.urls().isEmpty()) { QMessageBox::critical(this, QStringLiteral("Zeal"), tr("Invalid docset feed!")); @@ -240,7 +314,7 @@ void SettingsDialog::downloadCompleted() } m_userFeeds[metadata.name()] = metadata; - QNetworkReply *reply = startDownload(metadata.url()); + QNetworkReply *reply = download(metadata.url()); reply->setProperty(DocsetNameProperty, metadata.name()); reply->setProperty(DownloadTypeProperty, DownloadDocset); connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); @@ -269,8 +343,13 @@ void SettingsDialog::downloadCompleted() }); } - QTemporaryFile *tmpFile = new QTemporaryFile(); - tmpFile->open(); + QTemporaryFile *tmpFile = m_tmpFiles[docsetName]; + if (!tmpFile) { + tmpFile = new QTemporaryFile(this); + tmpFile->open(); + m_tmpFiles.insert(docsetName, tmpFile); + } + while (reply->bytesAvailable()) tmpFile->write(reply->read(1024 * 1024)); // Use small chunks tmpFile->close(); @@ -293,54 +372,33 @@ void SettingsDialog::downloadCompleted() resetProgress(); } -void SettingsDialog::loadSettings() -{ - const Core::Settings * const settings = m_application->settings(); - // General Tab - ui->startMinimizedCheckBox->setChecked(settings->startMinimized); - - ui->systrayGroupBox->setChecked(settings->showSystrayIcon); - ui->minimizeToSystrayCheckBox->setChecked(settings->minimizeToSystray); - ui->hideToSystrayCheckBox->setChecked(settings->hideOnClose); - - ui->toolButton->setKeySequence(settings->showShortcut); - - // - ui->minFontSize->setValue(settings->minimumFontSize); - ui->storageEdit->setText(QDir::toNativeSeparators(settings->docsetPath)); - - // Network Tab - switch (settings->proxyType) { - case Core::Settings::ProxyType::None: - ui->noProxySettings->setChecked(true); - break; - case Core::Settings::ProxyType::System: - ui->systemProxySettings->setChecked(true); - break; - case Core::Settings::ProxyType::UserDefined: - ui->manualProxySettings->setChecked(true); - ui->httpProxy->setText(settings->proxyHost); - ui->httpProxyPort->setValue(settings->proxyPort); - ui->httpProxyNeedsAuth->setChecked(settings->proxyAuthenticate); - ui->httpProxyUser->setText(settings->proxyUserName); - ui->httpProxyPass->setText(settings->proxyPassword); - break; - } -} - // creates a total download progress for multiple QNetworkReplies -void SettingsDialog::on_downloadProgress(qint64 received, qint64 total) +void SettingsDialog::downloadProgress(qint64 received, qint64 total) { // Don't show progress for non-docset pages if (total == -1 || received < 10240) return; QNetworkReply *reply = qobject_cast(sender()); - if (!reply) + if (!reply || !reply->isOpen()) return; + if (reply->property(DownloadTypeProperty).toInt() == DownloadDocset) { + const QString docsetName = reply->property(DocsetNameProperty).toString(); + + QTemporaryFile *tmpFile = m_tmpFiles[docsetName]; + if (!tmpFile) { + tmpFile = new QTemporaryFile(this); + tmpFile->open(); + m_tmpFiles.insert(docsetName, tmpFile); + } + + tmpFile->write(reply->read(received)); + } + // Try to get the item associated to the request - QListWidgetItem *item = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); + QListWidgetItem *item + = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); if (item) item->setData(ProgressItemDelegate::ValueRole, percent(received, total)); @@ -357,38 +415,200 @@ void SettingsDialog::on_downloadProgress(qint64 received, qint64 total) displayProgress(); } -void SettingsDialog::displayProgress() +void SettingsDialog::extractionCompleted(const QString &filePath) { - ui->docsetsProgress->setValue(percent(m_combinedReceived, m_combinedTotal)); - ui->docsetsProgress->setMaximum(100); - ui->docsetsProgress->setVisible(!m_replies.isEmpty()); + QString docsetName; + + /// FIXME: Come up with a better approach + for (const QString &key : m_tmpFiles.keys()) { + if (m_tmpFiles[key]->fileName() == filePath) { + docsetName = key; + break; + } + } + + const QDir dataDir(m_application->settings()->docsetPath); + const QString docsetPath = dataDir.absoluteFilePath(docsetName + QLatin1String(".docset")); + + // Write metadata about docset + DocsetMetadata metadata = m_availableDocsets.contains(docsetName) + ? m_availableDocsets[docsetName] + : m_userFeeds[docsetName]; + metadata.save(docsetPath, metadata.latestVersion()); + + m_docsetRegistry->addDocset(docsetPath); + + QListWidgetItem *listItem = findDocsetListItem(metadata.title()); + if (listItem) { + listItem->setHidden(true); + listItem->setCheckState(Qt::Unchecked); + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); + } + resetProgress(); + delete m_tmpFiles.take(docsetName); } -void SettingsDialog::resetProgress() +void SettingsDialog::extractionError(const QString &filePath, const QString &errorString) { - if (!m_replies.isEmpty()) + const QString docsetName = QFileInfo(filePath).baseName() + QLatin1String(".docset"); + QMessageBox::warning(this, tr("Extraction Error"), + QString(tr("Cannot extract docset %1: %2")).arg(docsetName, errorString)); + /// TODO: Update list item state (hide progress bar) + delete m_tmpFiles.take(docsetName); +} + +void SettingsDialog::extractionProgress(const QString &filePath, qint64 extracted, qint64 total) +{ + QString docsetName; + + /// FIXME: Come up with a better approach + for (const QString &key : m_tmpFiles.keys()) { + if (m_tmpFiles[key]->fileName() == filePath) { + docsetName = key; + break; + } + } + + DocsetMetadata metadata = m_availableDocsets.contains(docsetName) + ? m_availableDocsets[docsetName] + : m_userFeeds[docsetName]; + + QListWidgetItem *listItem = findDocsetListItem(metadata.title()); + if (listItem) + listItem->setData(ProgressItemDelegate::ValueRole, percent(extracted, total)); +} + +void SettingsDialog::on_downloadDocsetButton_clicked() +{ + if (!m_replies.isEmpty()) { + cancelDownloads(); return; + } - m_combinedReceived = 0; - m_combinedTotal = 0; + // Find each checked item, and create a NetworkRequest for it. + for (int i = 0; i < ui->availableDocsetList->count(); ++i) { + QListWidgetItem *item = ui->availableDocsetList->item(i); + if (item->checkState() != Qt::Checked) + continue; + + item->setData(ProgressItemDelegate::FormatRole, tr("Downloading: %p%")); + item->setData(ProgressItemDelegate::ValueRole, 0); + item->setData(ProgressItemDelegate::ShowProgressRole, true); + + downloadDashDocset(item->data(ListModel::DocsetNameRole).toString()); + } +} + +void SettingsDialog::on_storageButton_clicked() +{ + const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory")); + if (!dir.isEmpty()) + ui->storageEdit->setText(QDir::toNativeSeparators(dir)); + +} + +void SettingsDialog::on_tabWidget_currentChanged(int current) +{ + if (ui->tabWidget->widget(current) != ui->docsetsTab || ui->availableDocsetList->count()) + return; + + const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + if (!cacheDir.exists()) + QDir().mkpath(cacheDir.absolutePath()); + const QFileInfo fi(cacheDir.filePath(DocsetListCacheFileName)); + + if (!fi.exists() || fi.lastModified().msecsTo(QDateTime::currentDateTime()) > CacheTimeout) { + downloadDocsetList(); + return; + } + + QScopedPointer file(new QFile(fi.filePath())); + if (!file->open(QIODevice::ReadOnly)) { + downloadDocsetList(); + return; + } + + QJsonParseError jsonError; + const QJsonDocument jsonDoc = QJsonDocument::fromJson(file->readAll(), &jsonError); + + if (jsonError.error != QJsonParseError::NoError) { + downloadDocsetList(); + return; + } + + /// TODO: Show more user friendly labels, like "5 hours ago" + ui->lastUpdatedLabel->setText(fi.lastModified().toString(Qt::SystemLocaleShortDate)); + processDocsetList(jsonDoc.array()); +} + +QListWidgetItem *SettingsDialog::findDocsetListItem(const QString &title) const +{ + const QList items + = ui->availableDocsetList->findItems(title, Qt::MatchFixedString); + + if (items.isEmpty()) + return nullptr; + + return items.first(); +} + +bool SettingsDialog::updatesAvailable() const +{ + for (Docset *docset : m_docsetRegistry->docsets()) { + if (docset->hasUpdate) + return true; + } + + return false; +} + +QNetworkReply *SettingsDialog::download(const QUrl &url) +{ displayProgress(); - ui->availableDocsetList->setEnabled(true); - ui->downloadDocsetButton->setText(tr("Download")); - ui->refreshButton->setEnabled(true); - ui->updateButton->setEnabled(true); - ui->addFeedButton->setEnabled(true); - ui->availableDocsetList->setEnabled(true); + QNetworkReply *reply = m_application->download(url); + connect(reply, &QNetworkReply::downloadProgress, this, &SettingsDialog::downloadProgress); + m_replies.append(reply); + + // Installed docsets + ui->addFeedButton->setEnabled(false); + ui->updateSelectedDocsetsButton->setEnabled(false); + ui->updateAllDocsetsButton->setEnabled(false); + ui->removeDocsetsButton->setEnabled(false); + + // Available docsets + ui->availableDocsetList->setEnabled(false); + ui->refreshButton->setEnabled(false); + ui->downloadDocsetButton->setText(tr("Stop downloads")); + + return reply; } -void SettingsDialog::updateDocsets() +void SettingsDialog::cancelDownloads() { - for (const Docset * const docset : m_docsetRegistry->docsets()) { - if (!docset->hasUpdate) - continue; + for (QNetworkReply *reply : m_replies) { + // Hide progress bar + QListWidgetItem *listItem + = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); + if (listItem) + listItem->setData(ProgressItemDelegate::ShowProgressRole, false); - downloadDashDocset(docset->name()); + if (reply->property(DownloadTypeProperty).toInt() == DownloadDocset) + delete m_tmpFiles.take(reply->property(DocsetNameProperty).toString()); + + reply->abort(); } + resetProgress(); +} + +void SettingsDialog::downloadDocsetList() +{ + ui->availableDocsetList->clear(); + m_availableDocsets.clear(); + + QNetworkReply *reply = download(QUrl(ApiServerUrl + QLatin1String("/docsets"))); + reply->setProperty(DownloadTypeProperty, DownloadDocsetList); + connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); } void SettingsDialog::processDocsetList(const QJsonArray &list) @@ -400,9 +620,10 @@ void SettingsDialog::processDocsetList(const QJsonArray &list) m_availableDocsets.insert(metadata.name(), metadata); } - /// TODO: Move into a dedicated method + /// TODO: Move into dedicated method for (const DocsetMetadata &metadata : m_availableDocsets) { - QListWidgetItem *listItem = new QListWidgetItem(metadata.icon(), metadata.title(), ui->availableDocsetList); + QListWidgetItem *listItem + = new QListWidgetItem(metadata.icon(), metadata.title(), ui->availableDocsetList); listItem->setData(ListModel::DocsetNameRole, metadata.name()); listItem->setCheckState(Qt::Unchecked); @@ -415,7 +636,7 @@ void SettingsDialog::processDocsetList(const QJsonArray &list) || (metadata.latestVersion() == docset->version() && metadata.revision() > docset->revision())) { docset->hasUpdate = true; - ui->updateButton->setEnabled(true); + ui->updateAllDocsetsButton->setEnabled(true); } } } @@ -432,7 +653,7 @@ void SettingsDialog::downloadDashDocset(const QString &name) return; const QString urlString = RedirectServerUrl + QStringLiteral("/d/com.kapeli/%1/latest"); - QNetworkReply *reply = startDownload(urlString.arg(name)); + QNetworkReply *reply = download(urlString.arg(name)); reply->setProperty(DocsetNameProperty, name); reply->setProperty(DownloadTypeProperty, DownloadDocset); reply->setProperty(ListItemIndexProperty, @@ -441,127 +662,113 @@ void SettingsDialog::downloadDashDocset(const QString &name) connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); } -void SettingsDialog::downloadDocsetList() +void SettingsDialog::removeDocsets(const QStringList &names) { - ui->availableDocsetList->clear(); - m_availableDocsets.clear(); - - QNetworkReply *reply = startDownload(QUrl(ApiServerUrl + QLatin1String("/docsets"))); - reply->setProperty(DownloadTypeProperty, DownloadDocsetList); - connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); -} - -void SettingsDialog::on_availableDocsetList_itemSelectionChanged() -{ - ui->downloadDocsetButton->setEnabled(ui->availableDocsetList->selectedItems().count() > 0); -} - -void SettingsDialog::on_downloadDocsetButton_clicked() -{ - if (!m_replies.isEmpty()) { - stopDownloads(); - return; - } + for (const QString &name : names) { + const QString title = m_docsetRegistry->docset(name)->title(); + m_docsetRegistry->remove(name); + + const QDir dataDir(m_application->settings()->docsetPath); + if (dataDir.exists()) { + ui->docsetsProgress->show(); + ui->removeDocsetsButton->setEnabled(false); + displayProgress(); + + QFuture future = QtConcurrent::run([=] { + QDir docsetDir(dataDir); + return docsetDir.cd(name + QLatin1String(".docset")) + && docsetDir.removeRecursively(); + }); + QFutureWatcher *watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, [=] { + if (!watcher->result()) { + QMessageBox::warning(this, tr("Error"), + QString(tr("Cannot delete docset %1!")).arg(title)); + } - // Find each checked item, and create a NetworkRequest for it. - for (int i = 0; i < ui->availableDocsetList->count(); ++i) { - QListWidgetItem *item = ui->availableDocsetList->item(i); - if (item->checkState() != Qt::Checked) - continue; + resetProgress(); - item->setData(ProgressItemDelegate::FormatRole, tr("Downloading: %p%")); - item->setData(ProgressItemDelegate::ValueRole, 0); - item->setData(ProgressItemDelegate::ShowProgressRole, true); + QListWidgetItem *listItem = findDocsetListItem(title); + if (listItem) + listItem->setHidden(false); - downloadDashDocset(item->data(ListModel::DocsetNameRole).toString()); + watcher->deleteLater(); + }); + } } } -void SettingsDialog::on_storageButton_clicked() +void SettingsDialog::displayProgress() { - QString dir = QFileDialog::getExistingDirectory(0, tr("Open Directory")); - if (!dir.isEmpty()) - ui->storageEdit->setText(QDir::toNativeSeparators(dir)); - + ui->docsetsProgress->setValue(percent(m_combinedReceived, m_combinedTotal)); + ui->docsetsProgress->setMaximum(100); + ui->docsetsProgress->setVisible(!m_replies.isEmpty()); } -void SettingsDialog::on_deleteButton_clicked() +void SettingsDialog::resetProgress() { - const QString docsetTitle = ui->installedDocsetList->currentIndex().data().toString(); - const int answer - = QMessageBox::question(this, tr("Remove Docset"), - QString(tr("Do you really want to remove %1 docset?")) - .arg(docsetTitle)); - if (answer == QMessageBox::No) + if (!m_replies.isEmpty()) return; - const QDir dataDir(m_application->settings()->docsetPath); - const QString docsetName = ui->installedDocsetList->currentIndex().data(ListModel::DocsetNameRole).toString(); - m_docsetRegistry->remove(docsetName); - if (dataDir.exists()) { - ui->docsetsProgress->show(); - ui->deleteButton->hide(); - displayProgress(); - - QFuture future = QtConcurrent::run([=] { - QDir docsetDir(dataDir); - return docsetDir.cd(docsetName + QLatin1String(".docset")) && docsetDir.removeRecursively(); - }); - QFutureWatcher *watcher = new QFutureWatcher(); - watcher->setFuture(future); - connect(watcher, &QFutureWatcher::finished, [=] { - if (!watcher->result()) { - QMessageBox::warning(this, tr("Error"), - QString(tr("Cannot delete docset %1!")).arg(docsetTitle)); - } - - resetProgress(); - ui->deleteButton->show(); - - QListWidgetItem *listItem = findDocsetListItem(docsetTitle); - if (listItem) - listItem->setHidden(false); + m_combinedReceived = 0; + m_combinedTotal = 0; + displayProgress(); - watcher->deleteLater(); - }); + // Installed docsets + ui->addFeedButton->setEnabled(true); + QItemSelectionModel *selectionModel = ui->installedDocsetList->selectionModel(); + bool hasSelectedUpdates = false; + for (const QModelIndex &index : selectionModel->selectedIndexes()) { + if (index.data(Zeal::ListModel::UpdateAvailableRole).toBool()) { + hasSelectedUpdates = true; + break; + } } -} + ui->updateSelectedDocsetsButton->setEnabled(hasSelectedUpdates); + ui->updateAllDocsetsButton->setEnabled(updatesAvailable()); + ui->removeDocsetsButton->setEnabled(selectionModel->hasSelection()); -void SettingsDialog::on_installedDocsetList_clicked(const QModelIndex &index) -{ - Q_UNUSED(index) - ui->deleteButton->setEnabled(true); + // Available docsets + ui->availableDocsetList->setEnabled(true); + ui->refreshButton->setEnabled(true); + ui->downloadDocsetButton->setText(tr("Download")); } -QNetworkReply *SettingsDialog::startDownload(const QUrl &url) +void SettingsDialog::loadSettings() { - displayProgress(); - - QNetworkReply *reply = m_application->download(url); - connect(reply, &QNetworkReply::downloadProgress, this, &SettingsDialog::on_downloadProgress); - m_replies.append(reply); + const Core::Settings * const settings = m_application->settings(); + // General Tab + ui->startMinimizedCheckBox->setChecked(settings->startMinimized); + ui->checkForUpdateCheckBox->setChecked(settings->checkForUpdate); - ui->availableDocsetList->setEnabled(false); - ui->downloadDocsetButton->setText(tr("Stop downloads")); - ui->refreshButton->setEnabled(false); - ui->updateButton->setEnabled(false); - ui->addFeedButton->setEnabled(false); + ui->systrayGroupBox->setChecked(settings->showSystrayIcon); + ui->minimizeToSystrayCheckBox->setChecked(settings->minimizeToSystray); + ui->hideToSystrayCheckBox->setChecked(settings->hideOnClose); - return reply; -} + ui->toolButton->setKeySequence(settings->showShortcut); -void SettingsDialog::stopDownloads() -{ - for (QNetworkReply *reply : m_replies) { - // Hide progress bar - QListWidgetItem *listItem = ui->availableDocsetList->item(reply->property(ListItemIndexProperty).toInt()); - if (!listItem) - continue; + // + ui->minFontSize->setValue(settings->minimumFontSize); + ui->storageEdit->setText(QDir::toNativeSeparators(settings->docsetPath)); - listItem->setData(ProgressItemDelegate::ShowProgressRole, false); - reply->abort(); + // Network Tab + switch (settings->proxyType) { + case Core::Settings::ProxyType::None: + ui->noProxySettings->setChecked(true); + break; + case Core::Settings::ProxyType::System: + ui->systemProxySettings->setChecked(true); + break; + case Core::Settings::ProxyType::UserDefined: + ui->manualProxySettings->setChecked(true); + ui->httpProxy->setText(settings->proxyHost); + ui->httpProxyPort->setValue(settings->proxyPort); + ui->httpProxyNeedsAuth->setChecked(settings->proxyAuthenticate); + ui->httpProxyUser->setText(settings->proxyUserName); + ui->httpProxyPass->setText(settings->proxyPassword); + break; } - resetProgress(); } void SettingsDialog::saveSettings() @@ -569,6 +776,7 @@ void SettingsDialog::saveSettings() Core::Settings * const settings = m_application->settings(); // General Tab settings->startMinimized = ui->startMinimizedCheckBox->isChecked(); + settings->checkForUpdate = ui->checkForUpdateCheckBox->isChecked(); settings->showSystrayIcon = ui->systrayGroupBox->isChecked(); settings->minimizeToSystray = ui->minimizeToSystrayCheckBox->isChecked(); @@ -609,69 +817,3 @@ int SettingsDialog::percent(qint64 fraction, qint64 total) return fraction / static_cast(total) * 100; } - -void SettingsDialog::on_tabWidget_currentChanged(int current) -{ - if (ui->tabWidget->widget(current) != ui->docsetsTab || ui->availableDocsetList->count()) - return; - - const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - if (!cacheDir.exists()) - QDir().mkpath(cacheDir.absolutePath()); - const QFileInfo fi(cacheDir.filePath(DocsetListCacheFileName)); - - if (!fi.exists() || fi.lastModified().msecsTo(QDateTime::currentDateTime()) > CacheTimeout) { - downloadDocsetList(); - return; - } - - QScopedPointer file(new QFile(fi.filePath())); - if (!file->open(QIODevice::ReadOnly)) { - downloadDocsetList(); - return; - } - - QJsonParseError jsonError; - const QJsonDocument jsonDoc = QJsonDocument::fromJson(file->readAll(), &jsonError); - - if (jsonError.error != QJsonParseError::NoError) { - downloadDocsetList(); - return; - } - - /// TODO: Show more user friendly labels, like "5 hours ago" - ui->lastUpdatedLabel->setText(fi.lastModified().toString(Qt::SystemLocaleShortDate)); - processDocsetList(jsonDoc.array()); -} - -void SettingsDialog::addDashFeed() -{ - QString txt = QApplication::clipboard()->text(); - if (!txt.startsWith(QLatin1String("dash-feed://"))) - txt.clear(); - - QString feedUrl = QInputDialog::getText(this, QStringLiteral("Zeal"), tr("Feed URL:"), - QLineEdit::Normal, txt); - if (feedUrl.isEmpty()) - return; - - if (feedUrl.startsWith(QLatin1String("dash-feed://"))) { - feedUrl = feedUrl.remove(0, 12); - feedUrl = QUrl::fromPercentEncoding(feedUrl.toUtf8()); - } - - QNetworkReply *reply = startDownload(feedUrl); - reply->setProperty(DownloadTypeProperty, DownloadDashFeed); - connect(reply, &QNetworkReply::finished, this, &SettingsDialog::downloadCompleted); -} - -QListWidgetItem *SettingsDialog::findDocsetListItem(const QString &title) const -{ - const QList items - = ui->availableDocsetList->findItems(title, Qt::MatchFixedString); - - if (items.isEmpty()) - return nullptr; - - return items.first(); -} diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index 418848ec6..e951a7e15 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H @@ -34,20 +57,21 @@ class SettingsDialog : public QDialog ~SettingsDialog() override; private slots: + void addDashFeed(); + void updateSelectedDocsets(); + void updateAllDocsets(); + void removeSelectedDocsets(); + + void downloadCompleted(); + void downloadProgress(qint64 received, qint64 total); + void extractionCompleted(const QString &filePath); void extractionError(const QString &filePath, const QString &errorString); void extractionProgress(const QString &filePath, qint64 extracted, qint64 total); - void downloadCompleted(); - - void on_downloadProgress(qint64 received, qint64 total); void on_downloadDocsetButton_clicked(); void on_storageButton_clicked(); - void on_deleteButton_clicked(); - void on_installedDocsetList_clicked(const QModelIndex &index); void on_tabWidget_currentChanged(int current); - void on_availableDocsetList_itemSelectionChanged(); - void addDashFeed(); private: enum DownloadType { @@ -56,7 +80,13 @@ private slots: DownloadDocsetList }; - QListWidgetItem *findDocsetListItem(const QString &title) const; + Ui::SettingsDialog *ui = nullptr; + Core::Application *m_application = nullptr; + DocsetRegistry *m_docsetRegistry = nullptr; + + QList m_replies; + qint64 m_combinedTotal = 0; + qint64 m_combinedReceived = 0; /// TODO: Create a special model QMap m_availableDocsets; @@ -64,27 +94,24 @@ private slots: QHash m_tmpFiles; + QListWidgetItem *findDocsetListItem(const QString &title) const; + bool updatesAvailable() const; + + QNetworkReply *download(const QUrl &url); + void cancelDownloads(); + void downloadDocsetList(); void processDocsetList(const QJsonArray &list); + void downloadDashDocset(const QString &name); + void removeDocsets(const QStringList &names); void displayProgress(); void resetProgress(); void loadSettings(); - void updateDocsets(); - QNetworkReply *startDownload(const QUrl &url); - void stopDownloads(); void saveSettings(); static inline int percent(qint64 fraction, qint64 total); - - Ui::SettingsDialog *ui = nullptr; - Core::Application *m_application = nullptr; - DocsetRegistry *m_docsetRegistry = nullptr; - - QList m_replies; - qint64 m_combinedTotal = 0; - qint64 m_combinedReceived = 0; }; } // namespace Zeal diff --git a/src/ui/ui.pri b/src/ui/ui.pri index dcb5693e1..e839d71be 100644 --- a/src/ui/ui.pri +++ b/src/ui/ui.pri @@ -10,13 +10,10 @@ FORMS += \ $$files($$PWD/forms/*.ui) unix:!macx { - CONFIG += link_pkgconfig - PKGCONFIG += x11 - - QMAKE_DEL_DIR = rmdir --ignore-fail-on-non-empty - - packagesExist(appindicator) { - PKGCONFIG += appindicator - DEFINES += USE_LIBAPPINDICATOR + packagesExist(appindicator-0.1) { + CONFIG += link_pkgconfig + PKGCONFIG += appindicator-0.1 gtk+-2.0 + DEFINES += USE_APPINDICATOR + message("AppIndicator support enabled") } } diff --git a/src/ui/widgets/searchablewebview.cpp b/src/ui/widgets/searchablewebview.cpp index 82d17e166..e58cb21e2 100644 --- a/src/ui/widgets/searchablewebview.cpp +++ b/src/ui/widgets/searchablewebview.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchablewebview.h" #include "webview.h" @@ -8,45 +31,27 @@ #include #ifdef USE_WEBENGINE - #include - #include +#include +#include #else - #include - #include - #include +#include +#include +#include #endif SearchableWebView::SearchableWebView(QWidget *parent) : QWidget(parent), - m_lineEdit(new QLineEdit(this)), + m_searchLineEdit(new QLineEdit(this)), m_webView(new WebView(this)) { m_webView->setAttribute(Qt::WA_AcceptTouchEvents, false); - m_lineEdit->hide(); - connect(m_lineEdit, &QLineEdit::textChanged, [&](const QString &text) { - // clear selection: -#ifdef USE_WEBENGINE - m_webView->findText(text); -#else - m_webView->findText(QString()); - m_webView->findText(QString(), QWebPage::HighlightAllOccurrences); - if (!text.isEmpty()) { - // select&scroll to one occurence: - m_webView->findText(text, QWebPage::FindWrapsAroundDocument); - // highlight other occurences: - m_webView->findText(text, QWebPage::HighlightAllOccurrences); - } -#endif - // store text for later searches - m_searchText = text; - }); + m_searchLineEdit->hide(); + m_searchLineEdit->installEventFilter(this); + connect(m_searchLineEdit, &QLineEdit::textChanged, this, &SearchableWebView::find); QShortcut *shortcut = new QShortcut(QKeySequence::Find, this); - connect(shortcut, &QShortcut::activated, [&] { - m_lineEdit->show(); - m_lineEdit->setFocus(); - }); + connect(shortcut, &QShortcut::activated, this, &SearchableWebView::showSearch); connect(m_webView, &QWebView::loadFinished, [&](bool ok) { Q_UNUSED(ok) @@ -61,38 +66,46 @@ SearchableWebView::SearchableWebView(QWidget *parent) : #else connect(m_webView, &QWebView::linkClicked, this, &SearchableWebView::linkClicked); #endif +} - connect(m_webView, &QWebView::loadStarted, [&]() { - m_lineEdit->clear(); - }); +void SearchableWebView::setPage(QWebPage *page) +{ + m_webView->setPage(page); - // Display tooltip showing link location when hovered over. -#ifdef USE_WEBENGINE - connect(m_webView->page(), &QWebPage::linkHovered, [&](const QString &link) { -#else - connect(m_webView->page(), &QWebPage::linkHovered, - [&](const QString &link, const QString &title, const QString &textContent) { - Q_UNUSED(title) - Q_UNUSED(textContent) -#endif - if (!link.startsWith(QLatin1String("file:///"))) + connect(page, &QWebPage::linkHovered, [&](const QString &link) { + if (!link.startsWith(QLatin1String("file:"))) setToolTip(link); }); } -void SearchableWebView::setPage(QWebPage *page) +int SearchableWebView::zoomFactor() const { - m_webView->setPage(page); + return m_webView->zealZoomFactor(); } -int SearchableWebView::zealZoomFactor() const +void SearchableWebView::setZoomFactor(int value) { - return m_webView->zealZoomFactor(); + m_webView->setZealZoomFactor(value); } -void SearchableWebView::setZealZoomFactor(int zf) +bool SearchableWebView::eventFilter(QObject *object, QEvent *event) { - m_webView->setZealZoomFactor(zf); + if (object == m_searchLineEdit && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = reinterpret_cast(event); + switch (keyEvent->key()) { + case Qt::Key_Escape: + hideSearch(); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: + findNext(m_searchLineEdit->text(), keyEvent->modifiers() & Qt::ShiftModifier); + return true; + default: + break; + } + } + + return QWidget::eventFilter(object, event); } void SearchableWebView::load(const QUrl &url) @@ -137,31 +150,74 @@ bool SearchableWebView::canGoForward() const void SearchableWebView::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { + switch (event->key()) { + case Qt::Key_Slash: + showSearch(); + event->accept(); + break; + default: + event->ignore(); + break; + } +} + +void SearchableWebView::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + m_webView->resize(event->size().width(), event->size().height()); + moveLineEdit(); +} + +void SearchableWebView::showSearch() +{ + m_searchLineEdit->show(); + m_searchLineEdit->setFocus(); + if (!m_searchLineEdit->text().isEmpty()) { + m_searchLineEdit->selectAll(); + find(m_searchLineEdit->text()); + } +} + +void SearchableWebView::hideSearch() +{ + m_searchLineEdit->hide(); #ifdef USE_WEBENGINE - QWebPage::FindFlags flags = 0; + m_webView->findText(QString()); #else - QWebPage::FindFlags flags = QWebPage::FindWrapsAroundDocument; + m_webView->findText(QString(), QWebPage::HighlightAllOccurrences); #endif - if (event->modifiers() & Qt::ShiftModifier) - flags |= QWebPage::FindBackward; - m_webView->findText(m_searchText, flags); - } +} - if (event->key() == Qt::Key_Slash) { - m_lineEdit->show(); - m_lineEdit->setFocus(); +void SearchableWebView::find(const QString &text) +{ +#ifdef USE_WEBENGINE + /// FIXME: There's no way to just show highlight when search term is already selected. + /// So we need a workaround before switching to Qt WebEngine. + m_webView->findText(text); +#else + if (m_webView->selectedText() != text) { + m_webView->findText(QString(), QWebPage::HighlightAllOccurrences); + m_webView->findText(QString()); + if (text.isEmpty()) + return; + + m_webView->findText(text, QWebPage::FindWrapsAroundDocument); } - // Ignore all other events and pass them to the parent widget. - event->ignore(); + m_webView->findText(text, QWebPage::HighlightAllOccurrences); +#endif } -void SearchableWebView::resizeEvent(QResizeEvent *event) +void SearchableWebView::findNext(const QString &text, bool backward) { - QWidget::resizeEvent(event); - m_webView->resize(event->size().width(), event->size().height()); - moveLineEdit(); +#ifdef USE_WEBENGINE + QWebPage::FindFlags flags = 0; +#else + QWebPage::FindFlags flags = QWebPage::FindWrapsAroundDocument; +#endif + if (backward) + flags |= QWebPage::FindBackward; + m_webView->findText(text, flags); } void SearchableWebView::moveLineEdit() @@ -172,6 +228,6 @@ void SearchableWebView::moveLineEdit() #else frameWidth += m_webView->page()->currentFrame()->scrollBarGeometry(Qt::Vertical).width(); #endif - m_lineEdit->move(rect().right() - frameWidth - m_lineEdit->sizeHint().width(), rect().top()); - m_lineEdit->raise(); + m_searchLineEdit->move(rect().right() - frameWidth - m_searchLineEdit->sizeHint().width(), rect().top()); + m_searchLineEdit->raise(); } diff --git a/src/ui/widgets/searchablewebview.h b/src/ui/widgets/searchablewebview.h index 6998acc0f..6384e1ce9 100644 --- a/src/ui/widgets/searchablewebview.h +++ b/src/ui/widgets/searchablewebview.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHABLEWEBVIEW_H #define SEARCHABLEWEBVIEW_H @@ -9,7 +32,6 @@ class QLineEdit; class QWebPage; -class QWebSettings; class WebView; @@ -27,8 +49,10 @@ class SearchableWebView : public QWidget bool canGoForward() const; void setPage(QWebPage *page); - int zealZoomFactor() const; - void setZealZoomFactor(int zf); + int zoomFactor() const; + void setZoomFactor(int value); + + bool eventFilter(QObject *object, QEvent *event) override; signals: void urlChanged(const QUrl &url); @@ -44,11 +68,14 @@ public slots: void resizeEvent(QResizeEvent *event) override; private: + void showSearch(); + void hideSearch(); + void find(const QString &text); + void findNext(const QString &text, bool backward = false); void moveLineEdit(); - QLineEdit *m_lineEdit = nullptr; + QLineEdit *m_searchLineEdit = nullptr; WebView *m_webView = nullptr; - QString m_searchText; }; #endif // SEARCHABLEWEBVIEW_H diff --git a/src/ui/widgets/searchedit.cpp b/src/ui/widgets/searchedit.cpp index 60775e954..77b5a3020 100644 --- a/src/ui/widgets/searchedit.cpp +++ b/src/ui/widgets/searchedit.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "searchedit.h" #include "registry/searchquery.h" @@ -10,11 +33,9 @@ SearchEdit::SearchEdit(QWidget *parent) : QLineEdit(parent) { - setClearButtonEnabled(true); - m_completionLabel = new QLabel(this); - m_completionLabel->setObjectName("completer"); - m_completionLabel->setStyleSheet("QLabel#completer { color: gray; }"); + m_completionLabel->setObjectName(QStringLiteral("completer")); + m_completionLabel->setStyleSheet(QStringLiteral("QLabel#completer { color: gray; }")); m_completionLabel->setFont(font()); connect(this, &SearchEdit::textChanged, this, &SearchEdit::showCompletions); @@ -49,41 +70,13 @@ void SearchEdit::selectQuery() setSelection(queryStart(), text().size()); } -void SearchEdit::clear() -{ - clearQuery(); -} - -QString SearchEdit::currentCompletion(const QString &text) const -{ - if (text.isEmpty()) - return QString(); - else - return m_prefixCompleter->currentCompletion(); -} - -void SearchEdit::showCompletions(const QString &newValue) -{ - const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - const int textWidth = fontMetrics().width(newValue); - - m_prefixCompleter->setCompletionPrefix(text()); - - const QString completed = currentCompletion(newValue).mid(newValue.size()); - const QSize labelSize(fontMetrics().width(completed), size().height()); - - m_completionLabel->setMinimumSize(labelSize); - m_completionLabel->move(frameWidth + 2 + textWidth, 0); - m_completionLabel->setText(completed); -} - bool SearchEdit::event(QEvent *event) { if (event->type() != QEvent::KeyPress) return QLineEdit::event(event); - QKeyEvent *keyEvent = reinterpret_cast(event); - if (keyEvent->key() != Qt::Key_Tab) + // Tab key cannot be overriden in keyPressEvent() + if (reinterpret_cast(event)->key() != Qt::Key_Tab) return QLineEdit::event(event); const QString completed = currentCompletion(text()); @@ -95,15 +88,13 @@ bool SearchEdit::event(QEvent *event) void SearchEdit::focusInEvent(QFocusEvent *event) { - // Focus on the widget. QLineEdit::focusInEvent(event); - // Override the default selection. - Zeal::SearchQuery currentQuery(text()); - int selectionOffset = currentQuery.keywordPrefixSize(); - if (selectionOffset > 0) - selectionOffset++; // add the delimeter - setSelection(selectionOffset, text().size() - selectionOffset); + // Do not change default behaviour when focused with mouse + if (event->reason() == Qt::MouseFocusReason) + return; + + selectQuery(); m_focusing = true; } @@ -111,7 +102,7 @@ void SearchEdit::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Escape: - clear(); + clearQuery(); event->accept(); break; case Qt::Key_Return: @@ -122,9 +113,10 @@ void SearchEdit::keyPressEvent(QKeyEvent *event) case Qt::Key_Up: { const QModelIndex index = m_treeView->currentIndex(); const int nextRow = index.row() + (event->key() == Qt::Key_Down ? 1 : -1); - const QModelIndex sibling = index.sibling(nextRow, 0); - if (nextRow >= 0 && nextRow < m_treeView->model()->rowCount()) + if (nextRow >= 0 && nextRow < m_treeView->model()->rowCount()) { + const QModelIndex sibling = index.sibling(nextRow, 0); m_treeView->setCurrentIndex(sibling); + } event->accept(); break; } @@ -137,19 +129,39 @@ void SearchEdit::keyPressEvent(QKeyEvent *event) void SearchEdit::mousePressEvent(QMouseEvent *event) { // Let the focusInEvent code deal with initial selection on focus. - if (m_focusing) - return; + if (!m_focusing) + QLineEdit::mousePressEvent(event); - QLineEdit::mousePressEvent(event); m_focusing = false; } +void SearchEdit::showCompletions(const QString &newValue) +{ + const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + const int textWidth = fontMetrics().width(newValue); + + if (!m_prefixCompleter) + m_prefixCompleter->setCompletionPrefix(text()); + + const QString completed = currentCompletion(newValue).mid(newValue.size()); + const QSize labelSize(fontMetrics().width(completed), size().height()); + + m_completionLabel->setMinimumSize(labelSize); + m_completionLabel->move(frameWidth + 2 + textWidth, 0); + m_completionLabel->setText(completed); +} + +QString SearchEdit::currentCompletion(const QString &text) const +{ + if (text.isEmpty() || !m_prefixCompleter) + return QString(); + + return m_prefixCompleter->currentCompletion(); +} + int SearchEdit::queryStart() const { - Zeal::SearchQuery currentQuery = Zeal::SearchQuery::fromString(text()); - // Keep the filter for the first esc press - if (currentQuery.keywordPrefixSize() > 0 && currentQuery.query().size() > 0) - return currentQuery.keywordPrefixSize() + 1; - else - return 0; + const Zeal::SearchQuery currentQuery = Zeal::SearchQuery::fromString(text()); + // Keep the filter for the first Escape press + return currentQuery.query().isEmpty() ? 0 : currentQuery.keywordPrefixSize(); } diff --git a/src/ui/widgets/searchedit.h b/src/ui/widgets/searchedit.h index 91b12345b..a2c19a7f2 100644 --- a/src/ui/widgets/searchedit.h +++ b/src/ui/widgets/searchedit.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SEARCHEDIT_H #define SEARCHEDIT_H @@ -19,18 +42,17 @@ class SearchEdit : public QLineEdit void selectQuery(); void setCompletions(const QStringList &completions); -public slots: - void clear(); - QString currentCompletion(const QString &text) const; - void showCompletions(const QString &text); - protected: bool event(QEvent *event) override; void focusInEvent(QFocusEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void mousePressEvent(QMouseEvent *event) override; +private slots: + void showCompletions(const QString &text); + private: + QString currentCompletion(const QString &text) const; int queryStart() const; QCompleter *m_prefixCompleter = nullptr; diff --git a/src/ui/widgets/shortcutedit.cpp b/src/ui/widgets/shortcutedit.cpp index 5b9ff0cec..8dcc60ccf 100644 --- a/src/ui/widgets/shortcutedit.cpp +++ b/src/ui/widgets/shortcutedit.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "shortcutedit.h" #include @@ -12,8 +35,7 @@ ShortcutEdit::ShortcutEdit(const QString &text, QWidget *parent) : QLineEdit(text, parent) { connect(this, &QLineEdit::textChanged, [this](const QString &text) { - if (text.isEmpty()) - m_key = 0; + m_key = QKeySequence(text, QKeySequence::NativeText)[0]; }); } diff --git a/src/ui/widgets/shortcutedit.h b/src/ui/widgets/shortcutedit.h index 1ba6f8764..7a8dceb21 100644 --- a/src/ui/widgets/shortcutedit.h +++ b/src/ui/widgets/shortcutedit.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef SHORTCUTEDIT_H #define SHORTCUTEDIT_H diff --git a/src/ui/widgets/webview.cpp b/src/ui/widgets/webview.cpp index 13d06df6e..47564ef99 100644 --- a/src/ui/widgets/webview.cpp +++ b/src/ui/widgets/webview.cpp @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "webview.h" #include "../mainwindow.h" @@ -5,6 +28,10 @@ #include #include +#ifndef USE_WEBENGINE +#include +#endif + WebView::WebView(QWidget *parent) : QWebView(parent) { @@ -30,6 +57,55 @@ QWebView *WebView::createWindow(QWebPage::WebWindowType type) return this; } +void WebView::mousePressEvent(QMouseEvent *event) +{ + switch (event->button()) { + case Qt::BackButton: + back(); + event->accept(); + break; + case Qt::ForwardButton: + forward(); + event->accept(); + break; +#ifndef USE_WEBENGINE + case Qt::LeftButton: + if (!(event->modifiers() & Qt::ControlModifier || event->modifiers() & Qt::ShiftModifier)) + break; + case Qt::MiddleButton: + m_clickedLink = clickedLink(event->pos()); + if (m_clickedLink.isValid()) + event->accept(); + break; +#endif + default: + break; + } + + QWebView::mousePressEvent(event); +} + +#ifndef USE_WEBENGINE +void WebView::mouseReleaseEvent(QMouseEvent *event) +{ + switch (event->button()) { + case Qt::LeftButton: + if (!(event->modifiers() & Qt::ControlModifier || event->modifiers() & Qt::ShiftModifier)) + break; + case Qt::MiddleButton: + if (m_clickedLink == clickedLink(event->pos()) && m_clickedLink.isValid()) { + QWebView *webView = createWindow(QWebPage::WebBrowserWindow); + webView->setUrl(m_clickedLink); + event->accept(); + } + break; + default: + break; + } + QWebView::mouseReleaseEvent(event); +} +#endif + void WebView::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { @@ -40,6 +116,17 @@ void WebView::wheelEvent(QWheelEvent *event) } } +#ifndef USE_WEBENGINE +QUrl WebView::clickedLink(const QPoint &pos) const +{ + QWebFrame *frame = page()->frameAt(pos); + if (!frame) + return QUrl(); + + return frame->hitTestContent(pos).linkUrl(); +} +#endif + void WebView::updateZoomFactor() { setZoomFactor(1 + m_zoomFactor / 10.); diff --git a/src/ui/widgets/webview.h b/src/ui/widgets/webview.h index 27ca20cdf..508de3966 100644 --- a/src/ui/widgets/webview.h +++ b/src/ui/widgets/webview.h @@ -1,3 +1,26 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Copyright (C) 2013-2014 Jerzy Kozera +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef WEBVIEW_H #define WEBVIEW_H @@ -21,9 +44,17 @@ class WebView : public QWebView protected: QWebView *createWindow(QWebPage::WebWindowType type) override; + void mousePressEvent(QMouseEvent *event) override; +#ifndef USE_WEBENGINE + void mouseReleaseEvent(QMouseEvent *event) override; +#endif void wheelEvent(QWheelEvent *event) override; private: +#ifndef USE_WEBENGINE + QUrl clickedLink(const QPoint &pos) const; + QUrl m_clickedLink; +#endif void updateZoomFactor(); int m_zoomFactor = 0; diff --git a/src/util/plist.cpp b/src/util/plist.cpp new file mode 100644 index 000000000..c4aa408a6 --- /dev/null +++ b/src/util/plist.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "plist.h" + +#include +#include + +using namespace Zeal::Util; + +Plist::Plist() : + QHash() +{ +} + +bool Plist::read(const QString &fileName) +{ + QScopedPointer file(new QFile(fileName)); + if (!file->open(QIODevice::ReadOnly)) { + /// TODO: Report/log error + m_hasError = true; + return false; + } + + QXmlStreamReader xml(file.data()); + + while (!xml.atEnd()) { + const QXmlStreamReader::TokenType token = xml.readNext(); + if (token == QXmlStreamReader::StartDocument || token != QXmlStreamReader::StartElement) + continue; + + if (xml.name() != QLatin1String("key")) + continue; /// TODO: Should it fail here? + + const QString key = xml.readElementText(); + + // Skip whitespaces between tags + while (xml.readNext() == QXmlStreamReader::Characters); + + if (xml.tokenType() != QXmlStreamReader::StartElement) + continue; + + QVariant value; + if (xml.name() == QLatin1String("string")) + value = xml.readElementText(); + else if (xml.name() == QLatin1String("true")) + value = true; + else if (xml.name() == QLatin1String("false")) + value = false; + else + continue; // Skip unknown types + + insert(key, value); + } + + return m_hasError; +} + +bool Plist::hasError() const +{ + return m_hasError; +} diff --git a/src/util/plist.h b/src/util/plist.h new file mode 100644 index 000000000..15cf6d34d --- /dev/null +++ b/src/util/plist.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef PLIST_H +#define PLIST_H + +#include +#include + +namespace Zeal { +namespace Util { + +class Plist : public QHash +{ +public: + Plist(); + + bool read(const QString &fileName); + bool hasError() const; + +private: + bool m_hasError = false; +}; + +} // namespace Zeal +} // namespace Util + +#endif // PLIST_H diff --git a/src/util/version.cpp b/src/util/version.cpp index a6ace8497..dc632ee39 100644 --- a/src/util/version.cpp +++ b/src/util/version.cpp @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #include "version.h" #include diff --git a/src/util/version.h b/src/util/version.h index a59448e77..cc74760f0 100644 --- a/src/util/version.h +++ b/src/util/version.h @@ -1,3 +1,25 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Oleg Shparber +** Contact: http://zealdocs.org/contact.html +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + #ifndef VERSION_H #define VERSION_H