1 /*
2 * SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de>
3 * SPDX-FileCopyrightText: 2015 Boudhayan Gupta <bgupta@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "KSWidget.h"
9 #include "spectacle_gui_debug.h"
10
11 #include "ExportManager.h"
12 #include "KSImageWidget.h"
13 #include "ProgressButton.h"
14 #include "SmartSpinBox.h"
15 #include "settings.h"
16
17 #include <QAction>
18 #include <QApplication>
19 #include <QCheckBox>
20 #include <QComboBox>
21 #include <QFormLayout>
22 #include <QGridLayout>
23 #include <QLabel>
24 #include <QShortcut>
25 #include <QStackedLayout>
26
27 #ifdef KIMAGEANNOTATOR_FOUND
28 #include <kImageAnnotator/KImageAnnotator.h>
29 #endif
30
31 #include <KConfigDialogManager>
32 #include <KLocalizedString>
33
KSWidget(Platform::GrabModes theGrabModes,QWidget * parent)34 KSWidget::KSWidget(Platform::GrabModes theGrabModes, QWidget *parent)
35 : QWidget(parent)
36 {
37 mStack = new QStackedLayout(this);
38
39 // we'll init the widget that holds the image first
40 mImageWidget = new KSImageWidget(this);
41 mImageWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
42 connect(mImageWidget, &KSImageWidget::dragInitiated, this, &KSWidget::dragInitiated);
43
44 #ifdef KIMAGEANNOTATOR_FOUND
45 mAnnotator = new kImageAnnotator::KImageAnnotator();
46 mAnnotator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
47 mAnnotator->setCanvasColor(QColor(255, 255, 255, 0));
48 mStack->addWidget(mAnnotator);
49 #endif
50
51 // the capture mode options first
52 mCaptureModeLabel = new QLabel(i18n("<b>Capture Mode</b>"), this);
53 mCaptureArea = new QComboBox(this);
54
55 if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) {
56 QString lFullScreenLabel = QApplication::screens().count() == 1 ? i18n("Full Screen") : i18n("Full Screen (All Monitors)");
57
58 mCaptureArea->insertItem(0, lFullScreenLabel, Spectacle::CaptureMode::AllScreens);
59 }
60 if (theGrabModes.testFlag(Platform::GrabMode::AllScreensScaled) && QApplication::screens().count() > 1) {
61 QString lFullScreenLabel = i18n("Full Screen (All Monitors, scaled)");
62 mCaptureArea->insertItem(1, lFullScreenLabel, Spectacle::CaptureMode::AllScreensScaled);
63 }
64 if (theGrabModes.testFlag(Platform::GrabMode::PerScreenImageNative)) {
65 mCaptureArea->insertItem(2, i18n("Rectangular Region"), Spectacle::CaptureMode::RectangularRegion);
66 }
67 if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) {
68 mCaptureArea->insertItem(3, i18n("Current Screen"), Spectacle::CaptureMode::CurrentScreen);
69 }
70 if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) {
71 mCaptureArea->insertItem(4, i18n("Active Window"), Spectacle::CaptureMode::ActiveWindow);
72 }
73 if (theGrabModes.testFlag(Platform::GrabMode::WindowUnderCursor)) {
74 mCaptureArea->insertItem(5, i18n("Window Under Cursor"), Spectacle::CaptureMode::WindowUnderCursor);
75 }
76 if (theGrabModes.testFlag(Platform::GrabMode::TransientWithParent)) {
77 mTransientWithParentAvailable = true;
78 }
79 mCaptureArea->setMinimumWidth(240);
80 mCaptureArea->setObjectName(QStringLiteral("kcfg_captureMode"));
81 mCaptureArea->setProperty("kcfg_property", QByteArray("currentData"));
82 connect(mCaptureArea, qOverload<int>(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged);
83
84 mDelayMsec = new SmartSpinBox(this);
85 mDelayMsec->setDecimals(1);
86 mDelayMsec->setSingleStep(1.0);
87 mDelayMsec->setMinimum(0.0);
88 mDelayMsec->setMaximum(999.9);
89 mDelayMsec->setSpecialValueText(i18n("No Delay"));
90 mDelayMsec->setMinimumWidth(160);
91 mDelayMsec->setObjectName(QStringLiteral("kcfg_captureDelay"));
92
93 mCaptureOnClick = new QCheckBox(i18n("On Click"), this);
94 mCaptureOnClick->setToolTip(i18n("Wait for a mouse click before capturing the screenshot image"));
95 connect(mCaptureOnClick, &QCheckBox::stateChanged, this, &KSWidget::onClickStateChanged);
96 mCaptureOnClick->setObjectName(QStringLiteral("kcfg_onClickChecked"));
97
98 mDelayLayout = new QHBoxLayout;
99 mDelayLayout->addWidget(mDelayMsec);
100 mDelayLayout->addWidget(mCaptureOnClick);
101
102 mCaptureModeForm = new QFormLayout;
103 mCaptureModeForm->addRow(i18n("Area:"), mCaptureArea);
104 mCaptureModeForm->addRow(i18n("Delay:"), mDelayLayout);
105 mCaptureModeForm->setContentsMargins(24, 0, 0, 0);
106
107 // options (mouse pointer, window decorations, quit after saving or copying)
108 mContentOptionsLabel = new QLabel(this);
109 mContentOptionsLabel->setText(i18n("<b>Options</b>"));
110
111 mMousePointer = new QCheckBox(i18n("Include mouse pointer"), this);
112 mMousePointer->setToolTip(i18n("Show the mouse cursor in the screenshot image"));
113 mMousePointer->setObjectName(QStringLiteral("kcfg_includePointer"));
114
115 mWindowDecorations = new QCheckBox(i18n("Include window titlebar and borders"), this);
116 mWindowDecorations->setToolTip(i18n("Show the window title bar, the minimize/maximize/close buttons, and the window border"));
117 mWindowDecorations->setEnabled(false);
118 mWindowDecorations->setObjectName(QStringLiteral("kcfg_includeDecorations"));
119
120 mCaptureTransientOnly = new QCheckBox(i18n("Capture the current pop-up only"), this);
121 mCaptureTransientOnly->setToolTip(
122 i18n("Capture only the current pop-up window (like a menu, tooltip etc).\n"
123 "If disabled, the pop-up is captured along with the parent window"));
124 mCaptureTransientOnly->setEnabled(false);
125 mCaptureTransientOnly->setObjectName(QStringLiteral("kcfg_transientOnly"));
126
127 mQuitAfterSaveOrCopy = new QCheckBox(i18n("Quit after manual Save or Copy"), this);
128 mQuitAfterSaveOrCopy->setToolTip(i18n("Quit Spectacle after manually saving or copying the image"));
129 mQuitAfterSaveOrCopy->setObjectName(QStringLiteral("kcfg_quitAfterSaveCopyExport"));
130
131 mContentOptionsForm = new QVBoxLayout;
132 mContentOptionsForm->addWidget(mMousePointer);
133 mContentOptionsForm->addWidget(mWindowDecorations);
134 mContentOptionsForm->addWidget(mCaptureTransientOnly);
135 mContentOptionsForm->addWidget(mQuitAfterSaveOrCopy);
136 mContentOptionsForm->setContentsMargins(24, 0, 0, 0);
137
138 mTakeNewScreenshotAction = new QAction(QIcon::fromTheme(QStringLiteral("spectacle")), i18n("Take a New Screenshot"), this);
139 mTakeNewScreenshotAction->setShortcut(QKeySequence::New);
140 connect(mTakeNewScreenshotAction, &QAction::triggered, this, &KSWidget::newScreenshotClicked);
141
142 mCancelAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("Cancel"), this);
143 mCancelAction->setShortcut(QKeySequence::Cancel);
144 connect(mCancelAction, &QAction::triggered, this, [this] {
145 Q_EMIT screenshotCanceled();
146 setButtonState(State::TakeNewScreenshot);
147 });
148
149 // the take a new screenshot button
150 mTakeScreenshotButton = new ProgressButton(this);
151 mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
152 mTakeScreenshotButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
153 setButtonState(State::TakeNewScreenshot);
154 mTakeScreenshotButton->setFocus();
155
156 // finally, finish up the layouts
157 mRightLayout = new QVBoxLayout;
158 mRightLayout->addStretch(1);
159 mRightLayout->addWidget(mCaptureModeLabel);
160 mRightLayout->addLayout(mCaptureModeForm);
161 mRightLayout->addStretch(1);
162 mRightLayout->addWidget(mContentOptionsLabel);
163 mRightLayout->addLayout(mContentOptionsForm);
164 mRightLayout->addStretch(10);
165 mRightLayout->addWidget(mTakeScreenshotButton, 1, Qt::AlignHCenter);
166 mRightLayout->setContentsMargins(10, 0, 0, 10);
167
168 mPlaceholderLabel = new QLabel;
169
170 QFont placeholderLabelFont;
171 // To match the size of a level 2 Heading/KTitleWidget
172 placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3));
173 mPlaceholderLabel->setFont(placeholderLabelFont);
174 mPlaceholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
175 mPlaceholderLabel->setWordWrap(true);
176 mPlaceholderLabel->setAlignment(Qt::AlignCenter);
177 mPlaceholderLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
178 // Match opacity of QML placeholder label component
179 auto *effect = new QGraphicsOpacityEffect(mPlaceholderLabel);
180 effect->setOpacity(0.5);
181 mPlaceholderLabel->setGraphicsEffect(effect);
182
183 mMainLayout = new QGridLayout();
184
185 mMainLayout->addWidget(mPlaceholderLabel, 0, 0, 1, 1);
186 mMainLayout->addWidget(mImageWidget, 0, 0, 1, 1);
187 mMainLayout->addLayout(mRightLayout, 0, 1, 1, 1);
188 mMainLayout->setColumnMinimumWidth(0, 320);
189 mMainLayout->setColumnMinimumWidth(1, 320);
190
191 int index = mCaptureArea->findData(Settings::captureMode());
192 mCaptureArea->setCurrentIndex(index >= 0 ? index : 0);
193 auto mConfigManager = new KConfigDialogManager(this, Settings::self());
194 connect(mConfigManager, &KConfigDialogManager::widgetModified, mConfigManager, &KConfigDialogManager::updateSettings);
195
196 placeHolder = new QWidget();
197 placeHolder->setLayout(mMainLayout);
198
199 mStack->addWidget(placeHolder);
200
201 #ifdef KIMAGEANNOTATOR_FOUND
202 mStack->addWidget(mAnnotator);
203 #endif
204 }
205
imagePaddingWidth() const206 int KSWidget::imagePaddingWidth() const
207 {
208 int lRightLayoutLeft = 0;
209 int lRightLayoutRight = 0;
210 int lMainLayoutRight = 0;
211
212 mRightLayout->getContentsMargins(&lRightLayoutLeft, nullptr, &lRightLayoutRight, nullptr);
213 mMainLayout->getContentsMargins(nullptr, nullptr, &lMainLayoutRight, nullptr);
214
215 int lPaddingWidth = (lRightLayoutLeft + lRightLayoutRight + lMainLayoutRight);
216 lPaddingWidth += mRightLayout->contentsRect().width();
217 lPaddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow
218
219 return lPaddingWidth;
220 }
221
isScreenshotSet()222 bool KSWidget::isScreenshotSet()
223 {
224 return mImageWidget->isPixmapSet();
225 }
226
227 // public slots
228
showPlaceholderText(const QString & label)229 void KSWidget::showPlaceholderText(const QString &label)
230 {
231 mImageWidget->hide();
232 mPlaceholderLabel->setText(label);
233 mPlaceholderLabel->show();
234 }
235
setScreenshotPixmap(const QPixmap & thePixmap)236 void KSWidget::setScreenshotPixmap(const QPixmap &thePixmap)
237 {
238 if (mPlaceholderLabel->isVisible()) {
239 mPlaceholderLabel->hide();
240 }
241 mImageWidget->show();
242 mImageWidget->setScreenshot(thePixmap);
243 }
244
lockOnClickEnabled()245 void KSWidget::lockOnClickEnabled()
246 {
247 mCaptureOnClick->setCheckState(Qt::Checked);
248 mCaptureOnClick->setEnabled(false);
249 mDelayMsec->setEnabled(false);
250 }
251
lockOnClickDisabled()252 void KSWidget::lockOnClickDisabled()
253 {
254 mCaptureOnClick->setCheckState(Qt::Unchecked);
255 mCaptureOnClick->hide();
256 mDelayMsec->setEnabled(true);
257 }
258
259 // private slots
260
newScreenshotClicked()261 void KSWidget::newScreenshotClicked()
262 {
263 int lDelay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000);
264 auto lMode = static_cast<Spectacle::CaptureMode>(mCaptureArea->currentData().toInt());
265 if (mTransientWithParentAvailable && lMode == Spectacle::CaptureMode::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) {
266 lMode = Spectacle::CaptureMode::TransientWithParent;
267 }
268 setButtonState(State::Cancel);
269 Q_EMIT newScreenshotRequest(lMode, lDelay, mMousePointer->isChecked(), mWindowDecorations->isChecked());
270 }
271
onClickStateChanged(int theState)272 void KSWidget::onClickStateChanged(int theState)
273 {
274 if (theState == Qt::Checked) {
275 mDelayMsec->setEnabled(false);
276 } else if (theState == Qt::Unchecked) {
277 mDelayMsec->setEnabled(true);
278 }
279 }
280
captureModeChanged(int theIndex)281 void KSWidget::captureModeChanged(int theIndex)
282 {
283 Spectacle::CaptureMode captureMode = static_cast<Spectacle::CaptureMode>(mCaptureArea->itemData(theIndex).toInt());
284 switch (captureMode) {
285 case Spectacle::CaptureMode::WindowUnderCursor:
286 mWindowDecorations->setEnabled(true);
287 if (mTransientWithParentAvailable) {
288 mCaptureTransientOnly->setEnabled(true);
289 } else {
290 mCaptureTransientOnly->setEnabled(false);
291 }
292 break;
293 case Spectacle::CaptureMode::ActiveWindow:
294 mWindowDecorations->setEnabled(true);
295 mCaptureTransientOnly->setEnabled(false);
296 break;
297 case Spectacle::CaptureMode::AllScreens:
298 case Spectacle::CaptureMode::AllScreensScaled:
299 case Spectacle::CaptureMode::CurrentScreen:
300 case Spectacle::CaptureMode::RectangularRegion:
301 mWindowDecorations->setEnabled(false);
302 mCaptureTransientOnly->setEnabled(false);
303 break;
304 case Spectacle::CaptureMode::TransientWithParent:
305 case Spectacle::CaptureMode::InvalidChoice:
306 default:
307 qCWarning(SPECTACLE_GUI_LOG) << "Skipping invalid or unreachable enum value";
308 break;
309 }
310 }
311
setButtonState(State state)312 void KSWidget::setButtonState(State state)
313 {
314 switch (state) {
315 case State::TakeNewScreenshot:
316 mTakeScreenshotButton->removeAction(mCancelAction);
317 mTakeScreenshotButton->setDefaultAction(mTakeNewScreenshotAction);
318 mTakeScreenshotButton->setProgress(0);
319 break;
320 case State::Cancel:
321 mTakeScreenshotButton->removeAction(mTakeNewScreenshotAction);
322 mTakeScreenshotButton->setDefaultAction(mCancelAction);
323 break;
324 }
325 }
326
setProgress(double progress)327 void KSWidget::setProgress(double progress)
328 {
329 mTakeScreenshotButton->setProgress(progress);
330 }
331
332 #ifdef KIMAGEANNOTATOR_FOUND
showAnnotator()333 void KSWidget::showAnnotator()
334 {
335 mStack->setCurrentIndex(1);
336 QPixmap px = ExportManager::instance()->pixmap();
337 px.setDevicePixelRatio(qApp->devicePixelRatio());
338 mAnnotator->loadImage(px);
339 }
340
hideAnnotator()341 void KSWidget::hideAnnotator()
342 {
343 mStack->setCurrentIndex(0);
344 QImage image = mAnnotator->image();
345 QPixmap px = QPixmap::fromImage(image);
346 setScreenshotPixmap(px);
347 ExportManager::instance()->setPixmap(px);
348 }
349 #endif
350