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);
