Commit 877fd0ca authored by Tomaz  Canabrava's avatar Tomaz Canabrava Committed by Kurt Hindenburg
Browse files

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.

!93

FIXED-IN: 20.08
FEATURE:
GUI:
CHANGELOG: Add thumbnails for certain file types on mouse hover
parent 86aaef4a
......@@ -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
......
......@@ -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));
}
......@@ -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);
......
......@@ -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);
}
......
......@@ -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) {
......
......@@ -886,6 +886,8 @@ private:
Qt::Edge _overlayEdge;
bool _hasCompositeFocus;
QSharedPointer<Filter::HotSpot> _currentlyHoveredHotspot;
};
class AutoScrollHandler : public QObject
......
......@@ -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);
......
#include "ThumbnailsSettings.h"
using namespace Konsole;
ThumbnailsSettings::ThumbnailsSettings(QWidget *aParent)
: QWidget(aParent)
{
setupUi(this);
}
/*
Copyright 2020 Tomaz Canabrava <tcanabrava@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
<?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>
......@@ -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>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment