LibreOffice Module cui (master) 1
screenshotannotationdlg.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
21
22#include <strings.hrc>
23#include <dialmgr.hxx>
24
26#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
27#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
28#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
29
30#include <comphelper/random.hxx>
33#include <tools/stream.hxx>
34#include <tools/urlobj.hxx>
35#include <vcl/bitmapex.hxx>
36#include <vcl/customweld.hxx>
37#include <vcl/event.hxx>
39#include <vcl/svapp.hxx>
40#include <vcl/salgtype.hxx>
41#include <vcl/virdev.hxx>
42#include <vcl/weld.hxx>
45#include <set>
46#include <string_view>
47
48using namespace com::sun::star;
49
50namespace
51{
52 OUString lcl_genRandom( std::u16string_view rId )
53 {
54 //FIXME: plus timestamp
55 unsigned int nRand = comphelper::rng::uniform_uint_distribution(0, 0xFFFF);
56 return OUString( rId + OUString::number( nRand ) );
57 }
58
59
60 OUString lcl_AltDescr()
61 {
62 OUString aTempl("<alt id=\"%1\">"
63 " " //FIXME real dialog title or something
64 "</alt>");
65 aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"alt_id") );
66
67 return aTempl;
68 }
69
70 OUString lcl_Image( std::u16string_view rScreenshotId, const Size& rSize )
71 {
72 OUString aTempl("<image id=\"%1\" src=\"media/screenshots/%2.png\""
73 " width=\"%3cm\" height=\"%4cm\">"
74 "%5"
75 "</image>");
76 aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"img_id") );
77 aTempl = aTempl.replaceFirst( "%2", rScreenshotId );
78 aTempl = aTempl.replaceFirst( "%3", OUString::number( rSize.Width() ) );
79 aTempl = aTempl.replaceFirst( "%4", OUString::number( rSize.Height() ) );
80 aTempl = aTempl.replaceFirst( "%5", lcl_AltDescr() );
81
82 return aTempl;
83 }
84
85 OUString lcl_ParagraphWithImage( std::u16string_view rScreenshotId, const Size& rSize )
86 {
87 OUString aTempl( "<paragraph id=\"%1\" role=\"paragraph\">%2"
88 "</paragraph>" SAL_NEWLINE_STRING );
89 aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"par_id") );
90 aTempl = aTempl.replaceFirst( "%2", lcl_Image(rScreenshotId, rSize) );
91
92 return aTempl;
93 }
94
95 OUString lcl_Bookmark( std::u16string_view rWidgetId )
96 {
97 OUString aTempl = "<!-- Bookmark for widget %1 -->" SAL_NEWLINE_STRING
98 "<bookmark branch=\"hid/%2\" id=\"%3\" localize=\"false\"/>" SAL_NEWLINE_STRING;
99 aTempl = aTempl.replaceFirst( "%1", rWidgetId );
100 aTempl = aTempl.replaceFirst( "%2", rWidgetId );
101 aTempl = aTempl.replaceFirst( "%3", lcl_genRandom(u"bm_id") );
102
103 return aTempl;
104 }
105}
106
107namespace
108{
110 {
111 private:
113 bool m_bMouseOver;
114 private:
115 virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override;
116 virtual bool MouseMove(const MouseEvent& rMouseEvent) override;
117 virtual bool MouseButtonUp(const MouseEvent& rMouseEvent) override;
118 public:
120 : m_pDialog(pDialog)
121 , m_bMouseOver(false)
122 {
123 }
124
125 bool IsMouseOver() const
126 {
127 return m_bMouseOver;
128 }
129 };
130}
131
133{
134public:
136 weld::Window* pParent,
137 weld::Builder& rParent,
138 weld::Dialog& rParentDialog);
140
141private:
142 // Handler for click on save
143 DECL_LINK(saveButtonHandler, weld::Button&, void);
144
145 // helper methods
148 const weld::ScreenShotEntry& rEntry,
149 const Color& rColor,
150 double fLineWidth,
151 double fTransparency);
152 void RepaintToBuffer(
153 bool bUseDimmed = false,
154 bool bPaintHilight = false);
157
158 // local variables
164
165 // VirtualDevice for buffered interaction paints
167
168 // all detected children
170
171 // highlighted/selected children
173 std::set< weld::ScreenShotEntry* >
175
176 // list of detected controls
177 Picture maPicture;
178 std::unique_ptr<weld::CustomWeld> mxPicture;
179 std::unique_ptr<weld::TextView> mxText;
180 std::unique_ptr<weld::Button> mxSave;
181
182 // save as text
183 OUString maSaveAsText;
185
186 // folder URL
187 static OUString maLastFolderURL;
188public:
189 void Paint(vcl::RenderContext& rRenderContext);
190 bool MouseMove(const MouseEvent& rMouseEvent);
191 bool MouseButtonUp();
192};
193
195
197 weld::Window* pParent,
198 weld::Builder& rParentBuilder,
199 weld::Dialog& rParentDialog)
200: mpParentWindow(pParent),
201 mrParentDialog(rParentDialog),
202 mxVirtualBufferDevice(nullptr),
203 mpHilighted(nullptr),
204 maPicture(this),
205 maSaveAsText(CuiResId(RID_CUISTR_SAVE_SCREENSHOT_AS))
206{
207 VclPtr<VirtualDevice> xParentDialogSurface(rParentDialog.screenshot());
208 maParentDialogSize = xParentDialogSurface->GetOutputSizePixel();
209 maParentDialogBitmap = xParentDialogSurface->GetBitmapEx(Point(), maParentDialogSize);
211
212 // image ain't empty
213 assert(!maParentDialogBitmap.IsEmpty());
214 assert(0 != maParentDialogBitmap.GetSizePixel().Width());
216
217 // get needed widgets
218 mxPicture.reset(new weld::CustomWeld(rParentBuilder, "picture", maPicture));
219 assert(mxPicture);
220 mxText = rParentBuilder.weld_text_view("text");
221 assert(mxText);
222 mxSave = rParentBuilder.weld_button("save");
223 assert(mxSave);
224
225 // set screenshot image at DrawingArea, resize, set event listener
226 if (mxPicture)
227 {
229
230 // to make clear that maParentDialogBitmap is a background image, adjust
231 // luminance a bit for maDimmedDialogBitmap - other methods may be applied
232 maDimmedDialogBitmap.Adjust(-15, 0, 0, 0, 0);
233
234 // init paint buffering VirtualDevice
236 mxVirtualBufferDevice->SetOutputSizePixel(maParentDialogSize);
238
239 // initially set image for picture control
241
242 // set size for picture control, this will re-layout so that
243 // the picture control shows the whole dialog
244 maPicture.SetOutputSizePixel(maParentDialogSize);
246
247 mxPicture->queue_draw();
248 }
249
250 // set some test text at VclMultiLineEdit and make read-only - only
251 // copying content to clipboard is allowed
252 if (mxText)
253 {
254 mxText->set_size_request(400, mxText->get_height_rows(10));
255 OUString aHelpId = mrParentDialog.get_help_id();
257 maMainMarkupText = lcl_ParagraphWithImage( aHelpId, aSizeCm );
258 mxText->set_text( maMainMarkupText );
259 mxText->set_editable(false);
260 }
261
262 // set click handler for save button
263 if (mxSave)
264 {
265 mxSave->connect_clicked(LINK(this, ScreenshotAnnotationDlg_Impl, saveButtonHandler));
266 }
267}
268
270{
272}
273
275{
276 // 'save screenshot...' pressed, offer to save maParentDialogBitmap
277 // as PNG image, use *.id file name as screenshot file name offering
278 // get a suggestion for the filename from buildable name
279 OUString aDerivedFileName = mrParentDialog.get_buildable_name();
280
281 auto xFileDlg = std::make_unique<sfx2::FileDialogHelper>(ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION,
282 FileDialogFlags::NONE, mpParentWindow);
284
285 const uno::Reference< ui::dialogs::XFilePicker3 > xFilePicker = xFileDlg->GetFilePicker();
286
287 xFilePicker->setTitle(maSaveAsText);
288
289 if (!maLastFolderURL.isEmpty())
290 {
291 xFilePicker->setDisplayDirectory(maLastFolderURL);
292 }
293
294 xFilePicker->appendFilter("*.png", "*.png");
295 xFilePicker->setCurrentFilter("*.png");
296 xFilePicker->setDefaultName(aDerivedFileName);
297 xFilePicker->setMultiSelectionMode(false);
298
299 if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK)
300 return;
301
302 maLastFolderURL = xFilePicker->getDisplayDirectory();
303 const uno::Sequence< OUString > files(xFilePicker->getSelectedFiles());
304
305 if (!files.hasElements())
306 return;
307
308 OUString aConfirmedName = files[0];
309
310 if (aConfirmedName.isEmpty())
311 return;
312
313 INetURLObject aConfirmedURL(aConfirmedName);
314 OUString aCurrentExtension(aConfirmedURL.getExtension());
315
316 if (!aCurrentExtension.isEmpty() && aCurrentExtension != "png")
317 {
318 aConfirmedURL.removeExtension();
319 aCurrentExtension.clear();
320 }
321
322 if (aCurrentExtension.isEmpty())
323 {
324 aConfirmedURL.setExtension(u"png");
325 }
326
327 // open stream
328 SvFileStream aNew(aConfirmedURL.PathToFileName(), StreamMode::WRITE | StreamMode::TRUNC);
329
330 if (!aNew.IsOpen())
331 return;
332
333 // prepare bitmap to save - do use the original screenshot here,
334 // not the dimmed one
335 RepaintToBuffer();
336
337 // extract Bitmap
338 const BitmapEx aTargetBitmap(
339 mxVirtualBufferDevice->GetBitmapEx(
340 Point(0, 0),
341 mxVirtualBufferDevice->GetOutputSizePixel()));
342
343 // write as PNG
344 vcl::PngImageWriter aPNGWriter(aNew);
345 aPNGWriter.write(aTargetBitmap);
346}
347
349{
350 weld::ScreenShotEntry* pRetval = nullptr;
351
352 for (auto&& rCandidate : maAllChildren)
353 {
354 if (rCandidate.getB2IRange().isInside(rPosition))
355 {
356 if (pRetval)
357 {
358 if (pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMinimum())
359 && pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMaximum()))
360 {
361 pRetval = &rCandidate;
362 }
363 }
364 else
365 {
366 pRetval = &rCandidate;
367 }
368 }
369 }
370
371 return pRetval;
372}
373
375 const weld::ScreenShotEntry& rEntry,
376 const Color& rColor,
377 double fLineWidth,
378 double fTransparency)
379{
381 return;
382
383 basegfx::B2DRange aB2DRange(rEntry.getB2IRange());
384
385 // grow in pixels to be a little bit 'outside'. This also
386 // ensures that getWidth()/getHeight() ain't 0.0 (see division below)
387 static const double fGrowTopLeft(1.5);
388 static const double fGrowBottomRight(0.5);
389 aB2DRange.expand(aB2DRange.getMinimum() - basegfx::B2DPoint(fGrowTopLeft, fGrowTopLeft));
390 aB2DRange.expand(aB2DRange.getMaximum() + basegfx::B2DPoint(fGrowBottomRight, fGrowBottomRight));
391
392 // edge rounding in pixel. Need to convert, value for
393 // createPolygonFromRect is relative [0.0 .. 1.0]
394 static const double fEdgeRoundPixel(8.0);
395 const basegfx::B2DPolygon aPolygon(
397 aB2DRange,
398 fEdgeRoundPixel / aB2DRange.getWidth(),
399 fEdgeRoundPixel / aB2DRange.getHeight()));
400
401 mxVirtualBufferDevice->SetLineColor(rColor);
402
403 // try to use transparency
404 if (!mxVirtualBufferDevice->DrawPolyLineDirect(
406 aPolygon,
407 fLineWidth,
408 fTransparency,
409 nullptr, // MM01
411 {
412 // no transparency, draw without
413 mxVirtualBufferDevice->DrawPolyLine(
414 aPolygon,
415 fLineWidth);
416 }
417}
418
420{
421 const Size aPixelSizeTarget(maPicture.GetOutputSizePixel());
422
423 return Point(
424 aPixelSizeTarget.Width() > maParentDialogSize.Width() ? (aPixelSizeTarget.Width() - maParentDialogSize.Width()) >> 1 : 0,
425 aPixelSizeTarget.Height() > maParentDialogSize.Height() ? (aPixelSizeTarget.Height() - maParentDialogSize.Height()) >> 1 : 0);
426}
427
429 bool bUseDimmed,
430 bool bPaintHilight)
431{
433 return;
434
435 // reset with original screenshot bitmap
436 mxVirtualBufferDevice->DrawBitmapEx(
437 Point(0, 0),
439
440 // get various options
441 const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor());
442 const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01);
443 const bool bIsAntiAliasing(SvtOptionsDrawinglayer::IsAntiAliasing());
444 const AntialiasingFlags nOldAA(mxVirtualBufferDevice->GetAntialiasing());
445
446 if (bIsAntiAliasing)
447 {
448 mxVirtualBufferDevice->SetAntialiasing(AntialiasingFlags::Enable);
449 }
450
451 // paint selected entries
452 for (auto&& rCandidate : maSelected)
453 {
454 static const double fLineWidthEntries(5.0);
455 PaintScreenShotEntry(*rCandidate, COL_LIGHTRED, fLineWidthEntries, fTransparence * 0.2);
456 }
457
458 // paint highlighted entry
459 if (mpHilighted && bPaintHilight)
460 {
461 static const double fLineWidthHilight(7.0);
462 PaintScreenShotEntry(*mpHilighted, aHilightColor, fLineWidthHilight, fTransparence);
463 }
464
465 if (bIsAntiAliasing)
466 {
467 mxVirtualBufferDevice->SetAntialiasing(nOldAA);
468 }
469}
470
472{
474 {
475 // reset image in buffer, use dimmed version and allow highlight
476 RepaintToBuffer(true, true);
477 mxPicture->queue_draw();
478 }
479}
480
482{
484 Size aSize(mxVirtualBufferDevice->GetOutputSizePixel());
485 rRenderContext.DrawOutDev(aPos, aSize, Point(), aSize, *mxVirtualBufferDevice);
486}
487
488void Picture::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
489{
490 m_pDialog->Paint(rRenderContext);
491}
492
494{
495 bool bRepaint(false);
496
497 if (maPicture.IsMouseOver())
498 {
499 const weld::ScreenShotEntry* pOldHit = mpHilighted;
500 const Point aOffset(GetOffsetInPicture());
501 const basegfx::B2IPoint aMousePos(
502 rMouseEvent.GetPosPixel().X() - aOffset.X(),
503 rMouseEvent.GetPosPixel().Y() - aOffset.Y());
504 const weld::ScreenShotEntry* pHit = CheckHit(aMousePos);
505
506 if (pHit && pOldHit != pHit)
507 {
508 mpHilighted = const_cast<weld::ScreenShotEntry*>(pHit);
509 bRepaint = true;
510 }
511 }
512 else if (mpHilighted)
513 {
514 mpHilighted = nullptr;
515 bRepaint = true;
516 }
517
518 if (bRepaint)
519 {
521 }
522
523 return true;
524}
525
526bool Picture::MouseMove(const MouseEvent& rMouseEvent)
527{
528 if (rMouseEvent.IsEnterWindow())
529 m_bMouseOver = true;
530 if (rMouseEvent.IsLeaveWindow())
531 m_bMouseOver = false;
532 return m_pDialog->MouseMove(rMouseEvent);
533}
534
536{
537 // event in picture frame
538 bool bRepaint(false);
539
540 if (maPicture.IsMouseOver() && mpHilighted)
541 {
542 if (maSelected.erase(mpHilighted) == 0)
543 {
544 maSelected.insert(mpHilighted);
545 }
546
547 OUStringBuffer aBookmarks(maMainMarkupText);
548 for (auto&& rCandidate : maSelected)
549 aBookmarks.append(lcl_Bookmark(rCandidate->GetHelpId()));
550
551 mxText->set_text( aBookmarks.makeStringAndClear() );
552 bRepaint = true;
553 }
554
555 if (bRepaint)
556 {
558 }
559
560 return true;
561}
562
564{
565 return m_pDialog->MouseButtonUp();
566}
567
569 : GenericDialogController(&rParentDialog, "cui/ui/screenshotannotationdialog.ui", "ScreenshotAnnotationDialog")
570{
571 m_pImpl.reset(new ScreenshotAnnotationDlg_Impl(m_xDialog.get(), *m_xBuilder, rParentDialog));
572}
573
575{
576}
577
578/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
AntialiasingFlags
static OutputDevice * GetDefaultDevice()
bool IsEmpty() const
bool Adjust(short nLuminancePercent, short nContrastPercent, short nChannelRPercent, short nChannelGPercent, short nChannelBPercent, double fGamma=1.0, bool bInvert=false, bool msoBrightness=false)
const Size & GetSizePixel() const
bool removeExtension(sal_Int32 nIndex=LAST_SEGMENT, bool bIgnoreFinalSlash=true)
OUString getExtension(sal_Int32 nIndex=LAST_SEGMENT, bool bIgnoreFinalSlash=true, DecodeMechanism eMechanism=DecodeMechanism::ToIUri, rtl_TextEncoding eCharset=RTL_TEXTENCODING_UTF8) const
OUString PathToFileName() const
bool setExtension(std::u16string_view rTheExtension, sal_Int32 nIndex=LAST_SEGMENT, bool bIgnoreFinalSlash=true, rtl_TextEncoding eCharset=RTL_TEXTENCODING_UTF8)
bool IsEnterWindow() const
bool IsLeaveWindow() const
const Point & GetPosPixel() const
SAL_WARN_UNUSED_RESULT Point PixelToLogic(const Point &rDevicePt) const
SAL_DLLPRIVATE void DrawOutDev(const Point &, const Size &, const Point &, const Size &, const Printer &)=delete
constexpr tools::Long Y() const
constexpr tools::Long X() const
void Paint(vcl::RenderContext &rRenderContext)
weld::ScreenShotEntry * CheckHit(const basegfx::B2IPoint &rPosition)
weld::ScreenShotCollection maAllChildren
void PaintScreenShotEntry(const weld::ScreenShotEntry &rEntry, const Color &rColor, double fLineWidth, double fTransparency)
VclPtr< VirtualDevice > mxVirtualBufferDevice
std::unique_ptr< weld::TextView > mxText
std::unique_ptr< weld::CustomWeld > mxPicture
void RepaintToBuffer(bool bUseDimmed=false, bool bPaintHilight=false)
std::unique_ptr< weld::Button > mxSave
std::set< weld::ScreenShotEntry * > maSelected
bool MouseMove(const MouseEvent &rMouseEvent)
DECL_LINK(saveButtonHandler, weld::Button &, void)
ScreenshotAnnotationDlg_Impl(weld::Window *pParent, weld::Builder &rParent, weld::Dialog &rParentDialog)
std::unique_ptr< ScreenshotAnnotationDlg_Impl > m_pImpl
ScreenshotAnnotationDlg(const ScreenshotAnnotationDlg &)=delete
virtual ~ScreenshotAnnotationDlg() override
constexpr tools::Long Height() const
constexpr tools::Long Width() const
bool IsOpen() const
void disposeAndClear()
static VclPtr< reference_type > Create(Arg &&... arg)
B2DPoint getMaximum() const
B2DPoint getMinimum() const
TYPE getWidth() const
void expand(const Tuple2D< TYPE > &rTuple)
bool isInside(const Tuple2D< TYPE > &rTuple) const
TYPE getHeight() const
bool write(const BitmapEx &rBitmap)
virtual std::unique_ptr< TextView > weld_text_view(const OUString &id)=0
virtual std::unique_ptr< Button > weld_button(const OUString &id)=0
virtual void Paint(vcl::RenderContext &rRenderContext, const tools::Rectangle &rRect)=0
virtual bool MouseMove(const MouseEvent &)
virtual bool MouseButtonUp(const MouseEvent &)
std::shared_ptr< weld::Dialog > m_xDialog
std::unique_ptr< weld::Builder > m_xBuilder
const basegfx::B2IRange & getB2IRange() const
virtual OUString get_help_id() const=0
virtual ScreenShotCollection collect_screenshot_data()=0
virtual VclPtr< VirtualDevice > screenshot()=0
constexpr ::Color COL_LIGHTRED(0xFF, 0x00, 0x00)
constexpr ::Color COL_TRANSPARENT(ColorTransparency, 0xFF, 0xFF, 0xFF, 0xFF)
#define SAL_NEWLINE_STRING
OUString CuiResId(TranslateId aKey)
Definition: cuiresmgr.cxx:23
float u
sal_uInt16 GetTransparentSelectionPercent()
B2DPolygon createPolygonFromRect(const B2DRectangle &rRect, double fRadiusX, double fRadiusY)
unsigned int uniform_uint_distribution(unsigned int a, unsigned int b)
std::vector< ScreenShotEntry > ScreenShotCollection
IMPL_LINK_NOARG(ScreenshotAnnotationDlg_Impl, saveButtonHandler, weld::Button &, void)