Files
libreoffice/cui/source/dialogs/screenshotannotationdlg.cxx
Olivier Hallot b9d0d608b0 Fix screenshot missing quotes in code snippet
The code snippet suggested has no quotes in atttributes values.

Change-Id: I604fb34827da3bebf3f6759b491d719a96854754
Reviewed-on: https://gerrit.libreoffice.org/38893
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Katarina Behrens <Katarina.Behrens@cib.de>
2017-06-19 15:24:15 +02:00

652 lines
21 KiB
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 "cuires.hrc"
#include "dialmgr.hxx"
#include <basegfx/range/b2irange.hxx>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/ui/dialogs/FilePicker.hpp>
#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
#include <comphelper/random.hxx>
#include <vcl/pngwrite.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <tools/urlobj.hxx>
#include <vcl/fixed.hxx>
#include <vcl/svapp.hxx>
#include <vcl/salgtype.hxx>
#include <vcl/virdev.hxx>
#include <vcl/vclmedit.hxx>
#include <vcl/button.hxx>
#include <svtools/optionsdrawinglayer.hxx>
using namespace com::sun::star;
namespace
{
OUString lcl_genRandom( const OUString &rId )
{
//FIXME: plus timestamp
unsigned int nRand = comphelper::rng::uniform_uint_distribution(0, 0xFFFF);
return OUString( rId + OUString::number( nRand ) );
}
OUString lcl_AltDescr()
{
OUString aTempl = OUString("<alt xml-lang=\"en-US\" id=\"%1\">"
" " //FIXME real dialog title or something
"</alt>");
aTempl = aTempl.replaceFirst( "%1", lcl_genRandom("alt_id") );
return aTempl;
}
OUString lcl_Image( const OUString& rScreenshotId, const Size& rSize )
{
OUString aTempl = OUString("<image id=\"%1\" src=\"media/screenshots/%2.png\""
" width=\"%3cm\" height=\"%4cm\">"
"%5"
"</image>");
aTempl = aTempl.replaceFirst( "%1", lcl_genRandom("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( const OUString& rScreenshotId, const Size& rSize )
{
OUString aTempl = OUString( "<paragraph id=\"%1\" role=\"paragraph\" xml-lang=\"en-US\">%2"
"</paragraph>" SAL_NEWLINE_STRING );
aTempl = aTempl.replaceFirst( "%1", lcl_genRandom("par_id") );
aTempl = aTempl.replaceFirst( "%2", lcl_Image(rScreenshotId, rSize) );
return aTempl;
}
OUString lcl_Bookmark( const OUString& rWidgetId )
{
OUString aTempl = "<!-- Bookmark for widget %1 -->" SAL_NEWLINE_STRING
"<bookmark xml-lang=\"en-US\" branch=\"hid/%2\" id=\"%3\" localize=\"false\"/>" SAL_NEWLINE_STRING;
aTempl = aTempl.replaceFirst( "%1", rWidgetId );
aTempl = aTempl.replaceFirst( "%2", rWidgetId );
aTempl = aTempl.replaceFirst( "%3", lcl_genRandom("bm_id") );
return aTempl;
}
}
class ControlDataEntry
{
public:
ControlDataEntry(
const vcl::Window& rControl,
const basegfx::B2IRange& rB2IRange)
: mrControl(rControl),
maB2IRange(rB2IRange)
{
}
const basegfx::B2IRange& getB2IRange() const
{
return maB2IRange;
}
const OString GetHelpId() const { return mrControl.GetHelpId(); }
private:
const vcl::Window& mrControl;
basegfx::B2IRange maB2IRange;
};
typedef std::vector< ControlDataEntry > ControlDataCollection;
typedef std::set< ControlDataEntry* > ControlDataSet;
class ScreenshotAnnotationDlg_Impl // : public ModalDialog
{
public:
ScreenshotAnnotationDlg_Impl(
ScreenshotAnnotationDlg& rParent,
Dialog& rParentDialog);
~ScreenshotAnnotationDlg_Impl();
private:
// Handler for click on save
DECL_LINK(saveButtonHandler, Button*, void);
// Handler for clicks on picture frame
DECL_LINK(pictureFrameListener, VclWindowEvent&, void);
// helper methods
void CollectChildren(
const vcl::Window& rCurrent,
const basegfx::B2IPoint& rTopLeft,
ControlDataCollection& rControlDataCollection);
ControlDataEntry* CheckHit(const basegfx::B2IPoint& rPosition);
void PaintControlDataEntry(
const ControlDataEntry& rEntry,
const Color& rColor,
double fLineWidth,
double fTransparency = 0.0);
void RepaintToBuffer(
bool bUseDimmed = false,
bool bPaintHilight = false);
void RepaintPictureElement();
Point GetOffsetInPicture() const;
// local variables
ScreenshotAnnotationDlg& mrParent;
Dialog& mrParentDialog;
Bitmap maParentDialogBitmap;
Bitmap maDimmedDialogBitmap;
Size maParentDialogSize;
// VirtualDevice for buffered interation paints
VclPtr<VirtualDevice> mpVirtualBufferDevice;
// all detected children
ControlDataCollection maAllChildren;
// hilighted/selected children
ControlDataEntry* mpHilighted;
ControlDataSet maSelected;
// list of detected controls
VclPtr<FixedImage> mpPicture;
VclPtr<VclMultiLineEdit> mpText;
VclPtr<PushButton> mpSave;
// save as text
OUString maSaveAsText;
OUString maMainMarkupText;
// folder URL
static OUString maLastFolderURL;
};
OUString ScreenshotAnnotationDlg_Impl::maLastFolderURL = OUString();
ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl(
ScreenshotAnnotationDlg& rParent,
Dialog& rParentDialog)
: mrParent(rParent),
mrParentDialog(rParentDialog),
maParentDialogBitmap(rParentDialog.createScreenshot()),
maDimmedDialogBitmap(maParentDialogBitmap),
maParentDialogSize(maParentDialogBitmap.GetSizePixel()),
mpVirtualBufferDevice(nullptr),
maAllChildren(),
mpHilighted(nullptr),
maSelected(),
mpPicture(nullptr),
mpText(nullptr),
mpSave(nullptr),
maSaveAsText(CuiResId(RID_SVXSTR_SAVE_SCREENSHOT_AS))
{
// image ain't empty
assert(!maParentDialogBitmap.IsEmpty());
assert(0 != maParentDialogBitmap.GetSizePixel().Width());
assert(0 != maParentDialogBitmap.GetSizePixel().Height());
// get needed widgets
mrParent.get(mpPicture, "picture");
assert(mpPicture.get());
mrParent.get(mpText, "text");
assert(mpText.get());
mrParent.get(mpSave, "save");
assert(mpSave.get());
// set screenshot image at FixedImage, resize, set event listener
if (mpPicture)
{
// collect all children. Choose start pos to be negative
// of target dialog's position to get all positions relative to (0,0)
const Point aParentPos(mrParentDialog.GetPosPixel());
const basegfx::B2IPoint aTopLeft(-aParentPos.X(), -aParentPos.Y());
CollectChildren(
mrParentDialog,
aTopLeft,
maAllChildren);
// to make clear that maParentDialogBitmap is a background image, adjust
// luminance a bit for maDimmedDialogBitmap - other methods may be applied
maDimmedDialogBitmap.Adjust(-15);
// init paint buffering VirtualDevice
mpVirtualBufferDevice = VclPtr<VirtualDevice>::Create(*Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::BITMASK);
mpVirtualBufferDevice->SetOutputSizePixel(maParentDialogSize);
mpVirtualBufferDevice->SetFillColor(COL_TRANSPARENT);
// initially set image for picture control
mpPicture->SetImage(Image(maDimmedDialogBitmap));
// set size for picture control, this will re-layout so that
// the picture control shows the whole dialog
mpPicture->set_width_request(maParentDialogSize.Width());
mpPicture->set_height_request(maParentDialogSize.Height());
// add local event listener to allow interactions with mouse
mpPicture->AddEventListener(LINK(this, ScreenshotAnnotationDlg_Impl, pictureFrameListener));
// avoid image scaling, this is needed for images smaller than the
// minimal dialog size
const WinBits aWinBits(mpPicture->GetStyle());
mpPicture->SetStyle(aWinBits & ~WB_SCALE);
}
// set some test text at VclMultiLineEdit and make read-only - only
// copying content to clipboard is allowed
if (mpText)
{
mpText->set_width_request(400);
mpText->set_height_request( mpText->GetTextHeight() * 10 );
OUString aHelpId = OStringToOUString( mrParentDialog.GetHelpId(), RTL_TEXTENCODING_UTF8 );
Size aSizeCm = mrParentDialog.PixelToLogic( maParentDialogSize, MapUnit::MapCM );
maMainMarkupText = lcl_ParagraphWithImage( aHelpId, aSizeCm );
mpText->SetText( maMainMarkupText );
mpText->SetReadOnly();
}
// set click handler for save button
if (mpSave)
{
mpSave->SetClickHdl(LINK(this, ScreenshotAnnotationDlg_Impl, saveButtonHandler));
}
}
void ScreenshotAnnotationDlg_Impl::CollectChildren(
const vcl::Window& rCurrent,
const basegfx::B2IPoint& rTopLeft,
ControlDataCollection& rControlDataCollection)
{
if (rCurrent.IsVisible())
{
const Point aCurrentPos(rCurrent.GetPosPixel());
const Size aCurrentSize(rCurrent.GetSizePixel());
const basegfx::B2IPoint aCurrentTopLeft(rTopLeft.getX() + aCurrentPos.X(), rTopLeft.getY() + aCurrentPos.Y());
const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(aCurrentSize.Width(), aCurrentSize.Height()));
if (!aCurrentRange.isEmpty())
{
rControlDataCollection.push_back(ControlDataEntry(rCurrent, aCurrentRange));
}
for (sal_uInt16 a(0); a < rCurrent.GetChildCount(); a++)
{
vcl::Window* pChild = rCurrent.GetChild(a);
if (nullptr != pChild)
{
CollectChildren(*pChild, aCurrentTopLeft, rControlDataCollection);
}
}
}
}
ScreenshotAnnotationDlg_Impl::~ScreenshotAnnotationDlg_Impl()
{
mpVirtualBufferDevice.disposeAndClear();
}
IMPL_LINK(ScreenshotAnnotationDlg_Impl, saveButtonHandler, Button*, pButton, void)
{
(void)pButton;
// 'save screenshot...' pressed, offer to save maParentDialogBitmap
// as PNG image, use *.id file name as screenshot file name offering
OString aDerivedFileName;
// get a suggestion for the filename from ui file name
{
const OString& rUIFileName = mrParentDialog.getUIFile();
sal_Int32 nIndex(0);
do
{
const OString aToken(rUIFileName.getToken(0, '/', nIndex));
if (!aToken.isEmpty())
{
aDerivedFileName = aToken;
}
} while (nIndex >= 0);
}
uno::Reference< uno::XComponentContext > xContext = cppu::defaultBootstrap_InitialComponentContext();
const uno::Reference< ui::dialogs::XFilePicker3 > xFilePicker =
ui::dialogs::FilePicker::createWithMode(xContext, ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION);
xFilePicker->setTitle(maSaveAsText);
if (!maLastFolderURL.isEmpty())
{
xFilePicker->setDisplayDirectory(maLastFolderURL);
}
xFilePicker->appendFilter("*.png", "*.png");
xFilePicker->setCurrentFilter("*.png");
xFilePicker->setDefaultName(OStringToOUString(aDerivedFileName, RTL_TEXTENCODING_UTF8));
xFilePicker->setMultiSelectionMode(false);
if (xFilePicker->execute() == ui::dialogs::ExecutableDialogResults::OK)
{
maLastFolderURL = xFilePicker->getDisplayDirectory();
const uno::Sequence< OUString > files(xFilePicker->getSelectedFiles());
if (files.getLength())
{
OUString aConfirmedName = files[0];
if (!aConfirmedName.isEmpty())
{
INetURLObject aConfirmedURL(aConfirmedName);
OUString aCurrentExtension(aConfirmedURL.getExtension());
if (!aCurrentExtension.isEmpty() && 0 != aCurrentExtension.compareTo("png"))
{
aConfirmedURL.removeExtension();
aCurrentExtension.clear();
}
if (aCurrentExtension.isEmpty())
{
aConfirmedURL.setExtension("png");
}
// open stream
SvFileStream aNew(aConfirmedURL.PathToFileName(), StreamMode::WRITE | StreamMode::TRUNC);
if (aNew.IsOpen())
{
// prepare bitmap to save - do use the original screenshot here,
// not the dimmed one
RepaintToBuffer();
// extract Bitmap
const Bitmap aTargetBitmap(
mpVirtualBufferDevice->GetBitmap(
Point(0, 0),
mpVirtualBufferDevice->GetOutputSizePixel()));
// write as PNG
vcl::PNGWriter aPNGWriter(aTargetBitmap);
aPNGWriter.Write(aNew);
}
}
}
}
}
ControlDataEntry* ScreenshotAnnotationDlg_Impl::CheckHit(const basegfx::B2IPoint& rPosition)
{
ControlDataEntry* 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::PaintControlDataEntry(
const ControlDataEntry& rEntry,
const Color& rColor,
double fLineWidth,
double fTransparency)
{
if (mpPicture && mpVirtualBufferDevice)
{
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 double fGrowTopLeft(1.5);
static 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 double fEdgeRoundPixel(8.0);
const basegfx::B2DPolygon aPolygon(
basegfx::tools::createPolygonFromRect(
aB2DRange,
fEdgeRoundPixel / aB2DRange.getWidth(),
fEdgeRoundPixel / aB2DRange.getHeight()));
mpVirtualBufferDevice->SetLineColor(rColor);
// try to use transparency
if (!mpVirtualBufferDevice->DrawPolyLineDirect(
aPolygon,
fLineWidth,
fTransparency,
basegfx::B2DLineJoin::Round))
{
// no transparency, draw without
mpVirtualBufferDevice->DrawPolyLine(
aPolygon,
fLineWidth);
}
}
}
Point ScreenshotAnnotationDlg_Impl::GetOffsetInPicture() const
{
if (!mpPicture)
{
return Point(0, 0);
}
const Size aPixelSizeTarget(mpPicture->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 (mpVirtualBufferDevice)
{
// reset with original screenshot bitmap
mpVirtualBufferDevice->DrawBitmap(
Point(0, 0),
bUseDimmed ? maDimmedDialogBitmap : maParentDialogBitmap);
// get various options
const SvtOptionsDrawinglayer aSvtOptionsDrawinglayer;
const Color aHilightColor(aSvtOptionsDrawinglayer.getHilightColor());
const double fTransparence(aSvtOptionsDrawinglayer.GetTransparentSelectionPercent() * 0.01);
const bool bIsAntiAliasing(aSvtOptionsDrawinglayer.IsAntiAliasing());
const AntialiasingFlags nOldAA(mpVirtualBufferDevice->GetAntialiasing());
if (bIsAntiAliasing)
{
mpVirtualBufferDevice->SetAntialiasing(AntialiasingFlags::EnableB2dDraw);
}
// paint selected entries
for (auto&& rCandidate : maSelected)
{
static double fLineWidthEntries(5.0);
PaintControlDataEntry(*rCandidate, Color(COL_LIGHTRED), fLineWidthEntries, fTransparence * 0.2);
}
// paint hilighted entry
if (mpHilighted && bPaintHilight)
{
static double fLineWidthHilight(7.0);
PaintControlDataEntry(*mpHilighted, aHilightColor, fLineWidthHilight, fTransparence);
}
if (bIsAntiAliasing)
{
mpVirtualBufferDevice->SetAntialiasing(nOldAA);
}
}
}
void ScreenshotAnnotationDlg_Impl::RepaintPictureElement()
{
if (mpPicture && mpVirtualBufferDevice)
{
// reset image in buffer, use dimmed version and allow hilight
RepaintToBuffer(true, true);
// copy new content to picture control (hard paint)
mpPicture->DrawOutDev(
GetOffsetInPicture(),
maParentDialogSize,
Point(0, 0),
maParentDialogSize,
*mpVirtualBufferDevice);
// also set image to get repaints right, but trigger no repaint
mpPicture->SetImage(
Image(
mpVirtualBufferDevice->GetBitmap(
Point(0, 0),
mpVirtualBufferDevice->GetOutputSizePixel())));
mpPicture->Validate();
}
}
IMPL_LINK(ScreenshotAnnotationDlg_Impl, pictureFrameListener, VclWindowEvent&, rEvent, void)
{
// event in picture frame
bool bRepaint(false);
switch (rEvent.GetId())
{
case VclEventId::WindowMouseMove:
case VclEventId::WindowMouseButtonUp:
{
MouseEvent* pMouseEvent = static_cast< MouseEvent* >(rEvent.GetData());
if (pMouseEvent)
{
switch (rEvent.GetId())
{
case VclEventId::WindowMouseMove:
{
if (mpPicture->IsMouseOver())
{
const ControlDataEntry* pOldHit = mpHilighted;
const Point aOffset(GetOffsetInPicture());
const basegfx::B2IPoint aMousePos(
pMouseEvent->GetPosPixel().X() - aOffset.X(),
pMouseEvent->GetPosPixel().Y() - aOffset.Y());
const ControlDataEntry* pHit = CheckHit(aMousePos);
if (pHit && pOldHit != pHit)
{
mpHilighted = const_cast< ControlDataEntry* >(pHit);
bRepaint = true;
}
}
else if (mpHilighted)
{
mpHilighted = nullptr;
bRepaint = true;
}
break;
}
case VclEventId::WindowMouseButtonUp:
{
if (mpPicture->IsMouseOver() && mpHilighted)
{
if (maSelected.erase(mpHilighted) == 0)
{
maSelected.insert(mpHilighted);
}
OUString aBookmarks;
for (auto&& rCandidate : maSelected)
{
OUString aHelpId = OStringToOUString( rCandidate->GetHelpId(), RTL_TEXTENCODING_UTF8 );
aBookmarks += lcl_Bookmark( aHelpId );
}
mpText->SetText( maMainMarkupText + aBookmarks );
bRepaint = true;
}
break;
}
default:
{
break;
}
}
}
break;
}
default:
{
break;
}
}
if (bRepaint)
{
RepaintPictureElement();
}
}
ScreenshotAnnotationDlg::ScreenshotAnnotationDlg(
vcl::Window* pParent,
Dialog& rParentDialog)
: SfxModalDialog(pParent, "ScreenshotAnnotationDialog", "cui/ui/screenshotannotationdialog.ui")
{
m_pImpl.reset(new ScreenshotAnnotationDlg_Impl(*this, rParentDialog));
}
ScreenshotAnnotationDlg::~ScreenshotAnnotationDlg()
{
disposeOnce();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */