[spectacle] /: Add optional window title to filename templates

Roman Inflianskas null at kde.org
Tue Mar 6 16:44:02 UTC 2018


Git commit d47ab66eb9a2a0ce7a62dabbc3bf632898186b8e by Roman Inflianskas.
Committed on 06/03/2018 at 16:43.
Pushed by romaninflianskas into branch 'master'.

Add optional window title to filename templates

Summary:
Add '%T' placeholder for filename pattern to insert window title.

Example filenames for pattern `%Y-%M-%D %H-%m-%S. %T`:
2018-02-21 19-11-55. romas : byobu.png
2018-02-21 19-12-20.png

Example filenames for pattern `%T`:
romas : byobu.png
Screenshot.png

Closes T8036
FEATURE: 378463
FIXED-IN: 18.04.0

Test Plan:
Steps:
# Go to `Configure... -> Save`.
# Enter to `Default Save Filename -> Filename` tempate `%Y-%M-%D %H-%m-%S. %T`.
# Press `OK`.
# Save screenshot.
# Observe that screenshot filename matches pattern.

Examples of filenames with empty window title:
> | Input | Result |
> | --- | --- |
> | Screenshot_%T_ProjectX | Screenshot_ProjectX |
> | %T_ProjectX | ProjectX |
> | Screenshot_%T | Screenshot |
> | %T | Screenshot |

Reviewers: rkflx, #spectacle

Reviewed By: rkflx, #spectacle

Subscribers: ngraham, rkflx

Tags: #spectacle

Maniphest Tasks: T8036

Differential Revision: https://phabricator.kde.org/D10709

M  +1    -0    doc/index.docbook
M  +68   -13   src/ExportManager.cpp
M  +11   -0    src/ExportManager.h
M  +3    -1    src/Gui/SettingsDialog/SaveOptionsPage.cpp
M  +1    -0    src/PlatformBackends/ImageGrabber.h
M  +16   -2    src/PlatformBackends/X11ImageGrabber.cpp
M  +1    -0    src/PlatformBackends/X11ImageGrabber.h
M  +4    -2    src/SpectacleCore.cpp

https://commits.kde.org/spectacle/d47ab66eb9a2a0ce7a62dabbc3bf632898186b8e

diff --git a/doc/index.docbook b/doc/index.docbook
index 9fef8c6..0ada2ed 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -311,6 +311,7 @@
 						<listitem><para><userinput>%H</userinput>: Hour</para></listitem>
 						<listitem><para><userinput>%m</userinput>: Minute</para></listitem>
 						<listitem><para><userinput>%S</userinput>: Second</para></listitem>
+						<listitem><para><userinput>%T</userinput>: Window title</para></listitem>
 						</itemizedlist>
 						<para>If a file with this name already exists, a serial number will be appended to the filename. For example, if the filename is 						<filename>Screenshot</filename>, and <filename>Screenshot.png</filename> already exists, the image will be saved as <filename>Screenshot-1.png</filename>.</para>
 						<para>Typing an extension into the filename will automatically set the image format correctly and remove the extension from the filename field.</para>
diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp
index fd06325..591ecc4 100644
--- a/src/ExportManager.cpp
+++ b/src/ExportManager.cpp
@@ -29,6 +29,7 @@
 #include <QPainter>
 #include <QFileDialog>
 #include <QBuffer>
+#include <QRegularExpression>
 
 #include <KLocalizedString>
 #include <KSharedConfig>
@@ -87,6 +88,26 @@ QString ExportManager::pixmapDataUri() const
     return uri;
 }
 
+void ExportManager::setWindowTitle(const QString &windowTitle)
+{
+    mWindowTitle = windowTitle;
+}
+
+QString ExportManager::windowTitle() const
+{
+    return mWindowTitle;
+}
+
+ImageGrabber::GrabMode ExportManager::grabMode() const
+{
+    return mGrabMode;
+}
+
+void ExportManager::setGrabMode(const ImageGrabber::GrabMode &grabMode)
+{
+    mGrabMode = grabMode;
+}
+
 void ExportManager::setPixmap(const QPixmap &pixmap)
 {
     mSavePixmap = pixmap;
@@ -153,6 +174,17 @@ QUrl ExportManager::getAutosaveFilename()
     }
 }
 
+QString ExportManager::truncatedFilename(QString const &filename)
+{
+    QString result = filename;
+    constexpr auto maxFilenameLength = 255;
+    constexpr auto maxExtensionLength = 5;  // For example, ".jpeg"
+    constexpr auto maxCounterLength = 20;  // std::numeric_limits<quint64>::max() == 18446744073709551615
+    constexpr auto maxLength = maxFilenameLength - maxCounterLength - maxExtensionLength;
+    result.truncate(maxLength);
+    return result;
+}
+
 QString ExportManager::makeAutosaveFilename()
 {
     KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc"));
@@ -161,33 +193,56 @@ QString ExportManager::makeAutosaveFilename()
     const QDateTime timestamp = QDateTime::currentDateTime();
     QString baseName = generalConfig.readEntry("save-filename-format", "Screenshot_%Y%M%D_%H%m%S");
 
-    return baseName.replace(QLatin1String("%Y"), timestamp.toString(QStringLiteral("yyyy")))
-                   .replace(QLatin1String("%y"), timestamp.toString(QStringLiteral("yy")))
-                   .replace(QLatin1String("%M"), timestamp.toString(QStringLiteral("MM")))
-                   .replace(QLatin1String("%D"), timestamp.toString(QStringLiteral("dd")))
-                   .replace(QLatin1String("%H"), timestamp.toString(QStringLiteral("hh")))
-                   .replace(QLatin1String("%m"), timestamp.toString(QStringLiteral("mm")))
-                   .replace(QLatin1String("%S"), timestamp.toString(QStringLiteral("ss")));
+    QString title;
+
+    if (mGrabMode == ImageGrabber::GrabMode::ActiveWindow ||
+        mGrabMode == ImageGrabber::GrabMode::TransientWithParent ||
+        mGrabMode == ImageGrabber::GrabMode::WindowUnderCursor) {
+        title = mWindowTitle;
+    } else {
+        // Remove '%T' with separators around it
+        const auto wordSymbol = QStringLiteral(R"(\p{L}\p{M}\p{N})");
+        const auto separator = QStringLiteral("([^%1]+)").arg(wordSymbol);
+        const auto re = QRegularExpression(QStringLiteral("(.*?)(%1%T|%T%1)(.*?)").arg(separator));
+        baseName.replace(re, QStringLiteral(R"(\1\5)"));
+    }
+
+    QString result = baseName.replace(QLatin1String("%Y"), timestamp.toString(QStringLiteral("yyyy")))
+                             .replace(QLatin1String("%y"), timestamp.toString(QStringLiteral("yy")))
+                             .replace(QLatin1String("%M"), timestamp.toString(QStringLiteral("MM")))
+                             .replace(QLatin1String("%D"), timestamp.toString(QStringLiteral("dd")))
+                             .replace(QLatin1String("%H"), timestamp.toString(QStringLiteral("hh")))
+                             .replace(QLatin1String("%m"), timestamp.toString(QStringLiteral("mm")))
+                             .replace(QLatin1String("%S"), timestamp.toString(QStringLiteral("ss")))
+                             .replace(QLatin1String("%T"), title)
+                             .replace(QLatin1String("/"), QLatin1String("_"));  // POSIX doesn't allow "/" in filenames
+    if (result.isEmpty()) {
+        result = QStringLiteral("Screenshot");
+    }
+    return truncatedFilename(result);
 }
 
 QString ExportManager::autoIncrementFilename(const QString &baseName, const QString &extension,
                                              FileNameAlreadyUsedCheck isFileNameUsed)
 {
-    if (!((this->*isFileNameUsed)(QUrl::fromUserInput(baseName + QLatin1Char('.') + extension)))) {
-        return baseName + QLatin1Char('.') + extension;
+    QString result = truncatedFilename(baseName) + QLatin1Literal(".") + extension;
+    if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) {
+        return result;
     }
 
-    QString fileNameFmt(baseName + QStringLiteral("-%1.") + extension);
+    QString fileNameFmt = truncatedFilename(baseName) + QStringLiteral("-%1.");
     for (quint64 i = 1; i < std::numeric_limits<quint64>::max(); i++) {
-        if (!((this->*isFileNameUsed)(QUrl::fromUserInput(fileNameFmt.arg(i))))) {
-            return fileNameFmt.arg(i);
+        result = fileNameFmt.arg(i) + extension;
+        if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) {
+            return result;
         }
     }
 
     // unlikely this will ever happen, but just in case we've run
     // out of numbers
 
-    return fileNameFmt.arg(QStringLiteral("OVERFLOW-") + QString::number(qrand() % 10000));
+    result = fileNameFmt.arg(QStringLiteral("OVERFLOW-") + QString::number(qrand() % 10000));
+    return truncatedFilename(result) + extension;
 }
 
 QString ExportManager::makeSaveMimetype(const QUrl &url)
diff --git a/src/ExportManager.h b/src/ExportManager.h
index 8101267..09a8bba 100644
--- a/src/ExportManager.h
+++ b/src/ExportManager.h
@@ -26,6 +26,8 @@
 #include <QPixmap>
 #include <QUrl>
 
+#include "PlatformBackends/ImageGrabber.h"
+
 class QTemporaryDir;
 
 class ExportManager : public QObject
@@ -52,6 +54,8 @@ class ExportManager : public QObject
 
     Q_PROPERTY(QString saveLocation READ saveLocation WRITE setSaveLocation NOTIFY saveLocationChanged)
     Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap NOTIFY pixmapChanged)
+    Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle)
+    Q_PROPERTY(ImageGrabber::GrabMode grabMode READ grabMode WRITE setGrabMode)
 
     void setSaveLocation(const QString &location);
     QString saveLocation() const;
@@ -60,6 +64,10 @@ class ExportManager : public QObject
     void setPixmap(const QPixmap &pixmap);
     QPixmap pixmap() const;
     QString pixmapDataUri() const;
+    void setWindowTitle(const QString &windowTitle);
+    QString windowTitle() const;
+    ImageGrabber::GrabMode grabMode() const;
+    void setGrabMode(const ImageGrabber::GrabMode &grabMode);
 
     signals:
 
@@ -81,6 +89,7 @@ class ExportManager : public QObject
 
     private:
 
+    QString truncatedFilename(const QString &filename);
     QString makeAutosaveFilename();
     using FileNameAlreadyUsedCheck = bool (ExportManager::*)(const QUrl&) const;
     QString autoIncrementFilename(const QString &baseName, const QString &extension,
@@ -97,6 +106,8 @@ class ExportManager : public QObject
     QUrl mTempFile;
     QTemporaryDir *mTempDir;
     QList<QUrl> mUsedTempFileNames;
+    QString mWindowTitle;
+    ImageGrabber::GrabMode mGrabMode;
 };
 
 #endif // EXPORTMANAGER_H
diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp
index def6b05..23cdb19 100644
--- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp
+++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp
@@ -103,13 +103,15 @@ SaveOptionsPage::SaveOptionsPage(QWidget *parent) :
             "<b>%D</b>: Day<br />"
             "<b>%H</b>: Hour<br />"
             "<b>%m</b>: Minute<br />"
-            "<b>%S</b>: Second"
+            "<b>%S</b>: Second<br />"
+            "<b>%T</b>: Window title"
         "</blockquote>"
     );
 
     QLabel *fmtHelpText = new QLabel(helpText, this);
     fmtHelpText->setWordWrap(true);
     fmtHelpText->setTextFormat(Qt::RichText);
+    fmtHelpText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
     fmtLayout->addWidget(fmtHelpText);
 
     // read in the data
diff --git a/src/PlatformBackends/ImageGrabber.h b/src/PlatformBackends/ImageGrabber.h
index f890558..7ac0f71 100644
--- a/src/PlatformBackends/ImageGrabber.h
+++ b/src/PlatformBackends/ImageGrabber.h
@@ -69,6 +69,7 @@ class ImageGrabber : public QObject
     signals:
 
     void pixmapChanged(const QPixmap &pixmap);
+    void windowTitleChanged(const QString &windowTitle);
     void imageGrabFailed();
     void capturePointerChanged(bool capturePointer);
     void captureDecorationsChanged(bool captureDecorations);
diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp
index 26bca7f..372b552 100644
--- a/src/PlatformBackends/X11ImageGrabber.cpp
+++ b/src/PlatformBackends/X11ImageGrabber.cpp
@@ -44,6 +44,9 @@
 #include <xcb/xcb_util.h>
 #include <xcb/xfixes.h>
 
