1 /* This file is part of Spectacle, the KDE screenshot utility
2 * SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de>
3 * SPDX-FileCopyrightText: 2015 Boudhayan Gupta <bgupta@kde.org>
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "KSMainWindow.h"
8
9 #include "SettingsDialog/GeneralOptionsPage.h"
10 #include "SettingsDialog/SaveOptionsPage.h"
11 #include "SettingsDialog/SettingsDialog.h"
12 #include "SettingsDialog/ShortcutsOptionsPage.h"
13 #include "settings.h"
14
15 #include <QApplication>
16 #include <QClipboard>
17 #include <QDBusConnection>
18 #include <QDBusMessage>
19 #include <QDesktopServices>
20 #include <QKeyEvent>
21 #include <QPainter>
22 #include <QPrintDialog>
23 #include <QPushButton>
24 #include <QTimer>
25 #include <QVBoxLayout>
26 #include <QVariantAnimation>
27 #include <QtMath>
28
29 #ifdef XCB_FOUND
30 #include <QX11Info>
31 #include <xcb/xcb.h>
32 #endif
33
34 #include <KAboutData>
35 #include <KConfigGroup>
36 #include <KGuiItem>
37 #include <KHelpMenu>
38 #include <KIO/JobUiDelegate>
39 #include <KIO/OpenFileManagerWindowJob>
40 #include <KIO/OpenUrlJob>
41 #include <KLocalizedString>
42 #include <KSharedConfig>
43 #include <KStandardAction>
44 #include <KWindowSystem>
45
46 static const int DEFAULT_WINDOW_HEIGHT = 420;
47 static const int DEFAULT_WINDOW_WIDTH = 840;
48 static const int MAXIMUM_WINDOW_WIDTH = 1000;
49
KSMainWindow(Platform::GrabModes theGrabModes,Platform::ShutterModes theShutterModes,QWidget * parent)50 KSMainWindow::KSMainWindow(Platform::GrabModes theGrabModes, Platform::ShutterModes theShutterModes, QWidget *parent)
51 : QDialog(parent)
52 , mKSWidget(new KSWidget(theGrabModes, this))
53 , mDivider(new QFrame(this))
54 , mDialogButtonBox(new QDialogButtonBox(this))
55 , mConfigureButton(new QToolButton(this))
56 , mToolsButton(new QPushButton(this))
57 , mSendToButton(new QPushButton(this))
58 , mClipboardButton(new QToolButton(this))
59 , mClipboardMenu(new QMenu(this))
60 , mClipboardLocationAction(new QAction(this))
61 , mClipboardImageAction(new QAction(this))
62 , mSaveButton(new QToolButton(this))
63 , mSaveMenu(new QMenu(this))
64 , mSaveAsAction(new QAction(this))
65 , mSaveAction(new QAction(this))
66 , mMessageWidget(new KMessageWidget(this))
67 , mToolsMenu(new QMenu(this))
68 , mScreenRecorderToolsMenu(new QMenu(this))
69 , mExportMenu(new ExportMenu(this))
70 , mShutterModes(theShutterModes)
71 #ifdef KIMAGEANNOTATOR_FOUND
72 , mAnnotateButton(new QToolButton(this))
73 , mAnnotatorActive(false)
74 #endif
75 {
76 // before we do anything, we need to set a window property
77 // that skips the close/hide window animation on kwin. this
78 // fixes a ghost image of the spectacle window that appears
79 // on subsequent screenshots taken with the take new screenshot
80 // button
81 //
82 // credits for this goes to Thomas Lübking <thomas.luebking@gmail.com>
83
84 #ifdef XCB_FOUND
85 if (KWindowSystem::isPlatformX11()) {
86 // create a window if we haven't already. note that the QWidget constructor
87 // should already have done this
88
89 if (winId() == 0) {
90 create(0, true, true);
91 }
92
93 // do the xcb shenanigans
94 xcb_connection_t *xcbConn = QX11Info::connection();
95 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION");
96
97 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, false, effectName.length(), effectName.constData());
98 QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atom(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr));
99 if (atom.isNull()) {
100 goto done;
101 }
102
103 uint32_t value = 1;
104 xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value);
105 }
106 done:
107 #endif
108
109 QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
110 }
111
112 // GUI init
113
init()114 void KSMainWindow::init()
115 {
116 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc"));
117 KConfigGroup guiConfig(config, "GuiConfig");
118
119 QPoint location = guiConfig.readEntry("window-position", QPoint(50, 50));
120 move(location);
121
122 // change window title on save and on autosave
123
124 connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::imageSaved);
125 connect(ExportManager::instance(), &ExportManager::imageCopied, this, &KSMainWindow::imageCopied);
126 connect(ExportManager::instance(), &ExportManager::imageLocationCopied, this, &KSMainWindow::imageSavedAndLocationCopied);
127 connect(ExportManager::instance(), &ExportManager::imageSavedAndCopied, this, &KSMainWindow::imageSavedAndCopied);
128
129 // the KSGWidget
130
131 connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot);
132 connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest);
133
134 // the Button Bar
135
136 mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help);
137 mDialogButtonBox->button(QDialogButtonBox::Help)->setAutoDefault(false);
138
139 mConfigureButton->setDefaultAction(KStandardAction::preferences(this, SLOT(showPreferencesDialog()), this));
140 mConfigureButton->setText(i18n("Configure..."));
141 mConfigureButton->setToolTip(i18n("Change Spectacle's settings."));
142 mConfigureButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
143 mDialogButtonBox->addButton(mConfigureButton, QDialogButtonBox::ResetRole);
144
145 #ifdef KIMAGEANNOTATOR_FOUND
146 mAnnotateButton->setText(i18n("Annotate"));
147 mAnnotateButton->setToolTip(i18n("Add annotation to the screenshot"));
148 mAnnotateButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
149 mAnnotateButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
150 connect(mAnnotateButton, &QToolButton::clicked, this, [this] {
151 if (mAnnotatorActive) {
152 mKSWidget->hideAnnotator();
153 mAnnotateButton->setText(i18n("Annotate"));
154 } else {
155 mKSWidget->showAnnotator();
156 mAnnotateButton->setText(i18n("Annotation done"));
157 }
158
159 mToolsButton->setEnabled(mAnnotatorActive);
160 mSendToButton->setEnabled(mAnnotatorActive);
161 mClipboardButton->setEnabled(mAnnotatorActive);
162 mSaveButton->setEnabled(mAnnotatorActive);
163
164 mAnnotatorActive = !mAnnotatorActive;
165 });
166
167 mDialogButtonBox->addButton(mAnnotateButton, QDialogButtonBox::ActionRole);
168 #endif
169
170 KGuiItem::assign(mToolsButton, KGuiItem(i18n("Tools")));
171 mToolsButton->setIcon(QIcon::fromTheme(QStringLiteral("tools"), QIcon::fromTheme(QStringLiteral("application-menu"))));
172 mToolsButton->setAutoDefault(false);
173 mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole);
174 mToolsButton->setMenu(mToolsMenu);
175
176 KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export")));
177 mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share")));
178 mSendToButton->setAutoDefault(false);
179 mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole);
180
181 mClipboardButton->setMenu(mClipboardMenu);
182 mClipboardButton->setPopupMode(QToolButton::MenuButtonPopup);
183 mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
184 mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole);
185
186 mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
187 mSaveButton->setMenu(mSaveMenu);
188 mSaveButton->setPopupMode(QToolButton::MenuButtonPopup);
189 mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole);
190
191 // the help menu
192 KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true);
193 mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu());
194
195 // the tools menu
196 mToolsMenu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")),
197 i18n("Open Default Screenshots Folder"),
198 this,
199 &KSMainWindow::openScreenshotsFolder);
200 mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this));
201 mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen"));
202 mScreenRecorderToolsMenu->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
203 connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, this, [this]() {
204 KMoreToolsMenuFactory *moreToolsMenuFactory = new KMoreToolsMenuFactory(QStringLiteral("spectacle/screenrecorder-tools"));
205 moreToolsMenuFactory->setParentWidget(this);
206 mScreenrecorderToolsMenuFactory.reset(moreToolsMenuFactory);
207 mScreenRecorderToolsMenu->clear();
208 mScreenrecorderToolsMenuFactory->fillMenuFromGroupingNames(mScreenRecorderToolsMenu, {QStringLiteral("screenrecorder")});
209 });
210
211 // the save menu
212 mSaveAsAction = KStandardAction::saveAs(this, &KSMainWindow::saveAs, this);
213 mSaveAction = KStandardAction::save(this, &KSMainWindow::save, this);
214 mSaveMenu->addAction(mSaveAsAction);
215 mSaveMenu->addAction(mSaveAction);
216 setDefaultSaveAction();
217
218 // the clipboard menu
219 mClipboardImageAction = KStandardAction::copy(this, &KSMainWindow::copyImage, this);
220 mClipboardImageAction->setText(i18n("Copy Image to Clipboard"));
221 mClipboardLocationAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Location to Clipboard"), this);
222 connect(mClipboardLocationAction, &QAction::triggered, this, &KSMainWindow::copyLocation);
223 mClipboardMenu->addAction(mClipboardImageAction);
224 mClipboardMenu->addAction(mClipboardLocationAction);
225 setDefaultCopyAction();
226
227 // message widget
228 connect(mMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &str) {
229 QDesktopServices::openUrl(QUrl(str));
230 });
231
232 // layouts
233 mDivider->setFrameShape(QFrame::HLine);
234 mDivider->setLineWidth(2);
235
236 QVBoxLayout *layout = new QVBoxLayout(this);
237 layout->addWidget(mKSWidget);
238 layout->addWidget(mMessageWidget);
239 layout->addWidget(mDivider);
240 layout->addWidget(mDialogButtonBox);
241 mMessageWidget->hide();
242
243 // populate our send-to actions
244 mSendToButton->setMenu(mExportMenu);
245 connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback);
246
247 // lock down the onClick mode depending on available shutter modes
248 if (!mShutterModes.testFlag(Platform::ShutterMode::OnClick)) {
249 mKSWidget->lockOnClickDisabled();
250 } else if (!mShutterModes.testFlag(Platform::ShutterMode::Immediate)) {
251 mKSWidget->lockOnClickEnabled();
252 }
253
254 // Allow Ctrl+Q to quit the app
255 QAction *actionQuit = KStandardAction::quit(qApp, &QApplication::quit, this);
256 actionQuit->setShortcut(QKeySequence::Quit);
257 addAction(actionQuit);
258
259 // message: open containing folder
260 mOpenContaining = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder"), mMessageWidget);
261 connect(mOpenContaining, &QAction::triggered, [=] {
262 QUrl imageUrl;
263 if (ExportManager::instance()->isImageSavedNotInTemp()) {
264 imageUrl = Settings::lastSaveLocation();
265 } else {
266 imageUrl = ExportManager::instance()->tempSave();
267 }
268
269 KIO::highlightInFileManager({imageUrl});
270 });
271
272 mHideMessageWidgetTimer = new QTimer(this);
273 // connect(mHideMessageWidgetTimer, &QTimer::timeout,
274 // mMessageWidget, &KMessageWidget::animatedHide);
275 mHideMessageWidgetTimer->setInterval(10000);
276 // done with the init
277 }
278
sizeHint() const279 QSize KSMainWindow::sizeHint() const
280 {
281 return QSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT).expandedTo(minimumSize());
282 }
283
windowWidth(const QPixmap & pixmap) const284 int KSMainWindow::windowWidth(const QPixmap &pixmap) const
285 {
286 // Calculates what the width of the window should be for the captured image to perfectly fit
287 // the area reserved for the image, with the height already set.
288
289 const float pixmapAspectRatio = (float)pixmap.width() / pixmap.height();
290 const int imageHeight = mKSWidget->height() - 2 * layout()->spacing();
291 const int imageWidth = pixmapAspectRatio * imageHeight;
292
293 int alignedWindowWidth = qMin(mKSWidget->imagePaddingWidth() + imageWidth, MAXIMUM_WINDOW_WIDTH);
294 alignedWindowWidth += layout()->contentsMargins().left() + layout()->contentsMargins().right();
295 alignedWindowWidth += 2; // margins is removing 1 - 1 pixel for some reason
296 return alignedWindowWidth;
297 }
298
setDefaultSaveAction()299 void KSMainWindow::setDefaultSaveAction()
300 {
301 switch (Settings::lastUsedSaveMode()) {
302 case Settings::SaveAs:
303 mSaveButton->setDefaultAction(mSaveAsAction);
304 mSaveButton->setText(i18n("Save As..."));
305 break;
306 case Settings::Save:
307 mSaveButton->setDefaultAction(mSaveAction);
308 break;
309 }
310 mSaveButton->setEnabled(mPixmapExists);
311 }
312
setDefaultCopyAction()313 void KSMainWindow::setDefaultCopyAction()
314 {
315 switch (Settings::lastUsedCopyMode()) {
316 case Settings::CopyImage:
317 mClipboardButton->setText(i18n("Copy Image to Clipboard"));
318 mClipboardButton->setDefaultAction(mClipboardImageAction);
319 break;
320 case Settings::CopyLocation:
321 mClipboardButton->setText(i18n("Copy Location to Clipboard"));
322 mClipboardButton->setDefaultAction(mClipboardLocationAction);
323 break;
324 }
325 mClipboardButton->setEnabled(mPixmapExists);
326 }
327
328 // overrides
329
moveEvent(QMoveEvent * event)330 void KSMainWindow::moveEvent(QMoveEvent *event)
331 {
332 Q_UNUSED(event)
333
334 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc"));
335 KConfigGroup guiConfig(config, "GuiConfig");
336
337 guiConfig.writeEntry("window-position", pos());
338 guiConfig.sync();
339 }
340
341 // slots
captureScreenshot(Spectacle::CaptureMode theCaptureMode,int theTimeout,bool theIncludePointer,bool theIncludeDecorations)342 void KSMainWindow::captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations)
343 {
344 if (theTimeout < 0) { // OnClick is checked (always the case on Wayland)
345 hide();
346 Q_EMIT newScreenshotRequest(theCaptureMode, theTimeout, theIncludePointer, theIncludeDecorations);
347 return;
348 }
349
350 showMinimized();
351 mMessageWidget->hide();
352 QTimer *timer = new QTimer;
353 timer->setSingleShot(true);
354 timer->setInterval(theTimeout);
355 auto unityUpdate = [](const QVariantMap &properties) {
356 QDBusMessage message =
357 QDBusMessage::createSignal(QStringLiteral("/org/kde/Spectacle"), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"));
358 message.setArguments({QGuiApplication::desktopFileName(), properties});
359 QDBusConnection::sessionBus().send(message);
360 };
361 auto delayAnimation = new QVariantAnimation(timer);
362 delayAnimation->setStartValue(0.0);
363 delayAnimation->setEndValue(1.0);
364 delayAnimation->setDuration(timer->interval());
365 connect(delayAnimation, &QVariantAnimation::valueChanged, this, [=] {
366 const double progress = delayAnimation->currentValue().toDouble();
367 const double timeoutInSeconds = theTimeout / 1000.0;
368 mKSWidget->setProgress(progress);
369 unityUpdate({{QStringLiteral("progress"), progress}});
370 setWindowTitle(i18ncp("@title:window", "%1 second", "%1 seconds", qMin(int(timeoutInSeconds), qCeil((1 - progress) * timeoutInSeconds))));
371 });
372 connect(timer, &QTimer::timeout, this, [=] {
373 this->hide();
374 timer->deleteLater();
375 mKSWidget->setProgress(0);
376 unityUpdate({{QStringLiteral("progress-visible"), false}});
377 Q_EMIT newScreenshotRequest(theCaptureMode, 0, theIncludePointer, theIncludeDecorations);
378 });
379
380 connect(mKSWidget, &KSWidget::screenshotCanceled, timer, [=] {
381 timer->stop();
382 timer->deleteLater();
383 restoreWindowTitle();
384 unityUpdate({{QStringLiteral("progress-visible"), false}});
385 });
386
387 unityUpdate({{QStringLiteral("progress-visible"), true}, {QStringLiteral("progress"), 0}});
388 timer->start();
389 delayAnimation->start();
390 }
391
setScreenshotAndShow(const QPixmap & pixmap)392 void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap)
393 {
394 mPixmapExists = !pixmap.isNull();
395 if (mPixmapExists) {
396 mKSWidget->setScreenshotPixmap(pixmap);
397 mExportMenu->imageUpdated();
398 setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]"));
399 setWindowModified(true);
400 } else {
401 restoreWindowTitle();
402 }
403
404 #ifdef KIMAGEANNOTATOR_FOUND
405 mAnnotateButton->setEnabled(mPixmapExists);
406 #endif
407 mSendToButton->setEnabled(mPixmapExists);
408 mClipboardButton->setEnabled(mPixmapExists);
409 mSaveButton->setEnabled(mPixmapExists);
410
411 mKSWidget->setButtonState(KSWidget::State::TakeNewScreenshot);
412 show();
413 activateWindow();
414 /* NOTE windowWidth only produces the right result if it is called after the window is visible.
415 * Because of this the call is not moved into the if above */
416 if (mPixmapExists) {
417 resize(QSize(windowWidth(pixmap), DEFAULT_WINDOW_HEIGHT));
418 }
419 }
420
showPrintDialog()421 void KSMainWindow::showPrintDialog()
422 {
423 QPrinter *printer = new QPrinter(QPrinter::HighResolution);
424 QPrintDialog printDialog(printer, this);
425 if (printDialog.exec() == QDialog::Accepted) {
426 ExportManager::instance()->doPrint(printer);
427 return;
428 }
429 delete printer;
430 }
431
openScreenshotsFolder()432 void KSMainWindow::openScreenshotsFolder()
433 {
434 auto job = new KIO::OpenUrlJob(Settings::defaultSaveLocation());
435 job->setUiDelegate(new KIO::JobUiDelegate(KIO::JobUiDelegate::AutoHandlingEnabled, this));
436 job->start();
437 }
438
quit(const QuitBehavior quitBehavior)439 void KSMainWindow::quit(const QuitBehavior quitBehavior)
440 {
441 qApp->setQuitOnLastWindowClosed(false);
442 hide();
443
444 if (quitBehavior == QuitBehavior::QuitImmediately) {
445 // Allow some time for clipboard content to transfer
446 // TODO: Find better solution
447 QTimer::singleShot(250, qApp, &QApplication::quit);
448 }
449 }
450
showInlineMessage(const QString & message,const KMessageWidget::MessageType messageType,const MessageDuration messageDuration,const QList<QAction * > & actions)451 void KSMainWindow::showInlineMessage(const QString &message,
452 const KMessageWidget::MessageType messageType,
453 const MessageDuration messageDuration,
454 const QList<QAction *> &actions)
455 {
456 const auto messageWidgetActions = mMessageWidget->actions();
457 for (QAction *action : messageWidgetActions) {
458 mMessageWidget->removeAction(action);
459 }
460 for (QAction *action : actions) {
461 mMessageWidget->addAction(action);
462 }
463 mMessageWidget->setText(message);
464 mMessageWidget->setMessageType(messageType);
465
466 switch (messageType) {
467 case KMessageWidget::Error:
468 mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error")));
469 break;
470 case KMessageWidget::Warning:
471 mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning")));
472 break;
473 case KMessageWidget::Positive:
474 mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
475 break;
476 case KMessageWidget::Information:
477 mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
478 break;
479 }
480
481 mHideMessageWidgetTimer->stop();
482 mMessageWidget->animatedShow();
483 if (messageDuration == MessageDuration::AutoHide) {
484 mHideMessageWidgetTimer->start();
485 }
486 }
487
showImageSharedFeedback(bool error,const QString & message)488 void KSMainWindow::showImageSharedFeedback(bool error, const QString &message)
489 {
490 if (error == 1) {
491 // error == 1 means the user cancelled the sharing
492 return;
493 }
494
495 if (error) {
496 showInlineMessage(i18n("There was a problem sharing the image: %1", message), KMessageWidget::Error);
497 } else {
498 if (message.isEmpty()) {
499 showInlineMessage(i18n("Image shared"), KMessageWidget::Positive);
500 } else {
501 showInlineMessage(i18n("The shared image link (<a href=\"%1\">%1</a>) has been copied to the clipboard.", message),
502 KMessageWidget::Positive,
503 MessageDuration::Persistent);
504 QApplication::clipboard()->setText(message);
505 }
506 }
507 }
508
copyLocation()509 void KSMainWindow::copyLocation()
510 {
511 Settings::setLastUsedCopyMode(Settings::CopyLocation);
512 setDefaultCopyAction();
513
514 const bool quitChecked = Settings::quitAfterSaveCopyExport();
515 ExportManager::instance()->doCopyLocationToClipboard();
516 if (quitChecked) {
517 quit(QuitBehavior::QuitExternally);
518 }
519 }
520
copyImage()521 void KSMainWindow::copyImage()
522 {
523 Settings::setLastUsedCopyMode(Settings::CopyImage);
524 setDefaultCopyAction();
525
526 const bool quitChecked = Settings::quitAfterSaveCopyExport();
527 ExportManager::instance()->doCopyToClipboard();
528 if (quitChecked) {
529 quit(QuitBehavior::QuitExternally);
530 }
531 }
532
imageCopied()533 void KSMainWindow::imageCopied()
534 {
535 showInlineMessage(i18n("The screenshot has been copied to the clipboard."), KMessageWidget::Information);
536 }
537
imageSavedAndLocationCopied(const QUrl & location)538 void KSMainWindow::imageSavedAndLocationCopied(const QUrl &location)
539 {
540 showInlineMessage(
541 i18n("The screenshot has been saved as <a href=\"%1\">%2</a> and its location has been copied to clipboard", location.toString(), location.fileName()),
542 KMessageWidget::Positive,
543 MessageDuration::AutoHide,
544 {mOpenContaining});
545 }
546
screenshotFailed()547 void KSMainWindow::screenshotFailed()
548 {
549 showInlineMessage(i18n("Could not take a screenshot. Please report this bug here: <a href=\"https://bugs.kde.org/enter_bug.cgi?product=Spectacle\">create "
550 "a spectacle bug</a>"),
551 KMessageWidget::Warning);
552 #ifdef KIMAGEANNOTATOR_FOUND
553 mAnnotateButton->setEnabled(false);
554 #endif
555 }
556
setPlaceholderTextOnLaunch()557 void KSMainWindow::setPlaceholderTextOnLaunch()
558 {
559 QString placeholderText(i18n("Ready to take a screenshot"));
560 mKSWidget->showPlaceholderText(placeholderText);
561 setWindowTitle(placeholderText);
562 }
563
showPreferencesDialog()564 void KSMainWindow::showPreferencesDialog()
565 {
566 if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
567 return;
568 }
569 (new SettingsDialog(this))->show();
570 }
571
imageSaved(const QUrl & location)572 void KSMainWindow::imageSaved(const QUrl &location)
573 {
574 setWindowTitle(location.fileName());
575 setWindowModified(false);
576 showInlineMessage(i18n("The screenshot was saved as <a href=\"%1\">%2</a>", location.toString(), location.fileName()),
577 KMessageWidget::Positive,
578 MessageDuration::AutoHide,
579 {mOpenContaining});
580 }
581
imageSavedAndCopied(const QUrl & location)582 void KSMainWindow::imageSavedAndCopied(const QUrl &location)
583 {
584 setWindowTitle(location.fileName());
585 setWindowModified(false);
586 showInlineMessage(i18n("The screenshot was copied to the clipboard and saved as <a href=\"%1\">%2</a>", location.toString(), location.fileName()),
587 KMessageWidget::Positive,
588 MessageDuration::AutoHide,
589 {mOpenContaining});
590 }
591
save()592 void KSMainWindow::save()
593 {
594 Settings::setLastUsedSaveMode(Settings::Save);
595 setDefaultSaveAction();
596
597 const bool quitChecked = Settings::quitAfterSaveCopyExport();
598 ExportManager::instance()->doSave(QUrl(), /* notify */ quitChecked);
599 if (quitChecked) {
600 quit(QuitBehavior::QuitExternally);
601 }
602 }
603
saveAs()604 void KSMainWindow::saveAs()
605 {
606 Settings::setLastUsedSaveMode(Settings::SaveAs);
607 setDefaultSaveAction();
608
609 const bool quitChecked = Settings::quitAfterSaveCopyExport();
610 if (ExportManager::instance()->doSaveAs(this, /* notify */ quitChecked) && quitChecked) {
611 quit(QuitBehavior::QuitExternally);
612 }
613 }
614
restoreWindowTitle()615 void KSMainWindow::restoreWindowTitle()
616 {
617 if (isWindowModified()) {
618 setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]"));
619 } else {
620 // if a screenshot is not visible inside of mKSWidget, it means we have launched spectacle
621 // with the last mode set to 'rectangular region' and canceled the screenshot
622 if (mKSWidget->isScreenshotSet()) {
623 setWindowTitle(Settings::lastSaveLocation().fileName());
624 } else {
625 setPlaceholderTextOnLaunch();
626 }
627 #ifdef KIMAGEANNOTATOR_FOUND
628 mAnnotateButton->setEnabled(mKSWidget->isScreenshotSet());
629 #endif
630 }
631 }
632
633 /* This event handler enables all Buttons to be activated with Enter. Normally only QPushButton can
634 * be activated with Enter but we also use QToolButtons so we handle the event ourselves */
keyPressEvent(QKeyEvent * event)635 void KSMainWindow::keyPressEvent(QKeyEvent *event)
636 {
637 if (event->key() == Qt::Key_Return) {
638 QWidget *fw = focusWidget();
639 auto pb = qobject_cast<QPushButton *>(fw);
640 if (pb) {
641 pb->animateClick();
642 return;
643 }
644 auto tb = qobject_cast<QToolButton *>(fw);
645 if (tb) {
646 tb->animateClick();
647 return;
648 }
649 }
650 QDialog::keyPressEvent(event);
651 }
652