diff --git a/documentation/CMakeLists.txt b/documentation/CMakeLists.txt --- a/documentation/CMakeLists.txt +++ b/documentation/CMakeLists.txt @@ -24,7 +24,7 @@ ki18n_wrap_ui(KDevPlatformDocumentation_LIB_SRCS documentationfindwidget.ui) kdevplatform_add_library(KDevPlatformDocumentation SOURCES ${KDevPlatformDocumentation_LIB_SRCS}) -target_link_libraries(KDevPlatformDocumentation PUBLIC KDev::Interfaces) +target_link_libraries(KDevPlatformDocumentation PUBLIC KDev::Interfaces PRIVATE KDev::Util) if(USE_QTWEBKIT) target_link_libraries(KDevPlatformDocumentation PRIVATE Qt5::WebKitWidgets) diff --git a/documentation/standarddocumentationview.h b/documentation/standarddocumentationview.h --- a/documentation/standarddocumentationview.h +++ b/documentation/standarddocumentationview.h @@ -1,6 +1,7 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez + * Copyright 2016 Igor Kushnir * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -42,6 +43,17 @@ public: explicit StandardDocumentationView(DocumentationFindWidget* findWidget, QWidget* parent = nullptr ); ~StandardDocumentationView() override; + + /** + * @brief Enables zoom functionality + * + * @param configSubGroup KConfigGroup nested group name used to store zoom factor. + * Should uniquely describe current documentation provider. + * + * @warning Call this function at most once + */ + void initZoom(const QString& configSubGroup); + void setDocumentation(const IDocumentation::Ptr& doc); void setOverrideCss(const QUrl &url); @@ -74,7 +86,13 @@ */ void update(); +private slots: + void updateZoomFactor(double zoomFactor); + private: + void keyPressEvent(QKeyEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + const QScopedPointer d; }; diff --git a/documentation/standarddocumentationview.cpp b/documentation/standarddocumentationview.cpp --- a/documentation/standarddocumentationview.cpp +++ b/documentation/standarddocumentationview.cpp @@ -1,6 +1,7 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez + * Copyright 2016 Igor Kushnir * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -22,6 +23,11 @@ #include "documentationfindwidget.h" #include "debug.h" +#include + +#include +#include + #include #ifdef USE_QTWEBKIT @@ -44,6 +50,7 @@ struct KDevelop::StandardDocumentationViewPrivate { + ZoomController* m_zoomController = nullptr; IDocumentation::Ptr m_doc; #ifdef USE_QTWEBKIT @@ -69,7 +76,6 @@ #ifdef USE_QTWEBKIT QFont sansSerifFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont monospaceFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); - QFont minimalFont = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); QWebSettings* s = d->m_view->settings(); @@ -80,7 +86,6 @@ s->setFontSize(QWebSettings::DefaultFontSize, QFontInfo(sansSerifFont).pixelSize()); s->setFontSize(QWebSettings::DefaultFixedFontSize, QFontInfo(monospaceFont).pixelSize()); - s->setFontSize(QWebSettings::MinimumFontSize, QFontInfo(minimalFont).pixelSize()); // Fixes for correct positioning. The problem looks like the following: // @@ -129,6 +134,18 @@ d->m_view->page()->findText(text, ff); } +void StandardDocumentationView::initZoom(const QString& configSubGroup) +{ + Q_ASSERT_X(!d->m_zoomController, "StandardDocumentationView::initZoom", "Can not initZoom a second time."); + + const KConfigGroup outerGroup(KSharedConfig::openConfig(), QStringLiteral("Documentation View")); + const KConfigGroup configGroup(&outerGroup, configSubGroup); + d->m_zoomController = new ZoomController(configGroup, this); + connect(d->m_zoomController, &ZoomController::factorChanged, + this, &StandardDocumentationView::updateZoomFactor); + updateZoomFactor(d->m_zoomController->factor()); +} + void StandardDocumentationView::setDocumentation(const IDocumentation::Ptr& doc) { if(d->m_doc) @@ -249,3 +266,25 @@ #endif return d->m_view->pageAction(WebkitThing::Copy); } + + +void StandardDocumentationView::updateZoomFactor(double zoomFactor) +{ + d->m_view->setZoomFactor(zoomFactor); +} + +void StandardDocumentationView::keyPressEvent(QKeyEvent* event) +{ + if (d->m_zoomController && d->m_zoomController->handleKeyPressEvent(event)) { + return; + } + QWidget::keyPressEvent(event); +} + +void StandardDocumentationView::wheelEvent(QWheelEvent* event) +{ + if (d->m_zoomController && d->m_zoomController->handleWheelEvent(event)) { + return; + } + QWidget::wheelEvent(event); +} diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -6,6 +6,7 @@ autoorientedsplitter.cpp foregroundlock.cpp formattinghelpers.cpp + zoomcontroller.cpp kdevstringhandler.cpp focusedtreeview.cpp processlinemaker.cpp @@ -67,6 +68,7 @@ autoorientedsplitter.h foregroundlock.h formattinghelpers.h + zoomcontroller.h kdevstringhandler.h ksharedobject.h focusedtreeview.h diff --git a/util/zoomcontroller.h b/util/zoomcontroller.h new file mode 100644 --- /dev/null +++ b/util/zoomcontroller.h @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Igor Kushnir + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef KDEVPLATFORM_ZOOMCONTROLLER_H +#define KDEVPLATFORM_ZOOMCONTROLLER_H + +#include "utilexport.h" + +#include +#include + +class KConfigGroup; +class QKeyEvent; +class QWheelEvent; + +namespace KDevelop { + +class ZoomControllerPrivate; + +/** + * @brief Stores zoom factor; provides common zoom operations and notifications + */ +class KDEVPLATFORMUTIL_EXPORT ZoomController : public QObject +{ + Q_OBJECT +public: + /** + * @param configGroup A place to store zoom factor in + */ + explicit ZoomController(const KConfigGroup& configGroup, + QObject* parent = nullptr); + + ~ZoomController(); + + /** + * @return Current zoom factor + */ + double factor() const; + + /** + * @brief Sets current zoom factor to @p factor + */ + void setFactor(double factor); + + /** + * @brief Changes current zoom factor according to @p scale + * + * @param scale If equal to 0, factor remains the same; + * otherwise, larger scale leads to larger factor. + * Can be both positive (zoom in) and negative (zoom out). + */ + void zoomBy(double scale); + + /** + * @brief Changes current zoom factor in response to designated shortcuts + * + * @param event Event to be analyzed and possibly accepted + * + * @return true If the event was accepted as a zoom shortcut + */ + bool handleKeyPressEvent(QKeyEvent* event); + + /** + * @brief Changes current zoom factor in response to Ctrl+mouse_scroll + * + * @param event Event to be analyzed and possibly accepted + * + * @return true If the event was accepted as a zoom scroll + */ + bool handleWheelEvent(QWheelEvent* event); + +public Q_SLOTS: + /** + * @brief Increases current zoom factor by the default multiplier + */ + void zoomIn(); + + /** + * @brief Decreases current zoom factor by the default multiplier + */ + void zoomOut(); + + /** + * @brief Sets current zoom factor to the default value (1.0) + */ + void resetZoom(); + +Q_SIGNALS: + /** + * @brief Notifies when current zoom factor is changed + * + * @param factor Current zoom factor + */ + void factorChanged(double factor); + +private: + QScopedPointer const d; +}; + +} + +#endif // KDEVPLATFORM_ZOOMCONTROLLER_H diff --git a/util/zoomcontroller.cpp b/util/zoomcontroller.cpp new file mode 100644 --- /dev/null +++ b/util/zoomcontroller.cpp @@ -0,0 +1,159 @@ +/* + * Copyright 2016 Igor Kushnir + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "zoomcontroller.h" + +#include + +#include +#include +#include + +#include + + +namespace { + +constexpr const char* factorConfigEntryKey() { return "Zoom Factor"; } + +constexpr double defaultFactor{1}; +constexpr double defaultMultiplier{1.1}; + +double multiplier(double scale) +{ + // Allow finer-grained control over zoom factor compared to defaultMultiplier + constexpr double smallMultiplier{1.05}; + return std::pow(smallMultiplier, scale); +} + +} // namespace + + +using KDevelop::ZoomControllerPrivate; +using KDevelop::ZoomController; + +class KDevelop::ZoomControllerPrivate +{ +public: + explicit ZoomControllerPrivate(const KConfigGroup& configGroup); + void writeConfig(); + + KConfigGroup m_configGroup; + double m_factor{defaultFactor}; +}; + +ZoomControllerPrivate::ZoomControllerPrivate(const KConfigGroup& configGroup) + : m_configGroup{configGroup} +{ + m_factor = m_configGroup.readEntry(factorConfigEntryKey(), defaultFactor); +} + +void ZoomControllerPrivate::writeConfig() +{ + m_configGroup.writeEntry(factorConfigEntryKey(), m_factor); + m_configGroup.sync(); +} + +ZoomController::ZoomController(const KConfigGroup& configGroup, QObject* parent) + : QObject(parent) + , d{new ZoomControllerPrivate(configGroup)} +{ +} + +ZoomController::~ZoomController() = default; + +double ZoomController::factor() const +{ + return d->m_factor; +} + +void ZoomController::setFactor(double factor) +{ + factor = qBound(0.1, factor, 10.0); + if (d->m_factor == factor) { + return; + } + d->m_factor = factor; + d->writeConfig(); + emit factorChanged(d->m_factor); +} + +void ZoomController::zoomBy(double scale) +{ + setFactor(d->m_factor * multiplier(scale)); +} + +bool ZoomController::handleKeyPressEvent(QKeyEvent* event) +{ + Q_ASSERT(event); + + const auto requiredModifiers = Qt::ControlModifier; + if (!(event->modifiers() & requiredModifiers)) { + return false; + } + // Qt::ShiftModifier is required for the 0 key in some keyboard layouts + // (such as Programmer Dvorak). Allow Qt::KeypadModifier to support numpad keys. + // Don't allow other modifiers, such as Alt and Meta, to minimize shortcut conflicts. + const auto allowedModifiers = Qt::ControlModifier | Qt::ShiftModifier | Qt::KeypadModifier; + if (event->modifiers() & ~allowedModifiers) { + return false; + } + + if (event->key() == Qt::Key_0) { + resetZoom(); + event->accept(); + return true; + } + + return false; +} + +bool ZoomController::handleWheelEvent(QWheelEvent* event) +{ + Q_ASSERT(event); + + if (!(event->modifiers() & Qt::ControlModifier)) { + return false; + } + + constexpr double minStandardDelta{120}; + const QPoint delta = event->angleDelta(); + const double scale{(delta.x() + delta.y()) / minStandardDelta}; + + zoomBy(scale); + event->accept(); + return true; +} + +void ZoomController::zoomIn() +{ + setFactor(d->m_factor * defaultMultiplier); +} + +void ZoomController::zoomOut() +{ + setFactor(d->m_factor / defaultMultiplier); +} + +void ZoomController::resetZoom() +{ + setFactor(defaultFactor); +}