cui/source/dialogs/screenshotannotationdlg.cxx | 83 ++++++++++++++++++++++++- cui/uiconfig/ui/screenshotannotationdialog.ui | 76 +++++++++++++--------- 2 files changed, 125 insertions(+), 34 deletions(-)
New commits: commit 70c7f6ba49cfc506fc47dac32af98976ecf33cf8 Author: Olivier Hallot <[email protected]> AuthorDate: Fri Sep 19 09:11:51 2025 -0300 Commit: Olivier Hallot <[email protected]> CommitDate: Mon Sep 22 12:57:45 2025 +0200 tdf#166391 Add Copy Image button to screenshot dialog. This patch is the continuation/restoring of https://gerrit.libreoffice.org/c/core/+/184907 Big thank you to Noel Grandin for the help. Noel's suggestions were used to invoke CoPilot LLM to create the BitmapTransferable class under VSCode IDE. Some adjustment on the mime type were necessary. LLM was aware of the Dev Guide at [1]. (lines 205-239) The copyButtonHandler method was manually created and had support from LLM/code completion. (lines 330-353) The patch is open to further suggestions for simplification and code improvement or refactoring. Some lessons learned: LLM cannot guess solutions for wide and open demands. Narrowing the query to code fragments produces acceptable and easily debuggable code. PS1: Added namespace{} to silence clang plugin PS2: Fix clang issue PS3: Fix clang issue PS4: Add M. Kaganski suggestions, fix clang issue [1] https://wiki.documentfoundation.org/Documentation/DevGuide/Office_Development#Copying_Data Change-Id: Ia5c38978e9186f4c1171fafb6a415bf33e81d123 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191201 Tested-by: Jenkins Reviewed-by: Olivier Hallot <[email protected]> diff --git a/cui/source/dialogs/screenshotannotationdlg.cxx b/cui/source/dialogs/screenshotannotationdlg.cxx index 9a83237821c1..ffe0402e7770 100644 --- a/cui/source/dialogs/screenshotannotationdlg.cxx +++ b/cui/source/dialogs/screenshotannotationdlg.cxx @@ -26,8 +26,14 @@ #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 <com/sun/star/datatransfer/clipboard/SystemClipboard.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/DataFlavor.hpp> +#include <com/sun/star/uno/Any.hxx> #include <comphelper/random.hxx> +#include <comphelper/processfactory.hxx> + #include <basegfx/polygon/b2dpolygontools.hxx> #include <sfx2/filedlghelper.hxx> #include <tools/stream.hxx> @@ -40,10 +46,14 @@ #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> +#include <sot/formats.hxx> +#include <vcl/transfer.hxx> +#include <cppuhelper/implbase.hxx> using namespace com::sun::star; @@ -142,6 +152,9 @@ private: // Handler for click on save DECL_LINK(saveButtonHandler, weld::Button&, void); + // Handler for click on copy + DECL_LINK(copyButtonHandler, weld::Button&, void); + // helper methods weld::ScreenShotEntry* CheckHit(const basegfx::B2IPoint& rPosition); void PaintScreenShotEntry( @@ -178,6 +191,7 @@ private: std::unique_ptr<weld::CustomWeld> mxPicture; std::unique_ptr<weld::TextView> mxText; std::unique_ptr<weld::Button> mxSave; + std::unique_ptr<weld::Button> mxCopy; // save as text OUString maSaveAsText; @@ -190,6 +204,39 @@ public: bool MouseMove(const MouseEvent& rMouseEvent); bool MouseButtonUp(); }; +namespace +{ +class BitmapTransferable : public TransferableHelper +{ +private: + Bitmap maBitmap; + +protected: + // Add supported formats for the Bitmap + virtual void AddSupportedFormats() override + { + AddFormat(SotClipboardFormatId::BITMAP); + } + // Provide the Bitmap data for the requested DataFlavor + virtual bool GetData(const css::datatransfer::DataFlavor& rFlavor, const OUString& ) override + { + if (SotExchange::GetFormat(rFlavor) == SotClipboardFormatId::BITMAP) + { + SetBitmap(maBitmap, rFlavor); + return true; + } + return false; + } + +public: + // Constructor to initialize the Bitmap + explicit BitmapTransferable(const Bitmap& rBitmap) + : maBitmap(rBitmap) + { + } +}; +} + OUString ScreenshotAnnotationDlg_Impl::maLastFolderURL = OUString(); @@ -221,6 +268,9 @@ ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl( assert(mxText); mxSave = rParentBuilder.weld_button(u"save"_ustr); assert(mxSave); + mxCopy = rParentBuilder.weld_button(u"copy"_ustr); + assert(mxCopy); + // set screenshot image at DrawingArea, resize, set event listener if (mxPicture) @@ -237,7 +287,7 @@ ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl( mxVirtualBufferDevice->SetFillColor(COL_TRANSPARENT); // initially set image for picture control - mxVirtualBufferDevice->DrawBitmapEx(Point(0, 0), maDimmedDialogBitmap); + mxVirtualBufferDevice->DrawBitmap(Point(0, 0), maDimmedDialogBitmap); // set size for picture control, this will re-layout so that // the picture control shows the whole dialog @@ -264,6 +314,10 @@ ScreenshotAnnotationDlg_Impl::ScreenshotAnnotationDlg_Impl( { mxSave->connect_clicked(LINK(this, ScreenshotAnnotationDlg_Impl, saveButtonHandler)); } + if(mxCopy) + { + mxCopy->connect_clicked(LINK(this, ScreenshotAnnotationDlg_Impl, copyButtonHandler)); + } } ScreenshotAnnotationDlg_Impl::~ScreenshotAnnotationDlg_Impl() @@ -271,6 +325,31 @@ ScreenshotAnnotationDlg_Impl::~ScreenshotAnnotationDlg_Impl() mxVirtualBufferDevice.disposeAndClear(); } +IMPL_LINK_NOARG(ScreenshotAnnotationDlg_Impl, copyButtonHandler, weld::Button&, void) +{ + // Repaint the buffer to get the latest Bitmap + RepaintToBuffer(); + + // Extract the Bitmap + const Bitmap aTargetBitmap( + mxVirtualBufferDevice->GetBitmap( + Point(0, 0), + mxVirtualBufferDevice->GetOutputSizePixel())); + + // Create a BitmapTransferable + rtl::Reference<TransferableHelper> xClipCntnr = new BitmapTransferable(aTargetBitmap); + // Get the system clipboard + css::uno::Reference<css::datatransfer::clipboard::XClipboard> xClipboard = + css::datatransfer::clipboard::SystemClipboard::create( + comphelper::getProcessComponentContext()); + + if (xClipboard.is()) + { + // Copy the BitmapTransferable to the clipboard + xClipboard->setContents(xClipCntnr, nullptr); + } +} + IMPL_LINK_NOARG(ScreenshotAnnotationDlg_Impl, saveButtonHandler, weld::Button&, void) { // 'save screenshot...' pressed, offer to save maParentDialogBitmap @@ -433,7 +512,7 @@ void ScreenshotAnnotationDlg_Impl::RepaintToBuffer( return; // reset with original screenshot bitmap - mxVirtualBufferDevice->DrawBitmapEx( + mxVirtualBufferDevice->DrawBitmap( Point(0, 0), bUseDimmed ? maDimmedDialogBitmap : maParentDialogBitmap); diff --git a/cui/uiconfig/ui/screenshotannotationdialog.ui b/cui/uiconfig/ui/screenshotannotationdialog.ui index 346af2f59288..a6cde9a09973 100644 --- a/cui/uiconfig/ui/screenshotannotationdialog.ui +++ b/cui/uiconfig/ui/screenshotannotationdialog.ui @@ -1,40 +1,52 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> +<!-- Generated with glade 3.40.0 --> <interface domain="cui"> <requires lib="gtk+" version="3.20"/> <object class="GtkDialog" id="ScreenshotAnnotationDialog"> - <property name="can_focus">False</property> - <property name="border_width">6</property> + <property name="can-focus">False</property> + <property name="border-width">6</property> <property name="title" translatable="yes" context="screenshotannotationdialog|ScreenshotAnnotationDialog">Interactive Screenshot Annotation</property> <property name="modal">True</property> - <property name="default_width">600</property> - <property name="default_height">460</property> - <property name="type_hint">normal</property> - <child> - <placeholder/> - </child> + <property name="default-width">600</property> + <property name="default-height">460</property> + <property name="type-hint">normal</property> <child internal-child="vbox"> <object class="GtkBox" id="dialog-vbox1"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">12</property> <child internal-child="action_area"> <object class="GtkButtonBox" id="dialog-action_area1"> - <property name="can_focus">False</property> - <property name="layout_style">end</property> + <property name="can-focus">False</property> + <property name="layout-style">end</property> + <child> + <object class="GtkButton" id="copy"> + <property name="label" translatable="yes" context="screenshotannotationdialog|copy">C_opy Image</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> <child> <object class="GtkButton" id="cancel"> <property name="label" translatable="yes" context="stock">_Cancel</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="has-focus">True</property> + <property name="receives-default">True</property> <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">1</property> + <property name="position">2</property> </packing> </child> <child> @@ -42,15 +54,15 @@ <property name="label" translatable="yes" context="screenshotannotationdialog|save">Save Screenshot...</property> <property name="name">save</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="has_default">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="has-default">True</property> + <property name="receives-default">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">2</property> + <property name="position">3</property> <property name="secondary">True</property> </packing> </child> @@ -58,18 +70,18 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="pack_type">end</property> + <property name="pack-type">end</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkLabel" id="label2"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="halign">start</property> <property name="label" translatable="yes" context="screenshotannotationdialog|label2">Click the widgets to add annotation:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">picture</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">picture</property> </object> <packing> <property name="expand">False</property> @@ -81,7 +93,7 @@ <object class="GtkDrawingArea" id="picture"> <property name="name">image</property> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="events">GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK</property> </object> <packing> @@ -93,11 +105,11 @@ <child> <object class="GtkLabel" id="label1"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="halign">start</property> <property name="label" translatable="yes" context="screenshotannotationdialog|label1">Paste the following markup into the help file:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">text</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">text</property> <property name="ellipsize">end</property> </object> <packing> @@ -109,14 +121,14 @@ <child> <object class="GtkScrolledWindow" id="scrolledwindow1"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="can-focus">True</property> <property name="hexpand">True</property> <property name="vexpand">True</property> - <property name="shadow_type">in</property> + <property name="shadow-type">in</property> <child> <object class="GtkTextView" id="text"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="can-focus">True</property> <property name="hexpand">True</property> <property name="vexpand">True</property> </object>
