[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