tdf#130857 qt weld: Implement QtInstanceWidget::get_text_height
[LibreOffice.git] / cui / source / dialogs / screenshotannotationdlg.cxx
blob4fc295f842867105078fa0372cd432c10f39d918
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <screenshotannotationdlg.hxx>
22 #include <strings.hrc>
23 #include <dialmgr.hxx>
25 #include <basegfx/range/b2irange.hxx>
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>
30 #include <comphelper/random.hxx>
31 #include <basegfx/polygon/b2dpolygontools.hxx>
32 #include <sfx2/filedlghelper.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>
38 #include <vcl/filter/PngImageWriter.hxx>
39 #include <vcl/svapp.hxx>
40 #include <vcl/salgtype.hxx>
41 #include <vcl/virdev.hxx>
42 #include <vcl/weld.hxx>
43 #include <svtools/optionsdrawinglayer.hxx>
44 #include <basegfx/matrix/b2dhommatrix.hxx>
45 #include <set>
46 #include <string_view>
48 using namespace com::sun::star;
50 namespace
52 OUString lcl_genRandom( std::u16string_view rId )
54 //FIXME: plus timestamp
55 unsigned int nRand = comphelper::rng::uniform_uint_distribution(0, 0xFFFF);
56 return OUString( rId + OUString::number( nRand ) );
60 OUString lcl_AltDescr()
62 OUString aTempl(u"<alt id=\"%1\">"
63 " " //FIXME real dialog title or something
64 "</alt>"_ustr);
65 aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"alt_id") );
67 return aTempl;
70 OUString lcl_Image( std::u16string_view rScreenshotId, const Size& rSize )
72 OUString aTempl(u"<image id=\"%1\" src=\"media/screenshots/%2.png\""
73 " width=\"%3cm\" height=\"%4cm\">"
74 "%5"
75 "</image>"_ustr);
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() );
82 return aTempl;
85 OUString lcl_ParagraphWithImage( std::u16string_view rScreenshotId, const Size& rSize )
87 OUString aTempl( u"<paragraph id=\"%1\" role=\"paragraph\">%2"
88 "</paragraph>" SAL_NEWLINE_STRING ""_ustr );
89 aTempl = aTempl.replaceFirst( "%1", lcl_genRandom(u"par_id") );
90 aTempl = aTempl.replaceFirst( "%2", lcl_Image(rScreenshotId, rSize) );
92 return aTempl;
95 OUString lcl_Bookmark( std::u16string_view rWidgetId )
97 OUString aTempl = u"<!-- Bookmark for widget %1 -->" SAL_NEWLINE_STRING
98 "<bookmark branch=\"hid/%2\" id=\"%3\" localize=\"false\"/>" SAL_NEWLINE_STRING ""_ustr;
99 aTempl = aTempl.replaceFirst( "%1", rWidgetId );
100 aTempl = aTempl.replaceFirst( "%2", rWidgetId );
101 aTempl = aTempl.replaceFirst( "%3", lcl_genRandom(u"bm_id") );
103 return aTempl;
107 namespace
109 class Picture : public weld::CustomWidgetController
111 private:
112 ScreenshotAnnotationDlg_Impl *m_pDialog;
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:
119 Picture(ScreenshotAnnotationDlg_Impl* pDialog)
120 : m_pDialog(pDialog)
121 , m_bMouseOver(false)
125 bool IsMouseOver() const
127 return m_bMouseOver;
132 class ScreenshotAnnotationDlg_Impl
134 public:
135 ScreenshotAnnotationDlg_Impl(
136 weld::Window* pParent,
137 weld::Builder& rParent,
138 weld::Dialog& rParentDialog);
139 ~ScreenshotAnnotationDlg_Impl();
141 private:
142 // Handler for click on save
143 DECL_LINK(saveButtonHandler, weld::Button&, void);
145 // helper methods
146 weld::ScreenShotEntry* CheckHit(const basegfx::B2IPoint& rPosition);
147 void PaintScreenShotEntry(
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);
155 void RepaintPictureElement();
156 Point GetOffsetInPicture() const;
158 // local variables
159 weld::Window* mpParentWindow;
160 weld::Dialog& mrParentDialog;
161 BitmapEx maParentDialogBitmap;
162 BitmapEx maDimmedDialogBitmap;
163 Size maParentDialogSize;
165 // VirtualDevice for buffered interaction paints
166 VclPtr<VirtualDevice> mxVirtualBufferDevice;
168 // all detected children
169 weld::ScreenShotCollection maAllChildren;
171 // highlighted/selected children
172 weld::ScreenShotEntry* mpHilighted;
173 std::set< weld::ScreenShotEntry* >
174 maSelected;
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;
182 // save as text
183 OUString maSaveAsText;
184 OUString maMainMarkupText;
186 // folder URL
187 static OUString maLastFolderURL;
188 public:
189 void Paint(vcl::RenderContext& rRenderContext);
190 bool MouseMove(const MouseEvent& rMouseEvent);
191 bool MouseButtonUp();
194 OUString ScreenshotAnnotationDlg_Impl::maLastFolderURL = OUString();
196 ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl(
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))
207 VclPtr<VirtualDevice> xParentDialogSurface(rParentDialog.screenshot());
208 maParentDialogSize = xParentDialogSurface->GetOutputSizePixel();
209 maParentDialogBitmap = xParentDialogSurface->GetBitmapEx(Point(), maParentDialogSize);
210 maDimmedDialogBitmap = maParentDialogBitmap;
212 // image ain't empty
213 assert(!maParentDialogBitmap.IsEmpty());
214 assert(0 != maParentDialogBitmap.GetSizePixel().Width());
215 assert(0 != maParentDialogBitmap.GetSizePixel().Height());
217 // get needed widgets
218 mxPicture.reset(new weld::CustomWeld(rParentBuilder, u"picture"_ustr, maPicture));
219 assert(mxPicture);
220 mxText = rParentBuilder.weld_text_view(u"text"_ustr);
221 assert(mxText);
222 mxSave = rParentBuilder.weld_button(u"save"_ustr);
223 assert(mxSave);
225 // set screenshot image at DrawingArea, resize, set event listener
226 if (mxPicture)
228 maAllChildren = mrParentDialog.collect_screenshot_data();
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);
234 // init paint buffering VirtualDevice
235 mxVirtualBufferDevice = VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA);
236 mxVirtualBufferDevice->SetOutputSizePixel(maParentDialogSize);
237 mxVirtualBufferDevice->SetFillColor(COL_TRANSPARENT);
239 // initially set image for picture control
240 mxVirtualBufferDevice->DrawBitmapEx(Point(0, 0), maDimmedDialogBitmap);
242 // set size for picture control, this will re-layout so that
243 // the picture control shows the whole dialog
244 maPicture.SetOutputSizePixel(maParentDialogSize);
245 mxPicture->set_size_request(maParentDialogSize.Width(), maParentDialogSize.Height());
247 mxPicture->queue_draw();
250 // set some test text at VclMultiLineEdit and make read-only - only
251 // copying content to clipboard is allowed
252 if (mxText)
254 mxText->set_size_request(400, mxText->get_height_rows(10));
255 OUString aHelpId = mrParentDialog.get_help_id();
256 Size aSizeCm = Application::GetDefaultDevice()->PixelToLogic(maParentDialogSize, MapMode(MapUnit::MapCM));
257 maMainMarkupText = lcl_ParagraphWithImage( aHelpId, aSizeCm );
258 mxText->set_text( maMainMarkupText );
259 mxText->set_editable(false);
262 // set click handler for save button
263 if (mxSave)
265 mxSave->connect_clicked(LINK(this, ScreenshotAnnotationDlg_Impl, saveButtonHandler));
269 ScreenshotAnnotationDlg_Impl::~ScreenshotAnnotationDlg_Impl()
271 mxVirtualBufferDevice.disposeAndClear();
274 IMPL_LINK_NOARG(ScreenshotAnnotationDlg_Impl, saveButtonHandler, weld::Button&, void)
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();
281 sfx2::FileDialogHelper aFileDlg(ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION,
282 FileDialogFlags::NONE, mpParentWindow);
283 aFileDlg.SetContext(sfx2::FileDialogHelper::ScreenshotAnnotation);
285 const uno::Reference< ui::dialogs::XFilePicker3 > xFilePicker = aFileDlg.GetFilePicker();
287 xFilePicker->setTitle(maSaveAsText);
289 if (!maLastFolderURL.isEmpty())
291 xFilePicker->setDisplayDirectory(maLastFolderURL);
294 xFilePicker->appendFilter(u"*.png"_ustr, u"*.png"_ustr);
295 xFilePicker->setCurrentFilter(u"*.png"_ustr);
296 xFilePicker->setDefaultName(aDerivedFileName);
297 xFilePicker->setMultiSelectionMode(false);
299 if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK)
300 return;
302 maLastFolderURL = xFilePicker->getDisplayDirectory();
303 const uno::Sequence< OUString > files(xFilePicker->getSelectedFiles());
305 if (!files.hasElements())
306 return;
308 const OUString& aConfirmedName = files[0];
310 if (aConfirmedName.isEmpty())
311 return;
313 INetURLObject aConfirmedURL(aConfirmedName);
314 OUString aCurrentExtension(aConfirmedURL.getExtension());
316 if (!aCurrentExtension.isEmpty() && aCurrentExtension != "png")
318 aConfirmedURL.removeExtension();
319 aCurrentExtension.clear();
322 if (aCurrentExtension.isEmpty())
324 aConfirmedURL.setExtension(u"png");
327 // open stream
328 SvFileStream aNew(aConfirmedURL.PathToFileName(), StreamMode::WRITE | StreamMode::TRUNC);
330 if (!aNew.IsOpen())
331 return;
333 // prepare bitmap to save - do use the original screenshot here,
334 // not the dimmed one
335 RepaintToBuffer();
337 // extract Bitmap
338 const BitmapEx aTargetBitmap(
339 mxVirtualBufferDevice->GetBitmapEx(
340 Point(0, 0),
341 mxVirtualBufferDevice->GetOutputSizePixel()));
343 // write as PNG
344 vcl::PngImageWriter aPNGWriter(aNew);
345 aPNGWriter.write(aTargetBitmap);
348 weld::ScreenShotEntry* ScreenshotAnnotationDlg_Impl::CheckHit(const basegfx::B2IPoint& rPosition)
350 weld::ScreenShotEntry* pRetval = nullptr;
352 for (auto&& rCandidate : maAllChildren)
354 if (rCandidate.getB2IRange().isInside(rPosition))
356 if (pRetval)
358 if (pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMinimum())
359 && pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMaximum()))
361 pRetval = &rCandidate;
364 else
366 pRetval = &rCandidate;
371 return pRetval;
374 void ScreenshotAnnotationDlg_Impl::PaintScreenShotEntry(
375 const weld::ScreenShotEntry& rEntry,
376 const Color& rColor,
377 double fLineWidth,
378 double fTransparency)
380 if (!(mxPicture && mxVirtualBufferDevice))
381 return;
383 basegfx::B2DRange aB2DRange(rEntry.getB2IRange());
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));
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(
396 basegfx::utils::createPolygonFromRect(
397 aB2DRange,
398 fEdgeRoundPixel / aB2DRange.getWidth(),
399 fEdgeRoundPixel / aB2DRange.getHeight()));
401 mxVirtualBufferDevice->SetLineColor(rColor);
403 // try to use transparency
404 if (!mxVirtualBufferDevice->DrawPolyLineDirect(
405 basegfx::B2DHomMatrix(),
406 aPolygon,
407 fLineWidth,
408 fTransparency,
409 nullptr, // MM01
410 basegfx::B2DLineJoin::Round))
412 // no transparency, draw without
413 mxVirtualBufferDevice->DrawPolyLine(
414 aPolygon,
415 fLineWidth);
419 Point ScreenshotAnnotationDlg_Impl::GetOffsetInPicture() const
421 const Size aPixelSizeTarget(maPicture.GetOutputSizePixel());
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);
428 void ScreenshotAnnotationDlg_Impl::RepaintToBuffer(
429 bool bUseDimmed,
430 bool bPaintHilight)
432 if (!mxVirtualBufferDevice)
433 return;
435 // reset with original screenshot bitmap
436 mxVirtualBufferDevice->DrawBitmapEx(
437 Point(0, 0),
438 bUseDimmed ? maDimmedDialogBitmap : maParentDialogBitmap);
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());
446 if (bIsAntiAliasing)
448 mxVirtualBufferDevice->SetAntialiasing(AntialiasingFlags::Enable);
451 // paint selected entries
452 for (auto&& rCandidate : maSelected)
454 static const double fLineWidthEntries(5.0);
455 PaintScreenShotEntry(*rCandidate, COL_LIGHTRED, fLineWidthEntries, fTransparence * 0.2);
458 // paint highlighted entry
459 if (mpHilighted && bPaintHilight)
461 static const double fLineWidthHilight(7.0);
462 PaintScreenShotEntry(*mpHilighted, aHilightColor, fLineWidthHilight, fTransparence);
465 if (bIsAntiAliasing)
467 mxVirtualBufferDevice->SetAntialiasing(nOldAA);
471 void ScreenshotAnnotationDlg_Impl::RepaintPictureElement()
473 if (mxPicture && mxVirtualBufferDevice)
475 // reset image in buffer, use dimmed version and allow highlight
476 RepaintToBuffer(true, true);
477 mxPicture->queue_draw();
481 void ScreenshotAnnotationDlg_Impl::Paint(vcl::RenderContext& rRenderContext)
483 Point aPos(GetOffsetInPicture());
484 Size aSize(mxVirtualBufferDevice->GetOutputSizePixel());
485 rRenderContext.DrawOutDev(aPos, aSize, Point(), aSize, *mxVirtualBufferDevice);
488 void Picture::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
490 m_pDialog->Paint(rRenderContext);
493 bool ScreenshotAnnotationDlg_Impl::MouseMove(const MouseEvent& rMouseEvent)
495 bool bRepaint(false);
497 if (maPicture.IsMouseOver())
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);
506 if (pHit && pOldHit != pHit)
508 mpHilighted = const_cast<weld::ScreenShotEntry*>(pHit);
509 bRepaint = true;
512 else if (mpHilighted)
514 mpHilighted = nullptr;
515 bRepaint = true;
518 if (bRepaint)
520 RepaintPictureElement();
523 return true;
526 bool Picture::MouseMove(const MouseEvent& rMouseEvent)
528 if (rMouseEvent.IsEnterWindow())
529 m_bMouseOver = true;
530 if (rMouseEvent.IsLeaveWindow())
531 m_bMouseOver = false;
532 return m_pDialog->MouseMove(rMouseEvent);
535 bool ScreenshotAnnotationDlg_Impl::MouseButtonUp()
537 // event in picture frame
538 bool bRepaint(false);
540 if (maPicture.IsMouseOver() && mpHilighted)
542 if (maSelected.erase(mpHilighted) == 0)
544 maSelected.insert(mpHilighted);
547 OUStringBuffer aBookmarks(maMainMarkupText);
548 for (auto&& rCandidate : maSelected)
549 aBookmarks.append(lcl_Bookmark(rCandidate->GetHelpId()));
551 mxText->set_text( aBookmarks.makeStringAndClear() );
552 bRepaint = true;
555 if (bRepaint)
557 RepaintPictureElement();
560 return true;
563 bool Picture::MouseButtonUp(const MouseEvent&)
565 return m_pDialog->MouseButtonUp();
568 ScreenshotAnnotationDlg::ScreenshotAnnotationDlg(weld::Dialog& rParentDialog)
569 : GenericDialogController(&rParentDialog, u"cui/ui/screenshotannotationdialog.ui"_ustr, u"ScreenshotAnnotationDialog"_ustr)
571 m_pImpl.reset(new ScreenshotAnnotationDlg_Impl(m_xDialog.get(), *m_xBuilder, rParentDialog));
574 ScreenshotAnnotationDlg::~ScreenshotAnnotationDlg()
578 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */