Quelle screenshotannotationdlg.cxx
Sprache: C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project .
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2 . 0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice :
*
* Licensed to the Apache Software Foundation ( ASF ) under one or more
* contributor license agreements . See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership . The ASF licenses this file to you under the Apache
* License , Version 2 . 0 ( the " License " ) ; you may not use this file
* except in compliance with the License . You may obtain a copy of
* the License at http : //www.apache.org/licenses/LICENSE-2.0 .
*/
#include <screenshotannotationdlg.hxx>
#include <strings.hrc>
#include <dialmgr.hxx>
#include <basegfx/range/b2irange.hxx>
#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
#include <comphelper/random.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <sfx2/filedlghelper.hxx>
#include <tools/stream.hxx>
#include <tools/urlobj.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/customweld.hxx>
#include <vcl/event.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#include <vcl/svapp.hxx>
#include <vcl/salgtype.hxx>
#include <vcl/virdev.hxx>
#include <vcl/weld.hxx>
#include <svtools/optionsdrawinglayer.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <set>
#include <string_view>
using namespace com::sun::star;
namespace
{
OUString lcl_genRandom( std::u16string_view rId )
{
//FIXME: plus timestamp
unsigned int nRand = comphelper::rng::uniform_uint_distribution(0 , 0 xFFFF);
return OUString( rId + OUString::number( nRand ) );
}
OUString lcl_AltDescr()
{
OUString aTempl(u"<alt id=\" %1 \">"
" " //FIXME real dialog title or something
"</alt>" _ustr);
aTempl = aTempl.replaceFirst( "%1" , lcl_genRandom(u"alt_id" ) );
return aTempl;
}
OUString lcl_Image( std::u16string_view rScreenshotId, const Size& rSize )
{
OUString aTempl(u"<image id=\" %1 \" src=\" media/screenshots/%2 .png\""
" width=\" %3 cm\" height=\" %4 cm\">"
"%5"
"</image>" _ustr);
aTempl = aTempl.replaceFirst( "%1" , lcl_genRandom(u"img_id" ) );
aTempl = aTempl.replaceFirst( "%2" , rScreenshotId );
aTempl = aTempl.replaceFirst( "%3" , OUString::number( rSize.Width() ) );
aTempl = aTempl.replaceFirst( "%4" , OUString::number( rSize.Height() ) );
aTempl = aTempl.replaceFirst( "%5" , lcl_AltDescr() );
return aTempl;
}
OUString lcl_ParagraphWithImage( std::u16string_view rScreenshotId, const Size& rSize )
{
OUString aTempl( u"<paragraph id=\" %1 \" role=\" paragraph\">%2"
"</paragraph>" SAL_NEWLINE_STRING "" _ustr );
aTempl = aTempl.replaceFirst( "%1" , lcl_genRandom(u"par_id" ) );
aTempl = aTempl.replaceFirst( "%2" , lcl_Image(rScreenshotId, rSize) );
return aTempl;
}
OUString lcl_Bookmark( std::u16string_view rWidgetId )
{
OUString aTempl = u"<!-- Bookmark for widget %1 -->" SAL_NEWLINE_STRING
"<bookmark branch=\" hid/%2 \" id=\" %3 \" localize=\" false \"/>" SAL_NEWLINE_STRING "" _ustr;
aTempl = aTempl.replaceFirst( "%1" , rWidgetId );
aTempl = aTempl.replaceFirst( "%2" , rWidgetId );
aTempl = aTempl.replaceFirst( "%3" , lcl_genRandom(u"bm_id" ) );
return aTempl;
}
}
namespace
{
class Picture : public weld::CustomWidgetController
{
private :
ScreenshotAnnotationDlg_Impl *m_pDialog;
bool m_bMouseOver;
private :
virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override;
virtual bool MouseMove(const MouseEvent& rMouseEvent) override;
virtual bool MouseButtonUp(const MouseEvent& rMouseEvent) override;
public :
Picture(ScreenshotAnnotationDlg_Impl* pDialog)
: m_pDialog(pDialog)
, m_bMouseOver(false )
{
}
bool IsMouseOver() const
{
return m_bMouseOver;
}
};
}
class ScreenshotAnnotationDlg_Impl
{
public :
ScreenshotAnnotationDlg_Impl(
weld::Window* pParent,
weld::Builder& rParent,
weld::Dialog& rParentDialog);
~ScreenshotAnnotationDlg_Impl();
private :
// Handler for click on save
DECL_LINK(saveButtonHandler, weld::Button&, void );
// helper methods
weld::ScreenShotEntry* CheckHit(const basegfx::B2IPoint& rPosition);
void PaintScreenShotEntry(
const weld::ScreenShotEntry& rEntry,
const Color& rColor,
double fLineWidth,
double fTransparency);
void RepaintToBuffer(
bool bUseDimmed = false ,
bool bPaintHilight = false );
void RepaintPictureElement();
Point GetOffsetInPicture() const ;
// local variables
weld::Window* mpParentWindow;
weld::Dialog& mrParentDialog;
BitmapEx maParentDialogBitmap;
BitmapEx maDimmedDialogBitmap;
Size maParentDialogSize;
// VirtualDevice for buffered interaction paints
VclPtr<VirtualDevice> mxVirtualBufferDevice;
// all detected children
weld::ScreenShotCollection maAllChildren;
// highlighted/selected children
weld::ScreenShotEntry* mpHilighted;
std::set< weld::ScreenShotEntry* >
maSelected;
// list of detected controls
Picture maPicture;
std::unique_ptr<weld::CustomWeld> mxPicture;
std::unique_ptr<weld::TextView> mxText;
std::unique_ptr<weld::Button> mxSave;
// save as text
OUString maSaveAsText;
OUString maMainMarkupText;
// folder URL
static OUString maLastFolderURL;
public :
void Paint(vcl::RenderContext& rRenderContext);
bool MouseMove(const MouseEvent& rMouseEvent);
bool MouseButtonUp();
};
OUString ScreenshotAnnotationDlg_Impl::maLastFolderURL = OUString();
ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl(
weld::Window* pParent,
weld::Builder& rParentBuilder,
weld::Dialog& rParentDialog)
: mpParentWindow(pParent),
mrParentDialog(rParentDialog),
mxVirtualBufferDevice(nullptr),
mpHilighted(nullptr),
maPicture(this ),
maSaveAsText(CuiResId(RID_CUISTR_SAVE_SCREENSHOT_AS))
{
VclPtr<VirtualDevice> xParentDialogSurface(rParentDialog.screenshot());
maParentDialogSize = xParentDialogSurface->GetOutputSizePixel();
maParentDialogBitmap = xParentDialogSurface->GetBitmapEx(Point(), maParentDialogSize);
maDimmedDialogBitmap = maParentDialogBitmap;
// image ain't empty
assert(!maParentDialogBitmap.IsEmpty());
assert(0 != maParentDialogBitmap.GetSizePixel().Width());
assert(0 != maParentDialogBitmap.GetSizePixel().Height());
// get needed widgets
mxPicture.reset(new weld::CustomWeld(rParentBuilder, u"picture" _ustr, maPicture));
assert(mxPicture);
mxText = rParentBuilder.weld_text_view(u"text" _ustr);
assert(mxText);
mxSave = rParentBuilder.weld_button(u"save" _ustr);
assert(mxSave);
// set screenshot image at DrawingArea, resize, set event listener
if (mxPicture)
{
maAllChildren = mrParentDialog.collect_screenshot_data();
// to make clear that maParentDialogBitmap is a background image, adjust
// luminance a bit for maDimmedDialogBitmap - other methods may be applied
maDimmedDialogBitmap.Adjust(-15 , 0 , 0 , 0 , 0 );
// init paint buffering VirtualDevice
mxVirtualBufferDevice = VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA);
mxVirtualBufferDevice->SetOutputSizePixel(maParentDialogSize);
mxVirtualBufferDevice->SetFillColor(COL_TRANSPARENT);
// initially set image for picture control
mxVirtualBufferDevice->DrawBitmapEx(Point(0 , 0 ), maDimmedDialogBitmap);
// set size for picture control, this will re-layout so that
// the picture control shows the whole dialog
maPicture.SetOutputSizePixel(maParentDialogSize);
mxPicture->set_size_request(maParentDialogSize.Width(), maParentDialogSize.Height());
mxPicture->queue_draw();
}
// set some test text at VclMultiLineEdit and make read-only - only
// copying content to clipboard is allowed
if (mxText)
{
mxText->set_size_request(400 , mxText->get_height_rows(10 ));
OUString aHelpId = mrParentDialog.get_help_id();
Size aSizeCm = Application::GetDefaultDevice()->PixelToLogic(maParentDialogSize, MapMode(MapUnit::MapCM));
maMainMarkupText = lcl_ParagraphWithImage( aHelpId, aSizeCm );
mxText->set_text( maMainMarkupText );
mxText->set_editable(false );
}
// set click handler for save button
if (mxSave)
{
mxSave->connect_clicked(LINK(this , ScreenshotAnnotationDlg_Impl, saveButtonHandler));
}
}
ScreenshotAnnotationDlg_Impl::~ScreenshotAnnotationDlg_Impl()
{
mxVirtualBufferDevice.disposeAndClear();
}
IMPL_LINK_NOARG(ScreenshotAnnotationDlg_Impl, saveButtonHandler, weld::Button&, void )
{
// 'save screenshot...' pressed, offer to save maParentDialogBitmap
// as PNG image, use *.id file name as screenshot file name offering
// get a suggestion for the filename from buildable name
OUString aDerivedFileName = mrParentDialog.get_buildable_name();
sfx2::FileDialogHelper aFileDlg(ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION,
FileDialogFlags::NONE, mpParentWindow);
aFileDlg.SetContext(sfx2::FileDialogHelper::ScreenshotAnnotation);
const uno::Reference< ui::dialogs::XFilePicker3 > xFilePicker = aFileDlg.GetFilePicker();
xFilePicker->setTitle(maSaveAsText);
if (!maLastFolderURL.isEmpty())
{
xFilePicker->setDisplayDirectory(maLastFolderURL);
}
xFilePicker->appendFilter(u"*.png" _ustr, u"*.png" _ustr);
xFilePicker->setCurrentFilter(u"*.png" _ustr);
xFilePicker->setDefaultName(aDerivedFileName);
xFilePicker->setMultiSelectionMode(false );
if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK)
return ;
maLastFolderURL = xFilePicker->getDisplayDirectory();
const uno::Sequence< OUString > files(xFilePicker->getSelectedFiles());
if (!files.hasElements())
return ;
const OUString& aConfirmedName = files[0 ];
if (aConfirmedName.isEmpty())
return ;
INetURLObject aConfirmedURL(aConfirmedName);
OUString aCurrentExtension(aConfirmedURL.getExtension());
if (!aCurrentExtension.isEmpty() && aCurrentExtension != "png" )
{
aConfirmedURL.removeExtension();
aCurrentExtension.clear();
}
if (aCurrentExtension.isEmpty())
{
aConfirmedURL.setExtension(u"png" );
}
// open stream
SvFileStream aNew(aConfirmedURL.PathToFileName(), StreamMode::WRITE | StreamMode::TRUNC);
if (!aNew.IsOpen())
return ;
// prepare bitmap to save - do use the original screenshot here,
// not the dimmed one
RepaintToBuffer();
// extract Bitmap
const BitmapEx aTargetBitmap(
mxVirtualBufferDevice->GetBitmapEx(
Point(0 , 0 ),
mxVirtualBufferDevice->GetOutputSizePixel()));
// write as PNG
vcl::PngImageWriter aPNGWriter(aNew);
aPNGWriter.write(aTargetBitmap);
}
weld::ScreenShotEntry* ScreenshotAnnotationDlg_Impl::CheckHit(const basegfx::B2IPoint& rPosition)
{
weld::ScreenShotEntry* pRetval = nullptr;
for (auto && rCandidate : maAllChildren)
{
if (rCandidate.getB2IRange().isInside(rPosition))
{
if (pRetval)
{
if (pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMinimum())
&& pRetval->getB2IRange().isInside(rCandidate.getB2IRange().getMaximum()))
{
pRetval = &rCandidate;
}
}
else
{
pRetval = &rCandidate;
}
}
}
return pRetval;
}
void ScreenshotAnnotationDlg_Impl::PaintScreenShotEntry(
const weld::ScreenShotEntry& rEntry,
const Color& rColor,
double fLineWidth,
double fTransparency)
{
if (!(mxPicture && mxVirtualBufferDevice))
return ;
basegfx::B2DRange aB2DRange(rEntry.getB2IRange());
// grow in pixels to be a little bit 'outside'. This also
// ensures that getWidth()/getHeight() ain't 0.0 (see division below)
static const double fGrowTopLeft(1 .5 );
static const double fGrowBottomRight(0 .5 );
aB2DRange.expand(aB2DRange.getMinimum() - basegfx::B2DPoint(fGrowTopLeft, fGrowTopLeft));
aB2DRange.expand(aB2DRange.getMaximum() + basegfx::B2DPoint(fGrowBottomRight, fGrowBottomRight));
// edge rounding in pixel. Need to convert, value for
// createPolygonFromRect is relative [0.0 .. 1.0]
static const double fEdgeRoundPixel(8 .0 );
const basegfx::B2DPolygon aPolygon(
basegfx::utils::createPolygonFromRect(
aB2DRange,
fEdgeRoundPixel / aB2DRange.getWidth(),
fEdgeRoundPixel / aB2DRange.getHeight()));
mxVirtualBufferDevice->SetLineColor(rColor);
// try to use transparency
if (!mxVirtualBufferDevice->DrawPolyLineDirect(
basegfx::B2DHomMatrix(),
aPolygon,
fLineWidth,
fTransparency,
nullptr, // MM01
basegfx::B2DLineJoin::Round))
{
// no transparency, draw without
mxVirtualBufferDevice->DrawPolyLine(
aPolygon,
fLineWidth);
}
}
Point ScreenshotAnnotationDlg_Impl::GetOffsetInPicture() const
{
const Size aPixelSizeTarget(maPicture.GetOutputSizePixel());
return Point(
aPixelSizeTarget.Width() > maParentDialogSize.Width() ? (aPixelSizeTarget.Width() - maParentDialogSize.Width()) >> 1 : 0 ,
aPixelSizeTarget.Height() > maParentDialogSize.Height() ? (aPixelSizeTarget.Height() - maParentDialogSize.Height()) >> 1 : 0 );
}
void ScreenshotAnnotationDlg_Impl::RepaintToBuffer(
bool bUseDimmed,
bool bPaintHilight)
{
if (!mxVirtualBufferDevice)
return ;
// reset with original screenshot bitmap
mxVirtualBufferDevice->DrawBitmapEx(
Point(0 , 0 ),
bUseDimmed ? maDimmedDialogBitmap : maParentDialogBitmap);
// get various options
const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor());
const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0 .01 );
const bool bIsAntiAliasing(SvtOptionsDrawinglayer::IsAntiAliasing());
const AntialiasingFlags nOldAA(mxVirtualBufferDevice->GetAntialiasing());
if (bIsAntiAliasing)
{
mxVirtualBufferDevice->SetAntialiasing(AntialiasingFlags::Enable);
}
// paint selected entries
for (auto && rCandidate : maSelected)
{
static const double fLineWidthEntries(5 .0 );
PaintScreenShotEntry(*rCandidate, COL_LIGHTRED, fLineWidthEntries, fTransparence * 0 .2 );
}
// paint highlighted entry
if (mpHilighted && bPaintHilight)
{
static const double fLineWidthHilight(7 .0 );
PaintScreenShotEntry(*mpHilighted, aHilightColor, fLineWidthHilight, fTransparence);
}
if (bIsAntiAliasing)
{
mxVirtualBufferDevice->SetAntialiasing(nOldAA);
}
}
void ScreenshotAnnotationDlg_Impl::RepaintPictureElement()
{
if (mxPicture && mxVirtualBufferDevice)
{
// reset image in buffer, use dimmed version and allow highlight
RepaintToBuffer(true , true );
mxPicture->queue_draw();
}
}
void ScreenshotAnnotationDlg_Impl::Paint(vcl::RenderContext& rRenderContext)
{
Point aPos(GetOffsetInPicture());
Size aSize(mxVirtualBufferDevice->GetOutputSizePixel());
rRenderContext.DrawOutDev(aPos, aSize, Point(), aSize, *mxVirtualBufferDevice);
}
void Picture::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
m_pDialog->Paint(rRenderContext);
}
bool ScreenshotAnnotationDlg_Impl::MouseMove(const MouseEvent& rMouseEvent)
{
bool bRepaint(false );
if (maPicture.IsMouseOver())
{
const weld::ScreenShotEntry* pOldHit = mpHilighted;
const Point aOffset(GetOffsetInPicture());
const basegfx::B2IPoint aMousePos(
rMouseEvent.GetPosPixel().X() - aOffset.X(),
rMouseEvent.GetPosPixel().Y() - aOffset.Y());
const weld::ScreenShotEntry* pHit = CheckHit(aMousePos);
if (pHit && pOldHit != pHit)
{
mpHilighted = const_cast <weld::ScreenShotEntry*>(pHit);
bRepaint = true ;
}
}
else if (mpHilighted)
{
mpHilighted = nullptr;
bRepaint = true ;
}
if (bRepaint)
{
RepaintPictureElement();
}
return true ;
}
bool Picture::MouseMove(const MouseEvent& rMouseEvent)
{
if (rMouseEvent.IsEnterWindow())
m_bMouseOver = true ;
if (rMouseEvent.IsLeaveWindow())
m_bMouseOver = false ;
return m_pDialog->MouseMove(rMouseEvent);
}
bool ScreenshotAnnotationDlg_Impl::MouseButtonUp()
{
// event in picture frame
bool bRepaint(false );
if (maPicture.IsMouseOver() && mpHilighted)
{
if (maSelected.erase(mpHilighted) == 0 )
{
maSelected.insert(mpHilighted);
}
OUStringBuffer aBookmarks(maMainMarkupText);
for (auto && rCandidate : maSelected)
aBookmarks.append(lcl_Bookmark(rCandidate->GetHelpId()));
mxText->set_text( aBookmarks.makeStringAndClear() );
bRepaint = true ;
}
if (bRepaint)
{
RepaintPictureElement();
}
return true ;
}
bool Picture::MouseButtonUp(const MouseEvent&)
{
return m_pDialog->MouseButtonUp();
}
ScreenshotAnnotationDlg::ScreenshotAnnotationDlg(weld::Dialog& rParentDialog)
: GenericDialogController(&rParentDialog, u"cui/ui/screenshotannotationdialog.ui" _ustr, u"ScreenshotAnnotationDialog" _ustr)
{
m_pImpl.reset(new ScreenshotAnnotationDlg_Impl(m_xDialog.get(), *m_xBuilder, rParentDialog));
}
ScreenshotAnnotationDlg::~ScreenshotAnnotationDlg()
{
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Messung V0.5 in Prozent C=90 H=97 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691