[utilities/konsole] src: Add thumbnails for certain file types on mouse hover

Kurt Hindenburg null at kde.org
Sat Jun 6 02:51:21 BST 2020


Git commit 877fd0caf4a12b85902a029bf0f127b5e8ad5e9e by Kurt Hindenburg, on behalf of Tomaz Canabrava.
Committed on 06/06/2020 at 01:49.
Pushed by hindenburg into branch 'master'.

Add thumbnails for certain file types on mouse hover

This allows for a user configured thumbnail image to be displayed
when the mouse is hovering over a file link.  Any file that KIO can
transform into a thumbnail (image, video, folder) will be displayed.

Simply move the mouse to a file while holding an user selected keypress
(Alt, Shift, Control or a combination of them).  The default
requires no key press.  The profile setting 'Underline files' much be
enabled for this to work.

https://invent.kde.org/utilities/konsole/-/merge_requests/93

FIXED-IN: 20.08
FEATURE:
GUI:
CHANGELOG: Add thumbnails for certain file types on mouse hover

M  +5    -2    src/CMakeLists.txt
M  +101  -0    src/Filter.cpp
M  +26   -0    src/Filter.h
M  +6    -0    src/MainWindow.cpp
M  +21   -0    src/TerminalDisplay.cpp
M  +2    -0    src/TerminalDisplay.h
M  +3    -0    src/settings/ConfigurationDialog.h
A  +9    -0    src/settings/ThumbnailsSettings.cpp  *
A  +38   -0    src/settings/ThumbnailsSettings.h     [License: GPL (v2/3)]
A  +73   -0    src/settings/ThumbnailsSettings.ui
M  +22   -0    src/settings/konsole.kcfg

The files marked with a * at the end have a non valid license. Please read: https://community.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.


https://invent.kde.org/utilities/konsole/commit/877fd0caf4a12b85902a029bf0f127b5e8ad5e9e

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eda6ea82c..eef7f82a4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -158,7 +158,8 @@ ki18n_wrap_ui(konsoleprivate_SRCS ColorSchemeEditor.ui
                                 settings/GeneralSettings.ui
                                 settings/PartInfo.ui
                                 settings/ProfileSettings.ui
-                                settings/TabBarSettings.ui)
+                                settings/TabBarSettings.ui
+                                settings/ThumbnailsSettings.ui)
 
 # add the resource files for the ui files
 qt5_add_resources( konsoleprivate_SRCS ../desktop/konsole.qrc)
@@ -182,7 +183,9 @@ set(konsole_KDEINIT_SRCS
    settings/TemporaryFilesSettings.cpp
    settings/GeneralSettings.cpp
    settings/ProfileSettings.cpp
-   settings/TabBarSettings.cpp)
+   settings/TabBarSettings.cpp
+   settings/ThumbnailsSettings.cpp
+)
 
 
 # Sets the icon on Windows and OSX
diff --git a/src/Filter.cpp b/src/Filter.cpp
index 21a00fd88..fece98ead 100644
--- a/src/Filter.cpp
+++ b/src/Filter.cpp
@@ -21,6 +21,8 @@
 #include "Filter.h"
 
 #include "konsoledebug.h"
+#include "KonsoleSettings.h"
+
 #include <algorithm>
 
 // Qt
@@ -33,6 +35,11 @@
 #include <QTextStream>
 #include <QUrl>
 #include <QMenu>
+#include <QMouseEvent>
+#include <QToolTip>
+#include <QBuffer>
+#include <QToolTip>
+#include <QTimer>
 
 // KDE
 #include <KLocalizedString>
@@ -40,6 +47,7 @@
 #include <KFileItem>
 #include <KFileItemListProperties>
 #include <KFileItemActions>
+#include <KIO/PreviewJob>
 
 // Konsole
 #include "Session.h"
@@ -599,3 +607,96 @@ void FileFilter::HotSpot::setupMenu(QMenu *menu)
     _menuActions.setItemListProperties(itemProperties);
     _menuActions.addOpenWithActionsTo(menu);
 }
