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 <[email protected]> + + 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>