+#include <X11/Xatom.h>
+#include <X11/Xdefs.h>
+
 X11ImageGrabber::X11ImageGrabber(QObject *parent) :
     ImageGrabber(parent)
 {
@@ -413,6 +416,12 @@ void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap, const Q
 
 // grabber methods
 
+void X11ImageGrabber::updateWindowTitle(xcb_window_t window)
+{
+    QString windowTitle = KWindowSystem::readNameProperty(window, XA_WM_NAME);
+    emit windowTitleChanged(windowTitle);
+}
+
 void X11ImageGrabber::grabFullScreen()
 {
     mPixmap = getToplevelPixmap(QRect(), mCapturePointer);
@@ -421,7 +430,8 @@ void X11ImageGrabber::grabFullScreen()
 
 void X11ImageGrabber::grabTransientWithParent()
 {
-    xcb_window_t curWin = getRealWindowUnderCursor();
+	xcb_window_t curWin = getRealWindowUnderCursor();
+    updateWindowTitle(curWin);
 
     // grab the image early
 
@@ -514,6 +524,7 @@ void X11ImageGrabber::grabTransientWithParent()
 void X11ImageGrabber::grabActiveWindow()
 {
     xcb_window_t activeWindow = KWindowSystem::activeWindow();
+    updateWindowTitle(activeWindow);
 
     // if KWin is available, use the KWin DBus interfaces
 
@@ -542,6 +553,9 @@ void X11ImageGrabber::grabActiveWindow()
 
 void X11ImageGrabber::grabWindowUnderCursor()
 {
+    const xcb_window_t windowUnderCursor = getRealWindowUnderCursor();
+    updateWindowTitle(windowUnderCursor);
+
     // if KWin is available, use the KWin DBus interfaces
 
     if (mCaptureDecorations && isKWinAvailable()) {
@@ -564,7 +578,7 @@ void X11ImageGrabber::grabWindowUnderCursor()
 
     // else, go native
 
-    return grabApplicationWindowHelper(getRealWindowUnderCursor());
+    return grabApplicationWindowHelper(windowUnderCursor);
 }
 
 void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window)
diff --git a/src/PlatformBackends/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h
index 4c76d5d..56d4fa3 100644
--- a/src/PlatformBackends/X11ImageGrabber.h
+++ b/src/PlatformBackends/X11ImageGrabber.h
@@ -87,6 +87,7 @@ class X11ImageGrabber : public ImageGrabber
     QPoint               getNativeCursorPosition();
 
     OnClickEventFilter          *mNativeEventFilter;
+    void updateWindowTitle(xcb_window_t window);
 };
 
 template <typename T> using CScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;
diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp
index 097cf65..83720a8 100644
--- a/src/SpectacleCore.cpp
+++ b/src/SpectacleCore.cpp
@@ -75,7 +75,7 @@ SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMod
         mImageGrabber = new DummyImageGrabber;
     }
 
-    mImageGrabber->setGrabMode(grabMode);
+    setGrabMode(grabMode);
     mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true));
     mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true));
 
@@ -86,6 +86,7 @@ SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMod
     connect(mExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage);
     connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage);
     connect(mImageGrabber, &ImageGrabber::pixmapChanged, this, &SpectacleCore::screenshotUpdated);
+    connect(mImageGrabber, &ImageGrabber::windowTitleChanged, mExportManager, &ExportManager::setWindowTitle);
     connect(mImageGrabber, &ImageGrabber::imageGrabFailed, this, &SpectacleCore::screenshotFailed);
     connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath);
     connect(mExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify);
@@ -132,6 +133,7 @@ ImageGrabber::GrabMode SpectacleCore::grabMode() const
 void SpectacleCore::setGrabMode(const ImageGrabber::GrabMode &grabMode)
 {
     mImageGrabber->setGrabMode(grabMode);
+    mExportManager->setGrabMode(grabMode);
 }
 
 // Slots
@@ -148,7 +150,7 @@ void SpectacleCore::dbusStartAgent()
 void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode,
                                const int &timeout, const bool &includePointer, const bool &includeDecorations)
 {
-    mImageGrabber->setGrabMode(mode);
+    setGrabMode(mode);
     mImageGrabber->setCapturePointer(includePointer);
     mImageGrabber->setCaptureDecorations(includeDecorations);
 


More information about the kde-doc-english mailing list