+
+// Static variables for the HotSpot
+qintptr FileFilter::HotSpot::currentThumbnailHotspot = 0;
+bool FileFilter::HotSpot::_canGenerateThumbnail = false;
+QPointer<KIO::PreviewJob> FileFilter::HotSpot::_previewJob;
+
+void FileFilter::HotSpot::requestThumbnail(Qt::KeyboardModifiers modifiers, const QPoint &pos) {
+    _canGenerateThumbnail = true;
+    currentThumbnailHotspot = reinterpret_cast<qintptr>(this);
+    _eventModifiers = modifiers;
+    _eventPos = pos;
+
+    // Defer the real creation of the thumbnail by a few msec.
+    QTimer::singleShot(250, this, [this]{
+        if (currentThumbnailHotspot != reinterpret_cast<qintptr>(this)) {
+            return;
+        }
+
+        thumbnailRequested();
+    });
+}
+
+void FileFilter::HotSpot::stopThumbnailGeneration()
+{
+    _canGenerateThumbnail = false;
+    if (_previewJob) {
+        _previewJob->deleteLater();
+        QToolTip::hideText();
+    }
+}
+
+void Konsole::FileFilter::HotSpot::showThumbnail(const KFileItem& item, const QPixmap& preview)
+{
+    if (!_canGenerateThumbnail) {
+        return;
+    }
+    _thumbnailFinished = true;
+    Q_UNUSED(item)
+    QByteArray data;
+    QBuffer buffer(&data);
+    preview.save(&buffer, "PNG", 100);
+
+    const auto tooltipString = QStringLiteral("<img src='data:image/png;base64, %0'>")
+        .arg(QString::fromLocal8Bit(data.toBase64()));
+
+    QToolTip::showText(_thumbnailPos, tooltipString, qApp->focusWidget());
+}
+
+void FileFilter::HotSpot::thumbnailRequested() {
+    if (!_canGenerateThumbnail) {
+        return;
+    }
+
+    auto *settings = KonsoleSettings::self();
+
+    Qt::KeyboardModifiers modifiers = settings->thumbnailCtrl() ? Qt::ControlModifier : Qt::NoModifier;
+    modifiers |= settings->thumbnailAlt() ? Qt::AltModifier : Qt::NoModifier;
+    modifiers |= settings->thumbnailShift() ? Qt::ShiftModifier : Qt::NoModifier;
+
+    if (_eventModifiers != modifiers) {
+        return;
+    }
+
+    _thumbnailPos = QPoint(_eventPos.x() + 100, _eventPos.y() - settings->thumbnailSize() / 2);
+
+    const int size = KonsoleSettings::thumbnailSize();
+    if (_previewJob) {
+        _previewJob->deleteLater();
+    }
+
+    _thumbnailFinished = false;
+
+    // Show a "Loading" if Preview takes a long time.
+    QTimer::singleShot(10, this, [this]{
+        if (!_previewJob) {
+            return;
+        }
+        if (!_thumbnailFinished) {
+            QToolTip::showText(_thumbnailPos, i18n("Generating Thumbnail"), qApp->focusWidget());
+        }
+    });
+
+    _previewJob = new KIO::PreviewJob(KFileItemList({fileItem()}), QSize(size, size));
+    connect(_previewJob, &KIO::PreviewJob::gotPreview, this, &FileFilter::HotSpot::showThumbnail);
+    connect(_previewJob, &KIO::PreviewJob::failed, this, []{ QToolTip::hideText(); });
+    _previewJob->setAutoDelete(true);
+    _previewJob->start();
+}
+
+KFileItem Konsole::FileFilter::HotSpot::fileItem() const
+{
+    return KFileItem(QUrl::fromLocalFile(_filePath));
+}
diff --git a/src/Filter.h b/src/Filter.h
index 7b2bc3b8a..1b39ed3d7 100644
--- a/src/Filter.h
+++ b/src/Filter.h
@@ -28,9 +28,13 @@
 #include <QStringList>
 #include <QRegularExpression>
 #include <QMultiHash>
+#include <QRect>
+#include <QPoint>
 
 // KDE
 #include <KFileItemActions>
+#include <KFileItem>
+#include <KIO/PreviewJob>
 
 #include <memory>
 
@@ -39,6 +43,8 @@
 
 class QAction;
 class QMenu;
+class QMouseEvent;
+class KFileItem;
 
 namespace Konsole {
 class Session;
@@ -316,9 +322,29 @@ public:
          */
         void activate(QObject *object = nullptr) override;
         void setupMenu(QMenu *menu) override;
+
+        KFileItem fileItem() const;
+        void requestThumbnail(Qt::KeyboardModifiers modifiers, const QPoint &pos);
+        void thumbnailRequested();
+
+        static void stopThumbnailGeneration();
     private:
+        void showThumbnail(const KFileItem& item, const QPixmap& preview);
         QString _filePath;
         KFileItemActions _menuActions;
+
+        QPoint _eventPos;
+        QPoint _thumbnailPos;
+        Qt::KeyboardModifiers _eventModifiers;
+        bool _thumbnailFinished;
+
+        /* This variable stores the pointer of the active HotSpot that
+         * is generating the thumbnail now, so we can bail out early.
+         * it's not used for pointer access.
+         */
+        static qintptr currentThumbnailHotspot;
+        static bool _canGenerateThumbnail;
+        static QPointer<KIO::PreviewJob> _previewJob;
     };
 
     explicit FileFilter(Session *session);
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 0bc87112e..5b6e441ad 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -58,6 +58,7 @@
 #include "settings/GeneralSettings.h"
 #include "settings/ProfileSettings.h"
 #include "settings/TabBarSettings.h"
+#include "settings/ThumbnailsSettings.h"
 
 using namespace Konsole;
 
@@ -773,6 +774,11 @@ void MainWindow::showSettingsDialog(const bool showProfilePage)
     temporaryFilesPage->setIcon(QIcon::fromTheme(QStringLiteral("folder-temp")));
     confDialog->addPage(temporaryFilesPage, true);
 
+    const QString thumbnailPageName = i18nc("@title Preferences page name", "Thumbnails");
+    auto thumbnailPage = new KPageWidgetItem(new ThumbnailsSettings(confDialog), thumbnailPageName);
+    thumbnailPage->setIcon(QIcon::fromTheme(QStringLiteral("image-jpeg")));
+    confDialog->addPage(thumbnailPage, true);
+
     if (showProfilePage) {
         confDialog->setCurrentPage(profilePage);
     }
diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp
index 55b793ad1..1a68c8df4 100644
--- a/src/TerminalDisplay.cpp
+++ b/src/TerminalDisplay.cpp
@@ -1835,6 +1835,7 @@ void TerminalDisplay::focusOutEvent(QFocusEvent*)
     Q_ASSERT(!_textBlinking);
 
     _showUrlHint = false;
+    FileFilter::HotSpot::stopThumbnailGeneration();
 }
 
 void TerminalDisplay::focusInEvent(QFocusEvent*)
@@ -2262,6 +2263,7 @@ void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
 {
     int charLine = 0;
     int charColumn = 0;
+
     getCharacterPosition(ev->pos(), charLine, charColumn, !_usesMouseTracking);
 
     processFilters();
@@ -2302,6 +2304,15 @@ void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
             setCursor(Qt::PointingHandCursor);
         }
 
+        /* can't use qobject_cast because moc is broken for inner classes */
+        auto fileSpot = spot.dynamicCast<FileFilter::HotSpot>();
+        if (fileSpot != _currentlyHoveredHotspot) {
+            _currentlyHoveredHotspot = fileSpot;
+            if (fileSpot) {
+                fileSpot->requestThumbnail(ev->modifiers(), ev->globalPos());
+            }
+        }
+
         update(_mouseOverHotspotArea | previousHotspotArea);
     } else if (!_mouseOverHotspotArea.isEmpty()) {
         if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u)) || (cursor().shape() == Qt::PointingHandCursor)) {
@@ -2311,6 +2322,8 @@ void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
         update(_mouseOverHotspotArea);
         // set hotspot area to an invalid rectangle
         _mouseOverHotspotArea = QRegion();
+        FileFilter::HotSpot::stopThumbnailGeneration();
+        _currentlyHoveredHotspot.clear();
     }
 
     // for auto-hiding the cursor, we need mouseTracking
@@ -3564,6 +3577,14 @@ void TerminalDisplay::keyPressEvent(QKeyEvent* event)
         }
     }
 
+    if (_currentlyHoveredHotspot) {
+        auto fileHotspot = _currentlyHoveredHotspot.dynamicCast<FileFilter::HotSpot>();
+        if (!fileHotspot) {
+            return;
+        }
+        fileHotspot->requestThumbnail(event->modifiers(), QCursor::pos());
+    }
+
     _screenWindow->screen()->setCurrentTerminalDisplay(this);
 
     if (!_readOnly) {
diff --git a/src/TerminalDisplay.h b/src/TerminalDisplay.h
index ce217d771..7587bd1a0 100644
--- a/src/TerminalDisplay.h
+++ b/src/TerminalDisplay.h
@@ -886,6 +886,8 @@ private:
     Qt::Edge _overlayEdge;
 
     bool _hasCompositeFocus;
+
+    QSharedPointer<Filter::HotSpot> _currentlyHoveredHotspot;
 };
 
 class AutoScrollHandler : public QObject
diff --git a/src/settings/ConfigurationDialog.h b/src/settings/ConfigurationDialog.h
index 9d092198f..9cc95e8dd 100644
--- a/src/settings/ConfigurationDialog.h
+++ b/src/settings/ConfigurationDialog.h
@@ -118,6 +118,9 @@ public:
 
     bool hasChanged() const {
         for(const QButtonGroup *group: qAsConst(_groups)) {
+            if (!group->checkedButton()) {
+                continue;
+            }
             int value = buttonToEnumValue(group->checkedButton());
             const auto *enumItem = groupToConfigItemEnum(group);
 
diff --git a/src/settings/ThumbnailsSettings.cpp b/src/settings/ThumbnailsSettings.cpp
new file mode 100644
index 000000000..da2c5faf0
--- /dev/null
+++ b/src/settings/ThumbnailsSettings.cpp
@@ -0,0 +1,9 @@
+#include "ThumbnailsSettings.h"
+
+using namespace Konsole;
+
+ThumbnailsSettings::ThumbnailsSettings(QWidget *aParent)
+: QWidget(aParent)
+{
+    setupUi(this);
+}
diff --git a/src/settings/ThumbnailsSettings.h b/src/settings/ThumbnailsSettings.h
new file mode 100644
index 000000000..44b122954
--- /dev/null
+++ b/src/settings/ThumbnailsSettings.h
@@ -0,0 +1,38 @@
+/*
+  Copyright 2020 Tomaz Canabrava <tcanabrava at kde.org>
+
+  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 appro-
+  ved 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 http://www.gnu.org/licenses/.
+*/
+
+#ifndef THUMBNAILSSETTINGS_H
+#define THUMBNAILSSETTINGS_H
+
+#include "ui_ThumbnailsSettings.h"
+
+namespace Konsole {
+class ThumbnailsSettings : public QWidget, private Ui::ThumbnailsSettings
+{
+    Q_OBJECT
+
+public:
+    explicit ThumbnailsSettings(QWidget *aParent = nullptr);
+    ~ThumbnailsSettings() override = default;
+};
+
+}
+
+#endif
diff --git a/src/settings/ThumbnailsSettings.ui b/src/settings/ThumbnailsSettings.ui
new file mode 100644
index 000000000..06ab42a1b
--- /dev/null
+++ b/src/settings/ThumbnailsSettings.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ThumbnailsSettings</class>
+ <widget class="QWidget" name="ThumbnailsSettings">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>347</width>
+    <height>347</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QFormLayout" name="formLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Size:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QSpinBox" name="kcfg_ThumbnailSize">
+     <property name="suffix">
+      <string>px</string>
+     </property>
+     <property name="maximum">
+      <number>1024</number>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Activation:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>Mouse hover plus</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QCheckBox" name="kcfg_ThumbnailShift">
+     <property name="text">
+      <string>Shift</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <widget class="QCheckBox" name="kcfg_ThumbnailAlt">
+     <property name="text">
+      <string>Alt</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="1">
+    <widget class="QCheckBox" name="kcfg_ThumbnailCtrl">
+     <property name="text">
+      <string>Ctrl</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/settings/konsole.kcfg b/src/settings/konsole.kcfg
index e878ede62..1340763bc 100644
--- a/src/settings/konsole.kcfg
+++ b/src/settings/konsole.kcfg
@@ -40,6 +40,28 @@
       <default>false</default>
     </entry>
   </group>
+  <group name="ThumbnailsSettings">
+      <entry name="ThumbnailSize" type="Int">
+        <label> Thumbnail Width </label>
+        <tooltip> Sets the width of the thumbnail </tooltip>
+        <default> 250 </default>
+      </entry>
+     <entry name="ThumbnailShift" type="bool">
+        <label> Use shift to display a thumbnail </label>
+        <tooltip> Use shift to display a thumbnail </tooltip>
+        <default> false </default>
+      </entry>
+     <entry name="ThumbnailAlt" type="bool">
+        <label> Use alt to display a thumbnail </label>
+        <tooltip> Use alt to display a thumbnail </tooltip>
+        <default> false </default>
+      </entry>
+     <entry name="ThumbnailCtrl" type="bool">
+        <label> Use ctrl to display a thumbnail </label>
+        <tooltip> Use ctrl to display a thumbnail </tooltip>
+        <default> false </default>
+      </entry>
+  </group>
   <group name="SearchSettings">
     <entry name="SearchCaseSensitive" type="Bool">
       <label>Search is case sensitive</label>


More information about the kde-doc-english mailing list