1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <sal/config.h>
15 #include <string_view>
17 #include <dndhelper.hxx>
18 #include <osl/process.h>
19 #include <unx/gtk/gtkdata.hxx>
20 #include <unx/gtk/gtkinst.hxx>
21 #include <unx/genprn.h>
22 #include <unx/salobj.h>
23 #include <unx/gtk/gtkgdi.hxx>
24 #include <unx/gtk/gtkframe.hxx>
25 #include <unx/gtk/gtkobject.hxx>
26 #include <unx/gtk/atkbridge.hxx>
27 #include <unx/gtk/gtksalmenu.hxx>
28 #include <headless/svpvd.hxx>
29 #include <headless/svpbmp.hxx>
30 #include <vcl/builder.hxx>
31 #include <vcl/inputtypes.hxx>
32 #include <vcl/specialchars.hxx>
33 #include <vcl/sysdata.hxx>
34 #include <vcl/transfer.hxx>
35 #include <vcl/toolkit/floatwin.hxx>
36 #include <unx/genpspgraphics.h>
37 #include <rtl/strbuf.hxx>
38 #include <sal/log.hxx>
39 #include <rtl/uri.hxx>
41 #include <vcl/settings.hxx>
47 #if !GTK_CHECK_VERSION(4, 0, 0)
48 #include "a11y/atkwrapper.hxx"
50 #include <com/sun/star/datatransfer/XTransferable.hpp>
51 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
52 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
53 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
54 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
55 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
56 #include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
57 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
58 #include <com/sun/star/lang/IllegalArgumentException.hpp>
59 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
60 #include <com/sun/star/lang/XServiceInfo.hpp>
61 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
62 #include <com/sun/star/lang/XInitialization.hpp>
63 #include <comphelper/lok.hxx>
64 #include <comphelper/processfactory.hxx>
65 #include <comphelper/propertyvalue.hxx>
66 #include <comphelper/sequence.hxx>
67 #include <comphelper/string.hxx>
68 #include <cppuhelper/compbase.hxx>
69 #include <cppuhelper/implbase.hxx>
70 #include <cppuhelper/supportsservice.hxx>
71 #include <officecfg/Office/Common.hxx>
72 #include <rtl/bootstrap.hxx>
73 #include <o3tl/unreachable.hxx>
74 #include <o3tl/string_view.hxx>
75 #include <svl/zforlist.hxx>
76 #include <svl/zformat.hxx>
77 #include <tools/helpers.hxx>
78 #include <tools/fract.hxx>
79 #include <tools/stream.hxx>
80 #include <unotools/resmgr.hxx>
81 #include <unx/gstsink.hxx>
82 #include <vcl/ImageTree.hxx>
83 #include <vcl/abstdlg.hxx>
84 #include <vcl/event.hxx>
85 #include <vcl/i18nhelp.hxx>
86 #include <vcl/quickselectionengine.hxx>
87 #include <vcl/mnemonic.hxx>
88 #include <vcl/pngwrite.hxx>
89 #include <vcl/stdtext.hxx>
90 #include <vcl/syswin.hxx>
91 #include <vcl/virdev.hxx>
92 #include <vcl/weld.hxx>
93 #include <vcl/wrkwin.hxx>
94 #include "customcellrenderer.hxx"
95 #include <strings.hrc>
98 #include <boost/property_tree/ptree.hpp>
99 #include <opengl/zone.hxx>
101 using namespace com::sun::star
;
102 using namespace com::sun::star::uno
;
103 using namespace com::sun::star::lang
;
107 #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalInstance()->GetYieldMutex())
108 #if !GTK_CHECK_VERSION(4, 0, 0)
109 static void GdkThreadsEnter()
111 GtkYieldMutex
*pYieldMutex
= GET_YIELD_MUTEX();
112 pYieldMutex
->ThreadsEnter();
114 static void GdkThreadsLeave()
116 GtkYieldMutex
*pYieldMutex
= GET_YIELD_MUTEX();
117 pYieldMutex
->ThreadsLeave();
121 VCLPLUG_GTK_PUBLIC SalInstance
* create_SalInstance()
125 "create vcl plugin instance with gtk version " << gtk_get_major_version()
126 << " " << gtk_get_minor_version() << " " << gtk_get_micro_version());
128 if (gtk_get_major_version() == 3 && gtk_get_minor_version() < 18)
130 g_warning("require gtk >= 3.18 for theme expectations");
134 // for gtk2 it is always built with X support, so this is always called
135 // for gtk3 it is normally built with X and Wayland support, if
136 // X is supported GDK_WINDOWING_X11 is defined and this is always
137 // called, regardless of if we're running under X or Wayland.
138 // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
139 // X, because we need to do it earlier than we have a display
140 #if defined(GDK_WINDOWING_X11)
141 /* #i92121# workaround deadlocks in the X11 implementation
143 static const char* pNoXInitThreads
= getenv( "SAL_NO_XINITTHREADS" );
145 from now on we know that an X connection will be
146 established, so protect X against itself
148 if( ! ( pNoXInitThreads
&& *pNoXInitThreads
) )
152 #if !GTK_CHECK_VERSION(4, 0, 0)
153 // init gdk thread protection
154 bool const sup
= g_thread_supported();
155 // extracted from the 'if' to avoid Clang -Wunreachable-code
157 g_thread_init( nullptr );
159 gdk_threads_set_lock_functions (GdkThreadsEnter
, GdkThreadsLeave
);
160 SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
163 auto pYieldMutex
= std::make_unique
<GtkYieldMutex
>();
165 #if !GTK_CHECK_VERSION(4, 0, 0)
169 GtkInstance
* pInstance
= new GtkInstance( std::move(pYieldMutex
) );
170 SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance
);
172 // Create SalData, this does not leak
179 #if !GTK_CHECK_VERSION(4, 0, 0)
180 static VclInputFlags
categorizeEvent(const GdkEvent
*pEvent
)
182 VclInputFlags nType
= VclInputFlags::NONE
;
183 switch (gdk_event_get_event_type(pEvent
))
185 case GDK_MOTION_NOTIFY
:
186 case GDK_BUTTON_PRESS
:
187 #if !GTK_CHECK_VERSION(4, 0, 0)
188 case GDK_2BUTTON_PRESS
:
189 case GDK_3BUTTON_PRESS
:
191 case GDK_BUTTON_RELEASE
:
192 case GDK_ENTER_NOTIFY
:
193 case GDK_LEAVE_NOTIFY
:
195 nType
= VclInputFlags::MOUSE
;
198 // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
199 nType
= VclInputFlags::KEYBOARD
;
201 #if !GTK_CHECK_VERSION(4, 0, 0)
203 nType
= VclInputFlags::PAINT
;
207 nType
= VclInputFlags::OTHER
;
214 GtkInstance::GtkInstance( std::unique_ptr
<SalYieldMutex
> pMutex
)
215 : SvpSalInstance( std::move(pMutex
) )
218 , m_pLastCairoFontOptions(nullptr)
220 m_bSupportsOpenGL
= true;
223 //We want to defer initializing gtk until we are after uno has been
224 //bootstrapped so we can ask the config what the UI language is so that we can
225 //force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
227 void GtkInstance::AfterAppInit()
232 void GtkInstance::EnsureInit()
236 // initialize SalData
237 GtkSalData
*pSalData
= GetGtkSalData();
239 GtkSalData::initNWF();
241 #if !GTK_CHECK_VERSION(4, 0, 0)
245 ImplSVData
* pSVData
= ImplGetSVData();
246 #ifdef GTK_TOOLKIT_NAME
247 pSVData
->maAppData
.mxToolkitName
= OUString(GTK_TOOLKIT_NAME
);
249 pSVData
->maAppData
.mxToolkitName
= OUString("gtk3");
255 GtkInstance::~GtkInstance()
257 assert( nullptr == m_pTimer
);
258 #if !GTK_CHECK_VERSION(4, 0, 0)
261 ResetLastSeenCairoFontOptions(nullptr);
264 SalFrame
* GtkInstance::CreateFrame( SalFrame
* pParent
, SalFrameStyleFlags nStyle
)
267 return new GtkSalFrame( pParent
, nStyle
);
270 SalFrame
* GtkInstance::CreateChildFrame( SystemParentData
* pParentData
, SalFrameStyleFlags
)
273 return new GtkSalFrame( pParentData
);
276 SalObject
* GtkInstance::CreateObject( SalFrame
* pParent
, SystemWindowData
* pWindowData
, bool bShow
)
279 //FIXME: Missing CreateObject functionality ...
280 if (pWindowData
&& pWindowData
->bClipUsingNativeWidget
)
281 return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame
*>(pParent
), bShow
);
282 return new GtkSalObject(static_cast<GtkSalFrame
*>(pParent
), bShow
);
287 typedef void*(* getDefaultFnc
)();
288 typedef void(* addItemFnc
)(void *, const char *);
291 void GtkInstance::AddToRecentDocumentList(const OUString
& rFileUrl
, const OUString
&, const OUString
&)
295 rtl_TextEncoding aSystemEnc
= osl_getThreadTextEncoding();
296 if ((aSystemEnc
== RTL_TEXTENCODING_UTF8
) || !rFileUrl
.startsWith( "file://" ))
297 sGtkURL
= OUStringToOString(rFileUrl
, RTL_TEXTENCODING_UTF8
);
300 //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
301 //Decode %XX components
302 OUString sDecodedUri
= rtl::Uri::decode(rFileUrl
.copy(7), rtl_UriDecodeToIuri
, RTL_TEXTENCODING_UTF8
);
303 //Convert back to system locale encoding
304 OString sSystemUrl
= OUStringToOString(sDecodedUri
, aSystemEnc
);
305 //Encode to an escaped ASCII-encoded URI
306 gchar
*g_uri
= g_filename_to_uri(sSystemUrl
.getStr(), nullptr, nullptr);
307 sGtkURL
= OString(g_uri
);
310 GtkRecentManager
*manager
= gtk_recent_manager_get_default ();
311 gtk_recent_manager_add_item (manager
, sGtkURL
.getStr());
314 SalInfoPrinter
* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo
* pQueueInfo
,
315 ImplJobSetup
* pSetupData
)
318 mbPrinterInit
= true;
319 // create and initialize SalInfoPrinter
320 PspSalInfoPrinter
* pPrinter
= new PspSalInfoPrinter
;
321 configurePspInfoPrinter(pPrinter
, pQueueInfo
, pSetupData
);
325 std::unique_ptr
<SalPrinter
> GtkInstance::CreatePrinter( SalInfoPrinter
* pInfoPrinter
)
328 mbPrinterInit
= true;
329 return std::unique_ptr
<SalPrinter
>(new PspSalPrinter(pInfoPrinter
));
333 * These methods always occur in pairs
334 * A ThreadsEnter is followed by a ThreadsLeave
335 * We need to queue up the recursive lock count
336 * for each pair, so we can accurately restore
339 thread_local
std::stack
<sal_uInt32
> GtkYieldMutex::yieldCounts
;
341 void GtkYieldMutex::ThreadsEnter()
344 if (yieldCounts
.empty())
346 auto n
= yieldCounts
.top();
349 const bool bUndoingLeaveWithoutEnter
= n
== 0;
350 // if the ThreadsLeave bLeaveWithoutEnter of true condition occurred to
351 // create this entry then return early undoing the initial acquire of the
353 if G_UNLIKELY(bUndoingLeaveWithoutEnter
)
365 void GtkYieldMutex::ThreadsLeave()
367 const bool bLeaveWithoutEnter
= m_nCount
== 0;
368 SAL_WARN_IF(bLeaveWithoutEnter
, "vcl.gtk", "gdk_threads_leave without matching gdk_threads_enter");
369 yieldCounts
.push(m_nCount
);
370 if G_UNLIKELY(bLeaveWithoutEnter
) // this ideally shouldn't happen, but can due to the gtk3 file dialog
375 std::unique_ptr
<SalVirtualDevice
> GtkInstance::CreateVirtualDevice( SalGraphics
&rG
,
376 tools::Long
&nDX
, tools::Long
&nDY
,
377 DeviceFormat
/*eFormat*/,
378 const SystemGraphicsData
* pGd
)
381 SvpSalGraphics
*pSvpSalGraphics
= dynamic_cast<SvpSalGraphics
*>(&rG
);
382 assert(pSvpSalGraphics
);
383 // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
384 cairo_surface_t
* pPreExistingTarget
= pGd
? static_cast<cairo_surface_t
*>(pGd
->pSurface
) : nullptr;
385 std::unique_ptr
<SalVirtualDevice
> pNew(new SvpSalVirtualDevice(pSvpSalGraphics
->getSurface(), pPreExistingTarget
));
386 pNew
->SetSize( nDX
, nDY
);
390 std::shared_ptr
<SalBitmap
> GtkInstance::CreateSalBitmap()
393 return SvpSalInstance::CreateSalBitmap();
396 std::unique_ptr
<SalMenu
> GtkInstance::CreateMenu( bool bMenuBar
, Menu
* pVCLMenu
)
399 GtkSalMenu
* pSalMenu
= new GtkSalMenu( bMenuBar
);
400 pSalMenu
->SetMenu( pVCLMenu
);
401 return std::unique_ptr
<SalMenu
>(pSalMenu
);
404 std::unique_ptr
<SalMenuItem
> GtkInstance::CreateMenuItem( const SalItemParams
& rItemData
)
407 return std::unique_ptr
<SalMenuItem
>(new GtkSalMenuItem( &rItemData
));
410 SalTimer
* GtkInstance::CreateSalTimer()
413 assert( nullptr == m_pTimer
);
414 if ( nullptr == m_pTimer
)
415 m_pTimer
= new GtkSalTimer();
419 void GtkInstance::RemoveTimer ()
425 bool GtkInstance::DoYield(bool bWait
, bool bHandleAllCurrentEvents
)
428 return GetGtkSalData()->Yield( bWait
, bHandleAllCurrentEvents
);
431 bool GtkInstance::IsTimerExpired()
434 return (m_pTimer
&& m_pTimer
->Expired());
439 bool DisplayHasAnyInput()
441 GdkDisplay
* pDisplay
= gdk_display_get_default();
442 #if defined(GDK_WINDOWING_WAYLAND)
443 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay
))
446 wl_display
* pWLDisplay
= gdk_wayland_display_get_wl_display(pDisplay
);
447 static auto wayland_display_get_fd
= reinterpret_cast<int (*) (wl_display
*)>(dlsym(nullptr, "wl_display_get_fd"));
448 if (wayland_display_get_fd
)
451 aPollFD
.fd
= wayland_display_get_fd(pWLDisplay
);
452 aPollFD
.events
= G_IO_IN
| G_IO_ERR
| G_IO_HUP
;
453 bRet
= g_poll(&aPollFD
, 1, 0) > 0;
458 #if defined(GDK_WINDOWING_X11)
459 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay
))
462 aPollFD
.fd
= ConnectionNumber(gdk_x11_display_get_xdisplay(pDisplay
));
463 aPollFD
.events
= G_IO_IN
;
464 return g_poll(&aPollFD
, 1, 0) > 0;
471 bool GtkInstance::AnyInput( VclInputFlags nType
)
474 if( (nType
& VclInputFlags::TIMER
) && IsTimerExpired() )
477 // strip timer bits now
478 nType
= nType
& ~VclInputFlags::TIMER
;
480 static constexpr VclInputFlags ANY_INPUT_EXCLUDING_TIMER
= VCL_INPUT_ANY
& ~VclInputFlags::TIMER
;
482 const bool bCheckForAnyInput
= nType
== ANY_INPUT_EXCLUDING_TIMER
;
486 if (bCheckForAnyInput
)
487 bRet
= DisplayHasAnyInput();
489 #if !GTK_CHECK_VERSION(4, 0, 0)
490 GdkDisplay
* pDisplay
= gdk_display_get_default();
491 if (!gdk_display_has_pending(pDisplay
))
494 if (bCheckForAnyInput
)
497 std::deque
<GdkEvent
*> aEvents
;
498 GdkEvent
*pEvent
= nullptr;
499 while ((pEvent
= gdk_display_get_event(pDisplay
)))
501 aEvents
.push_back(pEvent
);
502 VclInputFlags nEventType
= categorizeEvent(pEvent
);
503 if ( (nEventType
& nType
) || ( nEventType
== VclInputFlags::NONE
&& (nType
& VclInputFlags::OTHER
) ) )
509 while (!aEvents
.empty())
511 pEvent
= aEvents
.front();
512 gdk_display_put_event(pDisplay
, pEvent
);
513 gdk_event_free(pEvent
);
521 std::unique_ptr
<GenPspGraphics
> GtkInstance::CreatePrintGraphics()
524 return std::make_unique
<GenPspGraphics
>();
527 const cairo_font_options_t
* GtkInstance::GetCairoFontOptions()
529 #if !GTK_CHECK_VERSION(4, 0, 0)
530 const cairo_font_options_t
* pCairoFontOptions
= gdk_screen_get_font_options(gdk_screen_get_default());
532 auto pDefaultWin
= ImplGetDefaultWindow();
534 SalFrame
* pDefaultFrame
= pDefaultWin
->ImplGetFrame();
535 GtkSalFrame
* pGtkFrame
= dynamic_cast<GtkSalFrame
*>(pDefaultFrame
);
537 const cairo_font_options_t
* pCairoFontOptions
= pGtkFrame
->get_font_options();
539 if (!m_pLastCairoFontOptions
&& pCairoFontOptions
)
540 m_pLastCairoFontOptions
= cairo_font_options_copy(pCairoFontOptions
);
541 return pCairoFontOptions
;
544 const cairo_font_options_t
* GtkInstance::GetLastSeenCairoFontOptions() const
546 return m_pLastCairoFontOptions
;
549 void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t
* pCairoFontOptions
)
551 if (m_pLastCairoFontOptions
)
552 cairo_font_options_destroy(m_pLastCairoFontOptions
);
553 if (pCairoFontOptions
)
554 m_pLastCairoFontOptions
= cairo_font_options_copy(pCairoFontOptions
);
556 m_pLastCairoFontOptions
= nullptr;
564 const char* pNativeType
; // string corresponding to nAtom for the case of nAtom being uninitialized
565 const char* pType
; // Mime encoding on our side
568 const TypeEntry aConversionTab
[] =
570 { "ISO10646-1", "text/plain;charset=utf-16" },
571 { "UTF8_STRING", "text/plain;charset=utf-8" },
572 { "UTF-8", "text/plain;charset=utf-8" },
573 { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
575 { "ISO8859-2", "text/plain;charset=iso8859-2" },
576 { "ISO8859-3", "text/plain;charset=iso8859-3" },
577 { "ISO8859-4", "text/plain;charset=iso8859-4" },
578 { "ISO8859-5", "text/plain;charset=iso8859-5" },
579 { "ISO8859-6", "text/plain;charset=iso8859-6" },
580 { "ISO8859-7", "text/plain;charset=iso8859-7" },
581 { "ISO8859-8", "text/plain;charset=iso8859-8" },
582 { "ISO8859-9", "text/plain;charset=iso8859-9" },
583 { "ISO8859-10", "text/plain;charset=iso8859-10" },
584 { "ISO8859-13", "text/plain;charset=iso8859-13" },
585 { "ISO8859-14", "text/plain;charset=iso8859-14" },
586 { "ISO8859-15", "text/plain;charset=iso8859-15" },
588 { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
589 { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
590 { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
591 { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
592 { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
593 { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
594 // eastern european encodings
595 { "KOI8-R", "text/plain;charset=koi8-r" },
596 { "KOI8-U", "text/plain;charset=koi8-u" },
597 // String (== iso8859-1)
598 { "STRING", "text/plain;charset=iso8859-1" },
599 // special for compound text
600 { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
603 { "PIXMAP", "image/bmp" }
609 const css::datatransfer::DataFlavor
& m_rData
;
611 explicit DataFlavorEq(const css::datatransfer::DataFlavor
& rData
) : m_rData(rData
) {}
612 bool operator() (const css::datatransfer::DataFlavor
& rData
) const
614 return rData
.MimeType
== m_rData
.MimeType
&&
615 rData
.DataType
== m_rData
.DataType
;
620 #if GTK_CHECK_VERSION(4, 0, 0)
621 std::vector
<css::datatransfer::DataFlavor
> GtkTransferable::getTransferDataFlavorsAsVector(const char * const *targets
, gint n_targets
)
623 std::vector
<css::datatransfer::DataFlavor
> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom
*targets
, gint n_targets
)
626 std::vector
<css::datatransfer::DataFlavor
> aVector
;
628 bool bHaveText
= false, bHaveUTF16
= false;
630 for (gint i
= 0; i
< n_targets
; ++i
)
632 #if GTK_CHECK_VERSION(4, 0, 0)
633 const gchar
* pName
= targets
[i
];
635 gchar
* pName
= gdk_atom_name(targets
[i
]);
637 const char* pFinalName
= pName
;
638 css::datatransfer::DataFlavor aFlavor
;
640 // omit text/plain;charset=unicode since it is not well defined
641 if (rtl_str_compare(pName
, "text/plain;charset=unicode") == 0)
643 #if !GTK_CHECK_VERSION(4, 0, 0)
649 for (size_t j
= 0; j
< SAL_N_ELEMENTS(aConversionTab
); ++j
)
651 if (rtl_str_compare(pName
, aConversionTab
[j
].pNativeType
) == 0)
653 pFinalName
= aConversionTab
[j
].pType
;
658 // There are more non-MIME-types reported that are not translated by
659 // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
660 // them out for now before they confuse this code's clients:
661 if (rtl_str_indexOfChar(pFinalName
, '/') == -1)
663 #if !GTK_CHECK_VERSION(4, 0, 0)
669 aFlavor
.MimeType
= OUString(pFinalName
,
671 RTL_TEXTENCODING_UTF8
);
673 m_aMimeTypeToGtkType
[aFlavor
.MimeType
] = targets
[i
];
675 aFlavor
.DataType
= cppu::UnoType
<Sequence
< sal_Int8
>>::get();
678 if (o3tl::getToken(aFlavor
.MimeType
, 0, ';', nIndex
) == u
"text/plain")
681 std::u16string_view
aToken(o3tl::getToken(aFlavor
.MimeType
, 0, ';', nIndex
));
682 if (aToken
== u
"charset=utf-16")
685 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
688 aVector
.push_back(aFlavor
);
689 #if !GTK_CHECK_VERSION(4, 0, 0)
694 //If we have text, but no UTF-16 format which is basically the only
695 //text-format LibreOffice supports for cnp then claim we do and we
696 //will convert on demand
697 if (bHaveText
&& !bHaveUTF16
)
699 css::datatransfer::DataFlavor aFlavor
;
700 aFlavor
.MimeType
= "text/plain;charset=utf-16";
701 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
702 aVector
.push_back(aFlavor
);
708 css::uno::Sequence
<css::datatransfer::DataFlavor
> SAL_CALL
GtkTransferable::getTransferDataFlavors()
710 return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
713 sal_Bool SAL_CALL
GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor
& rFlavor
)
715 const std::vector
<css::datatransfer::DataFlavor
> aAll
=
716 getTransferDataFlavorsAsVector();
718 return std::any_of(aAll
.begin(), aAll
.end(), DataFlavorEq(rFlavor
));
721 #if GTK_CHECK_VERSION(4, 0, 0)
722 void read_transfer_result::read_block_async_completed(GObject
* source
, GAsyncResult
* res
, gpointer user_data
)
724 GInputStream
* stream
= G_INPUT_STREAM(source
);
725 read_transfer_result
* pRes
= static_cast<read_transfer_result
*>(user_data
);
727 gsize bytes_read
= g_input_stream_read_finish(stream
, res
, nullptr);
729 bool bFinished
= bytes_read
== 0;
733 g_object_unref(stream
);
734 pRes
->aVector
.resize(pRes
->nRead
);
736 g_main_context_wakeup(nullptr);
740 pRes
->nRead
+= bytes_read
;
742 pRes
->aVector
.resize(pRes
->nRead
+ read_transfer_result::BlockSize
);
744 g_input_stream_read_async(stream
,
745 pRes
->aVector
.data() + pRes
->nRead
,
746 read_transfer_result::BlockSize
,
749 read_block_async_completed
,
753 OUString
read_transfer_result::get_as_string() const
755 const char* pStr
= reinterpret_cast<const char*>(aVector
.data());
756 return OUString(pStr
, aVector
.size(), RTL_TEXTENCODING_UTF8
).replaceAll("\r\n", "\n");
759 css::uno::Sequence
<sal_Int8
> read_transfer_result::get_as_sequence() const
761 return css::uno::Sequence
<sal_Int8
>(aVector
.data(), aVector
.size());
767 GdkClipboard
* clipboard_get(SelectionType eSelection
)
769 #if GTK_CHECK_VERSION(4, 0, 0)
770 if (eSelection
== SELECTION_CLIPBOARD
)
771 return gdk_display_get_clipboard(gdk_display_get_default());
772 return gdk_display_get_primary_clipboard(gdk_display_get_default());
774 return gtk_clipboard_get(eSelection
== SELECTION_CLIPBOARD
? GDK_SELECTION_CLIPBOARD
: GDK_SELECTION_PRIMARY
);
778 #if GTK_CHECK_VERSION(4, 0, 0)
780 void read_clipboard_async_completed(GObject
* source
, GAsyncResult
* res
, gpointer user_data
)
782 GdkClipboard
* clipboard
= GDK_CLIPBOARD(source
);
783 read_transfer_result
* pRes
= static_cast<read_transfer_result
*>(user_data
);
785 GInputStream
* pResult
= gdk_clipboard_read_finish(clipboard
, res
, nullptr, nullptr);
790 g_main_context_wakeup(nullptr);
794 pRes
->aVector
.resize(read_transfer_result::BlockSize
);
796 g_input_stream_read_async(pResult
,
797 pRes
->aVector
.data(),
798 pRes
->aVector
.size(),
801 read_transfer_result::read_block_async_completed
,
807 class GtkClipboardTransferable
: public GtkTransferable
810 SelectionType m_eSelection
;
814 explicit GtkClipboardTransferable(SelectionType eSelection
)
815 : m_eSelection(eSelection
)
823 virtual css::uno::Any SAL_CALL
getTransferData(const css::datatransfer::DataFlavor
& rFlavor
) override
825 css::datatransfer::DataFlavor
aFlavor(rFlavor
);
826 if (aFlavor
.MimeType
== "text/plain;charset=utf-16")
827 aFlavor
.MimeType
= "text/plain;charset=utf-8";
829 auto it
= m_aMimeTypeToGtkType
.find(aFlavor
.MimeType
);
830 if (it
== m_aMimeTypeToGtkType
.end())
831 return css::uno::Any();
835 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
837 #if !GTK_CHECK_VERSION(4, 0, 0)
838 if (aFlavor
.MimeType
== "text/plain;charset=utf-8")
840 gchar
*pText
= gtk_clipboard_wait_for_text(clipboard
);
841 OUString
aStr(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
843 aRet
<<= aStr
.replaceAll("\r\n", "\n");
848 GtkSelectionData
* data
= gtk_clipboard_wait_for_contents(clipboard
,
852 return css::uno::Any();
855 const guchar
*rawdata
= gtk_selection_data_get_data_with_length(data
,
857 Sequence
<sal_Int8
> aSeq(reinterpret_cast<const sal_Int8
*>(rawdata
), length
);
858 gtk_selection_data_free(data
);
862 SalInstance
* pInstance
= GetSalInstance();
863 read_transfer_result aRes
;
864 const char *mime_types
[] = { it
->second
.getStr(), nullptr };
866 gdk_clipboard_read_async(clipboard
,
870 read_clipboard_async_completed
,
874 pInstance
->DoYield(true, false);
876 if (aFlavor
.MimeType
== "text/plain;charset=utf-8")
877 aRet
<<= aRes
.get_as_string();
879 aRet
<<= aRes
.get_as_sequence();
884 std::vector
<css::datatransfer::DataFlavor
> getTransferDataFlavorsAsVector()
887 std::vector
<css::datatransfer::DataFlavor
> aVector
;
889 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
891 #if GTK_CHECK_VERSION(4, 0, 0)
892 GdkContentFormats
* pFormats
= gdk_clipboard_get_formats(clipboard
);
894 const char * const *targets
= gdk_content_formats_get_mime_types(pFormats
, &n_targets
);
895 aVector
= GtkTransferable::getTransferDataFlavorsAsVector(targets
, n_targets
);
899 if (gtk_clipboard_wait_for_targets(clipboard
, &targets
, &n_targets
))
901 aVector
= GtkTransferable::getTransferDataFlavorsAsVector(targets
, n_targets
);
910 class VclGtkClipboard
:
911 public cppu::WeakComponentImplHelper
<
912 datatransfer::clipboard::XSystemClipboard
,
913 datatransfer::clipboard::XFlushableClipboard
,
916 SelectionType m_eSelection
;
918 gulong m_nOwnerChangedSignalId
;
919 ImplSVEvent
* m_pSetClipboardEvent
;
920 Reference
<css::datatransfer::XTransferable
> m_aContents
;
921 Reference
<css::datatransfer::clipboard::XClipboardOwner
> m_aOwner
;
922 std::vector
< Reference
<css::datatransfer::clipboard::XClipboardListener
> > m_aListeners
;
923 #if GTK_CHECK_VERSION(4, 0, 0)
924 std::vector
<OString
> m_aGtkTargets
;
925 TransferableContent
* m_pClipboardContent
;
927 std::vector
<GtkTargetEntry
> m_aGtkTargets
;
929 VclToGtkHelper m_aConversionHelper
;
931 DECL_LINK(AsyncSetGtkClipboard
, void*, void);
933 #if GTK_CHECK_VERSION(4, 0, 0)
934 DECL_LINK(DetachClipboard
, void*, void);
939 explicit VclGtkClipboard(SelectionType eSelection
);
940 virtual ~VclGtkClipboard() override
;
946 virtual OUString SAL_CALL
getImplementationName() override
;
947 virtual sal_Bool SAL_CALL
supportsService( const OUString
& ServiceName
) override
;
948 virtual Sequence
< OUString
> SAL_CALL
getSupportedServiceNames() override
;
954 virtual Reference
< css::datatransfer::XTransferable
> SAL_CALL
getContents() override
;
956 virtual void SAL_CALL
setContents(
957 const Reference
< css::datatransfer::XTransferable
>& xTrans
,
958 const Reference
< css::datatransfer::clipboard::XClipboardOwner
>& xClipboardOwner
) override
;
960 virtual OUString SAL_CALL
getName() override
;
966 virtual sal_Int8 SAL_CALL
getRenderingCapabilities() override
;
969 * XFlushableClipboard
971 virtual void SAL_CALL
flushClipboard() override
;
976 virtual void SAL_CALL
addClipboardListener(
977 const Reference
< css::datatransfer::clipboard::XClipboardListener
>& listener
) override
;
979 virtual void SAL_CALL
removeClipboardListener(
980 const Reference
< css::datatransfer::clipboard::XClipboardListener
>& listener
) override
;
982 #if !GTK_CHECK_VERSION(4, 0, 0)
983 void ClipboardGet(GtkSelectionData
*selection_data
, guint info
);
985 void OwnerPossiblyChanged(GdkClipboard
*clipboard
);
986 void ClipboardClear();
987 void SetGtkClipboard();
988 void SyncGtkClipboard();
993 OUString
VclGtkClipboard::getImplementationName()
995 return "com.sun.star.datatransfer.VclGtkClipboard";
998 Sequence
< OUString
> VclGtkClipboard::getSupportedServiceNames()
1000 Sequence
<OUString
> aRet
{ "com.sun.star.datatransfer.clipboard.SystemClipboard" };
1004 sal_Bool
VclGtkClipboard::supportsService( const OUString
& ServiceName
)
1006 return cppu::supportsService(this, ServiceName
);
1009 Reference
< css::datatransfer::XTransferable
> VclGtkClipboard::getContents()
1011 if (!m_aContents
.is())
1013 //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
1014 //the owner of the clipboard and have not already fetched it.
1015 m_aContents
= new GtkClipboardTransferable(m_eSelection
);
1016 #if GTK_CHECK_VERSION(4, 0, 0)
1017 if (m_pClipboardContent
)
1018 transerable_content_set_transferable(m_pClipboardContent
, m_aContents
.get());
1024 #if !GTK_CHECK_VERSION(4, 0, 0)
1025 void VclGtkClipboard::ClipboardGet(GtkSelectionData
*selection_data
, guint info
)
1027 if (!m_aContents
.is())
1029 // tdf#129809 take a reference in case m_aContents is replaced during this
1031 Reference
<datatransfer::XTransferable
> xCurrentContents(m_aContents
);
1032 m_aConversionHelper
.setSelectionData(xCurrentContents
, selection_data
, info
);
1037 const OString
& getPID()
1039 static OString sPID
;
1040 if (!sPID
.getLength())
1042 oslProcessIdentifier aProcessId
= 0;
1043 oslProcessInfo info
;
1044 info
.Size
= sizeof (oslProcessInfo
);
1045 if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER
, &info
) == osl_Process_E_None
)
1046 aProcessId
= info
.Ident
;
1047 sPID
= OString::number(aProcessId
);
1052 void ClipboardGetFunc(GdkClipboard
* /*clipboard*/, GtkSelectionData
*selection_data
,
1054 gpointer user_data_or_owner
)
1056 VclGtkClipboard
* pThis
= static_cast<VclGtkClipboard
*>(user_data_or_owner
);
1057 pThis
->ClipboardGet(selection_data
, info
);
1060 void ClipboardClearFunc(GdkClipboard
* /*clipboard*/, gpointer user_data_or_owner
)
1062 VclGtkClipboard
* pThis
= static_cast<VclGtkClipboard
*>(user_data_or_owner
);
1063 pThis
->ClipboardClear();
1070 #if GTK_CHECK_VERSION(4, 0, 0)
1071 void handle_owner_change(GdkClipboard
*clipboard
, gpointer user_data
)
1073 VclGtkClipboard
* pThis
= static_cast<VclGtkClipboard
*>(user_data
);
1074 pThis
->OwnerPossiblyChanged(clipboard
);
1077 void handle_owner_change(GdkClipboard
*clipboard
, GdkEvent
* /*event*/, gpointer user_data
)
1079 VclGtkClipboard
* pThis
= static_cast<VclGtkClipboard
*>(user_data
);
1080 pThis
->OwnerPossiblyChanged(clipboard
);
1085 void VclGtkClipboard::OwnerPossiblyChanged(GdkClipboard
* clipboard
)
1087 SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
1088 if (!m_aContents
.is())
1091 #if GTK_CHECK_VERSION(4, 0, 0)
1092 bool bSelf
= gdk_clipboard_is_local(clipboard
);
1094 //if gdk_display_supports_selection_notification is not supported, e.g. like
1095 //right now under wayland, then you only get owner-changed notifications at
1096 //opportune times when the selection might have changed. So here
1097 //we see if the selection supports a dummy selection type identifying
1098 //our pid, in which case it's us.
1101 //disconnect and reconnect after gtk_clipboard_wait_for_targets to
1102 //avoid possible recursion
1103 g_signal_handler_disconnect(clipboard
, m_nOwnerChangedSignalId
);
1105 OString sTunnel
= "application/x-libreoffice-internal-id-" + getPID();
1108 if (gtk_clipboard_wait_for_targets(clipboard
, &targets
, &n_targets
))
1110 for (gint i
= 0; i
< n_targets
&& !bSelf
; ++i
)
1112 gchar
* pName
= gdk_atom_name(targets
[i
]);
1113 if (strcmp(pName
, sTunnel
.getStr()) == 0)
1123 m_nOwnerChangedSignalId
= g_signal_connect(clipboard
, "owner-change",
1124 G_CALLBACK(handle_owner_change
), this);
1129 //null out m_aContents to return control to the system-one which
1130 //will be retrieved if getContents is called again
1131 setContents(Reference
<css::datatransfer::XTransferable
>(),
1132 Reference
<css::datatransfer::clipboard::XClipboardOwner
>());
1136 void VclGtkClipboard::ClipboardClear()
1138 if (m_pSetClipboardEvent
)
1140 Application::RemoveUserEvent(m_pSetClipboardEvent
);
1141 m_pSetClipboardEvent
= nullptr;
1143 #if !GTK_CHECK_VERSION(4, 0, 0)
1144 for (auto &a
: m_aGtkTargets
)
1147 m_aGtkTargets
.clear();
1150 #if GTK_CHECK_VERSION(4, 0, 0)
1151 IMPL_LINK_NOARG(VclGtkClipboard
, DetachClipboard
, void*, void)
1156 OString
VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor
& rFlavor
)
1158 OString aEntry
= OUStringToOString(rFlavor
.MimeType
, RTL_TEXTENCODING_UTF8
);
1159 auto it
= std::find_if(aInfoToFlavor
.begin(), aInfoToFlavor
.end(),
1160 DataFlavorEq(rFlavor
));
1161 if (it
== aInfoToFlavor
.end())
1162 aInfoToFlavor
.push_back(rFlavor
);
1166 GtkTargetEntry
VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor
& rFlavor
)
1168 GtkTargetEntry aEntry
;
1170 g_strdup(OUStringToOString(rFlavor
.MimeType
, RTL_TEXTENCODING_UTF8
).getStr());
1172 auto it
= std::find_if(aInfoToFlavor
.begin(), aInfoToFlavor
.end(),
1173 DataFlavorEq(rFlavor
));
1174 if (it
!= aInfoToFlavor
.end())
1175 aEntry
.info
= std::distance(aInfoToFlavor
.begin(), it
);
1178 aEntry
.info
= aInfoToFlavor
.size();
1179 aInfoToFlavor
.push_back(rFlavor
);
1185 #if GTK_CHECK_VERSION(4, 0, 0)
1189 void write_mime_type_done(GObject
* pStream
, GAsyncResult
* pResult
, gpointer pTaskPtr
)
1191 GTask
* pTask
= static_cast<GTask
*>(pTaskPtr
);
1193 GError
* pError
= nullptr;
1194 if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(pStream
),
1195 pResult
, nullptr, &pError
))
1197 g_task_return_error(pTask
, pError
);
1201 g_task_return_boolean(pTask
, true);
1204 g_object_unref(pTask
);
1210 const OUString
& m_rMimeType
;
1212 explicit MimeTypeEq(const OUString
& rMimeType
) : m_rMimeType(rMimeType
) {}
1213 bool operator() (const css::datatransfer::DataFlavor
& rData
) const
1215 return rData
.MimeType
== m_rMimeType
;
1220 void VclToGtkHelper::setSelectionData(const Reference
<css::datatransfer::XTransferable
> &rTrans
,
1221 GdkContentProvider
* provider
,
1222 const char* mime_type
,
1223 GOutputStream
* stream
,
1225 GCancellable
* cancellable
,
1226 GAsyncReadyCallback callback
,
1229 GTask
*task
= g_task_new(provider
, cancellable
, callback
, user_data
);
1230 g_task_set_priority(task
, io_priority
);
1232 OUString
sMimeType(mime_type
, strlen(mime_type
), RTL_TEXTENCODING_UTF8
);
1234 auto it
= std::find_if(aInfoToFlavor
.begin(), aInfoToFlavor
.end(),
1235 MimeTypeEq(sMimeType
));
1236 if (it
== aInfoToFlavor
.end())
1238 SAL_WARN( "vcl.gtk", "unknown mime-type request from clipboard");
1239 g_task_return_new_error(task
, G_IO_ERROR
, G_IO_ERROR_NOT_SUPPORTED
,
1240 "unknown mime-type “%s” request from clipboard", mime_type
);
1241 g_object_unref(task
);
1245 css::datatransfer::DataFlavor
aFlavor(*it
);
1246 if (aFlavor
.MimeType
== "UTF8_STRING" || aFlavor
.MimeType
== "STRING")
1247 aFlavor
.MimeType
= "text/plain;charset=utf-8";
1249 Sequence
<sal_Int8
> aData
;
1254 aValue
= rTrans
->getTransferData(aFlavor
);
1260 if (aValue
.getValueTypeClass() == TypeClass_STRING
)
1264 aData
= Sequence
< sal_Int8
>( reinterpret_cast<sal_Int8
const *>(aString
.getStr()), aString
.getLength() * sizeof( sal_Unicode
) );
1266 else if (aValue
.getValueType() == cppu::UnoType
<Sequence
< sal_Int8
>>::get())
1270 else if (aFlavor
.MimeType
== "text/plain;charset=utf-8")
1272 //didn't have utf-8, try utf-16 and convert
1273 aFlavor
.MimeType
= "text/plain;charset=utf-16";
1274 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
1277 aValue
= rTrans
->getTransferData(aFlavor
);
1284 OString
aUTF8String(OUStringToOString(aString
, RTL_TEXTENCODING_UTF8
));
1286 g_output_stream_write_all_async(stream
, aUTF8String
.getStr(), aUTF8String
.getLength(),
1287 io_priority
, cancellable
, write_mime_type_done
, task
);
1291 g_output_stream_write_all_async(stream
, aData
.getArray(), aData
.getLength(),
1292 io_priority
, cancellable
, write_mime_type_done
, task
);
1295 void VclToGtkHelper::setSelectionData(const Reference
<css::datatransfer::XTransferable
> &rTrans
,
1296 GtkSelectionData
*selection_data
, guint info
)
1298 GdkAtom
type(gdk_atom_intern(OUStringToOString(aInfoToFlavor
[info
].MimeType
,
1299 RTL_TEXTENCODING_UTF8
).getStr(),
1302 css::datatransfer::DataFlavor
aFlavor(aInfoToFlavor
[info
]);
1303 if (aFlavor
.MimeType
== "UTF8_STRING" || aFlavor
.MimeType
== "STRING")
1304 aFlavor
.MimeType
= "text/plain;charset=utf-8";
1306 Sequence
<sal_Int8
> aData
;
1311 aValue
= rTrans
->getTransferData(aFlavor
);
1317 if (aValue
.getValueTypeClass() == TypeClass_STRING
)
1321 aData
= Sequence
< sal_Int8
>( reinterpret_cast<sal_Int8
const *>(aString
.getStr()), aString
.getLength() * sizeof( sal_Unicode
) );
1323 else if (aValue
.getValueType() == cppu::UnoType
<Sequence
< sal_Int8
>>::get())
1327 else if (aFlavor
.MimeType
== "text/plain;charset=utf-8")
1329 //didn't have utf-8, try utf-16 and convert
1330 aFlavor
.MimeType
= "text/plain;charset=utf-16";
1331 aFlavor
.DataType
= cppu::UnoType
<OUString
>::get();
1334 aValue
= rTrans
->getTransferData(aFlavor
);
1341 OString
aUTF8String(OUStringToOString(aString
, RTL_TEXTENCODING_UTF8
));
1342 gtk_selection_data_set(selection_data
, type
, 8,
1343 reinterpret_cast<const guchar
*>(aUTF8String
.getStr()),
1344 aUTF8String
.getLength());
1348 gtk_selection_data_set(selection_data
, type
, 8,
1349 reinterpret_cast<const guchar
*>(aData
.getArray()),
1354 VclGtkClipboard::VclGtkClipboard(SelectionType eSelection
)
1355 : cppu::WeakComponentImplHelper
<datatransfer::clipboard::XSystemClipboard
,
1356 datatransfer::clipboard::XFlushableClipboard
, XServiceInfo
>
1358 , m_eSelection(eSelection
)
1359 , m_pSetClipboardEvent(nullptr)
1360 #if GTK_CHECK_VERSION(4, 0, 0)
1361 , m_pClipboardContent(nullptr)
1364 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
1365 #if GTK_CHECK_VERSION(4, 0, 0)
1366 m_nOwnerChangedSignalId
= g_signal_connect(clipboard
, "changed",
1367 G_CALLBACK(handle_owner_change
), this);
1369 m_nOwnerChangedSignalId
= g_signal_connect(clipboard
, "owner-change",
1370 G_CALLBACK(handle_owner_change
), this);
1374 void VclGtkClipboard::flushClipboard()
1376 #if !GTK_CHECK_VERSION(4, 0, 0)
1377 SolarMutexGuard aGuard
;
1379 if (m_eSelection
!= SELECTION_CLIPBOARD
)
1382 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
1383 gtk_clipboard_store(clipboard
);
1387 VclGtkClipboard::~VclGtkClipboard()
1389 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
1390 g_signal_handler_disconnect(clipboard
, m_nOwnerChangedSignalId
);
1391 if (!m_aGtkTargets
.empty())
1393 #if GTK_CHECK_VERSION(4, 0, 0)
1394 gdk_clipboard_set_content(clipboard
, nullptr);
1395 m_pClipboardContent
= nullptr;
1397 gtk_clipboard_clear(clipboard
);
1401 assert(!m_pSetClipboardEvent
);
1402 assert(m_aGtkTargets
.empty());
1405 #if GTK_CHECK_VERSION(4, 0, 0)
1406 std::vector
<OString
> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence
<css::datatransfer::DataFlavor
> &rFormats
)
1408 std::vector
<GtkTargetEntry
> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence
<css::datatransfer::DataFlavor
> &rFormats
)
1411 #if GTK_CHECK_VERSION(4, 0, 0)
1412 std::vector
<OString
> aGtkTargets
;
1414 std::vector
<GtkTargetEntry
> aGtkTargets
;
1417 bool bHaveText(false), bHaveUTF8(false);
1418 for (const css::datatransfer::DataFlavor
& rFlavor
: rFormats
)
1420 sal_Int32
nIndex(0);
1421 if (o3tl::getToken(rFlavor
.MimeType
, 0, ';', nIndex
) == u
"text/plain")
1424 std::u16string_view
aToken(o3tl::getToken(rFlavor
.MimeType
, 0, ';', nIndex
));
1425 if (aToken
== u
"charset=utf-8")
1430 aGtkTargets
.push_back(makeGtkTargetEntry(rFlavor
));
1435 css::datatransfer::DataFlavor aFlavor
;
1436 aFlavor
.DataType
= cppu::UnoType
<Sequence
< sal_Int8
>>::get();
1439 aFlavor
.MimeType
= "text/plain;charset=utf-8";
1440 aGtkTargets
.push_back(makeGtkTargetEntry(aFlavor
));
1442 aFlavor
.MimeType
= "UTF8_STRING";
1443 aGtkTargets
.push_back(makeGtkTargetEntry(aFlavor
));
1444 aFlavor
.MimeType
= "STRING";
1445 aGtkTargets
.push_back(makeGtkTargetEntry(aFlavor
));
1451 IMPL_LINK_NOARG(VclGtkClipboard
, AsyncSetGtkClipboard
, void*, void)
1453 osl::ClearableMutexGuard
aGuard( m_aMutex
);
1454 m_pSetClipboardEvent
= nullptr;
1458 void VclGtkClipboard::SyncGtkClipboard()
1460 osl::ClearableMutexGuard
aGuard(m_aMutex
);
1461 if (m_pSetClipboardEvent
)
1463 Application::RemoveUserEvent(m_pSetClipboardEvent
);
1464 m_pSetClipboardEvent
= nullptr;
1469 void VclGtkClipboard::SetGtkClipboard()
1471 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
1472 #if GTK_CHECK_VERSION(4, 0, 0)
1473 m_pClipboardContent
= TRANSFERABLE_CONTENT(transerable_content_new(&m_aConversionHelper
, m_aContents
.get()));
1474 transerable_content_set_detach_clipboard_link(m_pClipboardContent
, LINK(this, VclGtkClipboard
, DetachClipboard
));
1475 gdk_clipboard_set_content(clipboard
, GDK_CONTENT_PROVIDER(m_pClipboardContent
));
1477 gtk_clipboard_set_with_data(clipboard
, m_aGtkTargets
.data(), m_aGtkTargets
.size(),
1478 ClipboardGetFunc
, ClipboardClearFunc
, this);
1479 gtk_clipboard_set_can_store(clipboard
, m_aGtkTargets
.data(), m_aGtkTargets
.size());
1483 void VclGtkClipboard::setContents(
1484 const Reference
< css::datatransfer::XTransferable
>& xTrans
,
1485 const Reference
< css::datatransfer::clipboard::XClipboardOwner
>& xClipboardOwner
)
1487 css::uno::Sequence
<css::datatransfer::DataFlavor
> aFormats
;
1490 aFormats
= xTrans
->getTransferDataFlavors();
1493 osl::ClearableMutexGuard
aGuard( m_aMutex
);
1494 Reference
< datatransfer::clipboard::XClipboardOwner
> xOldOwner( m_aOwner
);
1495 Reference
< datatransfer::XTransferable
> xOldContents( m_aContents
);
1496 m_aContents
= xTrans
;
1497 #if GTK_CHECK_VERSION(4, 0, 0)
1498 if (m_pClipboardContent
)
1499 transerable_content_set_transferable(m_pClipboardContent
, m_aContents
.get());
1501 m_aOwner
= xClipboardOwner
;
1503 std::vector
< Reference
< datatransfer::clipboard::XClipboardListener
> > aListeners( m_aListeners
);
1504 datatransfer::clipboard::ClipboardEvent aEv
;
1506 GdkClipboard
* clipboard
= clipboard_get(m_eSelection
);
1507 if (!m_aGtkTargets
.empty())
1509 #if GTK_CHECK_VERSION(4, 0, 0)
1510 gdk_clipboard_set_content(clipboard
, nullptr);
1511 m_pClipboardContent
= nullptr;
1513 gtk_clipboard_clear(clipboard
);
1517 assert(m_aGtkTargets
.empty());
1518 if (m_aContents
.is())
1520 #if GTK_CHECK_VERSION(4, 0, 0)
1521 std::vector
<OString
> aGtkTargets(m_aConversionHelper
.FormatsToGtk(aFormats
));
1523 std::vector
<GtkTargetEntry
> aGtkTargets(m_aConversionHelper
.FormatsToGtk(aFormats
));
1525 if (!aGtkTargets
.empty())
1527 #if !GTK_CHECK_VERSION(4, 0, 0)
1528 GtkTargetEntry aEntry
;
1529 OString sTunnel
= "application/x-libreoffice-internal-id-" + getPID();
1530 aEntry
.target
= g_strdup(sTunnel
.getStr());
1533 aGtkTargets
.push_back(aEntry
);
1535 m_aGtkTargets
= aGtkTargets
;
1537 if (!m_pSetClipboardEvent
)
1538 m_pSetClipboardEvent
= Application::PostUserEvent(LINK(this, VclGtkClipboard
, AsyncSetGtkClipboard
));
1542 aEv
.Contents
= getContents();
1546 if (xOldOwner
.is() && xOldOwner
!= xClipboardOwner
)
1547 xOldOwner
->lostOwnership( this, xOldContents
);
1548 for (auto const& listener
: aListeners
)
1550 listener
->changedContents( aEv
);
1554 OUString
VclGtkClipboard::getName()
1556 return (m_eSelection
== SELECTION_CLIPBOARD
) ? OUString("CLIPBOARD") : OUString("PRIMARY");
1559 sal_Int8
VclGtkClipboard::getRenderingCapabilities()
1564 void VclGtkClipboard::addClipboardListener( const Reference
< datatransfer::clipboard::XClipboardListener
>& listener
)
1566 osl::ClearableMutexGuard
aGuard( m_aMutex
);
1568 m_aListeners
.push_back( listener
);
1571 void VclGtkClipboard::removeClipboardListener( const Reference
< datatransfer::clipboard::XClipboardListener
>& listener
)
1573 osl::ClearableMutexGuard
aGuard( m_aMutex
);
1575 m_aListeners
.erase(std::remove(m_aListeners
.begin(), m_aListeners
.end(), listener
), m_aListeners
.end());
1578 Reference
< XInterface
> GtkInstance::CreateClipboard(const Sequence
< Any
>& arguments
)
1580 if ( IsRunningUnitTest() )
1581 return SalInstance::CreateClipboard( arguments
);
1584 if (!arguments
.hasElements()) {
1586 } else if (arguments
.getLength() != 1 || !(arguments
[0] >>= sel
)) {
1587 throw css::lang::IllegalArgumentException(
1588 "bad GtkInstance::CreateClipboard arguments",
1589 css::uno::Reference
<css::uno::XInterface
>(), -1);
1592 SelectionType eSelection
= (sel
== "CLIPBOARD") ? SELECTION_CLIPBOARD
: SELECTION_PRIMARY
;
1594 if (m_aClipboards
[eSelection
].is())
1595 return m_aClipboards
[eSelection
];
1597 Reference
<XInterface
> xClipboard(static_cast<cppu::OWeakObject
*>(new VclGtkClipboard(eSelection
)));
1598 m_aClipboards
[eSelection
] = xClipboard
;
1602 GtkInstDropTarget::GtkInstDropTarget()
1603 : WeakComponentImplHelper(m_aMutex
)
1605 , m_pFormatConversionRequest(nullptr)
1607 #if !GTK_CHECK_VERSION(4, 0, 0)
1610 , m_nDefaultActions(0)
1614 OUString SAL_CALL
GtkInstDropTarget::getImplementationName()
1616 return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
1619 sal_Bool SAL_CALL
GtkInstDropTarget::supportsService(OUString
const & ServiceName
)
1621 return cppu::supportsService(this, ServiceName
);
1624 css::uno::Sequence
<OUString
> SAL_CALL
GtkInstDropTarget::getSupportedServiceNames()
1626 Sequence
<OUString
> aRet
{ "com.sun.star.datatransfer.dnd.GtkDropTarget" };
1630 GtkInstDropTarget::~GtkInstDropTarget()
1633 m_pFrame
->deregisterDropTarget(this);
1636 void GtkInstDropTarget::deinitialize()
1642 void GtkInstDropTarget::initialize(const Sequence
<Any
>& rArguments
)
1644 if (rArguments
.getLength() < 2)
1646 throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
1647 static_cast<OWeakObject
*>(this));
1650 sal_IntPtr nFrame
= 0;
1651 rArguments
.getConstArray()[1] >>= nFrame
;
1655 throw RuntimeException("DropTarget::initialize: missing SalFrame",
1656 static_cast<OWeakObject
*>(this));
1659 m_pFrame
= reinterpret_cast<GtkSalFrame
*>(nFrame
);
1660 m_pFrame
->registerDropTarget(this);
1664 void GtkInstDropTarget::addDropTargetListener( const Reference
< css::datatransfer::dnd::XDropTargetListener
>& xListener
)
1666 ::osl::Guard
< ::osl::Mutex
> aGuard( m_aMutex
);
1668 m_aListeners
.push_back( xListener
);
1671 void GtkInstDropTarget::removeDropTargetListener( const Reference
< css::datatransfer::dnd::XDropTargetListener
>& xListener
)
1673 ::osl::Guard
< ::osl::Mutex
> aGuard( m_aMutex
);
1675 m_aListeners
.erase(std::remove(m_aListeners
.begin(), m_aListeners
.end(), xListener
), m_aListeners
.end());
1678 void GtkInstDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent
& dtde
)
1680 osl::ClearableGuard
<osl::Mutex
> aGuard( m_aMutex
);
1681 std::vector
<css::uno::Reference
<css::datatransfer::dnd::XDropTargetListener
>> aListeners(m_aListeners
);
1684 for (auto const& listener
: aListeners
)
1686 listener
->drop( dtde
);
1690 void GtkInstDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent
& dtde
)
1692 osl::ClearableGuard
< ::osl::Mutex
> aGuard( m_aMutex
);
1693 std::vector
<css::uno::Reference
<css::datatransfer::dnd::XDropTargetListener
>> aListeners(m_aListeners
);
1696 for (auto const& listener
: aListeners
)
1698 listener
->dragEnter( dtde
);
1702 void GtkInstDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent
& dtde
)
1704 osl::ClearableGuard
< ::osl::Mutex
> aGuard( m_aMutex
);
1705 std::vector
<css::uno::Reference
<css::datatransfer::dnd::XDropTargetListener
>> aListeners(m_aListeners
);
1708 for (auto const& listener
: aListeners
)
1710 listener
->dragOver( dtde
);
1714 void GtkInstDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent
& dte
)
1716 osl::ClearableGuard
< ::osl::Mutex
> aGuard( m_aMutex
);
1717 std::vector
<css::uno::Reference
<css::datatransfer::dnd::XDropTargetListener
>> aListeners(m_aListeners
);
1720 for (auto const& listener
: aListeners
)
1722 listener
->dragExit( dte
);
1726 sal_Bool
GtkInstDropTarget::isActive()
1731 void GtkInstDropTarget::setActive(sal_Bool bActive
)
1733 m_bActive
= bActive
;
1736 sal_Int8
GtkInstDropTarget::getDefaultActions()
1738 return m_nDefaultActions
;
1741 void GtkInstDropTarget::setDefaultActions(sal_Int8 nDefaultActions
)
1743 m_nDefaultActions
= nDefaultActions
;
1746 Reference
<XInterface
> GtkInstance::ImplCreateDropTarget(const SystemEnvData
* pSysEnv
)
1748 return vcl::X11DnDHelper(new GtkInstDropTarget(), pSysEnv
->aShellWindow
);
1751 GtkInstDragSource::~GtkInstDragSource()
1754 m_pFrame
->deregisterDragSource(this);
1756 if (GtkInstDragSource::g_ActiveDragSource
== this)
1758 SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkInstDragSource before dtor");
1759 GtkInstDragSource::g_ActiveDragSource
= nullptr;
1763 void GtkInstDragSource::deinitialize()
1768 sal_Bool
GtkInstDragSource::isDragImageSupported()
1773 sal_Int32
GtkInstDragSource::getDefaultCursor( sal_Int8
)
1778 void GtkInstDragSource::initialize(const css::uno::Sequence
<css::uno::Any
>& rArguments
)
1780 if (rArguments
.getLength() < 2)
1782 throw RuntimeException("DragSource::initialize: Cannot install window event handler",
1783 static_cast<OWeakObject
*>(this));
1786 sal_IntPtr nFrame
= 0;
1787 rArguments
.getConstArray()[1] >>= nFrame
;
1791 throw RuntimeException("DragSource::initialize: missing SalFrame",
1792 static_cast<OWeakObject
*>(this));
1795 m_pFrame
= reinterpret_cast<GtkSalFrame
*>(nFrame
);
1796 m_pFrame
->registerDragSource(this);
1799 OUString SAL_CALL
GtkInstDragSource::getImplementationName()
1801 return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
1804 sal_Bool SAL_CALL
GtkInstDragSource::supportsService(OUString
const & ServiceName
)
1806 return cppu::supportsService(this, ServiceName
);
1809 css::uno::Sequence
<OUString
> SAL_CALL
GtkInstDragSource::getSupportedServiceNames()
1811 Sequence
<OUString
> aRet
{ "com.sun.star.datatransfer.dnd.GtkDragSource" };
1815 Reference
<XInterface
> GtkInstance::ImplCreateDragSource(const SystemEnvData
* pSysEnv
)
1817 return vcl::X11DnDHelper(new GtkInstDragSource(), pSysEnv
->aShellWindow
);
1822 class GtkOpenGLContext
: public OpenGLContext
1825 GtkWidget
*m_pGLArea
;
1826 GdkGLContext
*m_pContext
;
1827 gulong m_nDestroySignalId
;
1828 gulong m_nRenderSignalId
;
1829 guint m_nAreaFrameBuffer
;
1830 guint m_nFrameBuffer
;
1831 guint m_nRenderBuffer
;
1832 guint m_nDepthBuffer
;
1833 guint m_nFrameScratchBuffer
;
1834 guint m_nRenderScratchBuffer
;
1835 guint m_nDepthScratchBuffer
;
1839 : m_pGLArea(nullptr)
1840 , m_pContext(nullptr)
1841 , m_nDestroySignalId(0)
1842 , m_nRenderSignalId(0)
1843 , m_nAreaFrameBuffer(0)
1845 , m_nRenderBuffer(0)
1847 , m_nFrameScratchBuffer(0)
1848 , m_nRenderScratchBuffer(0)
1849 , m_nDepthScratchBuffer(0)
1853 virtual void initWindow() override
1855 if( !m_pChildWindow
)
1857 SystemWindowData winData
= generateWinData(mpWindow
, mbRequestLegacyContext
);
1858 m_pChildWindow
= VclPtr
<SystemChildWindow
>::Create(mpWindow
, 0, &winData
, false);
1863 InitChildWindow(m_pChildWindow
.get());
1868 virtual const GLWindow
& getOpenGLWindow() const override
{ return m_aGLWin
; }
1869 virtual GLWindow
& getModifiableOpenGLWindow() override
{ return m_aGLWin
; }
1871 static void signalDestroy(GtkWidget
*, gpointer context
)
1873 GtkOpenGLContext
* pThis
= static_cast<GtkOpenGLContext
*>(context
);
1874 pThis
->m_pGLArea
= nullptr;
1875 pThis
->m_nDestroySignalId
= 0;
1876 pThis
->m_nRenderSignalId
= 0;
1879 static gboolean
signalRender(GtkGLArea
*, GdkGLContext
*, gpointer window
)
1881 GtkOpenGLContext
* pThis
= static_cast<GtkOpenGLContext
*>(window
);
1883 int scale
= gtk_widget_get_scale_factor(pThis
->m_pGLArea
);
1884 int width
= pThis
->m_aGLWin
.Width
* scale
;
1885 int height
= pThis
->m_aGLWin
.Height
* scale
;
1887 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT
);
1889 glBindFramebuffer(GL_READ_FRAMEBUFFER
, pThis
->m_nAreaFrameBuffer
);
1890 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT
);
1892 glBlitFramebuffer(0, 0, width
, height
, 0, 0, width
, height
,
1893 GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
, GL_NEAREST
);
1895 gdk_gl_context_make_current(pThis
->m_pContext
);
1899 virtual void adjustToNewSize() override
1904 int scale
= gtk_widget_get_scale_factor(m_pGLArea
);
1905 int width
= m_aGLWin
.Width
* scale
;
1906 int height
= m_aGLWin
.Height
* scale
;
1908 // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
1909 int allocwidth
= std::max(width
, 1);
1910 int allocheight
= std::max(height
, 1);
1912 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea
));
1913 if (GError
*pError
= gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea
)))
1915 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError
->message
);
1919 glBindRenderbuffer(GL_RENDERBUFFER
, m_nRenderBuffer
);
1920 glRenderbufferStorage(GL_RENDERBUFFER
, GL_RGB8
, allocwidth
, allocheight
);
1921 glBindRenderbuffer(GL_RENDERBUFFER
, m_nDepthBuffer
);
1922 glRenderbufferStorage(GL_RENDERBUFFER
, GL_DEPTH_COMPONENT24
, allocwidth
, allocheight
);
1923 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_nAreaFrameBuffer
);
1925 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
,
1926 GL_RENDERBUFFER_EXT
, m_nRenderBuffer
);
1927 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_DEPTH_ATTACHMENT_EXT
,
1928 GL_RENDERBUFFER_EXT
, m_nDepthBuffer
);
1930 gdk_gl_context_make_current(m_pContext
);
1931 glBindRenderbuffer(GL_RENDERBUFFER
, m_nRenderBuffer
);
1932 glBindRenderbuffer(GL_RENDERBUFFER
, m_nDepthBuffer
);
1933 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_nFrameBuffer
);
1935 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
,
1936 GL_RENDERBUFFER_EXT
, m_nRenderBuffer
);
1937 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_DEPTH_ATTACHMENT_EXT
,
1938 GL_RENDERBUFFER_EXT
, m_nDepthBuffer
);
1939 glViewport(0, 0, width
, height
);
1941 glBindRenderbuffer(GL_RENDERBUFFER
, m_nRenderScratchBuffer
);
1942 glRenderbufferStorage(GL_RENDERBUFFER
, GL_RGB8
, allocwidth
, allocheight
);
1943 glBindRenderbuffer(GL_RENDERBUFFER
, m_nDepthScratchBuffer
);
1944 glRenderbufferStorage(GL_RENDERBUFFER
, GL_DEPTH_COMPONENT24
, allocwidth
, allocheight
);
1945 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_nFrameScratchBuffer
);
1947 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
,
1948 GL_RENDERBUFFER_EXT
, m_nRenderScratchBuffer
);
1949 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_DEPTH_ATTACHMENT_EXT
,
1950 GL_RENDERBUFFER_EXT
, m_nDepthScratchBuffer
);
1952 glViewport(0, 0, width
, height
);
1955 // Use a throw away toplevel to determine the OpenGL version because once
1956 // an GdkGLContext is created for a window then it seems that
1957 // glGenVertexArrays will always be called when the window gets rendered.
1958 static int GetOpenGLVersion()
1960 int nMajorGLVersion(0);
1963 #if !GTK_CHECK_VERSION(4,0,0)
1964 pWindow
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1966 pWindow
= gtk_window_new();
1969 gtk_widget_realize(pWindow
);
1971 if (GdkSurface
* pSurface
= widget_get_surface(pWindow
))
1973 if (GdkGLContext
* pContext
= surface_create_gl_context(pSurface
))
1975 if (gdk_gl_context_realize(pContext
, nullptr))
1978 gdk_gl_context_make_current(pContext
);
1979 gdk_gl_context_get_version(pContext
, &nMajorGLVersion
, nullptr);
1980 gdk_gl_context_clear_current();
1982 g_object_unref(pContext
);
1986 #if !GTK_CHECK_VERSION(4,0,0)
1987 gtk_widget_destroy(pWindow
);
1989 gtk_window_destroy(GTK_WINDOW(pWindow
));
1991 return nMajorGLVersion
;
1994 virtual bool ImplInit() override
1996 static int nOpenGLVersion
= GetOpenGLVersion();
1997 if (nOpenGLVersion
< 3)
1999 SAL_WARN("vcl.gtk", "gtk GL requires glGenVertexArrays which is OpenGL 3, while system provides: " << nOpenGLVersion
);
2003 const SystemEnvData
* pEnvData
= m_pChildWindow
->GetSystemData();
2004 GtkWidget
*pParent
= static_cast<GtkWidget
*>(pEnvData
->pWidget
);
2005 m_pGLArea
= gtk_gl_area_new();
2006 m_nDestroySignalId
= g_signal_connect(G_OBJECT(m_pGLArea
), "destroy", G_CALLBACK(signalDestroy
), this);
2007 m_nRenderSignalId
= g_signal_connect(G_OBJECT(m_pGLArea
), "render", G_CALLBACK(signalRender
), this);
2008 gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea
), true);
2009 gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea
), false);
2010 gtk_widget_set_hexpand(m_pGLArea
, true);
2011 gtk_widget_set_vexpand(m_pGLArea
, true);
2012 #if !GTK_CHECK_VERSION(4, 0, 0)
2013 gtk_container_add(GTK_CONTAINER(pParent
), m_pGLArea
);
2014 gtk_widget_show_all(pParent
);
2016 gtk_grid_attach(GTK_GRID(pParent
), m_pGLArea
, 0, 0, 1, 1);
2017 gtk_widget_show(pParent
);
2018 gtk_widget_show(m_pGLArea
);
2021 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea
));
2022 if (GError
*pError
= gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea
)))
2024 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError
->message
);
2028 gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea
));
2029 glGenFramebuffersEXT(1, &m_nAreaFrameBuffer
);
2031 GdkSurface
* pWindow
= widget_get_surface(pParent
);
2032 m_pContext
= surface_create_gl_context(pWindow
);
2036 if (!gdk_gl_context_realize(m_pContext
, nullptr))
2039 gdk_gl_context_make_current(m_pContext
);
2040 glGenFramebuffersEXT(1, &m_nFrameBuffer
);
2041 glGenRenderbuffersEXT(1, &m_nRenderBuffer
);
2042 glGenRenderbuffersEXT(1, &m_nDepthBuffer
);
2043 glGenFramebuffersEXT(1, &m_nFrameScratchBuffer
);
2044 glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer
);
2045 glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer
);
2047 bool bRet
= InitGL();
2052 virtual void restoreDefaultFramebuffer() override
2054 OpenGLContext::restoreDefaultFramebuffer();
2055 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_nFrameScratchBuffer
);
2056 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
,
2057 GL_RENDERBUFFER_EXT
, m_nRenderScratchBuffer
);
2060 virtual void makeCurrent() override
2069 int scale
= gtk_widget_get_scale_factor(m_pGLArea
);
2070 int width
= m_aGLWin
.Width
* scale
;
2071 int height
= m_aGLWin
.Height
* scale
;
2073 gdk_gl_context_make_current(m_pContext
);
2075 glBindRenderbuffer(GL_RENDERBUFFER
, m_nRenderScratchBuffer
);
2076 glBindRenderbuffer(GL_RENDERBUFFER
, m_nDepthScratchBuffer
);
2077 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, m_nFrameScratchBuffer
);
2078 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
,
2079 GL_RENDERBUFFER_EXT
, m_nRenderScratchBuffer
);
2080 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT
, GL_DEPTH_ATTACHMENT_EXT
,
2081 GL_RENDERBUFFER_EXT
, m_nDepthScratchBuffer
);
2082 glViewport(0, 0, width
, height
);
2085 registerAsCurrent();
2088 virtual void destroyCurrentContext() override
2090 gdk_gl_context_clear_current();
2093 virtual bool isCurrent() override
2095 return m_pGLArea
&& gdk_gl_context_get_current() == m_pContext
;
2098 virtual void sync() override
2102 virtual void resetCurrent() override
2105 gdk_gl_context_clear_current();
2108 virtual void swapBuffers() override
2110 int scale
= gtk_widget_get_scale_factor(m_pGLArea
);
2111 int width
= m_aGLWin
.Width
* scale
;
2112 int height
= m_aGLWin
.Height
* scale
;
2114 glBindFramebuffer(GL_DRAW_FRAMEBUFFER
, m_nFrameBuffer
);
2115 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT
);
2117 glBindFramebuffer(GL_READ_FRAMEBUFFER
, m_nFrameScratchBuffer
);
2118 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT
);
2120 glBlitFramebuffer(0, 0, width
, height
, 0, 0, width
, height
,
2121 GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
, GL_NEAREST
);
2123 glBindFramebuffer(GL_DRAW_FRAMEBUFFER
, m_nFrameScratchBuffer
);
2124 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT
);
2126 gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea
));
2130 virtual ~GtkOpenGLContext() override
2132 if (m_nDestroySignalId
)
2133 g_signal_handler_disconnect(m_pGLArea
, m_nDestroySignalId
);
2134 if (m_nRenderSignalId
)
2135 g_signal_handler_disconnect(m_pGLArea
, m_nRenderSignalId
);
2137 g_clear_object(&m_pContext
);
2143 OpenGLContext
* GtkInstance::CreateOpenGLContext()
2145 return new GtkOpenGLContext
;
2148 // tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
2149 bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay
* pDisplay
)
2151 static auto get_type
= reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
2154 static bool bResult
= G_TYPE_CHECK_INSTANCE_TYPE(pDisplay
, get_type());
2158 bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay
* pDisplay
)
2160 static auto get_type
= reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
2163 static bool bResult
= G_TYPE_CHECK_INSTANCE_TYPE(pDisplay
, get_type());
2170 class GtkInstanceBuilder
;
2172 void set_help_id(const GtkWidget
*pWidget
, const OString
& rHelpId
)
2174 gchar
*helpid
= g_strdup(rHelpId
.getStr());
2175 g_object_set_data_full(G_OBJECT(pWidget
), "g-lo-helpid", helpid
, g_free
);
2178 OString
get_help_id(const GtkWidget
*pWidget
)
2180 void* pData
= g_object_get_data(G_OBJECT(pWidget
), "g-lo-helpid");
2181 const gchar
* pStr
= static_cast<const gchar
*>(pData
);
2182 return OString(pStr
, pStr
? strlen(pStr
) : 0);
2185 KeyEvent
CreateKeyEvent(guint keyval
, guint16 hardware_keycode
, guint state
, guint8 group
)
2187 sal_uInt16 nKeyCode
= GtkSalFrame::GetKeyCode(keyval
);
2188 #if !GTK_CHECK_VERSION(4, 0, 0)
2191 guint updated_keyval
= GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), hardware_keycode
, group
);
2192 nKeyCode
= GtkSalFrame::GetKeyCode(updated_keyval
);
2195 (void)hardware_keycode
;
2198 nKeyCode
|= GtkSalFrame::GetKeyModCode(state
);
2199 return KeyEvent(gdk_keyval_to_unicode(keyval
), nKeyCode
, 0);
2202 #if !GTK_CHECK_VERSION(4, 0, 0)
2203 KeyEvent
GtkToVcl(const GdkEventKey
& rEvent
)
2205 return CreateKeyEvent(rEvent
.keyval
, rEvent
.hardware_keycode
, rEvent
.state
, rEvent
.group
);
2210 static MouseEventModifiers
ImplGetMouseButtonMode(sal_uInt16 nButton
, sal_uInt16 nCode
)
2212 MouseEventModifiers nMode
= MouseEventModifiers::NONE
;
2213 if ( nButton
== MOUSE_LEFT
)
2214 nMode
|= MouseEventModifiers::SIMPLECLICK
;
2215 if ( (nButton
== MOUSE_LEFT
) && !(nCode
& (MOUSE_MIDDLE
| MOUSE_RIGHT
)) )
2216 nMode
|= MouseEventModifiers::SELECT
;
2217 if ( (nButton
== MOUSE_LEFT
) && (nCode
& KEY_MOD1
) &&
2218 !(nCode
& (MOUSE_MIDDLE
| MOUSE_RIGHT
| KEY_SHIFT
)) )
2219 nMode
|= MouseEventModifiers::MULTISELECT
;
2220 if ( (nButton
== MOUSE_LEFT
) && (nCode
& KEY_SHIFT
) &&
2221 !(nCode
& (MOUSE_MIDDLE
| MOUSE_RIGHT
| KEY_MOD1
)) )
2222 nMode
|= MouseEventModifiers::RANGESELECT
;
2226 static MouseEventModifiers
ImplGetMouseMoveMode(sal_uInt16 nCode
)
2228 MouseEventModifiers nMode
= MouseEventModifiers::NONE
;
2230 nMode
|= MouseEventModifiers::SIMPLEMOVE
;
2231 if ( (nCode
& MOUSE_LEFT
) && !(nCode
& KEY_MOD1
) )
2232 nMode
|= MouseEventModifiers::DRAGMOVE
;
2233 if ( (nCode
& MOUSE_LEFT
) && (nCode
& KEY_MOD1
) )
2234 nMode
|= MouseEventModifiers::DRAGCOPY
;
2240 bool SwapForRTL(GtkWidget
* pWidget
)
2242 GtkTextDirection eDir
= gtk_widget_get_direction(pWidget
);
2243 if (eDir
== GTK_TEXT_DIR_RTL
)
2245 if (eDir
== GTK_TEXT_DIR_LTR
)
2247 return AllSettings::GetLayoutRTL();
2250 GtkWidget
* getPopupRect(GtkWidget
* pWidget
, const tools::Rectangle
& rInRect
, GdkRectangle
& rOutRect
)
2252 if (GtkSalFrame
* pFrame
= GtkSalFrame::getFromWindow(pWidget
))
2254 // this is the relatively unusual case where pParent is the toplevel GtkSalFrame and not a stock GtkWidget
2255 // so use the same style of logic as GtkSalMenu::ShowNativePopupMenu to get the right position
2256 tools::Rectangle aFloatRect
= FloatingWindow::ImplConvertToAbsPos(pFrame
->GetWindow(), rInRect
);
2257 aFloatRect
.Move(-pFrame
->maGeometry
.nX
, -pFrame
->maGeometry
.nY
);
2259 rOutRect
= GdkRectangle
{static_cast<int>(aFloatRect
.Left()), static_cast<int>(aFloatRect
.Top()),
2260 static_cast<int>(aFloatRect
.GetWidth()), static_cast<int>(aFloatRect
.GetHeight())};
2262 pWidget
= pFrame
->getMouseEventWidget();
2266 rOutRect
= GdkRectangle
{static_cast<int>(rInRect
.Left()), static_cast<int>(rInRect
.Top()),
2267 static_cast<int>(rInRect
.GetWidth()), static_cast<int>(rInRect
.GetHeight())};
2268 if (SwapForRTL(pWidget
))
2269 rOutRect
.x
= gtk_widget_get_allocated_width(pWidget
) - rOutRect
.width
- 1 - rOutRect
.x
;
2274 void replaceWidget(GtkWidget
* pWidget
, GtkWidget
* pReplacement
)
2276 // remove the widget and replace it with pReplacement
2277 GtkWidget
* pParent
= gtk_widget_get_parent(pWidget
);
2279 // if pWidget was un-parented then don't bother
2283 g_object_ref(pWidget
);
2285 gint
nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
2286 if (GTK_IS_GRID(pParent
))
2288 #if !GTK_CHECK_VERSION(4, 0, 0)
2289 gtk_container_child_get(GTK_CONTAINER(pParent
), pWidget
,
2290 "left-attach", &nLeftAttach
,
2291 "top-attach", &nTopAttach
,
2296 gtk_grid_query_child(GTK_GRID(pParent
), pWidget
,
2297 &nLeftAttach
, &nTopAttach
,
2302 #if !GTK_CHECK_VERSION(4, 0, 0)
2303 gboolean
bExpand(false), bFill(false);
2304 GtkPackType
ePackType(GTK_PACK_START
);
2307 if (GTK_IS_BOX(pParent
))
2309 gtk_container_child_get(GTK_CONTAINER(pParent
), pWidget
,
2312 "pack-type", &ePackType
,
2313 "padding", &nPadding
,
2314 "position", &nPosition
,
2319 #if !GTK_CHECK_VERSION(4, 0, 0)
2320 // for gtk3 remove before replacement inserted, or there are warnings
2321 // from GTK_BIN about having two children
2322 container_remove(pParent
, pWidget
);
2325 gtk_widget_set_visible(pReplacement
, gtk_widget_get_visible(pWidget
));
2326 #if !GTK_CHECK_VERSION(4, 0, 0)
2327 gtk_widget_set_no_show_all(pReplacement
, gtk_widget_get_no_show_all(pWidget
));
2330 int nReqWidth
, nReqHeight
;
2331 gtk_widget_get_size_request(pWidget
, &nReqWidth
, &nReqHeight
);
2332 gtk_widget_set_size_request(pReplacement
, nReqWidth
, nReqHeight
);
2334 static GQuark quark_size_groups
= g_quark_from_static_string("gtk-widget-size-groups");
2335 GSList
* pSizeGroups
= static_cast<GSList
*>(g_object_get_qdata(G_OBJECT(pWidget
), quark_size_groups
));
2338 GtkSizeGroup
*pSizeGroup
= static_cast<GtkSizeGroup
*>(pSizeGroups
->data
);
2339 pSizeGroups
= pSizeGroups
->next
;
2340 gtk_size_group_remove_widget(pSizeGroup
, pWidget
);
2341 gtk_size_group_add_widget(pSizeGroup
, pReplacement
);
2344 // tdf#135368 change the mnemonic to point to our replacement
2345 GList
* pLabels
= gtk_widget_list_mnemonic_labels(pWidget
);
2346 for (GList
* pLabel
= g_list_first(pLabels
); pLabel
; pLabel
= g_list_next(pLabel
))
2348 GtkWidget
* pLabelWidget
= static_cast<GtkWidget
*>(pLabel
->data
);
2349 if (!GTK_IS_LABEL(pLabelWidget
))
2351 gtk_label_set_mnemonic_widget(GTK_LABEL(pLabelWidget
), pReplacement
);
2353 g_list_free(pLabels
);
2356 if (GTK_IS_GRID(pParent
))
2358 gtk_grid_attach(GTK_GRID(pParent
), pReplacement
, nLeftAttach
, nTopAttach
, nWidth
, nHeight
);
2360 else if (GTK_IS_BOX(pParent
))
2362 #if !GTK_CHECK_VERSION(4, 0, 0)
2363 gtk_box_pack_start(GTK_BOX(pParent
), pReplacement
, bExpand
, bFill
, nPadding
);
2364 gtk_container_child_set(GTK_CONTAINER(pParent
), pReplacement
,
2365 "pack-type", ePackType
,
2366 "position", nPosition
,
2369 gtk_box_insert_child_after(GTK_BOX(pParent
), pReplacement
, pWidget
);
2372 #if !GTK_CHECK_VERSION(4, 0, 0)
2374 gtk_container_add(GTK_CONTAINER(pParent
), pReplacement
);
2377 if (gtk_widget_get_hexpand_set(pWidget
))
2378 gtk_widget_set_hexpand(pReplacement
, gtk_widget_get_hexpand(pWidget
));
2380 if (gtk_widget_get_vexpand_set(pWidget
))
2381 gtk_widget_set_vexpand(pReplacement
, gtk_widget_get_vexpand(pWidget
));
2383 gtk_widget_set_halign(pReplacement
, gtk_widget_get_halign(pWidget
));
2384 gtk_widget_set_valign(pReplacement
, gtk_widget_get_valign(pWidget
));
2386 #if GTK_CHECK_VERSION(4, 0, 0)
2387 // for gtk4 remove after replacement inserted so we could use gtk_box_insert_child_after
2388 container_remove(pParent
, pWidget
);
2391 // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
2392 g_object_unref(pWidget
);
2395 void insertAsParent(GtkWidget
* pWidget
, GtkWidget
* pReplacement
)
2397 g_object_ref(pWidget
);
2399 replaceWidget(pWidget
, pReplacement
);
2401 // coverity[pass_freed_arg : FALSE] - pWidget is not freed here due to initial g_object_ref
2402 container_add(pReplacement
, pWidget
);
2404 // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
2405 g_object_unref(pWidget
);
2408 GtkWidget
* ensureEventWidget(GtkWidget
* pWidget
)
2410 #if GTK_CHECK_VERSION(4, 0, 0)
2417 GtkWidget
* pMouseEventBox
;
2418 // not every widget has a GdkWindow and can get any event, so if we
2419 // want an event it doesn't have, insert a GtkEventBox so we can get
2421 if (gtk_widget_get_has_window(pWidget
))
2422 pMouseEventBox
= pWidget
;
2425 // remove the widget and replace it with an eventbox and put the old
2427 pMouseEventBox
= gtk_event_box_new();
2428 gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox
), false);
2429 gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox
), false);
2430 insertAsParent(pWidget
, pMouseEventBox
);
2433 return pMouseEventBox
;
2440 #if !GTK_CHECK_VERSION(4, 0, 0)
2441 GdkDragAction
VclToGdk(sal_Int8 dragOperation
)
2443 GdkDragAction
eRet(static_cast<GdkDragAction
>(0));
2444 if (dragOperation
& css::datatransfer::dnd::DNDConstants::ACTION_COPY
)
2445 eRet
= static_cast<GdkDragAction
>(eRet
| GDK_ACTION_COPY
);
2446 if (dragOperation
& css::datatransfer::dnd::DNDConstants::ACTION_MOVE
)
2447 eRet
= static_cast<GdkDragAction
>(eRet
| GDK_ACTION_MOVE
);
2448 if (dragOperation
& css::datatransfer::dnd::DNDConstants::ACTION_LINK
)
2449 eRet
= static_cast<GdkDragAction
>(eRet
| GDK_ACTION_LINK
);
2454 GtkWindow
* get_active_window()
2456 GtkWindow
* pFocus
= nullptr;
2458 GList
* pList
= gtk_window_list_toplevels();
2460 for (GList
* pEntry
= pList
; pEntry
; pEntry
= pEntry
->next
)
2462 #if GTK_CHECK_VERSION(4, 0, 0)
2463 if (gtk_window_is_active(GTK_WINDOW(pEntry
->data
)))
2465 if (gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry
->data
)))
2468 pFocus
= GTK_WINDOW(pEntry
->data
);
2478 void LocalizeDecimalSeparator(guint
& keyval
)
2480 // #i1820# use locale specific decimal separator
2481 if (keyval
== GDK_KEY_KP_Decimal
&& Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2483 GtkWindow
* pFocusWin
= get_active_window();
2484 GtkWidget
* pFocus
= pFocusWin
? gtk_window_get_focus(pFocusWin
) : nullptr;
2485 // tdf#138932 except if the target is a GtkEntry used for passwords
2486 // GTK4: TODO is it a GtkEntry or a child GtkText that has the focus in this situation?
2487 if (!pFocus
|| !GTK_IS_ENTRY(pFocus
) || gtk_entry_get_visibility(GTK_ENTRY(pFocus
)))
2489 OUString
aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
2495 void set_cursor(GtkWidget
* pWidget
, const char *pName
)
2497 #if !GTK_CHECK_VERSION(4, 0, 0)
2498 if (!gtk_widget_get_realized(pWidget
))
2499 gtk_widget_realize(pWidget
);
2500 GdkDisplay
*pDisplay
= gtk_widget_get_display(pWidget
);
2501 GdkCursor
*pCursor
= pName
? gdk_cursor_new_from_name(pDisplay
, pName
) : nullptr;
2502 widget_set_cursor(pWidget
, pCursor
);
2503 gdk_display_flush(pDisplay
);
2505 g_object_unref(pCursor
);
2512 vcl::Font
get_font(GtkWidget
* pWidget
)
2514 PangoContext
* pContext
= gtk_widget_get_pango_context(pWidget
);
2515 return pango_to_vcl(pango_context_get_font_description(pContext
),
2516 Application::GetSettings().GetUILanguageTag().getLocale());
2521 OString
get_buildable_id(GtkBuildable
* pWidget
)
2523 #if GTK_CHECK_VERSION(4, 0, 0)
2524 const gchar
* pStr
= gtk_buildable_get_buildable_id(pWidget
);
2526 const gchar
* pStr
= gtk_buildable_get_name(pWidget
);
2528 return OString(pStr
, pStr
? strlen(pStr
) : 0);
2531 void set_buildable_id(GtkBuildable
* pWidget
, const OString
& rId
)
2533 #if GTK_CHECK_VERSION(4, 0, 0)
2534 GtkBuildableIface
*iface
= GTK_BUILDABLE_GET_IFACE(pWidget
);
2535 (*iface
->set_id
)(pWidget
, rId
.getStr());
2537 gtk_buildable_set_name(pWidget
, rId
.getStr());
2543 class GtkInstanceWidget
: public virtual weld::Widget
2546 GtkWidget
* m_pWidget
;
2547 GtkWidget
* m_pMouseEventBox
;
2548 GtkInstanceBuilder
* m_pBuilder
;
2550 #if !GTK_CHECK_VERSION(4, 0, 0)
2551 DECL_LINK(async_drag_cancel
, void*, void);
2554 bool IsFirstFreeze() const { return m_nFreezeCount
== 0; }
2555 bool IsLastThaw() const { return m_nFreezeCount
== 1; }
2557 #if GTK_CHECK_VERSION(4, 0, 0)
2558 static void signalFocusIn(GtkEventControllerFocus
*, gpointer widget
)
2560 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2561 SolarMutexGuard aGuard
;
2562 pThis
->signal_focus_in();
2565 static gboolean
signalFocusIn(GtkWidget
*, GdkEvent
*, gpointer widget
)
2567 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2568 SolarMutexGuard aGuard
;
2569 pThis
->signal_focus_in();
2574 void signal_focus_in()
2576 GtkWidget
* pTopLevel
= widget_get_toplevel(m_pWidget
);
2577 // see commentary in GtkSalObjectWidgetClip::Show
2578 if (pTopLevel
&& g_object_get_data(G_OBJECT(pTopLevel
), "g-lo-BlockFocusChange"))
2581 m_aFocusInHdl
.Call(*this);
2584 static gboolean
signalMnemonicActivate(GtkWidget
*, gboolean
, gpointer widget
)
2586 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2587 SolarMutexGuard aGuard
;
2588 return pThis
->signal_mnemonic_activate();
2591 bool signal_mnemonic_activate()
2593 return m_aMnemonicActivateHdl
.Call(*this);
2596 #if GTK_CHECK_VERSION(4, 0, 0)
2597 static void signalFocusOut(GtkEventControllerFocus
*, gpointer widget
)
2599 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2600 SolarMutexGuard aGuard
;
2601 pThis
->signal_focus_in();
2604 static gboolean
signalFocusOut(GtkWidget
*, GdkEvent
*, gpointer widget
)
2606 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2607 SolarMutexGuard aGuard
;
2608 pThis
->signal_focus_out();
2613 #if !GTK_CHECK_VERSION(4, 0, 0)
2614 void launch_drag_cancel(GdkDragContext
* context
)
2616 // post our drag cancel to happen at the next available event cycle
2617 if (m_pDragCancelEvent
)
2619 g_object_ref(context
);
2620 m_pDragCancelEvent
= Application::PostUserEvent(LINK(this, GtkInstanceWidget
, async_drag_cancel
), context
);
2624 void signal_focus_out()
2626 GtkWidget
* pTopLevel
= widget_get_toplevel(m_pWidget
);
2627 // see commentary in GtkSalObjectWidgetClip::Show
2628 if (pTopLevel
&& g_object_get_data(G_OBJECT(pTopLevel
), "g-lo-BlockFocusChange"))
2631 m_aFocusOutHdl
.Call(*this);
2634 virtual void ensureMouseEventWidget()
2636 if (!m_pMouseEventBox
)
2637 m_pMouseEventBox
= ::ensureEventWidget(m_pWidget
);
2640 void ensureButtonPressSignal()
2642 if (!m_nButtonPressSignalId
)
2644 #if GTK_CHECK_VERSION(4, 0, 0)
2645 GtkEventController
* pClickController
= get_click_controller();
2646 m_nButtonPressSignalId
= g_signal_connect(pClickController
, "pressed", G_CALLBACK(signalButtonPress
), this);
2648 ensureMouseEventWidget();
2649 m_nButtonPressSignalId
= g_signal_connect(m_pMouseEventBox
, "button-press-event", G_CALLBACK(signalButtonPress
), this);
2654 void ensureButtonReleaseSignal()
2656 if (!m_nButtonReleaseSignalId
)
2658 #if GTK_CHECK_VERSION(4, 0, 0)
2659 GtkEventController
* pClickController
= get_click_controller();
2660 m_nButtonReleaseSignalId
= g_signal_connect(pClickController
, "released", G_CALLBACK(signalButtonRelease
), this);
2662 ensureMouseEventWidget();
2663 m_nButtonReleaseSignalId
= g_signal_connect(m_pMouseEventBox
, "button-release-event", G_CALLBACK(signalButtonRelease
), this);
2668 #if !GTK_CHECK_VERSION(4, 0, 0)
2669 static gboolean
signalPopupMenu(GtkWidget
* pWidget
, gpointer widget
)
2671 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2672 SolarMutexGuard aGuard
;
2673 //center it when we don't know where else to use
2674 Point
aPos(gtk_widget_get_allocated_width(pWidget
) / 2,
2675 gtk_widget_get_allocated_height(pWidget
) / 2);
2676 CommandEvent
aCEvt(aPos
, CommandEventId::ContextMenu
, false);
2677 return pThis
->signal_popup_menu(aCEvt
);
2681 bool SwapForRTL() const
2683 return ::SwapForRTL(m_pWidget
);
2686 void do_enable_drag_source(const rtl::Reference
<TransferDataContainer
>& rHelper
, sal_uInt8 eDNDConstants
)
2688 ensure_drag_source();
2690 #if !GTK_CHECK_VERSION(4, 0, 0)
2691 auto aFormats
= rHelper
->getTransferDataFlavors();
2692 std::vector
<GtkTargetEntry
> aGtkTargets(m_xDragSource
->FormatsToGtk(aFormats
));
2694 m_eDragAction
= VclToGdk(eDNDConstants
);
2695 drag_source_set(aGtkTargets
, m_eDragAction
);
2697 for (auto &a
: aGtkTargets
)
2700 m_xDragSource
->set_datatransfer(rHelper
, rHelper
);
2703 (void)eDNDConstants
;
2707 void localizeDecimalSeparator()
2709 // tdf#128867 if localize decimal separator is active we will always
2710 // need to be able to change the output of the decimal key press
2711 if (!m_nKeyPressSignalId
&& Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2713 #if GTK_CHECK_VERSION(4, 0, 0)
2714 m_nKeyPressSignalId
= g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed
), this);
2716 m_nKeyPressSignalId
= g_signal_connect(m_pWidget
, "key-press-event", G_CALLBACK(signalKey
), this);
2721 void ensure_drag_begin_end()
2723 if (!m_nDragBeginSignalId
)
2725 // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
2726 #if GTK_CHECK_VERSION(4, 0, 0)
2727 m_nDragBeginSignalId
= g_signal_connect_after(get_drag_controller(), "drag-begin", G_CALLBACK(signalDragBegin
), this);
2729 m_nDragBeginSignalId
= g_signal_connect_after(m_pWidget
, "drag-begin", G_CALLBACK(signalDragBegin
), this);
2732 if (!m_nDragEndSignalId
)
2734 #if GTK_CHECK_VERSION(4, 0, 0)
2735 m_nDragEndSignalId
= g_signal_connect(get_drag_controller(), "drag-end", G_CALLBACK(signalDragEnd
), this);
2737 m_nDragEndSignalId
= g_signal_connect(m_pWidget
, "drag-end", G_CALLBACK(signalDragEnd
), this);
2742 void DisconnectMouseEvents()
2744 if (m_nButtonPressSignalId
)
2746 #if GTK_CHECK_VERSION(4, 0, 0)
2747 g_signal_handler_disconnect(get_click_controller(), m_nButtonPressSignalId
);
2749 g_signal_handler_disconnect(m_pMouseEventBox
, m_nButtonPressSignalId
);
2751 m_nButtonPressSignalId
= 0;
2753 if (m_nMotionSignalId
)
2755 #if GTK_CHECK_VERSION(4, 0, 0)
2756 g_signal_handler_disconnect(get_motion_controller(), m_nMotionSignalId
);
2758 g_signal_handler_disconnect(m_pMouseEventBox
, m_nMotionSignalId
);
2760 m_nMotionSignalId
= 0;
2762 if (m_nLeaveSignalId
)
2764 #if GTK_CHECK_VERSION(4, 0, 0)
2765 g_signal_handler_disconnect(get_motion_controller(), m_nLeaveSignalId
);
2767 g_signal_handler_disconnect(m_pMouseEventBox
, m_nLeaveSignalId
);
2769 m_nLeaveSignalId
= 0;
2771 if (m_nEnterSignalId
)
2773 #if GTK_CHECK_VERSION(4, 0, 0)
2774 g_signal_handler_disconnect(get_motion_controller(), m_nEnterSignalId
);
2776 g_signal_handler_disconnect(m_pMouseEventBox
, m_nEnterSignalId
);
2778 m_nEnterSignalId
= 0;
2780 if (m_nButtonReleaseSignalId
)
2782 #if GTK_CHECK_VERSION(4, 0, 0)
2783 g_signal_handler_disconnect(get_click_controller(), m_nButtonReleaseSignalId
);
2785 g_signal_handler_disconnect(m_pMouseEventBox
, m_nButtonReleaseSignalId
);
2787 m_nButtonReleaseSignalId
= 0;
2790 #if !GTK_CHECK_VERSION(4, 0, 0)
2791 if (!m_pMouseEventBox
|| m_pMouseEventBox
== m_pWidget
)
2794 // GtkWindow replacement for GtkPopover case
2795 if (!GTK_IS_EVENT_BOX(m_pMouseEventBox
))
2797 m_pMouseEventBox
= nullptr;
2801 // put things back they way we found them
2802 GtkWidget
* pParent
= gtk_widget_get_parent(m_pMouseEventBox
);
2804 g_object_ref(m_pWidget
);
2805 gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox
), m_pWidget
);
2807 gtk_widget_destroy(m_pMouseEventBox
);
2809 gtk_container_add(GTK_CONTAINER(pParent
), m_pWidget
);
2810 // coverity[freed_arg : FALSE] - this does not free m_pWidget, it is reffed by pParent
2811 g_object_unref(m_pWidget
);
2813 m_pMouseEventBox
= m_pWidget
;
2818 bool m_bTakeOwnership
;
2819 #if !GTK_CHECK_VERSION(4, 0, 0)
2820 bool m_bDraggedOver
;
2824 sal_uInt16 m_nLastMouseButton
;
2825 #if !GTK_CHECK_VERSION(4, 0, 0)
2826 sal_uInt16 m_nLastMouseClicks
;
2828 int m_nPressedButton
;
2829 #if !GTK_CHECK_VERSION(4, 0, 0)
2833 ImplSVEvent
* m_pDragCancelEvent
;
2834 GtkCssProvider
* m_pBgCssProvider
;
2835 #if !GTK_CHECK_VERSION(4, 0, 0)
2836 GdkDragAction m_eDragAction
;
2838 gulong m_nFocusInSignalId
;
2839 gulong m_nMnemonicActivateSignalId
;
2840 gulong m_nFocusOutSignalId
;
2841 gulong m_nKeyPressSignalId
;
2842 gulong m_nKeyReleaseSignalId
;
2844 gulong m_nSizeAllocateSignalId
;
2846 gulong m_nButtonPressSignalId
;
2847 gulong m_nMotionSignalId
;
2848 gulong m_nLeaveSignalId
;
2849 gulong m_nEnterSignalId
;
2850 gulong m_nButtonReleaseSignalId
;
2851 gulong m_nDragMotionSignalId
;
2852 gulong m_nDragDropSignalId
;
2853 gulong m_nDragDropReceivedSignalId
;
2854 gulong m_nDragLeaveSignalId
;
2855 gulong m_nDragBeginSignalId
;
2856 gulong m_nDragEndSignalId
;
2857 gulong m_nDragFailedSignalId
;
2858 gulong m_nDragDataDeleteignalId
;
2859 gulong m_nDragGetSignalId
;
2861 #if GTK_CHECK_VERSION(4, 0, 0)
2863 GtkEventController
* m_pFocusController
;
2864 GtkEventController
* m_pClickController
;
2865 GtkEventController
* m_pMotionController
;
2866 GtkEventController
* m_pDragController
;
2867 GtkEventController
* m_pKeyController
;
2870 rtl::Reference
<GtkInstDropTarget
> m_xDropTarget
;
2871 rtl::Reference
<GtkInstDragSource
> m_xDragSource
;
2873 static void signalSizeAllocate(GtkWidget
*, GdkRectangle
* allocation
, gpointer widget
)
2875 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2876 SolarMutexGuard aGuard
;
2877 pThis
->signal_size_allocate(allocation
->width
, allocation
->height
);
2880 #if GTK_CHECK_VERSION(4, 0, 0)
2881 static gboolean
signalKeyPressed(GtkEventControllerKey
*, guint keyval
, guint keycode
, GdkModifierType state
, gpointer widget
)
2883 LocalizeDecimalSeparator(keyval
);
2884 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2885 return pThis
->signal_key_press(keyval
, keycode
, state
);
2888 static gboolean
signalKeyReleased(GtkEventControllerKey
*, guint keyval
, guint keycode
, GdkModifierType state
, gpointer widget
)
2890 LocalizeDecimalSeparator(keyval
);
2891 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2892 return pThis
->signal_key_release(keyval
, keycode
, state
);
2895 static gboolean
signalKey(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
2897 LocalizeDecimalSeparator(pEvent
->keyval
);
2898 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2899 if (pEvent
->type
== GDK_KEY_PRESS
)
2900 return pThis
->signal_key_press(pEvent
);
2901 return pThis
->signal_key_release(pEvent
);
2905 virtual bool signal_popup_menu(const CommandEvent
&)
2910 #if GTK_CHECK_VERSION(4, 0, 0)
2911 static void signalButtonPress(GtkGestureClick
* pGesture
, int n_press
, gdouble x
, gdouble y
, gpointer widget
)
2913 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2914 SolarMutexGuard aGuard
;
2915 pThis
->signal_button(pGesture
, SalEvent::MouseButtonDown
, n_press
, x
, y
);
2918 static void signalButtonRelease(GtkGestureClick
* pGesture
, int n_press
, gdouble x
, gdouble y
, gpointer widget
)
2920 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2921 SolarMutexGuard aGuard
;
2922 pThis
->signal_button(pGesture
, SalEvent::MouseButtonUp
, n_press
, x
, y
);
2925 void signal_button(GtkGestureClick
* pGesture
, SalEvent nEventType
, int n_press
, gdouble x
, gdouble y
)
2927 m_nPressedButton
= -1;
2931 aPos
.setX(gtk_widget_get_allocated_width(m_pWidget
) - 1 - aPos
.X());
2935 GdkEventSequence
* pSequence
= gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(pGesture
));
2936 GdkEvent
* pEvent
= gtk_gesture_get_last_event(GTK_GESTURE(pGesture
), pSequence
);
2937 if (gdk_event_triggers_context_menu(pEvent
))
2939 //if handled for context menu, stop processing
2940 CommandEvent
aCEvt(aPos
, CommandEventId::ContextMenu
, true);
2941 if (signal_popup_menu(aCEvt
))
2943 gtk_gesture_set_state(GTK_GESTURE(pGesture
), GTK_EVENT_SEQUENCE_CLAIMED
);
2949 GdkModifierType eType
= gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture
));
2950 int nButton
= gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture
));
2955 m_nLastMouseButton
= MOUSE_LEFT
;
2958 m_nLastMouseButton
= MOUSE_MIDDLE
;
2961 m_nLastMouseButton
= MOUSE_RIGHT
;
2967 sal_uInt32 nModCode
= GtkSalFrame::GetMouseModCode(eType
);
2968 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
2969 sal_uInt16 nCode
= m_nLastMouseButton
| (nModCode
& (KEY_SHIFT
| KEY_MOD1
| KEY_MOD2
));
2970 MouseEvent
aMEvt(aPos
, n_press
, ImplGetMouseButtonMode(m_nLastMouseButton
, nModCode
), nCode
, nCode
);
2972 if (nEventType
== SalEvent::MouseButtonDown
&& m_aMousePressHdl
.Call(aMEvt
))
2973 gtk_gesture_set_state(GTK_GESTURE(pGesture
), GTK_EVENT_SEQUENCE_CLAIMED
);
2975 if (nEventType
== SalEvent::MouseButtonUp
&& m_aMouseReleaseHdl
.Call(aMEvt
))
2976 gtk_gesture_set_state(GTK_GESTURE(pGesture
), GTK_EVENT_SEQUENCE_CLAIMED
);
2981 static gboolean
signalButtonPress(GtkWidget
*, GdkEventButton
* pEvent
, gpointer widget
)
2983 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2984 SolarMutexGuard aGuard
;
2985 return pThis
->signal_button(pEvent
);
2988 static gboolean
signalButtonRelease(GtkWidget
*, GdkEventButton
* pEvent
, gpointer widget
)
2990 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
2991 SolarMutexGuard aGuard
;
2992 return pThis
->signal_button(pEvent
);
2995 bool signal_button(GdkEventButton
* pEvent
)
2997 m_nPressedButton
= -1;
2999 Point
aPos(pEvent
->x
, pEvent
->y
);
3001 aPos
.setX(gtk_widget_get_allocated_width(m_pWidget
) - 1 - aPos
.X());
3003 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent
*>(pEvent
)) && pEvent
->type
== GDK_BUTTON_PRESS
)
3005 //if handled for context menu, stop processing
3006 CommandEvent
aCEvt(aPos
, CommandEventId::ContextMenu
, true);
3007 if (signal_popup_menu(aCEvt
))
3011 if (!m_aMousePressHdl
.IsSet() && !m_aMouseReleaseHdl
.IsSet())
3014 SalEvent nEventType
= SalEvent::NONE
;
3015 switch (pEvent
->type
)
3017 case GDK_BUTTON_PRESS
:
3018 if (GdkEvent
* pPeekEvent
= gdk_event_peek())
3020 bool bSkip
= pPeekEvent
->type
== GDK_2BUTTON_PRESS
||
3021 pPeekEvent
->type
== GDK_3BUTTON_PRESS
;
3022 gdk_event_free(pPeekEvent
);
3028 nEventType
= SalEvent::MouseButtonDown
;
3029 m_nLastMouseClicks
= 1;
3031 case GDK_2BUTTON_PRESS
:
3032 m_nLastMouseClicks
= 2;
3033 nEventType
= SalEvent::MouseButtonDown
;
3035 case GDK_3BUTTON_PRESS
:
3036 m_nLastMouseClicks
= 3;
3037 nEventType
= SalEvent::MouseButtonDown
;
3039 case GDK_BUTTON_RELEASE
:
3040 nEventType
= SalEvent::MouseButtonUp
;
3046 switch (pEvent
->button
)
3049 m_nLastMouseButton
= MOUSE_LEFT
;
3052 m_nLastMouseButton
= MOUSE_MIDDLE
;
3055 m_nLastMouseButton
= MOUSE_RIGHT
;
3061 /* Save press to possibly begin a drag */
3062 if (pEvent
->type
!= GDK_BUTTON_RELEASE
)
3064 m_nPressedButton
= pEvent
->button
;
3065 m_nPressStartX
= pEvent
->x
;
3066 m_nPressStartY
= pEvent
->y
;
3069 sal_uInt32 nModCode
= GtkSalFrame::GetMouseModCode(pEvent
->state
);
3070 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
3071 sal_uInt16 nCode
= m_nLastMouseButton
| (nModCode
& (KEY_SHIFT
| KEY_MOD1
| KEY_MOD2
));
3072 MouseEvent
aMEvt(aPos
, m_nLastMouseClicks
, ImplGetMouseButtonMode(m_nLastMouseButton
, nModCode
), nCode
, nCode
);
3074 if (nEventType
== SalEvent::MouseButtonDown
)
3076 if (!m_aMousePressHdl
.IsSet())
3078 return m_aMousePressHdl
.Call(aMEvt
);
3081 if (!m_aMouseReleaseHdl
.IsSet())
3083 return m_aMouseReleaseHdl
.Call(aMEvt
);
3087 bool simple_signal_motion(double x
, double y
, guint nState
)
3089 if (!m_aMouseMotionHdl
.IsSet())
3094 aPos
.setX(gtk_widget_get_allocated_width(m_pWidget
) - 1 - aPos
.X());
3095 sal_uInt32 nModCode
= GtkSalFrame::GetMouseModCode(nState
);
3096 MouseEvent
aMEvt(aPos
, 0, ImplGetMouseMoveMode(nModCode
), nModCode
, nModCode
);
3098 return m_aMouseMotionHdl
.Call(aMEvt
);
3101 #if GTK_CHECK_VERSION(4, 0, 0)
3102 static void signalMotion(GtkEventControllerMotion
*pController
, double x
, double y
, gpointer widget
)
3104 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3105 GdkModifierType eType
= gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController
));
3107 SolarMutexGuard aGuard
;
3108 pThis
->simple_signal_motion(x
, y
, eType
);
3112 static gboolean
signalMotion(GtkWidget
*, GdkEventMotion
* pEvent
, gpointer widget
)
3114 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3115 SolarMutexGuard aGuard
;
3116 return pThis
->signal_motion(pEvent
);
3119 bool signal_motion(const GdkEventMotion
* pEvent
)
3121 GtkTargetList
* pDragData
= (m_eDragAction
!= 0 && m_nPressedButton
!= -1 && m_xDragSource
.is()) ? gtk_drag_source_get_target_list(m_pWidget
) : nullptr;
3122 bool bUnsetDragIcon(false);
3123 if (pDragData
&& gtk_drag_check_threshold(m_pWidget
, m_nPressStartX
, m_nPressStartY
, pEvent
->x
, pEvent
->y
) && !do_signal_drag_begin(bUnsetDragIcon
))
3125 GdkDragContext
* pContext
= gtk_drag_begin_with_coordinates(m_pWidget
,
3129 const_cast<GdkEvent
*>(reinterpret_cast<const GdkEvent
*>(pEvent
)),
3130 m_nPressStartX
, m_nPressStartY
);
3132 if (pContext
&& bUnsetDragIcon
)
3134 cairo_surface_t
*surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, 0, 0);
3135 gtk_drag_set_icon_surface(pContext
, surface
);
3138 m_nPressedButton
= -1;
3142 return simple_signal_motion(pEvent
->x
, pEvent
->y
, pEvent
->state
);
3146 bool signal_crossing(double x
, double y
, guint nState
, MouseEventModifiers eMouseEventModifiers
)
3148 if (!m_aMouseMotionHdl
.IsSet())
3153 aPos
.setX(gtk_widget_get_allocated_width(m_pWidget
) - 1 - aPos
.X());
3154 sal_uInt32 nModCode
= GtkSalFrame::GetMouseModCode(nState
);
3155 MouseEventModifiers eModifiers
= ImplGetMouseMoveMode(nModCode
);
3156 eModifiers
= eModifiers
| eMouseEventModifiers
;
3157 MouseEvent
aMEvt(aPos
, 0, eModifiers
, nModCode
, nModCode
);
3159 m_aMouseMotionHdl
.Call(aMEvt
);
3163 #if GTK_CHECK_VERSION(4, 0, 0)
3164 static void signalEnter(GtkEventControllerMotion
*pController
, double x
, double y
, gpointer widget
)
3166 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3167 GdkModifierType eType
= gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController
));
3168 SolarMutexGuard aGuard
;
3169 pThis
->signal_crossing(x
, y
, eType
, MouseEventModifiers::ENTERWINDOW
);
3172 static void signalLeave(GtkEventControllerMotion
*pController
, gpointer widget
)
3174 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3175 GdkModifierType eType
= gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController
));
3176 SolarMutexGuard aGuard
;
3177 pThis
->signal_crossing(-1, -1, eType
, MouseEventModifiers::LEAVEWINDOW
);
3180 static gboolean
signalCrossing(GtkWidget
*, GdkEventCrossing
* pEvent
, gpointer widget
)
3182 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3183 MouseEventModifiers eMouseEventModifiers
= pEvent
->type
== GDK_ENTER_NOTIFY
? MouseEventModifiers::ENTERWINDOW
: MouseEventModifiers::LEAVEWINDOW
;
3184 SolarMutexGuard aGuard
;
3185 return pThis
->signal_crossing(pEvent
->x
, pEvent
->y
, pEvent
->state
, eMouseEventModifiers
);
3189 virtual void drag_started()
3193 #if !GTK_CHECK_VERSION(4, 0, 0)
3194 static gboolean
signalDragMotion(GtkWidget
*pWidget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, gpointer widget
)
3196 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3197 if (!pThis
->m_bDraggedOver
)
3199 pThis
->m_bDraggedOver
= true;
3200 pThis
->drag_started();
3202 return pThis
->m_xDropTarget
->signalDragMotion(pWidget
, context
, x
, y
, time
);
3205 static gboolean
signalDragDrop(GtkWidget
* pWidget
, GdkDragContext
* context
, gint x
, gint y
, guint time
, gpointer widget
)
3207 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3208 return pThis
->m_xDropTarget
->signalDragDrop(pWidget
, context
, x
, y
, time
);
3211 static void signalDragDropReceived(GtkWidget
* pWidget
, GdkDragContext
* context
, gint x
, gint y
, GtkSelectionData
* data
, guint ttype
, guint time
, gpointer widget
)
3213 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3214 pThis
->m_xDropTarget
->signalDragDropReceived(pWidget
, context
, x
, y
, data
, ttype
, time
);
3218 virtual void drag_ended()
3222 #if !GTK_CHECK_VERSION(4, 0, 0)
3223 static void signalDragLeave(GtkWidget
* pWidget
, GdkDragContext
*, guint
/*time*/, gpointer widget
)
3225 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3226 pThis
->m_xDropTarget
->signalDragLeave(pWidget
);
3227 if (pThis
->m_bDraggedOver
)
3229 pThis
->m_bDraggedOver
= false;
3230 pThis
->drag_ended();
3235 #if GTK_CHECK_VERSION(4, 0, 0)
3236 static void signalDragBegin(GtkDragSource
* context
, GdkDrag
*, gpointer widget
)
3238 static void signalDragBegin(GtkWidget
*, GdkDragContext
* context
, gpointer widget
)
3241 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3242 pThis
->signal_drag_begin(context
);
3245 void ensure_drag_source()
3249 m_xDragSource
.set(new GtkInstDragSource
);
3251 #if !GTK_CHECK_VERSION(4, 0, 0)
3252 m_nDragFailedSignalId
= g_signal_connect(m_pWidget
, "drag-failed", G_CALLBACK(signalDragFailed
), this);
3253 m_nDragDataDeleteignalId
= g_signal_connect(m_pWidget
, "drag-data-delete", G_CALLBACK(signalDragDelete
), this);
3254 m_nDragGetSignalId
= g_signal_connect(m_pWidget
, "drag-data-get", G_CALLBACK(signalDragDataGet
), this);
3257 ensure_drag_begin_end();
3261 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon
)
3263 rUnsetDragIcon
= false;
3267 #if GTK_CHECK_VERSION(4, 0, 0)
3268 void signal_drag_begin(GtkDragSource
* context
)
3270 void signal_drag_begin(GdkDragContext
* context
)
3273 bool bUnsetDragIcon(false);
3274 if (do_signal_drag_begin(bUnsetDragIcon
))
3276 #if !GTK_CHECK_VERSION(4, 0, 0)
3277 launch_drag_cancel(context
);
3283 #if !GTK_CHECK_VERSION(4, 0, 0)
3286 cairo_surface_t
*surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, 0, 0);
3287 gtk_drag_set_icon_surface(context
, surface
);
3292 m_xDragSource
->setActiveDragSource();
3295 virtual void do_signal_drag_end()
3299 #if GTK_CHECK_VERSION(4, 0, 0)
3300 static void signalDragEnd(GtkGestureDrag
* /*gesture*/, double /*offset_x*/, double /*offset_y*/, gpointer widget
)
3302 static void signalDragEnd(GtkWidget
* /*widget*/, GdkDragContext
* context
, gpointer widget
)
3305 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3306 pThis
->do_signal_drag_end();
3307 #if !GTK_CHECK_VERSION(4, 0, 0)
3308 if (pThis
->m_xDragSource
.is())
3309 pThis
->m_xDragSource
->dragEnd(context
);
3313 #if !GTK_CHECK_VERSION(4, 0, 0)
3314 static gboolean
signalDragFailed(GtkWidget
* /*widget*/, GdkDragContext
* /*context*/, GtkDragResult
/*result*/, gpointer widget
)
3316 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3317 pThis
->m_xDragSource
->dragFailed();
3321 static void signalDragDelete(GtkWidget
* /*widget*/, GdkDragContext
* /*context*/, gpointer widget
)
3323 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3324 pThis
->m_xDragSource
->dragDelete();
3327 static void signalDragDataGet(GtkWidget
* /*widget*/, GdkDragContext
* /*context*/, GtkSelectionData
*data
, guint info
,
3328 guint
/*time*/, gpointer widget
)
3330 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
3331 pThis
->m_xDragSource
->dragDataGet(data
, info
);
3335 #if !GTK_CHECK_VERSION(4, 0, 0)
3336 virtual void drag_source_set(const std::vector
<GtkTargetEntry
>& rGtkTargets
, GdkDragAction eDragAction
)
3338 if (rGtkTargets
.empty() && !eDragAction
)
3339 gtk_drag_source_unset(m_pWidget
);
3341 gtk_drag_source_set(m_pWidget
, GDK_BUTTON1_MASK
, rGtkTargets
.data(), rGtkTargets
.size(), eDragAction
);
3345 void do_set_background(const Color
& rColor
)
3347 const bool bRemoveColor
= rColor
== COL_AUTO
;
3348 if (bRemoveColor
&& !m_pBgCssProvider
)
3350 GtkStyleContext
*pWidgetContext
= gtk_widget_get_style_context(GTK_WIDGET(m_pWidget
));
3351 if (m_pBgCssProvider
)
3353 gtk_style_context_remove_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pBgCssProvider
));
3354 m_pBgCssProvider
= nullptr;
3358 OUString sColor
= rColor
.AsRGBHexString();
3359 m_pBgCssProvider
= gtk_css_provider_new();
3360 OUString aBuffer
= "* { background-color: #" + sColor
+ "; }";
3361 OString aResult
= OUStringToOString(aBuffer
, RTL_TEXTENCODING_UTF8
);
3362 css_provider_load_from_data(m_pBgCssProvider
, aResult
.getStr(), aResult
.getLength());
3363 gtk_style_context_add_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pBgCssProvider
),
3364 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
3367 #if !GTK_CHECK_VERSION(4, 0, 0)
3368 static void update_style(GtkWidget
* pWidget
, gpointer pData
)
3370 if (GTK_IS_CONTAINER(pWidget
))
3371 gtk_container_foreach(GTK_CONTAINER(pWidget
), update_style
, pData
);
3372 GtkWidgetClass
* pWidgetClass
= GTK_WIDGET_GET_CLASS(pWidget
);
3373 pWidgetClass
->style_updated(pWidget
);
3378 GtkInstanceWidget(GtkWidget
* pWidget
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
3379 : m_pWidget(pWidget
)
3380 , m_pMouseEventBox(nullptr)
3381 , m_pBuilder(pBuilder
)
3382 , m_bTakeOwnership(bTakeOwnership
)
3383 #if !GTK_CHECK_VERSION(4, 0, 0)
3384 , m_bDraggedOver(false)
3388 , m_nLastMouseButton(0)
3389 #if !GTK_CHECK_VERSION(4, 0, 0)
3390 , m_nLastMouseClicks(0)
3392 , m_nPressedButton(-1)
3393 #if !GTK_CHECK_VERSION(4, 0, 0)
3394 , m_nPressStartX(-1)
3395 , m_nPressStartY(-1)
3397 , m_pDragCancelEvent(nullptr)
3398 , m_pBgCssProvider(nullptr)
3399 #if !GTK_CHECK_VERSION(4, 0, 0)
3400 , m_eDragAction(GdkDragAction(0))
3402 , m_nFocusInSignalId(0)
3403 , m_nMnemonicActivateSignalId(0)
3404 , m_nFocusOutSignalId(0)
3405 , m_nKeyPressSignalId(0)
3406 , m_nKeyReleaseSignalId(0)
3407 , m_nSizeAllocateSignalId(0)
3408 , m_nButtonPressSignalId(0)
3409 , m_nMotionSignalId(0)
3410 , m_nLeaveSignalId(0)
3411 , m_nEnterSignalId(0)
3412 , m_nButtonReleaseSignalId(0)
3413 , m_nDragMotionSignalId(0)
3414 , m_nDragDropSignalId(0)
3415 , m_nDragDropReceivedSignalId(0)
3416 , m_nDragLeaveSignalId(0)
3417 , m_nDragBeginSignalId(0)
3418 , m_nDragEndSignalId(0)
3419 , m_nDragFailedSignalId(0)
3420 , m_nDragDataDeleteignalId(0)
3421 , m_nDragGetSignalId(0)
3422 #if GTK_CHECK_VERSION(4, 0, 0)
3424 , m_pFocusController(nullptr)
3425 , m_pClickController(nullptr)
3426 , m_pMotionController(nullptr)
3427 , m_pDragController(nullptr)
3428 , m_pKeyController(nullptr)
3431 if (!bTakeOwnership
)
3432 g_object_ref(m_pWidget
);
3434 localizeDecimalSeparator();
3437 virtual void connect_key_press(const Link
<const KeyEvent
&, bool>& rLink
) override
3439 if (!m_nKeyPressSignalId
)
3441 #if GTK_CHECK_VERSION(4, 0, 0)
3442 m_nKeyPressSignalId
= g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed
), this);
3444 m_nKeyPressSignalId
= g_signal_connect(m_pWidget
, "key-press-event", G_CALLBACK(signalKey
), this);
3447 weld::Widget::connect_key_press(rLink
);
3450 virtual void connect_key_release(const Link
<const KeyEvent
&, bool>& rLink
) override
3452 if (!m_nKeyReleaseSignalId
)
3454 #if GTK_CHECK_VERSION(4, 0, 0)
3455 m_nKeyReleaseSignalId
= g_signal_connect(get_key_controller(), "key-released", G_CALLBACK(signalKeyReleased
), this);
3457 m_nKeyReleaseSignalId
= g_signal_connect(m_pWidget
, "key-release-event", G_CALLBACK(signalKey
), this);
3460 weld::Widget::connect_key_release(rLink
);
3463 virtual void connect_mouse_press(const Link
<const MouseEvent
&, bool>& rLink
) override
3465 ensureButtonPressSignal();
3466 weld::Widget::connect_mouse_press(rLink
);
3469 virtual void connect_mouse_move(const Link
<const MouseEvent
&, bool>& rLink
) override
3471 #if GTK_CHECK_VERSION(4, 0, 0)
3472 GtkEventController
* pMotionController
= get_motion_controller();
3473 if (!m_nMotionSignalId
)
3474 m_nMotionSignalId
= g_signal_connect(pMotionController
, "motion", G_CALLBACK(signalMotion
), this);
3475 if (!m_nLeaveSignalId
)
3476 m_nLeaveSignalId
= g_signal_connect(pMotionController
, "leave", G_CALLBACK(signalEnter
), this);
3477 if (!m_nEnterSignalId
)
3478 m_nEnterSignalId
= g_signal_connect(pMotionController
, "enter", G_CALLBACK(signalLeave
), this);
3480 ensureMouseEventWidget();
3481 if (!m_nMotionSignalId
)
3482 m_nMotionSignalId
= g_signal_connect(m_pMouseEventBox
, "motion-notify-event", G_CALLBACK(signalMotion
), this);
3483 if (!m_nLeaveSignalId
)
3484 m_nLeaveSignalId
= g_signal_connect(m_pMouseEventBox
, "leave-notify-event", G_CALLBACK(signalCrossing
), this);
3485 if (!m_nEnterSignalId
)
3486 m_nEnterSignalId
= g_signal_connect(m_pMouseEventBox
, "enter-notify-event", G_CALLBACK(signalCrossing
), this);
3488 weld::Widget::connect_mouse_move(rLink
);
3491 virtual void connect_mouse_release(const Link
<const MouseEvent
&, bool>& rLink
) override
3493 ensureButtonReleaseSignal();
3494 weld::Widget::connect_mouse_release(rLink
);
3497 virtual void set_sensitive(bool sensitive
) override
3499 gtk_widget_set_sensitive(m_pWidget
, sensitive
);
3502 virtual bool get_sensitive() const override
3504 return gtk_widget_get_sensitive(m_pWidget
);
3507 virtual bool get_visible() const override
3509 return gtk_widget_get_visible(m_pWidget
);
3512 virtual bool is_visible() const override
3514 return gtk_widget_is_visible(m_pWidget
);
3517 virtual void set_can_focus(bool bCanFocus
) override
3519 gtk_widget_set_can_focus(m_pWidget
, bCanFocus
);
3522 virtual void grab_focus() override
3526 gtk_widget_grab_focus(m_pWidget
);
3529 virtual bool has_focus() const override
3531 return gtk_widget_has_focus(m_pWidget
);
3534 virtual bool is_active() const override
3536 GtkWindow
* pTopLevel
= GTK_WINDOW(widget_get_toplevel(m_pWidget
));
3537 return pTopLevel
&& gtk_window_is_active(pTopLevel
) && has_focus();
3540 // is the focus in a child of this widget, where a transient popup attached
3541 // to a widget is considered a child of that widget
3542 virtual bool has_child_focus() const override
3544 GtkWindow
* pFocusWin
= get_active_window();
3547 GtkWidget
* pFocus
= gtk_window_get_focus(pFocusWin
);
3548 if (pFocus
&& gtk_widget_is_ancestor(pFocus
, m_pWidget
))
3550 #if !GTK_CHECK_VERSION(4, 0, 0)
3551 GtkWidget
* pAttachedTo
= gtk_window_get_attached_to(pFocusWin
);
3554 if (pAttachedTo
== m_pWidget
|| gtk_widget_is_ancestor(pAttachedTo
, m_pWidget
))
3560 virtual void show() override
3562 gtk_widget_show(m_pWidget
);
3565 virtual void hide() override
3567 gtk_widget_hide(m_pWidget
);
3570 virtual void set_size_request(int nWidth
, int nHeight
) override
3572 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
3573 if (GTK_IS_VIEWPORT(pParent
))
3574 pParent
= gtk_widget_get_parent(pParent
);
3575 if (GTK_IS_SCROLLED_WINDOW(pParent
))
3577 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent
), nWidth
);
3578 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent
), nHeight
);
3580 gtk_widget_set_size_request(m_pWidget
, nWidth
, nHeight
);
3583 virtual Size
get_size_request() const override
3585 int nWidth
, nHeight
;
3586 gtk_widget_get_size_request(m_pWidget
, &nWidth
, &nHeight
);
3587 return Size(nWidth
, nHeight
);
3590 virtual Size
get_preferred_size() const override
3592 GtkRequisition size
;
3593 gtk_widget_get_preferred_size(m_pWidget
, nullptr, &size
);
3594 return Size(size
.width
, size
.height
);
3597 virtual float get_approximate_digit_width() const override
3599 PangoContext
* pContext
= gtk_widget_get_pango_context(m_pWidget
);
3600 PangoFontMetrics
* pMetrics
= pango_context_get_metrics(pContext
,
3601 pango_context_get_font_description(pContext
),
3602 pango_context_get_language(pContext
));
3603 float nDigitWidth
= pango_font_metrics_get_approximate_digit_width(pMetrics
);
3604 pango_font_metrics_unref(pMetrics
);
3606 return nDigitWidth
/ PANGO_SCALE
;
3609 virtual int get_text_height() const override
3611 PangoContext
* pContext
= gtk_widget_get_pango_context(m_pWidget
);
3612 PangoFontMetrics
* pMetrics
= pango_context_get_metrics(pContext
,
3613 pango_context_get_font_description(pContext
),
3614 pango_context_get_language(pContext
));
3615 int nLineHeight
= pango_font_metrics_get_ascent(pMetrics
) + pango_font_metrics_get_descent(pMetrics
);
3616 pango_font_metrics_unref(pMetrics
);
3617 return nLineHeight
/ PANGO_SCALE
;
3620 virtual Size
get_pixel_size(const OUString
& rText
) const override
3622 OString
aStr(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
3623 PangoLayout
* pLayout
= gtk_widget_create_pango_layout(m_pWidget
, aStr
.getStr());
3624 gint nWidth
, nHeight
;
3625 pango_layout_get_pixel_size(pLayout
, &nWidth
, &nHeight
);
3626 g_object_unref(pLayout
);
3627 return Size(nWidth
, nHeight
);
3630 virtual vcl::Font
get_font() override
3632 return ::get_font(m_pWidget
);
3635 virtual void set_grid_left_attach(int nAttach
) override
3637 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
3638 #if GTK_CHECK_VERSION(4, 0, 0)
3639 int row
, width
, height
;
3640 gtk_grid_query_child(GTK_GRID(pParent
), m_pWidget
, nullptr, &row
, &width
, &height
);
3641 g_object_ref(m_pWidget
);
3642 gtk_grid_remove(GTK_GRID(pParent
), m_pWidget
);
3643 gtk_grid_attach(GTK_GRID(pParent
), m_pWidget
, nAttach
, row
, width
, height
);
3644 g_object_unref(m_pWidget
);
3646 gtk_container_child_set(GTK_CONTAINER(pParent
), m_pWidget
, "left-attach", nAttach
, nullptr);
3650 virtual int get_grid_left_attach() const override
3653 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
3654 #if GTK_CHECK_VERSION(4, 0, 0)
3655 gtk_grid_query_child(GTK_GRID(pParent
), m_pWidget
, &nAttach
, nullptr, nullptr, nullptr);
3657 gtk_container_child_get(GTK_CONTAINER(pParent
), m_pWidget
, "left-attach", &nAttach
, nullptr);
3662 virtual void set_grid_width(int nCols
) override
3664 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
3665 #if GTK_CHECK_VERSION(4, 0, 0)
3666 int col
, row
, height
;
3667 gtk_grid_query_child(GTK_GRID(pParent
), m_pWidget
, &col
, &row
, nullptr, &height
);
3668 g_object_ref(m_pWidget
);
3669 gtk_grid_remove(GTK_GRID(pParent
), m_pWidget
);
3670 gtk_grid_attach(GTK_GRID(pParent
), m_pWidget
, col
, row
, nCols
, height
);
3671 g_object_unref(m_pWidget
);
3673 gtk_container_child_set(GTK_CONTAINER(pParent
), m_pWidget
, "width", nCols
, nullptr);
3677 virtual void set_grid_top_attach(int nAttach
) override
3679 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
3680 #if GTK_CHECK_VERSION(4, 0, 0)
3681 int col
, width
, height
;
3682 gtk_grid_query_child(GTK_GRID(pParent
), m_pWidget
, &col
, nullptr, &width
, &height
);
3683 g_object_ref(m_pWidget
);
3684 gtk_grid_remove(GTK_GRID(pParent
), m_pWidget
);
3685 gtk_grid_attach(GTK_GRID(pParent
), m_pWidget
, col
, nAttach
, width
, height
);
3686 g_object_unref(m_pWidget
);
3688 gtk_container_child_set(GTK_CONTAINER(pParent
), m_pWidget
, "top-attach", nAttach
, nullptr);
3692 virtual int get_grid_top_attach() const override
3695 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
3696 #if GTK_CHECK_VERSION(4, 0, 0)
3697 gtk_grid_query_child(GTK_GRID(pParent
), m_pWidget
, nullptr, &nAttach
, nullptr, nullptr);
3699 gtk_container_child_get(GTK_CONTAINER(pParent
), m_pWidget
, "top-attach", &nAttach
, nullptr);
3704 virtual void set_hexpand(bool bExpand
) override
3706 gtk_widget_set_hexpand(m_pWidget
, bExpand
);
3709 virtual bool get_hexpand() const override
3711 return gtk_widget_get_hexpand(m_pWidget
);
3714 virtual void set_vexpand(bool bExpand
) override
3716 gtk_widget_set_vexpand(m_pWidget
, bExpand
);
3719 virtual bool get_vexpand() const override
3721 return gtk_widget_get_vexpand(m_pWidget
);
3724 virtual void set_margin_top(int nMargin
) override
3726 gtk_widget_set_margin_top(m_pWidget
, nMargin
);
3729 virtual void set_margin_bottom(int nMargin
) override
3731 gtk_widget_set_margin_bottom(m_pWidget
, nMargin
);
3734 virtual void set_margin_start(int nMargin
) override
3736 gtk_widget_set_margin_start(m_pWidget
, nMargin
);
3739 virtual void set_margin_end(int nMargin
) override
3741 gtk_widget_set_margin_end(m_pWidget
, nMargin
);
3744 virtual int get_margin_top() const override
3746 return gtk_widget_get_margin_top(m_pWidget
);
3749 virtual int get_margin_bottom() const override
3751 return gtk_widget_get_margin_bottom(m_pWidget
);
3754 virtual int get_margin_start() const override
3756 return gtk_widget_get_margin_start(m_pWidget
);
3759 virtual int get_margin_end() const override
3761 return gtk_widget_get_margin_end(m_pWidget
);
3764 virtual void set_accessible_name(const OUString
& rName
) override
3766 #if GTK_CHECK_VERSION(4, 0, 0)
3767 gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget
), GTK_ACCESSIBLE_PROPERTY_LABEL
,
3768 OUStringToOString(rName
, RTL_TEXTENCODING_UTF8
).getStr(), -1);
3770 AtkObject
* pAtkObject
= gtk_widget_get_accessible(m_pWidget
);
3773 atk_object_set_name(pAtkObject
, OUStringToOString(rName
, RTL_TEXTENCODING_UTF8
).getStr());
3777 virtual void set_accessible_description(const OUString
& rDescription
) override
3779 #if GTK_CHECK_VERSION(4, 0, 0)
3780 gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget
), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION
,
3781 OUStringToOString(rDescription
, RTL_TEXTENCODING_UTF8
).getStr(), -1);
3783 AtkObject
* pAtkObject
= gtk_widget_get_accessible(m_pWidget
);
3786 atk_object_set_description(pAtkObject
, OUStringToOString(rDescription
, RTL_TEXTENCODING_UTF8
).getStr());
3790 virtual OUString
get_accessible_name() const override
3792 #if !GTK_CHECK_VERSION(4, 0, 0)
3793 AtkObject
* pAtkObject
= gtk_widget_get_accessible(m_pWidget
);
3794 const char* pStr
= pAtkObject
? atk_object_get_name(pAtkObject
) : nullptr;
3795 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
3797 char* pStr
= gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget
), GTK_ACCESSIBLE_PROPERTY_LABEL
, nullptr);
3798 OUString
sRet(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
3804 virtual OUString
get_accessible_description() const override
3806 #if !GTK_CHECK_VERSION(4, 0, 0)
3807 AtkObject
* pAtkObject
= gtk_widget_get_accessible(m_pWidget
);
3808 const char* pStr
= pAtkObject
? atk_object_get_description(pAtkObject
) : nullptr;
3809 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
3811 char* pStr
= gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget
), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION
, nullptr);
3812 OUString
sRet(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
3818 virtual void set_accessible_relation_labeled_by(weld::Widget
* pLabel
) override
3820 GtkWidget
* pGtkLabel
= pLabel
? dynamic_cast<GtkInstanceWidget
&>(*pLabel
).getWidget() : nullptr;
3821 #if GTK_CHECK_VERSION(4, 0, 0)
3822 gtk_accessible_update_relation(GTK_ACCESSIBLE(m_pWidget
),
3823 GTK_ACCESSIBLE_RELATION_LABELLED_BY
,
3827 AtkObject
* pAtkObject
= gtk_widget_get_accessible(m_pWidget
);
3830 AtkObject
*pAtkLabel
= pGtkLabel
? gtk_widget_get_accessible(pGtkLabel
) : nullptr;
3831 AtkRelationSet
*pRelationSet
= atk_object_ref_relation_set(pAtkObject
);
3832 AtkRelation
*pRelation
= atk_relation_set_get_relation_by_type(pRelationSet
, ATK_RELATION_LABELLED_BY
);
3835 // clear ATK_RELATION_LABEL_FOR from old label
3836 GPtrArray
* pOldLabelTarget
= atk_relation_get_target(pRelation
);
3837 guint nElements
= pOldLabelTarget
? pOldLabelTarget
->len
: 0;
3838 for (guint i
= 0; i
< nElements
; ++i
)
3840 gpointer pOldLabelObject
= g_ptr_array_index(pOldLabelTarget
, i
);
3841 AtkRelationSet
*pOldLabelRelationSet
= atk_object_ref_relation_set(ATK_OBJECT(pOldLabelObject
));
3842 if (AtkRelation
*pOldLabelRelation
= atk_relation_set_get_relation_by_type(pRelationSet
, ATK_RELATION_LABEL_FOR
))
3843 atk_relation_set_remove(pOldLabelRelationSet
, pOldLabelRelation
);
3844 g_object_unref(pOldLabelRelationSet
);
3846 atk_relation_set_remove(pRelationSet
, pRelation
);
3851 AtkObject
*obj_array_labelled_by
[1];
3852 obj_array_labelled_by
[0] = pAtkLabel
;
3853 pRelation
= atk_relation_new(obj_array_labelled_by
, 1, ATK_RELATION_LABELLED_BY
);
3854 atk_relation_set_add(pRelationSet
, pRelation
);
3856 // add ATK_RELATION_LABEL_FOR to new label to match
3857 AtkRelationSet
*pNewLabelRelationSet
= atk_object_ref_relation_set(pAtkLabel
);
3858 AtkRelation
*pNewLabelRelation
= atk_relation_set_get_relation_by_type(pNewLabelRelationSet
, ATK_RELATION_LABEL_FOR
);
3859 if (pNewLabelRelation
)
3860 atk_relation_set_remove(pNewLabelRelationSet
, pRelation
);
3861 AtkObject
*obj_array_label_for
[1];
3862 obj_array_label_for
[0] = pAtkObject
;
3863 pNewLabelRelation
= atk_relation_new(obj_array_label_for
, 1, ATK_RELATION_LABEL_FOR
);
3864 atk_relation_set_add(pNewLabelRelationSet
, pNewLabelRelation
);
3865 g_object_unref(pNewLabelRelationSet
);
3868 g_object_unref(pRelationSet
);
3872 virtual bool get_extents_relative_to(const weld::Widget
& rRelative
, int& x
, int &y
, int& width
, int &height
) const override
3874 //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
3875 //the document underneath to auto-scroll to place content in a visible location
3876 gtk_coord
fX(0.0), fY(0.0);
3877 bool ret
= gtk_widget_translate_coordinates(m_pWidget
,
3878 dynamic_cast<const GtkInstanceWidget
&>(rRelative
).getWidget(),
3882 width
= gtk_widget_get_allocated_width(m_pWidget
);
3883 height
= gtk_widget_get_allocated_height(m_pWidget
);
3887 virtual void set_tooltip_text(const OUString
& rTip
) override
3889 gtk_widget_set_tooltip_text(m_pWidget
, OUStringToOString(rTip
, RTL_TEXTENCODING_UTF8
).getStr());
3892 virtual OUString
get_tooltip_text() const override
3894 const gchar
* pStr
= gtk_widget_get_tooltip_text(m_pWidget
);
3895 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
3898 virtual std::unique_ptr
<weld::Container
> weld_parent() const override
;
3900 virtual OString
get_buildable_name() const override
3902 return ::get_buildable_id(GTK_BUILDABLE(m_pWidget
));
3905 virtual void set_buildable_name(const OString
& rId
) override
3907 ::set_buildable_id(GTK_BUILDABLE(m_pWidget
), rId
);
3910 virtual void set_help_id(const OString
& rHelpId
) override
3912 ::set_help_id(m_pWidget
, rHelpId
);
3915 virtual OString
get_help_id() const override
3917 OString sRet
= ::get_help_id(m_pWidget
);
3919 sRet
= OString("null");
3923 GtkWidget
* getWidget() const
3928 GtkWindow
* getWindow() const
3930 return GTK_WINDOW(widget_get_toplevel(m_pWidget
));
3933 #if GTK_CHECK_VERSION(4, 0, 0)
3934 GtkEventController
* get_focus_controller()
3936 if (!m_pFocusController
)
3938 gtk_widget_set_focusable(m_pWidget
, true);
3939 m_pFocusController
= gtk_event_controller_focus_new();
3940 gtk_widget_add_controller(m_pWidget
, m_pFocusController
);
3942 return m_pFocusController
;
3945 #if GTK_CHECK_VERSION(4, 0, 0)
3946 GtkEventController
* get_click_controller()
3948 if (!m_pClickController
)
3950 GtkGesture
*pClick
= gtk_gesture_click_new();
3951 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick
), 0);
3952 m_pClickController
= GTK_EVENT_CONTROLLER(pClick
);
3953 gtk_widget_add_controller(m_pWidget
, m_pClickController
);
3955 return m_pClickController
;
3958 GtkEventController
* get_motion_controller()
3960 if (!m_pMotionController
)
3962 m_pMotionController
= gtk_event_controller_motion_new();
3963 gtk_widget_add_controller(m_pWidget
, m_pMotionController
);
3965 return m_pMotionController
;
3968 GtkEventController
* get_drag_controller()
3970 if (!m_pDragController
)
3972 GtkDragSource
* pDrag
= gtk_drag_source_new();
3973 m_pDragController
= GTK_EVENT_CONTROLLER(pDrag
);
3974 gtk_widget_add_controller(m_pWidget
, m_pDragController
);
3976 return m_pDragController
;
3979 GtkEventController
* get_key_controller()
3981 if (!m_pKeyController
)
3983 m_pKeyController
= gtk_event_controller_key_new();
3984 gtk_widget_add_controller(m_pWidget
, m_pKeyController
);
3986 return m_pKeyController
;
3994 virtual void connect_focus_in(const Link
<Widget
&, void>& rLink
) override
3996 if (!m_nFocusInSignalId
)
3998 #if GTK_CHECK_VERSION(4, 0, 0)
3999 m_nFocusInSignalId
= g_signal_connect(get_focus_controller(), "enter", G_CALLBACK(signalFocusIn
), this);
4001 m_nFocusInSignalId
= g_signal_connect(m_pWidget
, "focus-in-event", G_CALLBACK(signalFocusIn
), this);
4005 weld::Widget::connect_focus_in(rLink
);
4008 virtual void connect_mnemonic_activate(const Link
<Widget
&, bool>& rLink
) override
4010 if (!m_nMnemonicActivateSignalId
)
4011 m_nMnemonicActivateSignalId
= g_signal_connect(m_pWidget
, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate
), this);
4012 weld::Widget::connect_mnemonic_activate(rLink
);
4015 virtual void connect_focus_out(const Link
<Widget
&, void>& rLink
) override
4017 if (!m_nFocusOutSignalId
)
4019 #if GTK_CHECK_VERSION(4, 0, 0)
4020 m_nFocusOutSignalId
= g_signal_connect(get_focus_controller(), "leave", G_CALLBACK(signalFocusOut
), this);
4022 m_nFocusOutSignalId
= g_signal_connect(m_pWidget
, "focus-out-event", G_CALLBACK(signalFocusOut
), this);
4025 weld::Widget::connect_focus_out(rLink
);
4028 virtual void connect_size_allocate(const Link
<const Size
&, void>& rLink
) override
4030 m_nSizeAllocateSignalId
= g_signal_connect(m_pWidget
, "size-allocate", G_CALLBACK(signalSizeAllocate
), this);
4031 weld::Widget::connect_size_allocate(rLink
);
4034 virtual void signal_size_allocate(guint nWidth
, guint nHeight
)
4036 m_aSizeAllocateHdl
.Call(Size(nWidth
, nHeight
));
4039 #if GTK_CHECK_VERSION(4, 0, 0)
4040 bool signal_key_press(guint keyval
, guint keycode
, GdkModifierType state
)
4042 if (m_aKeyPressHdl
.IsSet())
4044 SolarMutexGuard aGuard
;
4045 return m_aKeyPressHdl
.Call(CreateKeyEvent(keyval
, keycode
, state
, 0));
4050 bool signal_key_release(guint keyval
, guint keycode
, GdkModifierType state
)
4052 if (m_aKeyReleaseHdl
.IsSet())
4054 SolarMutexGuard aGuard
;
4055 return m_aKeyReleaseHdl
.Call(CreateKeyEvent(keyval
, keycode
, state
, 0));
4061 virtual bool do_signal_key_press(const GdkEventKey
* pEvent
)
4063 if (m_aKeyPressHdl
.IsSet())
4065 SolarMutexGuard aGuard
;
4066 return m_aKeyPressHdl
.Call(GtkToVcl(*pEvent
));
4071 virtual bool do_signal_key_release(const GdkEventKey
* pEvent
)
4073 if (m_aKeyReleaseHdl
.IsSet())
4075 SolarMutexGuard aGuard
;
4076 return m_aKeyReleaseHdl
.Call(GtkToVcl(*pEvent
));
4081 bool signal_key_press(const GdkEventKey
* pEvent
)
4083 return do_signal_key_press(pEvent
);
4086 bool signal_key_release(const GdkEventKey
* pEvent
)
4088 return do_signal_key_release(pEvent
);
4092 virtual void grab_add() override
4094 #if GTK_CHECK_VERSION(4, 0, 0)
4097 gtk_grab_add(m_pWidget
);
4101 virtual bool has_grab() const override
4103 #if GTK_CHECK_VERSION(4, 0, 0)
4104 return m_nGrabCount
!= 0;
4106 return gtk_widget_has_grab(m_pWidget
);
4110 virtual void grab_remove() override
4112 #if GTK_CHECK_VERSION(4, 0, 0)
4115 gtk_grab_remove(m_pWidget
);
4119 virtual bool get_direction() const override
4121 return gtk_widget_get_direction(m_pWidget
) == GTK_TEXT_DIR_RTL
;
4124 virtual void set_direction(bool bRTL
) override
4126 gtk_widget_set_direction(m_pWidget
, bRTL
? GTK_TEXT_DIR_RTL
: GTK_TEXT_DIR_LTR
);
4129 virtual void freeze() override
4132 #if !GTK_CHECK_VERSION(4, 0, 0)
4133 gtk_widget_freeze_child_notify(m_pWidget
);
4135 g_object_freeze_notify(G_OBJECT(m_pWidget
));
4138 virtual void thaw() override
4141 g_object_thaw_notify(G_OBJECT(m_pWidget
));
4142 #if !GTK_CHECK_VERSION(4, 0, 0)
4143 gtk_widget_thaw_child_notify(m_pWidget
);
4147 virtual void set_busy_cursor(bool bBusy
) override
4153 if (m_nWaitCount
== 1)
4154 set_cursor(m_pWidget
, "progress");
4155 else if (m_nWaitCount
== 0)
4156 set_cursor(m_pWidget
, nullptr);
4157 assert (m_nWaitCount
>= 0);
4160 virtual void queue_resize() override
4162 gtk_widget_queue_resize(m_pWidget
);
4165 virtual css::uno::Reference
<css::datatransfer::dnd::XDropTarget
> get_drop_target() override
4169 m_xDropTarget
.set(new GtkInstDropTarget
);
4170 #if !GTK_CHECK_VERSION(4, 0, 0)
4171 if (!gtk_drag_dest_get_track_motion(m_pWidget
))
4173 gtk_drag_dest_set(m_pWidget
, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
4174 gtk_drag_dest_set_track_motion(m_pWidget
, true);
4176 m_nDragMotionSignalId
= g_signal_connect(m_pWidget
, "drag-motion", G_CALLBACK(signalDragMotion
), this);
4177 m_nDragDropSignalId
= g_signal_connect(m_pWidget
, "drag-drop", G_CALLBACK(signalDragDrop
), this);
4178 m_nDragDropReceivedSignalId
= g_signal_connect(m_pWidget
, "drag-data-received", G_CALLBACK(signalDragDropReceived
), this);
4179 m_nDragLeaveSignalId
= g_signal_connect(m_pWidget
, "drag-leave", G_CALLBACK(signalDragLeave
), this);
4182 return m_xDropTarget
;
4185 virtual css::uno::Reference
<css::datatransfer::clipboard::XClipboard
> get_clipboard() const override
4187 // the gen backend can have per-frame clipboards which is (presumably) useful for LibreOffice Online
4188 // but normal usage is the shared system clipboard
4189 return GetSystemClipboard();
4192 virtual void connect_get_property_tree(const Link
<tools::JsonWriter
&, void>& /*rLink*/) override
4194 //not implemented for the gtk variant
4197 virtual void get_property_tree(tools::JsonWriter
& /*rJsonWriter*/) override
4199 //not implemented for the gtk variant
4202 virtual void call_attention_to() override
4204 // Change the class name to restart the animation under
4205 // its other name: https://css-tricks.com/restart-css-animation/
4206 #if GTK_CHECK_VERSION(4, 0, 0)
4207 if (gtk_widget_has_css_class(m_pWidget
, "call_attention_1"))
4209 gtk_widget_remove_css_class(m_pWidget
, "call_attention_1");
4210 gtk_widget_add_css_class(m_pWidget
, "call_attention_2");
4214 gtk_widget_remove_css_class(m_pWidget
, "call_attention_2");
4215 gtk_widget_add_css_class(m_pWidget
, "call_attention_1");
4218 GtkStyleContext
*pWidgetContext
= gtk_widget_get_style_context(m_pWidget
);
4219 if (gtk_style_context_has_class(pWidgetContext
, "call_attention_1"))
4221 gtk_style_context_remove_class(pWidgetContext
, "call_attention_1");
4222 gtk_style_context_add_class(pWidgetContext
, "call_attention_2");
4226 gtk_style_context_remove_class(pWidgetContext
, "call_attention_2");
4227 gtk_style_context_add_class(pWidgetContext
, "call_attention_1");
4232 virtual void set_stack_background() override
4234 do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());
4237 virtual void set_title_background() override
4239 do_set_background(Application::GetSettings().GetStyleSettings().GetShadowColor());
4242 virtual void set_highlight_background() override
4244 do_set_background(Application::GetSettings().GetStyleSettings().GetHighlightColor());
4247 virtual void set_background(const Color
& rColor
) override
4249 do_set_background(rColor
);
4252 virtual void set_toolbar_background() override
4257 virtual ~GtkInstanceWidget() override
4259 if (m_pDragCancelEvent
)
4260 Application::RemoveUserEvent(m_pDragCancelEvent
);
4261 if (m_nDragMotionSignalId
)
4262 g_signal_handler_disconnect(m_pWidget
, m_nDragMotionSignalId
);
4263 if (m_nDragDropSignalId
)
4264 g_signal_handler_disconnect(m_pWidget
, m_nDragDropSignalId
);
4265 if (m_nDragDropReceivedSignalId
)
4266 g_signal_handler_disconnect(m_pWidget
, m_nDragDropReceivedSignalId
);
4267 if (m_nDragLeaveSignalId
)
4268 g_signal_handler_disconnect(m_pWidget
, m_nDragLeaveSignalId
);
4269 if (m_nDragEndSignalId
)
4271 #if GTK_CHECK_VERSION(4, 0, 0)
4272 g_signal_handler_disconnect(get_drag_controller(), m_nDragEndSignalId
);
4274 g_signal_handler_disconnect(m_pWidget
, m_nDragEndSignalId
);
4277 if (m_nDragBeginSignalId
)
4279 #if GTK_CHECK_VERSION(4, 0, 0)
4280 g_signal_handler_disconnect(get_drag_controller(), m_nDragBeginSignalId
);
4282 g_signal_handler_disconnect(m_pWidget
, m_nDragBeginSignalId
);
4285 if (m_nDragFailedSignalId
)
4286 g_signal_handler_disconnect(m_pWidget
, m_nDragFailedSignalId
);
4287 if (m_nDragDataDeleteignalId
)
4288 g_signal_handler_disconnect(m_pWidget
, m_nDragDataDeleteignalId
);
4289 if (m_nDragGetSignalId
)
4290 g_signal_handler_disconnect(m_pWidget
, m_nDragGetSignalId
);
4291 if (m_nKeyPressSignalId
)
4293 #if GTK_CHECK_VERSION(4, 0, 0)
4294 g_signal_handler_disconnect(get_key_controller(), m_nKeyPressSignalId
);
4296 g_signal_handler_disconnect(m_pWidget
, m_nKeyPressSignalId
);
4299 if (m_nKeyReleaseSignalId
)
4301 #if GTK_CHECK_VERSION(4, 0, 0)
4302 g_signal_handler_disconnect(get_key_controller(), m_nKeyReleaseSignalId
);
4304 g_signal_handler_disconnect(m_pWidget
, m_nKeyReleaseSignalId
);
4308 if (m_nFocusInSignalId
)
4310 #if GTK_CHECK_VERSION(4, 0, 0)
4311 g_signal_handler_disconnect(get_focus_controller(), m_nFocusInSignalId
);
4313 g_signal_handler_disconnect(m_pWidget
, m_nFocusInSignalId
);
4316 if (m_nMnemonicActivateSignalId
)
4317 g_signal_handler_disconnect(m_pWidget
, m_nMnemonicActivateSignalId
);
4318 if (m_nFocusOutSignalId
)
4320 #if GTK_CHECK_VERSION(4, 0, 0)
4321 g_signal_handler_disconnect(get_focus_controller(), m_nFocusOutSignalId
);
4323 g_signal_handler_disconnect(m_pWidget
, m_nFocusOutSignalId
);
4326 if (m_nSizeAllocateSignalId
)
4327 g_signal_handler_disconnect(m_pWidget
, m_nSizeAllocateSignalId
);
4329 do_set_background(COL_AUTO
);
4331 DisconnectMouseEvents();
4333 if (m_bTakeOwnership
)
4335 #if !GTK_CHECK_VERSION(4, 0, 0)
4336 gtk_widget_destroy(m_pWidget
);
4338 gtk_window_destroy(GTK_WINDOW(m_pWidget
));
4342 g_object_unref(m_pWidget
);
4345 virtual void disable_notify_events()
4347 if (m_nFocusInSignalId
)
4349 #if GTK_CHECK_VERSION(4, 0, 0)
4350 g_signal_handler_block(get_focus_controller(), m_nFocusInSignalId
);
4352 g_signal_handler_block(m_pWidget
, m_nFocusInSignalId
);
4355 if (m_nMnemonicActivateSignalId
)
4356 g_signal_handler_block(m_pWidget
, m_nMnemonicActivateSignalId
);
4357 if (m_nFocusOutSignalId
)
4359 #if GTK_CHECK_VERSION(4, 0, 0)
4360 g_signal_handler_block(get_focus_controller(), m_nFocusOutSignalId
);
4362 g_signal_handler_block(m_pWidget
, m_nFocusOutSignalId
);
4365 if (m_nSizeAllocateSignalId
)
4366 g_signal_handler_block(m_pWidget
, m_nSizeAllocateSignalId
);
4369 virtual void enable_notify_events()
4371 if (m_nSizeAllocateSignalId
)
4372 g_signal_handler_unblock(m_pWidget
, m_nSizeAllocateSignalId
);
4373 if (m_nFocusOutSignalId
)
4375 #if GTK_CHECK_VERSION(4, 0, 0)
4376 g_signal_handler_unblock(get_focus_controller(), m_nFocusOutSignalId
);
4378 g_signal_handler_unblock(m_pWidget
, m_nFocusOutSignalId
);
4381 if (m_nMnemonicActivateSignalId
)
4382 g_signal_handler_unblock(m_pWidget
, m_nMnemonicActivateSignalId
);
4384 if (m_nFocusInSignalId
)
4386 #if GTK_CHECK_VERSION(4, 0, 0)
4387 g_signal_handler_unblock(get_focus_controller(), m_nFocusInSignalId
);
4389 g_signal_handler_unblock(m_pWidget
, m_nFocusInSignalId
);
4394 virtual void help_hierarchy_foreach(const std::function
<bool(const OString
&)>& func
) override
;
4396 virtual OUString
strip_mnemonic(const OUString
&rLabel
) const override
4398 return rLabel
.replaceFirst("_", "");
4401 virtual VclPtr
<VirtualDevice
> create_virtual_device() const override
4403 // create with no separate alpha layer like everything sane does
4404 auto xRet
= VclPtr
<VirtualDevice
>::Create();
4405 xRet
->SetBackground(COL_TRANSPARENT
);
4409 virtual void draw(OutputDevice
& rOutput
, const Point
& rPos
, const Size
& rPixelSize
) override
4411 // detect if we have to manually setup its size
4412 bool bAlreadyRealized
= gtk_widget_get_realized(m_pWidget
);
4413 // has to be visible for draw to work
4414 bool bAlreadyVisible
= gtk_widget_get_visible(m_pWidget
);
4415 // has to be mapped for draw to work
4416 bool bAlreadyMapped
= gtk_widget_get_mapped(m_pWidget
);
4418 if (!bAlreadyRealized
)
4420 #if !GTK_CHECK_VERSION(4, 0, 0)
4422 tdf#141633 The "sample db" example (Mockup.odb) has multiline
4423 entries used in its "Journal Entry" column. Those are painted by
4424 taking snapshots of a never-really-shown textview widget.
4425 Without this style_updated then the textview is always drawn
4426 using its original default font size and changing the page zoom
4427 has no effect on the size of text in the "Journal Entry" column.
4429 update_style(m_pWidget
, nullptr);
4431 gtk_widget_realize(m_pWidget
);
4433 if (!bAlreadyVisible
)
4434 gtk_widget_show(m_pWidget
);
4435 if (!bAlreadyMapped
)
4436 gtk_widget_map(m_pWidget
);
4438 assert(gtk_widget_is_drawable(m_pWidget
)); // all that should result in this holding
4440 // turn off animations, otherwise we get a frame of an animation sequence
4441 gboolean bAnimations
;
4442 GtkSettings
* pSettings
= gtk_widget_get_settings(m_pWidget
);
4443 g_object_get(pSettings
, "gtk-enable-animations", &bAnimations
, nullptr);
4445 g_object_set(pSettings
, "gtk-enable-animations", false, nullptr);
4447 Size
aSize(rPixelSize
);
4449 GtkAllocation aOrigAllocation
;
4450 gtk_widget_get_allocation(m_pWidget
, &aOrigAllocation
);
4452 GtkAllocation aNewAllocation
{aOrigAllocation
.x
,
4454 static_cast<int>(aSize
.Width()),
4455 static_cast<int>(aSize
.Height()) };
4456 #if !GTK_CHECK_VERSION(4, 0, 0)
4457 gtk_widget_size_allocate(m_pWidget
, &aNewAllocation
);
4459 gtk_widget_size_allocate(m_pWidget
, &aNewAllocation
, 0);
4462 #if !GTK_CHECK_VERSION(4, 0, 0)
4463 if (GTK_IS_CONTAINER(m_pWidget
))
4464 gtk_container_resize_children(GTK_CONTAINER(m_pWidget
));
4467 VclPtr
<VirtualDevice
> xOutput(VclPtr
<VirtualDevice
>::Create(DeviceFormat::DEFAULT
));
4468 xOutput
->SetOutputSizePixel(aSize
);
4470 switch (rOutput
.GetOutDevType())
4474 xOutput
->DrawOutDev(Point(), aSize
, rPos
, aSize
, rOutput
);
4476 case OUTDEV_PRINTER
:
4478 xOutput
->SetBackground(rOutput
.GetBackground());
4483 cairo_surface_t
* pSurface
= get_underlying_cairo_surface(*xOutput
);
4484 cairo_t
* cr
= cairo_create(pSurface
);
4486 #if !GTK_CHECK_VERSION(4, 0, 0)
4487 gtk_widget_draw(m_pWidget
, cr
);
4489 GtkSnapshot
* pSnapshot
= gtk_snapshot_new();
4490 GtkWidgetClass
* pWidgetClass
= GTK_WIDGET_GET_CLASS(m_pWidget
);
4491 pWidgetClass
->snapshot(m_pWidget
, pSnapshot
);
4492 GskRenderNode
* pNode
= gtk_snapshot_free_to_node(pSnapshot
);
4493 gsk_render_node_draw(pNode
, cr
);
4494 gsk_render_node_unref(pNode
);
4499 #if !GTK_CHECK_VERSION(4, 0, 0)
4500 gtk_widget_set_allocation(m_pWidget
, &aOrigAllocation
);
4501 gtk_widget_size_allocate(m_pWidget
, &aOrigAllocation
);
4503 gtk_widget_size_allocate(m_pWidget
, &aOrigAllocation
, 0);
4506 switch (rOutput
.GetOutDevType())
4510 rOutput
.DrawOutDev(rPos
, aSize
, Point(), aSize
, *xOutput
);
4512 case OUTDEV_PRINTER
:
4514 rOutput
.DrawBitmapEx(rPos
, xOutput
->GetBitmapEx(Point(), aSize
));
4519 g_object_set(pSettings
, "gtk-enable-animations", true, nullptr);
4521 if (!bAlreadyMapped
)
4522 gtk_widget_unmap(m_pWidget
);
4523 if (!bAlreadyVisible
)
4524 gtk_widget_hide(m_pWidget
);
4525 if (!bAlreadyRealized
)
4526 gtk_widget_unrealize(m_pWidget
);
4532 #if !GTK_CHECK_VERSION(4, 0, 0)
4533 IMPL_LINK(GtkInstanceWidget
, async_drag_cancel
, void*, arg
, void)
4535 m_pDragCancelEvent
= nullptr;
4536 GdkDragContext
* context
= static_cast<GdkDragContext
*>(arg
);
4538 // tdf#132477 simply calling gtk_drag_cancel on the treeview dnd under X
4539 // doesn't seem to work as hoped for (though under wayland all is well).
4540 // Under X the next (allowed) drag effort doesn't work to drop anything,
4541 // but a then repeated attempt does.
4542 // emitting cancel to get gtk to cancel the drag for us does work as hoped for.
4543 g_signal_emit_by_name(context
, "cancel", 0, GDK_DRAG_CANCEL_USER_CANCELLED
);
4545 g_object_unref(context
);
4551 OString
MapToGtkAccelerator(const OUString
&rStr
)
4553 return OUStringToOString(rStr
.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8
);
4556 OUString
get_label(GtkLabel
* pLabel
)
4558 const gchar
* pStr
= gtk_label_get_label(pLabel
);
4559 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
4562 void set_label(GtkLabel
* pLabel
, const OUString
& rText
)
4564 gtk_label_set_label(pLabel
, MapToGtkAccelerator(rText
).getStr());
4567 #if GTK_CHECK_VERSION(4, 0, 0)
4568 GtkWidget
* find_label_widget(GtkWidget
* pContainer
)
4570 GtkWidget
* pLabel
= nullptr;
4571 for (GtkWidget
* pChild
= gtk_widget_get_first_child(pContainer
);
4572 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
4574 if (GTK_IS_LABEL(pChild
))
4581 pLabel
= find_label_widget(pChild
);
4589 GtkWidget
* find_image_widget(GtkWidget
* pContainer
)
4591 GtkWidget
* pImage
= nullptr;
4592 for (GtkWidget
* pChild
= gtk_widget_get_first_child(pContainer
);
4593 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
4595 if (GTK_IS_IMAGE(pChild
))
4602 pImage
= find_image_widget(pChild
);
4610 GtkWidget
* find_label_widget(GtkContainer
* pContainer
)
4612 GList
* pChildren
= gtk_container_get_children(pContainer
);
4614 GtkWidget
* pChild
= nullptr;
4615 for (GList
* pCandidate
= pChildren
; pCandidate
; pCandidate
= pCandidate
->next
)
4617 if (GTK_IS_LABEL(pCandidate
->data
))
4619 pChild
= GTK_WIDGET(pCandidate
->data
);
4622 else if (GTK_IS_CONTAINER(pCandidate
->data
))
4624 pChild
= find_label_widget(GTK_CONTAINER(pCandidate
->data
));
4629 g_list_free(pChildren
);
4634 GtkWidget
* find_image_widget(GtkContainer
* pContainer
)
4636 GList
* pChildren
= gtk_container_get_children(pContainer
);
4638 GtkWidget
* pChild
= nullptr;
4639 for (GList
* pCandidate
= pChildren
; pCandidate
; pCandidate
= pCandidate
->next
)
4641 if (GTK_IS_IMAGE(pCandidate
->data
))
4643 pChild
= GTK_WIDGET(pCandidate
->data
);
4646 else if (GTK_IS_CONTAINER(pCandidate
->data
))
4648 pChild
= find_image_widget(GTK_CONTAINER(pCandidate
->data
));
4653 g_list_free(pChildren
);
4659 GtkLabel
* get_label_widget(GtkWidget
* pButton
)
4661 #if !GTK_CHECK_VERSION(4, 0, 0)
4662 GtkWidget
* pChild
= gtk_bin_get_child(GTK_BIN(pButton
));
4664 if (GTK_IS_CONTAINER(pChild
))
4665 pChild
= find_label_widget(GTK_CONTAINER(pChild
));
4666 else if (!GTK_IS_LABEL(pChild
))
4669 return GTK_LABEL(pChild
);
4671 return GTK_LABEL(find_label_widget(pButton
));
4675 GtkImage
* get_image_widget(GtkWidget
*pButton
)
4677 #if !GTK_CHECK_VERSION(4, 0, 0)
4678 GtkWidget
* pChild
= gtk_bin_get_child(GTK_BIN(pButton
));
4680 if (GTK_IS_CONTAINER(pChild
))
4681 pChild
= find_image_widget(GTK_CONTAINER(pChild
));
4682 else if (!GTK_IS_IMAGE(pChild
))
4685 return GTK_IMAGE(pChild
);
4687 return GTK_IMAGE(find_image_widget(pButton
));
4691 OUString
button_get_label(GtkButton
* pButton
)
4693 if (GtkLabel
* pLabel
= get_label_widget(GTK_WIDGET(pButton
)))
4694 return ::get_label(pLabel
);
4695 const gchar
* pStr
= gtk_button_get_label(pButton
);
4696 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
4699 void button_set_label(GtkButton
* pButton
, const OUString
& rText
)
4701 if (GtkLabel
* pLabel
= get_label_widget(GTK_WIDGET(pButton
)))
4703 ::set_label(pLabel
, rText
);
4704 gtk_widget_set_visible(GTK_WIDGET(pLabel
), true);
4707 gtk_button_set_label(pButton
, MapToGtkAccelerator(rText
).getStr());
4710 #if GTK_CHECK_VERSION(4, 0, 0)
4711 OUString
get_label(GtkCheckButton
* pButton
)
4713 const gchar
* pStr
= gtk_check_button_get_label(pButton
);
4714 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
4717 void set_label(GtkCheckButton
* pButton
, const OUString
& rText
)
4719 gtk_check_button_set_label(pButton
, MapToGtkAccelerator(rText
).getStr());
4723 OUString
get_title(GtkWindow
* pWindow
)
4725 const gchar
* pStr
= gtk_window_get_title(pWindow
);
4726 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
4729 void set_title(GtkWindow
* pWindow
, std::u16string_view rTitle
)
4731 gtk_window_set_title(pWindow
, OUStringToOString(rTitle
, RTL_TEXTENCODING_UTF8
).getStr());
4734 OUString
get_primary_text(GtkMessageDialog
* pMessageDialog
)
4736 gchar
* pText
= nullptr;
4737 g_object_get(G_OBJECT(pMessageDialog
), "text", &pText
, nullptr);
4738 return OUString(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
4741 void set_primary_text(GtkMessageDialog
* pMessageDialog
, std::u16string_view rText
)
4743 g_object_set(G_OBJECT(pMessageDialog
), "text",
4744 OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr(),
4748 void set_secondary_text(GtkMessageDialog
* pMessageDialog
, std::u16string_view rText
)
4750 g_object_set(G_OBJECT(pMessageDialog
), "secondary-text",
4751 OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr(),
4755 OUString
get_secondary_text(GtkMessageDialog
* pMessageDialog
)
4757 gchar
* pText
= nullptr;
4758 g_object_get(G_OBJECT(pMessageDialog
), "secondary-text", &pText
, nullptr);
4759 return OUString(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
4765 GdkPixbuf
* load_icon_from_stream(SvMemoryStream
& rStream
)
4767 auto nLength
= rStream
.TellEnd();
4770 const guchar
* pData
= static_cast<const guchar
*>(rStream
.GetData());
4771 assert((*pData
== 137 || *pData
== '<') && "if we want to support more than png or svg this function must change");
4772 // if we know the image type, it's a little faster to hand the type over and skip the type detection.
4773 GdkPixbufLoader
*pixbuf_loader
= gdk_pixbuf_loader_new_with_type(*pData
== 137 ? "png" : "svg", nullptr);
4774 gdk_pixbuf_loader_write(pixbuf_loader
, pData
, nLength
, nullptr);
4775 gdk_pixbuf_loader_close(pixbuf_loader
, nullptr);
4776 GdkPixbuf
* pixbuf
= gdk_pixbuf_loader_get_pixbuf(pixbuf_loader
);
4778 g_object_ref(pixbuf
);
4779 g_object_unref(pixbuf_loader
);
4783 GdkPixbuf
* load_icon_by_name_theme_lang(const OUString
& rIconName
, const OUString
& rIconTheme
, const OUString
& rUILang
)
4785 auto xMemStm
= ImageTree::get().getImageStream(rIconName
, rIconTheme
, rUILang
);
4788 return load_icon_from_stream(*xMemStm
);
4792 GdkPixbuf
* load_icon_by_name(const OUString
& rIconName
)
4794 OUString sIconTheme
= Application::GetSettings().GetStyleSettings().DetermineIconTheme();
4795 OUString sUILang
= Application::GetSettings().GetUILanguageTag().getBcp47();
4796 return load_icon_by_name_theme_lang(rIconName
, sIconTheme
, sUILang
);
4801 GdkPixbuf
* getPixbuf(const css::uno::Reference
<css::graphic::XGraphic
>& rImage
)
4803 Image
aImage(rImage
);
4805 OUString
sStock(aImage
.GetStock());
4806 if (!sStock
.isEmpty())
4807 return load_icon_by_name(sStock
);
4809 SvMemoryStream aMemStm
;
4811 // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
4812 css::uno::Sequence
<css::beans::PropertyValue
> aFilterData
{ comphelper::makePropertyValue(
4813 "Compression", sal_Int32(1)) };
4815 vcl::PNGWriter
aWriter(aImage
.GetBitmapEx(), &aFilterData
);
4816 aWriter
.Write(aMemStm
);
4818 return load_icon_from_stream(aMemStm
);
4821 GdkPixbuf
* getPixbuf(const VirtualDevice
& rDevice
)
4823 Size
aSize(rDevice
.GetOutputSizePixel());
4824 cairo_surface_t
* orig_surface
= get_underlying_cairo_surface(rDevice
);
4825 double m_fXScale
, m_fYScale
;
4826 dl_cairo_surface_get_device_scale(orig_surface
, &m_fXScale
, &m_fYScale
);
4828 cairo_surface_t
* surface
;
4829 if (m_fXScale
!= 1.0 || m_fYScale
!= -1)
4831 surface
= cairo_surface_create_similar_image(orig_surface
,
4832 CAIRO_FORMAT_ARGB32
,
4835 cairo_t
* cr
= cairo_create(surface
);
4836 cairo_set_source_surface(cr
, orig_surface
, 0, 0);
4841 surface
= orig_surface
;
4843 GdkPixbuf
* pRet
= gdk_pixbuf_get_from_surface(surface
, 0, 0, aSize
.Width(), aSize
.Height());
4845 if (surface
!= orig_surface
)
4846 cairo_surface_destroy(surface
);
4851 #if GTK_CHECK_VERSION(4, 0, 0)
4852 cairo_surface_t
* render_paintable_to_surface(GdkPaintable
*paintable
, int nWidth
, int nHeight
)
4854 cairo_surface_t
* surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, nWidth
, nHeight
);
4856 GtkSnapshot
* snapshot
= gtk_snapshot_new();
4857 gdk_paintable_snapshot(paintable
, snapshot
, nWidth
, nHeight
);
4858 GskRenderNode
* node
= gtk_snapshot_free_to_node(snapshot
);
4860 cairo_t
* cr
= cairo_create(surface
);
4861 gsk_render_node_draw(node
, cr
);
4864 gsk_render_node_unref(node
);
4870 GdkPixbuf
* getPixbuf(const OUString
& rIconName
)
4872 if (rIconName
.isEmpty())
4875 GdkPixbuf
* pixbuf
= nullptr;
4876 if (rIconName
.lastIndexOf('.') != rIconName
.getLength() - 4)
4878 assert((rIconName
== "dialog-warning" || rIconName
== "dialog-error" || rIconName
== "dialog-information") &&
4879 "unknown stock image");
4881 #if GTK_CHECK_VERSION(4, 0, 0)
4882 GtkIconTheme
*icon_theme
= gtk_icon_theme_get_for_display(gdk_display_get_default());
4883 GtkIconPaintable
*icon
= gtk_icon_theme_lookup_icon(icon_theme
,
4884 OUStringToOString(rIconName
, RTL_TEXTENCODING_UTF8
).getStr(),
4888 AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL
: GTK_TEXT_DIR_LTR
,
4889 static_cast<GtkIconLookupFlags
>(0));
4890 GdkPaintable
* paintable
= GDK_PAINTABLE(icon
);
4891 int nWidth
= gdk_paintable_get_intrinsic_width(paintable
);
4892 int nHeight
= gdk_paintable_get_intrinsic_height(paintable
);
4893 cairo_surface_t
* surface
= render_paintable_to_surface(paintable
, nWidth
, nHeight
);
4894 pixbuf
= gdk_pixbuf_get_from_surface(surface
, 0, 0, nWidth
, nHeight
);
4895 cairo_surface_destroy(surface
);
4897 GtkIconTheme
*icon_theme
= gtk_icon_theme_get_default();
4898 GError
*error
= nullptr;
4899 pixbuf
= gtk_icon_theme_load_icon(icon_theme
, OUStringToOString(rIconName
, RTL_TEXTENCODING_UTF8
).getStr(),
4900 16, GTK_ICON_LOOKUP_USE_BUILTIN
, &error
);
4905 const AllSettings
& rSettings
= Application::GetSettings();
4906 pixbuf
= load_icon_by_name_theme_lang(rIconName
,
4907 rSettings
.GetStyleSettings().DetermineIconTheme(),
4908 rSettings
.GetUILanguageTag().getBcp47());
4916 #if GTK_CHECK_VERSION(4, 0, 0)
4917 SurfacePaintable
* paintable_new_from_virtual_device(const VirtualDevice
& rImageSurface
)
4919 cairo_surface_t
* surface
= get_underlying_cairo_surface(rImageSurface
);
4921 Size
aSize(rImageSurface
.GetOutputSizePixel());
4922 cairo_surface_t
* target
= cairo_surface_create_similar(surface
,
4923 cairo_surface_get_content(surface
),
4926 cairo_t
* cr
= cairo_create(target
);
4927 cairo_set_source_surface(cr
, surface
, 0, 0);
4931 SurfacePaintable
* pPaintable
= SURFACE_PAINTABLE(g_object_new(surface_paintable_get_type(), nullptr));
4932 surface_paintable_set_source(pPaintable
, target
, aSize
.Width(), aSize
.Height());
4936 GtkWidget
* image_new_from_virtual_device(const VirtualDevice
& rImageSurface
)
4938 SurfacePaintable
* paintable
= paintable_new_from_virtual_device(rImageSurface
);
4939 return gtk_image_new_from_paintable(GDK_PAINTABLE(paintable
));
4942 GtkWidget
* picture_new_from_virtual_device(const VirtualDevice
& rImageSurface
)
4944 SurfacePaintable
* paintable
= paintable_new_from_virtual_device(rImageSurface
);
4945 return gtk_picture_new_for_paintable(GDK_PAINTABLE(paintable
));
4949 GtkWidget
* image_new_from_virtual_device(const VirtualDevice
& rImageSurface
)
4951 GtkWidget
* pImage
= nullptr;
4952 cairo_surface_t
* surface
= get_underlying_cairo_surface(rImageSurface
);
4954 Size
aSize(rImageSurface
.GetOutputSizePixel());
4955 cairo_surface_t
* target
= cairo_surface_create_similar(surface
,
4956 cairo_surface_get_content(surface
),
4959 cairo_t
* cr
= cairo_create(target
);
4960 cairo_set_source_surface(cr
, surface
, 0, 0);
4964 pImage
= gtk_image_new_from_surface(target
);
4965 cairo_surface_destroy(target
);
4970 void image_set_from_icon_name(GtkImage
* pImage
, const OUString
& rIconName
)
4972 GdkPixbuf
* pixbuf
= load_icon_by_name(rIconName
);
4973 gtk_image_set_from_pixbuf(pImage
, pixbuf
);
4976 g_object_unref(pixbuf
);
4979 void image_set_from_virtual_device(GtkImage
* pImage
, const VirtualDevice
* pDevice
)
4981 #if GTK_CHECK_VERSION(4, 0, 0)
4982 gtk_image_set_from_paintable(pImage
, pDevice
? GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice
)) : nullptr);
4984 gtk_image_set_from_surface(pImage
, pDevice
? get_underlying_cairo_surface(*pDevice
) : nullptr);
4988 void image_set_from_xgraphic(GtkImage
* pImage
, const css::uno::Reference
<css::graphic::XGraphic
>& rImage
)
4990 GdkPixbuf
* pixbuf
= getPixbuf(rImage
);
4991 gtk_image_set_from_pixbuf(pImage
, pixbuf
);
4993 g_object_unref(pixbuf
);
4996 #if GTK_CHECK_VERSION(4, 0, 0)
4997 void picture_set_from_icon_name(GtkPicture
* pPicture
, const OUString
& rIconName
)
4999 GdkPixbuf
* pixbuf
= load_icon_by_name(rIconName
);
5000 gtk_picture_set_pixbuf(pPicture
, pixbuf
);
5002 g_object_unref(pixbuf
);
5005 void picture_set_from_virtual_device(GtkPicture
* pPicture
, const VirtualDevice
* pDevice
)
5008 gtk_picture_set_paintable(pPicture
, nullptr);
5010 gtk_picture_set_paintable(pPicture
, GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice
)));
5013 void picture_set_from_xgraphic(GtkPicture
* pPicture
, const css::uno::Reference
<css::graphic::XGraphic
>& rPicture
)
5015 GdkPixbuf
* pixbuf
= getPixbuf(rPicture
);
5016 gtk_picture_set_pixbuf(pPicture
, pixbuf
);
5018 g_object_unref(pixbuf
);
5022 void button_set_from_icon_name(GtkButton
* pButton
, const OUString
& rIconName
)
5024 if (GtkImage
* pImage
= get_image_widget(GTK_WIDGET(pButton
)))
5026 ::image_set_from_icon_name(pImage
, rIconName
);
5027 gtk_widget_set_visible(GTK_WIDGET(pImage
), true);
5031 GdkPixbuf
* pixbuf
= load_icon_by_name(rIconName
);
5037 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
5038 g_object_unref(pixbuf
);
5040 #if GTK_CHECK_VERSION(4, 0, 0)
5041 gtk_button_set_child(pButton
, pImage
);
5043 gtk_button_set_image(pButton
, pImage
);
5047 void button_set_image(GtkButton
* pButton
, const VirtualDevice
* pDevice
)
5049 #if !GTK_CHECK_VERSION(4, 0, 0)
5050 gtk_button_set_always_show_image(pButton
, true);
5051 gtk_button_set_image_position(pButton
, GTK_POS_LEFT
);
5053 GtkWidget
* pImage
= pDevice
? image_new_from_virtual_device(*pDevice
) : nullptr;
5054 #if GTK_CHECK_VERSION(4, 0, 0)
5055 gtk_button_set_child(pButton
, pImage
);
5057 gtk_button_set_image(pButton
, pImage
);
5061 void button_set_image(GtkButton
* pButton
, const css::uno::Reference
<css::graphic::XGraphic
>& rImage
)
5063 if (GtkImage
* pImage
= get_image_widget(GTK_WIDGET(pButton
)))
5065 ::image_set_from_xgraphic(pImage
, rImage
);
5066 gtk_widget_set_visible(GTK_WIDGET(pImage
), true);
5070 GdkPixbuf
* pixbuf
= getPixbuf(rImage
);
5076 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
5077 g_object_unref(pixbuf
);
5079 #if GTK_CHECK_VERSION(4, 0, 0)
5080 gtk_button_set_child(pButton
, pImage
);
5082 gtk_button_set_image(pButton
, pImage
);
5090 #if !GTK_CHECK_VERSION(4, 0, 0)
5093 std::map
<OString
, GtkMenuItem
*> m_aMap
;
5095 GtkPopoverMenu
* m_pMenu
;
5097 o3tl::sorted_vector
<OString
> m_aInsertedActions
; // must outlive m_aActionEntries
5098 std::map
<OString
, OString
> m_aIdToAction
;
5099 std::set
<OString
> m_aHiddenIds
;
5100 std::vector
<GActionEntry
> m_aActionEntries
;
5101 GActionGroup
* m_pActionGroup
;
5102 // move 'invisible' entries to m_pHiddenActionGroup
5103 GActionGroup
* m_pHiddenActionGroup
;
5105 bool m_bTakeOwnership
;
5108 virtual void signal_item_activate(const OString
& rIdent
) = 0;
5110 #if !GTK_CHECK_VERSION(4, 0, 0)
5111 static void collect(GtkWidget
* pItem
, gpointer widget
)
5113 GtkMenuItem
* pMenuItem
= GTK_MENU_ITEM(pItem
);
5114 if (GtkWidget
* pSubMenu
= gtk_menu_item_get_submenu(pMenuItem
))
5115 gtk_container_foreach(GTK_CONTAINER(pSubMenu
), collect
, widget
);
5116 MenuHelper
* pThis
= static_cast<MenuHelper
*>(widget
);
5117 pThis
->add_to_map(pMenuItem
);
5120 static void signalActivate(GtkMenuItem
* pItem
, gpointer widget
)
5122 MenuHelper
* pThis
= static_cast<MenuHelper
*>(widget
);
5123 SolarMutexGuard aGuard
;
5124 pThis
->signal_item_activate(::get_buildable_id(GTK_BUILDABLE(pItem
)));
5127 static std::pair
<GMenuModel
*, int> find_id(GMenuModel
* pMenuModel
, const OString
& rId
)
5129 for (int i
= 0, nCount
= g_menu_model_get_n_items(pMenuModel
); i
< nCount
; ++i
)
5133 if (g_menu_model_get_item_attribute(pMenuModel
, i
, "target", "s", &id
))
5135 sTarget
= OString(id
);
5140 return std::make_pair(pMenuModel
, i
);
5142 if (GMenuModel
* pSectionModel
= g_menu_model_get_item_link(pMenuModel
, i
, G_MENU_LINK_SECTION
))
5144 std::pair
<GMenuModel
*, int> aRet
= find_id(pSectionModel
, rId
);
5148 if (GMenuModel
* pSubMenuModel
= g_menu_model_get_item_link(pMenuModel
, i
, G_MENU_LINK_SUBMENU
))
5150 std::pair
<GMenuModel
*, int> aRet
= find_id(pSubMenuModel
, rId
);
5156 return std::make_pair(nullptr, -1);
5159 void clear_actions()
5161 for (const auto& rAction
: m_aActionEntries
)
5163 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup
), rAction
.name
);
5164 g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup
), rAction
.name
);
5166 m_aActionEntries
.clear();
5167 m_aInsertedActions
.clear();
5168 m_aIdToAction
.clear();
5171 static void action_activated(GSimpleAction
*, GVariant
* pParameter
, gpointer widget
)
5174 const gchar
* pStr
= g_variant_get_string(pParameter
, &nLength
);
5175 OString
aStr(pStr
, nLength
);
5176 MenuHelper
* pThis
= static_cast<MenuHelper
*>(widget
);
5177 SolarMutexGuard aGuard
;
5178 pThis
->signal_item_activate(aStr
);
5183 #if !GTK_CHECK_VERSION(4, 0, 0)
5184 MenuHelper(GtkMenu
* pMenu
, bool bTakeOwnership
)
5186 MenuHelper(GtkPopoverMenu
* pMenu
, bool bTakeOwnership
)
5189 , m_bTakeOwnership(bTakeOwnership
)
5191 #if !GTK_CHECK_VERSION(4, 0, 0)
5194 gtk_container_foreach(GTK_CONTAINER(m_pMenu
), collect
, this);
5196 m_pActionGroup
= G_ACTION_GROUP(g_simple_action_group_new());
5197 m_pHiddenActionGroup
= G_ACTION_GROUP(g_simple_action_group_new());
5201 #if !GTK_CHECK_VERSION(4, 0, 0)
5202 void add_to_map(GtkMenuItem
* pMenuItem
)
5204 OString id
= ::get_buildable_id(GTK_BUILDABLE(pMenuItem
));
5205 m_aMap
[id
] = pMenuItem
;
5206 g_signal_connect(pMenuItem
, "activate", G_CALLBACK(signalActivate
), this);
5209 void remove_from_map(GtkMenuItem
* pMenuItem
)
5211 OString id
= ::get_buildable_id(GTK_BUILDABLE(pMenuItem
));
5212 auto iter
= m_aMap
.find(id
);
5213 g_signal_handlers_disconnect_by_data(pMenuItem
, this);
5217 void disable_item_notify_events()
5219 for (auto& a
: m_aMap
)
5220 g_signal_handlers_block_by_func(a
.second
, reinterpret_cast<void*>(signalActivate
), this);
5223 void enable_item_notify_events()
5225 for (auto& a
: m_aMap
)
5226 g_signal_handlers_unblock_by_func(a
.second
, reinterpret_cast<void*>(signalActivate
), this);
5230 #if GTK_CHECK_VERSION(4, 0, 0)
5231 /* LibreOffice likes to think of separators between menu entries, while gtk likes
5232 to think of sections of menus with separators drawn between sections. We always
5233 arrange to have a section in a menu so toplevel menumodels comprise of
5234 sections and we move entries between sections on pretending to insert separators */
5235 static std::pair
<GMenuModel
*, int> get_section_and_pos_for(GMenuModel
* pMenuModel
, int pos
)
5237 int nSectionCount
= g_menu_model_get_n_items(pMenuModel
);
5238 assert(nSectionCount
);
5240 GMenuModel
* pSectionModel
= nullptr;
5241 int nIndexWithinSection
= 0;
5243 int nExternalPos
= 0;
5244 for (int nSection
= 0; nSection
< nSectionCount
; ++nSection
)
5246 pSectionModel
= g_menu_model_get_item_link(pMenuModel
, nSection
, G_MENU_LINK_SECTION
);
5247 assert(pSectionModel
);
5248 int nCount
= g_menu_model_get_n_items(pSectionModel
);
5249 for (nIndexWithinSection
= 0; nIndexWithinSection
< nCount
; ++nIndexWithinSection
)
5251 if (pos
== nExternalPos
)
5258 return std::make_pair(pSectionModel
, nIndexWithinSection
);
5261 static int count_immediate_children(GMenuModel
* pMenuModel
)
5263 int nSectionCount
= g_menu_model_get_n_items(pMenuModel
);
5264 assert(nSectionCount
);
5266 int nExternalPos
= 0;
5267 for (int nSection
= 0; nSection
< nSectionCount
; ++nSection
)
5269 GMenuModel
* pSectionModel
= g_menu_model_get_item_link(pMenuModel
, nSection
, G_MENU_LINK_SECTION
);
5270 assert(pSectionModel
);
5271 int nCount
= g_menu_model_get_n_items(pSectionModel
);
5272 for (int nIndexWithinSection
= 0; nIndexWithinSection
< nCount
; ++nIndexWithinSection
)
5279 return nExternalPos
- 1;
5283 #if GTK_CHECK_VERSION(4, 0, 0)
5284 void process_menu_model(GMenuModel
* pMenuModel
)
5286 for (int i
= 0, nCount
= g_menu_model_get_n_items(pMenuModel
); i
< nCount
; ++i
)
5288 OString sAction
, sTarget
;
5290 if (g_menu_model_get_item_attribute(pMenuModel
, i
, "action", "s", &id
))
5292 assert(o3tl::starts_with(id
, "menu."));
5294 sAction
= OString(id
+ 5);
5296 auto res
= m_aInsertedActions
.insert(sAction
);
5299 // the const char* arg isn't copied by anything so it must continue to exist for the life time of
5301 if (sAction
.startsWith("radio."))
5302 m_aActionEntries
.push_back({res
.first
->getStr(), action_activated
, "s", "'none'", nullptr, {}});
5304 m_aActionEntries
.push_back({res
.first
->getStr(), action_activated
, "s", nullptr, nullptr, {}});
5310 if (g_menu_model_get_item_attribute(pMenuModel
, i
, "target", "s", &id
))
5312 sTarget
= OString(id
);
5316 m_aIdToAction
[sTarget
] = sAction
;
5318 if (GMenuModel
* pSectionModel
= g_menu_model_get_item_link(pMenuModel
, i
, G_MENU_LINK_SECTION
))
5319 process_menu_model(pSectionModel
);
5320 if (GMenuModel
* pSubMenuModel
= g_menu_model_get_item_link(pMenuModel
, i
, G_MENU_LINK_SUBMENU
))
5321 process_menu_model(pSubMenuModel
);
5325 // build an action group for the menu, "action" is the normal menu entry case
5326 // the others are radiogroups
5327 void update_action_group_from_popover_model()
5331 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5333 process_menu_model(pMenuModel
);
5336 // move hidden entries to m_pHiddenActionGroup
5337 g_action_map_add_action_entries(G_ACTION_MAP(m_pActionGroup
), m_aActionEntries
.data(), m_aActionEntries
.size(), this);
5338 for (const auto& id
: m_aHiddenIds
)
5340 GAction
* pAction
= g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup
), m_aIdToAction
[id
].getStr());
5341 g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup
), pAction
);
5342 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup
), m_aIdToAction
[id
].getStr());
5347 void insert_item(int pos
, const OUString
& rId
, const OUString
& rStr
,
5348 const OUString
* pIconName
, const VirtualDevice
* pImageSurface
,
5349 TriState eCheckRadioFalse
)
5351 #if !GTK_CHECK_VERSION(4, 0, 0)
5352 GtkWidget
* pImage
= nullptr;
5353 if (pIconName
&& !pIconName
->isEmpty())
5355 GdkPixbuf
* pixbuf
= load_icon_by_name(*pIconName
);
5358 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
5359 g_object_unref(pixbuf
);
5362 else if (pImageSurface
)
5363 pImage
= image_new_from_virtual_device(*pImageSurface
);
5368 GtkBox
*pBox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6));
5369 GtkWidget
*pLabel
= gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr
).getStr());
5370 pItem
= eCheckRadioFalse
!= TRISTATE_INDET
? gtk_check_menu_item_new() : gtk_menu_item_new();
5371 gtk_box_pack_start(pBox
, pImage
, true, true, 0);
5372 gtk_box_pack_start(pBox
, pLabel
, true, true, 0);
5373 gtk_container_add(GTK_CONTAINER(pItem
), GTK_WIDGET(pBox
));
5374 gtk_widget_show_all(pItem
);
5378 pItem
= eCheckRadioFalse
!= TRISTATE_INDET
? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr
).getStr())
5379 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr
).getStr());
5382 if (eCheckRadioFalse
== TRISTATE_FALSE
)
5383 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem
), true);
5385 ::set_buildable_id(GTK_BUILDABLE(pItem
), OUStringToOString(rId
, RTL_TEXTENCODING_UTF8
));
5386 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu
), pItem
);
5387 gtk_widget_show(pItem
);
5388 add_to_map(GTK_MENU_ITEM(pItem
));
5390 gtk_menu_reorder_child(m_pMenu
, pItem
, pos
);
5392 (void)pIconName
; (void)pImageSurface
;
5394 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5396 auto aSectionAndPos
= get_section_and_pos_for(pMenuModel
, pos
);
5397 GMenu
* pMenu
= G_MENU(aSectionAndPos
.first
);
5398 // action with a target value ... the action name and target value are separated by a double
5399 // colon ... For example: "app.action::target"
5400 OUString sActionAndTarget
;
5401 if (eCheckRadioFalse
== TRISTATE_INDET
)
5402 sActionAndTarget
= "menu.normal." + rId
+ "::" + rId
;
5404 sActionAndTarget
= "menu.radio." + rId
+ "::" + rId
;
5405 g_menu_insert(pMenu
, aSectionAndPos
.second
, MapToGtkAccelerator(rStr
).getStr(), sActionAndTarget
.toUtf8().getStr());
5407 assert(eCheckRadioFalse
== TRISTATE_INDET
); // come back to this later
5409 // TODO not redo entire group
5410 update_action_group_from_popover_model();
5415 void insert_separator(int pos
, const OUString
& rId
)
5417 #if !GTK_CHECK_VERSION(4, 0, 0)
5418 GtkWidget
* pItem
= gtk_separator_menu_item_new();
5419 ::set_buildable_id(GTK_BUILDABLE(pItem
), OUStringToOString(rId
, RTL_TEXTENCODING_UTF8
));
5420 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu
), pItem
);
5421 gtk_widget_show(pItem
);
5422 add_to_map(GTK_MENU_ITEM(pItem
));
5424 gtk_menu_reorder_child(m_pMenu
, pItem
, pos
);
5426 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5428 auto aSectionAndPos
= get_section_and_pos_for(pMenuModel
, pos
);
5430 for (int nSection
= 0, nSectionCount
= g_menu_model_get_n_items(pMenuModel
); nSection
< nSectionCount
; ++nSection
)
5432 GMenuModel
* pSectionModel
= g_menu_model_get_item_link(pMenuModel
, nSection
, G_MENU_LINK_SECTION
);
5433 assert(pSectionModel
);
5434 if (aSectionAndPos
.first
== pSectionModel
)
5436 GMenu
* pNewSection
= g_menu_new();
5437 GMenuItem
* pSectionItem
= g_menu_item_new_section(nullptr, G_MENU_MODEL(pNewSection
));
5438 OUString sActionAndTarget
= "menu.separator." + rId
+ "::" + rId
;
5439 g_menu_item_set_detailed_action(pSectionItem
, sActionAndTarget
.toUtf8().getStr());
5440 g_menu_insert_item(G_MENU(pMenuModel
), nSection
+ 1, pSectionItem
);
5441 int nOldSectionCount
= g_menu_model_get_n_items(pSectionModel
);
5442 for (int i
= nOldSectionCount
- 1; i
>= aSectionAndPos
.second
; --i
)
5444 GMenuItem
* pMenuItem
= g_menu_item_new_from_model(pSectionModel
, i
);
5445 g_menu_prepend_item(pNewSection
, pMenuItem
);
5446 g_menu_remove(G_MENU(pSectionModel
), i
);
5447 g_object_unref(pMenuItem
);
5449 g_object_unref(pSectionItem
);
5450 g_object_unref(pNewSection
);
5458 void remove_item(const OString
& rIdent
)
5460 #if !GTK_CHECK_VERSION(4, 0, 0)
5461 GtkMenuItem
* pMenuItem
= m_aMap
[rIdent
];
5462 remove_from_map(pMenuItem
);
5463 gtk_widget_destroy(GTK_WIDGET(pMenuItem
));
5465 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5467 std::pair
<GMenuModel
*, int> aRes
= find_id(pMenuModel
, rIdent
);
5470 g_menu_remove(G_MENU(aRes
.first
), aRes
.second
);
5475 void set_item_sensitive(const OString
& rIdent
, bool bSensitive
)
5477 #if GTK_CHECK_VERSION(4, 0, 0)
5478 GActionGroup
* pActionGroup
= m_aHiddenIds
.find(rIdent
) == m_aHiddenIds
.end() ? m_pActionGroup
: m_pHiddenActionGroup
;
5479 GAction
* pAction
= g_action_map_lookup_action(G_ACTION_MAP(pActionGroup
), m_aIdToAction
[rIdent
].getStr());
5480 g_simple_action_set_enabled(G_SIMPLE_ACTION(pAction
), bSensitive
);
5482 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap
[rIdent
]), bSensitive
);
5486 bool get_item_sensitive(const OString
& rIdent
) const
5488 #if GTK_CHECK_VERSION(4, 0, 0)
5489 GActionGroup
* pActionGroup
= m_aHiddenIds
.find(rIdent
) == m_aHiddenIds
.end() ? m_pActionGroup
: m_pHiddenActionGroup
;
5490 GAction
* pAction
= g_action_map_lookup_action(G_ACTION_MAP(pActionGroup
), m_aIdToAction
.find(rIdent
)->second
.getStr());
5491 return g_action_get_enabled(pAction
);
5493 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap
.find(rIdent
)->second
));
5497 void set_item_active(const OString
& rIdent
, bool bActive
)
5499 #if !GTK_CHECK_VERSION(4, 0, 0)
5500 disable_item_notify_events();
5501 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap
[rIdent
]), bActive
);
5502 enable_item_notify_events();
5504 GActionGroup
* pActionGroup
= m_aHiddenIds
.find(rIdent
) == m_aHiddenIds
.end() ? m_pActionGroup
: m_pHiddenActionGroup
;
5505 g_action_group_change_action_state(pActionGroup
, m_aIdToAction
[rIdent
].getStr(),
5506 g_variant_new_string(bActive
? rIdent
.getStr() : "'none'"));
5510 bool get_item_active(const OString
& rIdent
) const
5512 #if !GTK_CHECK_VERSION(4, 0, 0)
5513 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap
.find(rIdent
)->second
));
5515 GActionGroup
* pActionGroup
= m_aHiddenIds
.find(rIdent
) == m_aHiddenIds
.end() ? m_pActionGroup
: m_pHiddenActionGroup
;
5516 GVariant
* pState
= g_action_group_get_action_state(pActionGroup
, m_aIdToAction
.find(rIdent
)->second
.getStr());
5519 const char *pStateString
= g_variant_get_string(pState
, nullptr);
5520 bool bInactive
= g_strcmp0(pStateString
, "'none'") == 0;
5521 g_variant_unref(pState
);
5526 void set_item_label(const OString
& rIdent
, const OUString
& rText
)
5528 #if !GTK_CHECK_VERSION(4, 0, 0)
5529 gtk_menu_item_set_label(m_aMap
[rIdent
], MapToGtkAccelerator(rText
).getStr());
5531 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5533 std::pair
<GMenuModel
*, int> aRes
= find_id(pMenuModel
, rIdent
);
5536 // clone the original item, remove the original, insert the replacement at
5537 // the original location
5538 GMenuItem
* pMenuItem
= g_menu_item_new_from_model(aRes
.first
, aRes
.second
);
5539 g_menu_remove(G_MENU(aRes
.first
), aRes
.second
);
5540 g_menu_item_set_label(pMenuItem
, MapToGtkAccelerator(rText
).getStr());
5541 g_menu_insert_item(G_MENU(aRes
.first
), aRes
.second
, pMenuItem
);
5542 g_object_unref(pMenuItem
);
5547 OUString
get_item_label(const OString
& rIdent
) const
5549 #if !GTK_CHECK_VERSION(4, 0, 0)
5550 const gchar
* pText
= gtk_menu_item_get_label(m_aMap
.find(rIdent
)->second
);
5551 return OUString(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
5553 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5555 std::pair
<GMenuModel
*, int> aRes
= find_id(pMenuModel
, rIdent
);
5559 // clone the original item to query its label
5560 GMenuItem
* pMenuItem
= g_menu_item_new_from_model(aRes
.first
, aRes
.second
);
5561 char *pLabel
= nullptr;
5562 g_menu_item_get_attribute(pMenuItem
, G_MENU_ATTRIBUTE_LABEL
, "&s", &pLabel
);
5563 OUString
aRet(pLabel
, pLabel
? strlen(pLabel
) : 0, RTL_TEXTENCODING_UTF8
);
5565 g_object_unref(pMenuItem
);
5572 void set_item_visible(const OString
& rIdent
, bool bShow
)
5574 #if !GTK_CHECK_VERSION(4, 0, 0)
5575 GtkWidget
* pWidget
= GTK_WIDGET(m_aMap
[rIdent
]);
5577 gtk_widget_show(pWidget
);
5579 gtk_widget_hide(pWidget
);
5581 bool bOldVisible
= m_aHiddenIds
.find(rIdent
) == m_aHiddenIds
.end();
5582 if (bShow
== bOldVisible
)
5587 GAction
* pAction
= g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup
), m_aIdToAction
[rIdent
].getStr());
5588 g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup
), pAction
);
5589 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup
), m_aIdToAction
[rIdent
].getStr());
5590 m_aHiddenIds
.insert(rIdent
);
5594 GAction
* pAction
= g_action_map_lookup_action(G_ACTION_MAP(m_pHiddenActionGroup
), m_aIdToAction
[rIdent
].getStr());
5595 g_action_map_add_action(G_ACTION_MAP(m_pActionGroup
), pAction
);
5596 g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup
), m_aIdToAction
[rIdent
].getStr());
5597 m_aHiddenIds
.erase(rIdent
);
5602 OString
get_item_id(int pos
) const
5604 #if !GTK_CHECK_VERSION(4, 0, 0)
5605 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(m_pMenu
));
5606 gpointer pMenuItem
= g_list_nth_data(pChildren
, pos
);
5607 OString id
= ::get_buildable_id(GTK_BUILDABLE(pMenuItem
));
5608 g_list_free(pChildren
);
5612 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5614 auto aSectionAndPos
= get_section_and_pos_for(pMenuModel
, pos
);
5616 if (g_menu_model_get_item_attribute(aSectionAndPos
.first
, aSectionAndPos
.second
, "target", "s", &id
))
5618 sTarget
= OString(id
);
5626 int get_n_children() const
5628 #if !GTK_CHECK_VERSION(4, 0, 0)
5629 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(m_pMenu
));
5630 int nLen
= g_list_length(pChildren
);
5631 g_list_free(pChildren
);
5634 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5635 return count_immediate_children(pMenuModel
);
5642 #if !GTK_CHECK_VERSION(4, 0, 0)
5643 for (const auto& a
: m_aMap
)
5645 GtkMenuItem
* pMenuItem
= a
.second
;
5646 g_signal_handlers_disconnect_by_data(pMenuItem
, this);
5647 gtk_widget_destroy(GTK_WIDGET(pMenuItem
));
5651 if (GMenuModel
* pMenuModel
= m_pMenu
? gtk_popover_menu_get_menu_model(m_pMenu
) : nullptr)
5653 GMenu
* pMenu
= G_MENU(pMenuModel
);
5654 g_menu_remove_all(pMenu
);
5655 g_menu_insert_section(pMenu
, 0, nullptr, G_MENU_MODEL(g_menu_new()));
5656 m_aHiddenIds
.clear();
5657 update_action_group_from_popover_model();
5662 #if !GTK_CHECK_VERSION(4, 0, 0)
5663 GtkMenu
* getMenu() const
5665 GtkPopoverMenu
* getMenu() const
5671 virtual ~MenuHelper()
5673 #if !GTK_CHECK_VERSION(4, 0, 0)
5674 for (auto& a
: m_aMap
)
5675 g_signal_handlers_disconnect_by_data(a
.second
, this);
5676 if (m_bTakeOwnership
)
5677 gtk_widget_destroy(GTK_WIDGET(m_pMenu
));
5679 g_object_unref(m_pActionGroup
);
5680 g_object_unref(m_pHiddenActionGroup
);
5685 class GtkInstanceSizeGroup
: public weld::SizeGroup
5688 GtkSizeGroup
* m_pGroup
;
5690 GtkInstanceSizeGroup()
5691 : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
))
5694 virtual void add_widget(weld::Widget
* pWidget
) override
5696 GtkInstanceWidget
* pVclWidget
= dynamic_cast<GtkInstanceWidget
*>(pWidget
);
5698 gtk_size_group_add_widget(m_pGroup
, pVclWidget
->getWidget());
5700 virtual void set_mode(VclSizeGroupMode eVclMode
) override
5702 GtkSizeGroupMode
eGtkMode(GTK_SIZE_GROUP_NONE
);
5705 case VclSizeGroupMode::NONE
:
5706 eGtkMode
= GTK_SIZE_GROUP_NONE
;
5708 case VclSizeGroupMode::Horizontal
:
5709 eGtkMode
= GTK_SIZE_GROUP_HORIZONTAL
;
5711 case VclSizeGroupMode::Vertical
:
5712 eGtkMode
= GTK_SIZE_GROUP_VERTICAL
;
5714 case VclSizeGroupMode::Both
:
5715 eGtkMode
= GTK_SIZE_GROUP_BOTH
;
5718 gtk_size_group_set_mode(m_pGroup
, eGtkMode
);
5720 virtual ~GtkInstanceSizeGroup() override
5722 g_object_unref(m_pGroup
);
5726 class ChildFrame
: public WorkWindow
5731 DECL_LINK(ImplHandleLayoutTimerHdl
, Timer
*, void);
5733 ChildFrame(vcl::Window
* pParent
, WinBits nStyle
)
5734 : WorkWindow(pParent
, nStyle
)
5735 , maLayoutIdle( "ChildFrame maLayoutIdle" )
5737 maLayoutIdle
.SetPriority(TaskPriority::RESIZE
);
5738 maLayoutIdle
.SetInvokeHandler( LINK( this, ChildFrame
, ImplHandleLayoutTimerHdl
) );
5741 virtual void dispose() override
5743 maLayoutIdle
.Stop();
5744 WorkWindow::dispose();
5747 virtual void queue_resize(StateChangedType eReason
= StateChangedType::Layout
) override
5749 WorkWindow::queue_resize(eReason
);
5750 if (maLayoutIdle
.IsActive())
5752 maLayoutIdle
.Start();
5757 if (vcl::Window
*pChild
= GetWindow(GetWindowType::FirstChild
))
5758 pChild
->SetPosSizePixel(Point(0, 0), GetSizePixel());
5761 virtual void Resize() override
5763 maLayoutIdle
.Stop();
5765 WorkWindow::Resize();
5769 IMPL_LINK_NOARG(ChildFrame
, ImplHandleLayoutTimerHdl
, Timer
*, void)
5774 class GtkInstanceContainer
: public GtkInstanceWidget
, public virtual weld::Container
5777 #if !GTK_CHECK_VERSION(4, 0, 0)
5778 GtkContainer
* m_pContainer
;
5780 GtkWidget
* m_pContainer
;
5782 gulong m_nSetFocusChildSignalId
;
5783 bool m_bChildHasFocus
;
5785 void signal_set_focus_child(bool bChildHasFocus
)
5787 if (m_bChildHasFocus
!= bChildHasFocus
)
5789 m_bChildHasFocus
= bChildHasFocus
;
5790 signal_container_focus_changed();
5794 #if !GTK_CHECK_VERSION(4, 0, 0)
5795 static void signalSetFocusChild(GtkContainer
*, GtkWidget
* pChild
, gpointer widget
)
5797 GtkInstanceContainer
* pThis
= static_cast<GtkInstanceContainer
*>(widget
);
5798 pThis
->signal_set_focus_child(pChild
!= nullptr);
5803 #if !GTK_CHECK_VERSION(4, 0, 0)
5804 GtkInstanceContainer(GtkContainer
* pContainer
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
5805 : GtkInstanceWidget(GTK_WIDGET(pContainer
), pBuilder
, bTakeOwnership
)
5807 GtkInstanceContainer(GtkWidget
* pContainer
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
5808 : GtkInstanceWidget(pContainer
, pBuilder
, bTakeOwnership
)
5810 , m_pContainer(pContainer
)
5811 , m_nSetFocusChildSignalId(0)
5812 , m_bChildHasFocus(false)
5816 virtual void connect_container_focus_changed(const Link
<Container
&, void>& rLink
) override
5818 #if !GTK_CHECK_VERSION(4, 0, 0)
5819 if (!m_nSetFocusChildSignalId
)
5820 m_nSetFocusChildSignalId
= g_signal_connect(G_OBJECT(m_pContainer
), "set-focus-child", G_CALLBACK(signalSetFocusChild
), this);
5822 weld::Container::connect_container_focus_changed(rLink
);
5825 #if GTK_CHECK_VERSION(4, 0, 0)
5826 GtkWidget
* getContainer() { return m_pContainer
; }
5828 GtkContainer
* getContainer() { return m_pContainer
; }
5831 virtual void child_grab_focus() override
5833 gtk_widget_grab_focus(m_pWidget
);
5834 #if GTK_CHECK_VERSION(4, 0, 0)
5835 bool bHasFocusChild
= gtk_widget_get_focus_child(GTK_WIDGET(m_pContainer
));
5837 bool bHasFocusChild
= gtk_container_get_focus_child(m_pContainer
);
5839 if (!bHasFocusChild
)
5841 #if GTK_CHECK_VERSION(4, 0, 0)
5842 if (GtkWidget
* pChild
= gtk_widget_get_first_child(m_pContainer
))
5844 gtk_widget_set_focus_child(m_pContainer
, pChild
);
5845 bHasFocusChild
= true;
5848 GList
* pChildren
= gtk_container_get_children(m_pContainer
);
5849 if (GList
* pChild
= g_list_first(pChildren
))
5851 gtk_container_set_focus_child(m_pContainer
, static_cast<GtkWidget
*>(pChild
->data
));
5852 bHasFocusChild
= true;
5854 g_list_free(pChildren
);
5860 #if GTK_CHECK_VERSION(4, 0, 0)
5861 gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget
), GTK_DIR_TAB_FORWARD
);
5863 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget
)), GTK_DIR_TAB_FORWARD
);
5869 virtual void move(weld::Widget
* pWidget
, weld::Container
* pNewParent
) override
5871 GtkInstanceWidget
* pGtkWidget
= dynamic_cast<GtkInstanceWidget
*>(pWidget
);
5873 GtkWidget
* pChild
= pGtkWidget
->getWidget();
5874 g_object_ref(pChild
);
5875 auto pOldContainer
= getContainer();
5876 container_remove(GTK_WIDGET(pOldContainer
), pChild
);
5878 GtkInstanceContainer
* pNewGtkParent
= dynamic_cast<GtkInstanceContainer
*>(pNewParent
);
5879 assert(!pNewParent
|| pNewGtkParent
);
5882 auto pNewContainer
= pNewGtkParent
->getContainer();
5883 container_add(GTK_WIDGET(pNewContainer
), pChild
);
5885 g_object_unref(pChild
);
5888 virtual css::uno::Reference
<css::awt::XWindow
> CreateChildFrame() override
5890 // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
5891 // will create a toplevel GtkEventBox window
5892 auto xEmbedWindow
= VclPtr
<ChildFrame
>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW
| WB_DIALOGCONTROL
| WB_CHILDDLGCTRL
);
5893 SalFrame
* pFrame
= xEmbedWindow
->ImplGetFrame();
5894 GtkSalFrame
* pGtkFrame
= dynamic_cast<GtkSalFrame
*>(pFrame
);
5897 // relocate that toplevel GtkEventBox into this widget
5898 GtkWidget
* pWindow
= pGtkFrame
->getWindow();
5900 GtkWidget
* pParent
= gtk_widget_get_parent(pWindow
);
5902 g_object_ref(pWindow
);
5903 container_remove(pParent
, pWindow
);
5904 container_add(GTK_WIDGET(m_pContainer
), pWindow
);
5905 #if !GTK_CHECK_VERSION(4, 0, 0)
5906 gtk_container_child_set(m_pContainer
, pWindow
, "expand", true, "fill", true, nullptr);
5908 gtk_widget_set_hexpand(pWindow
, true);
5909 gtk_widget_set_vexpand(pWindow
, true);
5910 gtk_widget_realize(pWindow
);
5911 gtk_widget_set_can_focus(pWindow
, true);
5912 g_object_unref(pWindow
);
5914 // NoActivate otherwise Show grab focus to this widget
5915 xEmbedWindow
->Show(true, ShowFlags::NoActivate
);
5916 css::uno::Reference
<css::awt::XWindow
> xWindow(xEmbedWindow
->GetComponentInterface(), css::uno::UNO_QUERY
);
5920 virtual ~GtkInstanceContainer() override
5922 if (m_nSetFocusChildSignalId
)
5923 g_signal_handler_disconnect(m_pContainer
, m_nSetFocusChildSignalId
);
5929 std::unique_ptr
<weld::Container
> GtkInstanceWidget::weld_parent() const
5931 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
5934 #if !GTK_CHECK_VERSION(4, 0, 0)
5935 return std::make_unique
<GtkInstanceContainer
>(GTK_CONTAINER(pParent
), m_pBuilder
, false);
5937 return std::make_unique
<GtkInstanceContainer
>(pParent
, m_pBuilder
, false);
5943 bool sortButtons(const GtkWidget
* pA
, const GtkWidget
* pB
)
5945 //order within groups according to platform rules
5946 return getButtonPriority(get_buildable_id(GTK_BUILDABLE(pA
))) <
5947 getButtonPriority(get_buildable_id(GTK_BUILDABLE(pB
)));
5950 void sort_native_button_order(GtkBox
* pContainer
)
5952 std::vector
<GtkWidget
*> aChildren
;
5953 #if GTK_CHECK_VERSION(4, 0, 0)
5954 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(pContainer
));
5955 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
5957 aChildren
.push_back(pChild
);
5960 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(pContainer
));
5961 for (GList
* pChild
= g_list_first(pChildren
); pChild
; pChild
= g_list_next(pChild
))
5962 aChildren
.push_back(static_cast<GtkWidget
*>(pChild
->data
));
5963 g_list_free(pChildren
);
5966 //sort child order within parent so that we match the platform button order
5967 std::stable_sort(aChildren
.begin(), aChildren
.end(), sortButtons
);
5969 #if GTK_CHECK_VERSION(4, 0, 0)
5970 for (size_t pos
= 0; pos
< aChildren
.size(); ++pos
)
5971 gtk_box_reorder_child_after(pContainer
, aChildren
[pos
], pos
? aChildren
[pos
- 1] : nullptr);
5973 for (size_t pos
= 0; pos
< aChildren
.size(); ++pos
)
5974 gtk_box_reorder_child(pContainer
, aChildren
[pos
], pos
);
5978 class GtkInstanceBox
: public GtkInstanceContainer
, public virtual weld::Box
5984 GtkInstanceBox(GtkBox
* pBox
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
5985 #if !GTK_CHECK_VERSION(4, 0, 0)
5986 : GtkInstanceContainer(GTK_CONTAINER(pBox
), pBuilder
, bTakeOwnership
)
5988 : GtkInstanceContainer(GTK_WIDGET(pBox
), pBuilder
, bTakeOwnership
)
5994 virtual void reorder_child(weld::Widget
* pWidget
, int nNewPosition
) override
5996 GtkInstanceWidget
* pGtkWidget
= dynamic_cast<GtkInstanceWidget
*>(pWidget
);
5998 GtkWidget
* pChild
= pGtkWidget
->getWidget();
6000 #if !GTK_CHECK_VERSION(4, 0, 0)
6001 gtk_box_reorder_child(m_pBox
, pChild
, nNewPosition
);
6003 if (nNewPosition
== 0)
6004 gtk_box_reorder_child_after(m_pBox
, pChild
, nullptr);
6007 int nNewSiblingPos
= nNewPosition
- 1;
6008 int nChildPosition
= 0;
6009 for (GtkWidget
* pNewSibling
= gtk_widget_get_first_child(GTK_WIDGET(m_pBox
));
6010 pNewSibling
; pNewSibling
= gtk_widget_get_next_sibling(pNewSibling
))
6012 if (nChildPosition
== nNewSiblingPos
)
6014 gtk_box_reorder_child_after(m_pBox
, pChild
, pNewSibling
);
6023 virtual void sort_native_button_order() override
6025 ::sort_native_button_order(m_pBox
);
6033 Point
get_csd_offset(GtkWidget
* pTopLevel
)
6035 // try and omit drawing CSD under wayland
6036 GtkWidget
* pChild
= widget_get_first_child(pTopLevel
);
6039 gtk_widget_translate_coordinates(pChild
, pTopLevel
, 0, 0, &x
, &y
);
6041 #if !GTK_CHECK_VERSION(4, 0, 0)
6042 int innerborder
= gtk_container_get_border_width(GTK_CONTAINER(pChild
));
6043 int outerborder
= gtk_container_get_border_width(GTK_CONTAINER(pTopLevel
));
6044 int totalborder
= outerborder
+ innerborder
;
6052 void do_collect_screenshot_data(GtkWidget
* pItem
, gpointer data
)
6054 GtkWidget
* pTopLevel
= widget_get_toplevel(pItem
);
6057 gtk_widget_translate_coordinates(pItem
, pTopLevel
, 0, 0, &x
, &y
);
6059 Point aOffset
= get_csd_offset(pTopLevel
);
6061 GtkAllocation alloc
;
6062 gtk_widget_get_allocation(pItem
, &alloc
);
6064 const basegfx::B2IPoint
aCurrentTopLeft(x
- aOffset
.X(), y
- aOffset
.Y());
6065 const basegfx::B2IRange
aCurrentRange(aCurrentTopLeft
, aCurrentTopLeft
+ basegfx::B2IPoint(alloc
.width
, alloc
.height
));
6067 if (!aCurrentRange
.isEmpty())
6069 weld::ScreenShotCollection
* pCollection
= static_cast<weld::ScreenShotCollection
*>(data
);
6070 pCollection
->emplace_back(::get_help_id(pItem
), aCurrentRange
);
6073 #if GTK_CHECK_VERSION(4, 0, 0)
6074 for (GtkWidget
* pChild
= gtk_widget_get_first_child(pItem
);
6075 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
6077 do_collect_screenshot_data(pChild
, data
);
6080 if (GTK_IS_CONTAINER(pItem
))
6081 gtk_container_forall(GTK_CONTAINER(pItem
), do_collect_screenshot_data
, data
);
6085 tools::Rectangle
get_monitor_workarea(GtkWidget
* pWindow
)
6088 #if !GTK_CHECK_VERSION(4, 0, 0)
6089 GdkScreen
* pScreen
= gtk_widget_get_screen(pWindow
);
6090 gint nMonitor
= gdk_screen_get_monitor_at_window(pScreen
, widget_get_surface(pWindow
));
6091 gdk_screen_get_monitor_workarea(pScreen
, nMonitor
, &aRect
);
6093 GdkDisplay
* pDisplay
= gtk_widget_get_display(pWindow
);
6094 GdkSurface
* gdkWindow
= widget_get_surface(pWindow
);
6095 GdkMonitor
* pMonitor
= gdk_display_get_monitor_at_surface(pDisplay
, gdkWindow
);
6096 gdk_monitor_get_geometry(pMonitor
, &aRect
);
6098 return tools::Rectangle(aRect
.x
, aRect
.y
, aRect
.x
+ aRect
.width
, aRect
.y
+ aRect
.height
);
6102 class GtkInstanceWindow
: public GtkInstanceContainer
, public virtual weld::Window
6105 GtkWindow
* m_pWindow
;
6106 rtl::Reference
<SalGtkXWindow
> m_xWindow
; //uno api
6107 std::optional
<Point
> m_aPosWhileInvis
; //tdf#146648 store last known position when visible to return as pos if hidden
6108 gulong m_nToplevelFocusChangedSignalId
;
6110 #if !GTK_CHECK_VERSION(4, 0, 0)
6111 static void implResetDefault(GtkWidget
*pWidget
, gpointer user_data
)
6113 if (GTK_IS_BUTTON(pWidget
))
6114 g_object_set(G_OBJECT(pWidget
), "has-default", false, nullptr);
6115 if (GTK_IS_CONTAINER(pWidget
))
6116 gtk_container_forall(GTK_CONTAINER(pWidget
), implResetDefault
, user_data
);
6119 void recursively_unset_default_buttons()
6121 implResetDefault(GTK_WIDGET(m_pWindow
), nullptr);
6125 #if !GTK_CHECK_VERSION(4, 0, 0)
6126 static gboolean
help_pressed(GtkAccelGroup
*, GObject
*, guint
, GdkModifierType
, gpointer widget
)
6128 GtkInstanceWindow
* pThis
= static_cast<GtkInstanceWindow
*>(widget
);
6134 static void signalToplevelFocusChanged(GtkWindow
*, GParamSpec
*, gpointer widget
)
6136 GtkInstanceWindow
* pThis
= static_cast<GtkInstanceWindow
*>(widget
);
6137 pThis
->signal_container_focus_changed();
6140 bool isPositioningAllowed() const
6142 // no X/Y positioning under Wayland
6143 GdkDisplay
*pDisplay
= gtk_widget_get_display(m_pWidget
);
6144 return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay
);
6150 GtkInstanceWindow(GtkWindow
* pWindow
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
6151 #if !GTK_CHECK_VERSION(4, 0, 0)
6152 : GtkInstanceContainer(GTK_CONTAINER(pWindow
), pBuilder
, bTakeOwnership
)
6154 : GtkInstanceContainer(GTK_WIDGET(pWindow
), pBuilder
, bTakeOwnership
)
6156 , m_pWindow(pWindow
)
6157 , m_nToplevelFocusChangedSignalId(0)
6159 #if !GTK_CHECK_VERSION(4, 0, 0)
6160 const bool bIsFrameWeld
= pBuilder
== nullptr;
6163 //hook up F1 to show help
6164 GtkAccelGroup
*pGroup
= gtk_accel_group_new();
6165 GClosure
* closure
= g_cclosure_new(G_CALLBACK(help_pressed
), this, nullptr);
6166 gtk_accel_group_connect(pGroup
, GDK_KEY_F1
, static_cast<GdkModifierType
>(0), GTK_ACCEL_LOCKED
, closure
);
6167 gtk_window_add_accel_group(pWindow
, pGroup
);
6172 virtual void set_title(const OUString
& rTitle
) override
6174 ::set_title(m_pWindow
, rTitle
);
6177 virtual OUString
get_title() const override
6179 return ::get_title(m_pWindow
);
6182 virtual css::uno::Reference
<css::awt::XWindow
> GetXWindow() override
6184 if (!m_xWindow
.is())
6185 m_xWindow
.set(new SalGtkXWindow(this, m_pWidget
));
6189 virtual void set_modal(bool bModal
) override
6191 gtk_window_set_modal(m_pWindow
, bModal
);
6194 virtual bool get_modal() const override
6196 return gtk_window_get_modal(m_pWindow
);
6199 virtual void resize_to_request() override
6201 #if GTK_CHECK_VERSION(4, 0, 0)
6202 gtk_window_set_default_size(m_pWindow
, 1, 1);
6204 gtk_window_resize(m_pWindow
, 1, 1);
6208 virtual void window_move(int x
, int y
) override
6210 #if !GTK_CHECK_VERSION(4, 0, 0)
6211 gtk_window_move(m_pWindow
, x
, y
);
6218 virtual SystemEnvData
get_system_data() const override
6220 GtkSalFrame
* pFrame
= GtkSalFrame::getFromWindow(GTK_WIDGET(m_pWindow
));
6221 assert(pFrame
&& "nothing should call this impl, yet anyway, if ever, except on result of GetFrameWeld()");
6222 const SystemEnvData
* pEnvData
= pFrame
->GetSystemData();
6227 virtual Size
get_size() const override
6229 int current_width
, current_height
;
6230 #if !GTK_CHECK_VERSION(4, 0, 0)
6231 gtk_window_get_size(m_pWindow
, ¤t_width
, ¤t_height
);
6233 gtk_window_get_default_size(m_pWindow
, ¤t_width
, ¤t_height
);
6235 return Size(current_width
, current_height
);
6238 virtual Point
get_position() const override
6240 if (m_aPosWhileInvis
)
6242 assert(!get_visible());
6243 return *m_aPosWhileInvis
;
6246 int current_x(0), current_y(0);
6247 #if !GTK_CHECK_VERSION(4, 0, 0)
6248 gtk_window_get_position(m_pWindow
, ¤t_x
, ¤t_y
);
6250 return Point(current_x
, current_y
);
6253 virtual void show() override
6255 m_aPosWhileInvis
.reset();
6256 GtkInstanceContainer::show();
6259 virtual void hide() override
6262 m_aPosWhileInvis
= get_position();
6263 GtkInstanceContainer::hide();
6266 virtual tools::Rectangle
get_monitor_workarea() const override
6268 return ::get_monitor_workarea(GTK_WIDGET(m_pWindow
));
6271 virtual void set_centered_on_parent(bool bTrackGeometryRequests
) override
6273 #if !GTK_CHECK_VERSION(4, 0, 0)
6274 if (bTrackGeometryRequests
)
6275 gtk_window_set_position(m_pWindow
, GTK_WIN_POS_CENTER_ALWAYS
);
6277 gtk_window_set_position(m_pWindow
, GTK_WIN_POS_CENTER_ON_PARENT
);
6279 (void)bTrackGeometryRequests
;
6283 virtual bool get_resizable() const override
6285 return gtk_window_get_resizable(m_pWindow
);
6288 virtual bool has_toplevel_focus() const override
6290 #if GTK_CHECK_VERSION(4, 0, 0)
6291 return gtk_window_is_active(m_pWindow
);
6293 return gtk_window_has_toplevel_focus(m_pWindow
);
6297 virtual void present() override
6299 gtk_window_present(m_pWindow
);
6302 virtual void change_default_widget(weld::Widget
* pOld
, weld::Widget
* pNew
) override
6304 GtkInstanceWidget
* pGtkNew
= dynamic_cast<GtkInstanceWidget
*>(pNew
);
6305 GtkWidget
* pWidgetNew
= pGtkNew
? pGtkNew
->getWidget() : nullptr;
6306 #if GTK_CHECK_VERSION(4, 0, 0)
6307 gtk_window_set_default_widget(m_pWindow
, pWidgetNew
);
6310 GtkInstanceWidget
* pGtkOld
= dynamic_cast<GtkInstanceWidget
*>(pOld
);
6311 GtkWidget
* pWidgetOld
= pGtkOld
? pGtkOld
->getWidget() : nullptr;
6313 g_object_set(G_OBJECT(pWidgetOld
), "has-default", false, nullptr);
6315 recursively_unset_default_buttons();
6317 g_object_set(G_OBJECT(pWidgetNew
), "has-default", true, nullptr);
6321 virtual bool is_default_widget(const weld::Widget
* pCandidate
) const override
6323 const GtkInstanceWidget
* pGtkCandidate
= dynamic_cast<const GtkInstanceWidget
*>(pCandidate
);
6324 GtkWidget
* pWidget
= pGtkCandidate
? pGtkCandidate
->getWidget() : nullptr;
6325 #if GTK_CHECK_VERSION(4, 0, 0)
6326 return pWidget
&& gtk_window_get_default_widget(m_pWindow
) == pWidget
;
6328 gboolean
has_default(false);
6330 g_object_get(G_OBJECT(pWidget
), "has-default", &has_default
, nullptr);
6335 virtual void set_window_state(const OString
& rStr
) override
6337 WindowStateData aData
;
6338 ImplWindowStateFromStr( aData
, rStr
);
6340 auto nMask
= aData
.GetMask();
6341 auto nState
= aData
.GetState() & WindowStateState::SystemMask
;
6343 if (nMask
& WindowStateMask::Width
&& nMask
& WindowStateMask::Height
)
6345 gtk_window_set_default_size(m_pWindow
, aData
.GetWidth(), aData
.GetHeight());
6347 if (nMask
& WindowStateMask::State
)
6349 if (nState
& WindowStateState::Maximized
)
6350 gtk_window_maximize(m_pWindow
);
6352 gtk_window_unmaximize(m_pWindow
);
6355 #if !GTK_CHECK_VERSION(4, 0, 0)
6356 if (isPositioningAllowed() && (nMask
& WindowStateMask::X
&& nMask
& WindowStateMask::Y
))
6358 gtk_window_move(m_pWindow
, aData
.GetX(), aData
.GetY());
6363 virtual OString
get_window_state(WindowStateMask nMask
) const override
6365 bool bPositioningAllowed
= isPositioningAllowed();
6367 WindowStateData aData
;
6368 WindowStateMask nAvailable
= WindowStateMask::State
|
6369 WindowStateMask::Width
| WindowStateMask::Height
;
6370 if (bPositioningAllowed
)
6371 nAvailable
|= WindowStateMask::X
| WindowStateMask::Y
;
6372 aData
.SetMask(nMask
& nAvailable
);
6374 if (nMask
& WindowStateMask::State
)
6376 WindowStateState nState
= WindowStateState::Normal
;
6377 if (gtk_window_is_maximized(m_pWindow
))
6378 nState
|= WindowStateState::Maximized
;
6379 aData
.SetState(nState
);
6382 if (bPositioningAllowed
&& (nMask
& (WindowStateMask::X
| WindowStateMask::Y
)))
6384 auto aPos
= get_position();
6385 aData
.SetX(aPos
.X());
6386 aData
.SetY(aPos
.Y());
6389 if (nMask
& (WindowStateMask::Width
| WindowStateMask::Height
))
6391 auto aSize
= get_size();
6392 aData
.SetWidth(aSize
.Width());
6393 aData
.SetHeight(aSize
.Height());
6396 return aData
.ToStr();
6399 virtual void connect_container_focus_changed(const Link
<Container
&, void>& rLink
) override
6401 if (!m_nToplevelFocusChangedSignalId
)
6402 m_nToplevelFocusChangedSignalId
= g_signal_connect(m_pWindow
, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged
), this);
6403 GtkInstanceContainer::connect_container_focus_changed(rLink
);
6406 virtual void disable_notify_events() override
6408 if (m_nToplevelFocusChangedSignalId
)
6409 g_signal_handler_block(m_pWidget
, m_nToplevelFocusChangedSignalId
);
6410 GtkInstanceContainer::disable_notify_events();
6413 virtual void enable_notify_events() override
6415 GtkInstanceContainer::enable_notify_events();
6416 if (m_nToplevelFocusChangedSignalId
)
6417 g_signal_handler_unblock(m_pWidget
, m_nToplevelFocusChangedSignalId
);
6420 virtual VclPtr
<VirtualDevice
> screenshot() override
6422 // detect if we have to manually setup its size
6423 bool bAlreadyRealized
= gtk_widget_get_realized(GTK_WIDGET(m_pWindow
));
6424 // has to be visible for draw to work
6425 bool bAlreadyVisible
= gtk_widget_get_visible(GTK_WIDGET(m_pWindow
));
6426 #if !GTK_CHECK_VERSION(4, 0, 0)
6427 if (!bAlreadyVisible
)
6429 if (GTK_IS_DIALOG(m_pWindow
))
6430 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow
))));
6431 gtk_widget_show(GTK_WIDGET(m_pWindow
));
6435 if (!bAlreadyRealized
)
6437 GtkAllocation allocation
;
6438 gtk_widget_realize(GTK_WIDGET(m_pWindow
));
6439 gtk_widget_get_allocation(GTK_WIDGET(m_pWindow
), &allocation
);
6440 #if !GTK_CHECK_VERSION(4, 0, 0)
6441 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow
), &allocation
);
6443 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow
), &allocation
, 0);
6447 VclPtr
<VirtualDevice
> xOutput(VclPtr
<VirtualDevice
>::Create(DeviceFormat::DEFAULT
));
6448 xOutput
->SetOutputSizePixel(get_size());
6449 cairo_surface_t
* pSurface
= get_underlying_cairo_surface(*xOutput
);
6450 cairo_t
* cr
= cairo_create(pSurface
);
6452 Point aOffset
= get_csd_offset(GTK_WIDGET(m_pWindow
));
6454 cairo_translate(cr
, -aOffset
.X(), -aOffset
.Y());
6456 #if !GTK_CHECK_VERSION(4, 0, 0)
6457 gtk_widget_draw(GTK_WIDGET(m_pWindow
), cr
);
6459 GtkSnapshot
* pSnapshot
= gtk_snapshot_new();
6460 GtkWidgetClass
* pWidgetClass
= GTK_WIDGET_GET_CLASS(GTK_WIDGET(m_pWindow
));
6461 pWidgetClass
->snapshot(GTK_WIDGET(m_pWindow
), pSnapshot
);
6462 GskRenderNode
* pNode
= gtk_snapshot_free_to_node(pSnapshot
);
6463 gsk_render_node_draw(pNode
, cr
);
6464 gsk_render_node_unref(pNode
);
6469 if (!bAlreadyVisible
)
6470 gtk_widget_hide(GTK_WIDGET(m_pWindow
));
6471 if (!bAlreadyRealized
)
6472 gtk_widget_unrealize(GTK_WIDGET(m_pWindow
));
6477 virtual weld::ScreenShotCollection
collect_screenshot_data() override
6479 weld::ScreenShotCollection aRet
;
6481 #if GTK_CHECK_VERSION(4, 0, 0)
6482 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(m_pWindow
));
6483 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
6485 do_collect_screenshot_data(pChild
, &aRet
);
6488 gtk_container_foreach(GTK_CONTAINER(m_pWindow
), do_collect_screenshot_data
, &aRet
);
6494 virtual ~GtkInstanceWindow() override
6496 if (m_nToplevelFocusChangedSignalId
)
6497 g_signal_handler_disconnect(m_pWindow
, m_nToplevelFocusChangedSignalId
);
6503 class GtkInstanceDialog
;
6507 GtkWindow
* m_pDialog
;
6508 GtkInstanceDialog
*m_pInstance
;
6511 VclPtr
<vcl::Window
> m_xFrameWindow
;
6514 DialogRunner(GtkWindow
* pDialog
, GtkInstanceDialog
* pInstance
)
6515 : m_pDialog(pDialog
)
6516 , m_pInstance(pInstance
)
6517 , m_nResponseId(GTK_RESPONSE_NONE
)
6521 GtkWindow
* pParent
= gtk_window_get_transient_for(m_pDialog
);
6522 GtkSalFrame
* pFrame
= pParent
? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent
)) : nullptr;
6523 m_xFrameWindow
= pFrame
? pFrame
->GetWindow() : nullptr;
6526 bool loop_is_running() const
6528 return m_pLoop
&& g_main_loop_is_running(m_pLoop
);
6533 if (g_main_loop_is_running(m_pLoop
))
6534 g_main_loop_quit(m_pLoop
);
6537 static void signal_response(GtkDialog
*, gint nResponseId
, gpointer data
);
6538 static void signal_cancel(GtkAssistant
*, gpointer data
);
6540 #if !GTK_CHECK_VERSION(4, 0, 0)
6541 static gboolean
signal_delete(GtkDialog
* pDialog
, GdkEventAny
*, gpointer data
)
6543 DialogRunner
* pThis
= static_cast<DialogRunner
*>(data
);
6544 if (GTK_IS_ASSISTANT(pThis
->m_pDialog
))
6546 // An assistant isn't a dialog, but we want to treat it like one
6547 signal_response(pDialog
, GTK_RESPONSE_DELETE_EVENT
, data
);
6551 return true; /* Do not destroy */
6555 static void signal_destroy(GtkDialog
*, gpointer data
)
6557 DialogRunner
* pThis
= static_cast<DialogRunner
*>(data
);
6561 void inc_modal_count()
6565 m_xFrameWindow
->IncModalCount();
6566 if (m_nModalDepth
== 0)
6567 m_xFrameWindow
->ImplGetFrame()->NotifyModalHierarchy(true);
6572 void dec_modal_count()
6576 m_xFrameWindow
->DecModalCount();
6578 if (m_nModalDepth
== 0)
6579 m_xFrameWindow
->ImplGetFrame()->NotifyModalHierarchy(false);
6583 // same as gtk_dialog_run except that unmap doesn't auto-respond
6584 // so we can hide the dialog and restore it without a response getting
6588 g_object_ref(m_pDialog
);
6592 bool bWasModal
= gtk_window_get_modal(m_pDialog
);
6594 gtk_window_set_modal(m_pDialog
, true);
6596 if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog
)))
6597 gtk_widget_show(GTK_WIDGET(m_pDialog
));
6599 gulong nSignalResponseId
= GTK_IS_DIALOG(m_pDialog
) ? g_signal_connect(m_pDialog
, "response", G_CALLBACK(signal_response
), this) : 0;
6600 gulong nSignalCancelId
= GTK_IS_ASSISTANT(m_pDialog
) ? g_signal_connect(m_pDialog
, "cancel", G_CALLBACK(signal_cancel
), this) : 0;
6601 #if !GTK_CHECK_VERSION(4, 0, 0)
6602 gulong nSignalDeleteId
= g_signal_connect(m_pDialog
, "delete-event", G_CALLBACK(signal_delete
), this);
6604 gulong nSignalDestroyId
= g_signal_connect(m_pDialog
, "destroy", G_CALLBACK(signal_destroy
), this);
6606 m_pLoop
= g_main_loop_new(nullptr, false);
6607 m_nResponseId
= GTK_RESPONSE_NONE
;
6609 main_loop_run(m_pLoop
);
6611 g_main_loop_unref(m_pLoop
);
6616 gtk_window_set_modal(m_pDialog
, false);
6618 if (nSignalResponseId
)
6619 g_signal_handler_disconnect(m_pDialog
, nSignalResponseId
);
6620 if (nSignalCancelId
)
6621 g_signal_handler_disconnect(m_pDialog
, nSignalCancelId
);
6622 #if !GTK_CHECK_VERSION(4, 0, 0)
6623 g_signal_handler_disconnect(m_pDialog
, nSignalDeleteId
);
6625 g_signal_handler_disconnect(m_pDialog
, nSignalDestroyId
);
6629 g_object_unref(m_pDialog
);
6631 return m_nResponseId
;
6636 if (m_xFrameWindow
&& m_nModalDepth
)
6638 // if, like the calc validation dialog does, the modality was
6639 // toggled off during execution ensure that on cleanup the parent
6640 // is left in the state it was found
6641 while (m_nModalDepth
++ < 0)
6642 m_xFrameWindow
->IncModalCount();
6649 typedef std::set
<GtkWidget
*> winset
;
6653 #if GTK_CHECK_VERSION(4, 0, 0)
6654 void collectVisibleChildren(GtkWidget
* pTop
, winset
& rVisibleWidgets
)
6656 for (GtkWidget
* pChild
= gtk_widget_get_first_child(pTop
);
6657 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
6659 if (!gtk_widget_get_visible(pChild
))
6661 rVisibleWidgets
.insert(pChild
);
6662 collectVisibleChildren(pChild
, rVisibleWidgets
);
6667 void hideUnless(GtkWidget
* pTop
, const winset
& rVisibleWidgets
,
6668 std::vector
<GtkWidget
*> &rWasVisibleWidgets
)
6670 #if GTK_CHECK_VERSION(4, 0, 0)
6671 for (GtkWidget
* pChild
= gtk_widget_get_first_child(pTop
);
6672 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
6674 if (!gtk_widget_get_visible(pChild
))
6676 if (rVisibleWidgets
.find(pChild
) == rVisibleWidgets
.end())
6678 g_object_ref(pChild
);
6679 rWasVisibleWidgets
.emplace_back(pChild
);
6680 gtk_widget_hide(pChild
);
6684 hideUnless(pChild
, rVisibleWidgets
, rWasVisibleWidgets
);
6688 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(pTop
));
6689 for (GList
* pEntry
= g_list_first(pChildren
); pEntry
; pEntry
= g_list_next(pEntry
))
6691 GtkWidget
* pChild
= static_cast<GtkWidget
*>(pEntry
->data
);
6692 if (!gtk_widget_get_visible(pChild
))
6694 if (rVisibleWidgets
.find(pChild
) == rVisibleWidgets
.end())
6696 g_object_ref(pChild
);
6697 rWasVisibleWidgets
.emplace_back(pChild
);
6698 gtk_widget_hide(pChild
);
6700 else if (GTK_IS_CONTAINER(pChild
))
6702 hideUnless(pChild
, rVisibleWidgets
, rWasVisibleWidgets
);
6705 g_list_free(pChildren
);
6709 class GtkInstanceButton
;
6711 class GtkInstanceDialog
: public GtkInstanceWindow
, public virtual weld::Dialog
6714 GtkWindow
* m_pDialog
;
6715 DialogRunner m_aDialogRun
;
6716 std::shared_ptr
<weld::DialogController
> m_xDialogController
;
6717 // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
6718 std::shared_ptr
<weld::Dialog
> m_xRunAsyncSelf
;
6719 std::function
<void(sal_Int32
)> m_aFunc
;
6720 gulong m_nCloseSignalId
;
6721 gulong m_nResponseSignalId
;
6722 gulong m_nCancelSignalId
;
6723 gulong m_nSignalDeleteId
;
6725 // for calc ref dialog that shrink to range selection widgets and resize back
6726 GtkWidget
* m_pRefEdit
;
6727 std::vector
<GtkWidget
*> m_aHiddenWidgets
; // vector of hidden Controls
6728 int m_nOldEditWidth
; // Original width of the input field
6729 int m_nOldEditWidthReq
; // Original width request of the input field
6730 #if !GTK_CHECK_VERSION(4, 0, 0)
6731 int m_nOldBorderWidth
; // border width for expanded dialog
6739 static void signalClose(GtkWidget
*, gpointer widget
)
6741 GtkInstanceDialog
* pThis
= static_cast<GtkInstanceDialog
*>(widget
);
6742 pThis
->signal_close();
6745 static void signalAsyncResponse(GtkWidget
*, gint ret
, gpointer widget
)
6747 GtkInstanceDialog
* pThis
= static_cast<GtkInstanceDialog
*>(widget
);
6748 pThis
->asyncresponse(ret
);
6751 static void signalAsyncCancel(GtkAssistant
*, gpointer widget
)
6753 GtkInstanceDialog
* pThis
= static_cast<GtkInstanceDialog
*>(widget
);
6754 // make esc in an assistant act as if cancel button was pressed
6755 pThis
->close(false);
6758 #if !GTK_CHECK_VERSION(4, 0, 0)
6759 static gboolean
signalAsyncDelete(GtkWidget
* pDialog
, GdkEventAny
*, gpointer widget
)
6761 GtkInstanceDialog
* pThis
= static_cast<GtkInstanceDialog
*>(widget
);
6762 if (GTK_IS_ASSISTANT(pThis
->m_pDialog
))
6764 // An assistant isn't a dialog, but we want to treat it like one
6765 signalAsyncResponse(pDialog
, GTK_RESPONSE_DELETE_EVENT
, widget
);
6767 return true; /* Do not destroy */
6771 static int GtkToVcl(int ret
)
6773 if (ret
== GTK_RESPONSE_OK
)
6775 else if (ret
== GTK_RESPONSE_CANCEL
)
6777 else if (ret
== GTK_RESPONSE_DELETE_EVENT
)
6779 else if (ret
== GTK_RESPONSE_CLOSE
)
6781 else if (ret
== GTK_RESPONSE_YES
)
6783 else if (ret
== GTK_RESPONSE_NO
)
6785 else if (ret
== GTK_RESPONSE_HELP
)
6790 static int VclToGtk(int nResponse
)
6792 if (nResponse
== RET_OK
)
6793 return GTK_RESPONSE_OK
;
6794 else if (nResponse
== RET_CANCEL
)
6795 return GTK_RESPONSE_CANCEL
;
6796 else if (nResponse
== RET_CLOSE
)
6797 return GTK_RESPONSE_CLOSE
;
6798 else if (nResponse
== RET_YES
)
6799 return GTK_RESPONSE_YES
;
6800 else if (nResponse
== RET_NO
)
6801 return GTK_RESPONSE_NO
;
6802 else if (nResponse
== RET_HELP
)
6803 return GTK_RESPONSE_HELP
;
6807 void asyncresponse(gint ret
);
6809 #if !GTK_CHECK_VERSION(4, 0, 0)
6810 static void signalActivate(GtkMenuItem
*, gpointer data
)
6812 bool* pActivate
= static_cast<bool*>(data
);
6817 #if !GTK_CHECK_VERSION(4, 0, 0)
6818 bool signal_screenshot_popup_menu(const GdkEventButton
* pEvent
)
6820 GtkWidget
*pMenu
= gtk_menu_new();
6822 GtkWidget
* pMenuItem
= gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT
)).getStr());
6823 gtk_menu_shell_append(GTK_MENU_SHELL(pMenu
), pMenuItem
);
6824 bool bActivate(false);
6825 g_signal_connect(pMenuItem
, "activate", G_CALLBACK(signalActivate
), &bActivate
);
6826 gtk_widget_show(pMenuItem
);
6828 int button
, event_time
;
6831 button
= pEvent
->button
;
6832 event_time
= pEvent
->time
;
6837 event_time
= gtk_get_current_event_time();
6840 gtk_menu_attach_to_widget(GTK_MENU(pMenu
), GTK_WIDGET(m_pDialog
), nullptr);
6842 GMainLoop
* pLoop
= g_main_loop_new(nullptr, true);
6843 gulong nSignalId
= g_signal_connect_swapped(G_OBJECT(pMenu
), "deactivate", G_CALLBACK(g_main_loop_quit
), pLoop
);
6845 gtk_menu_popup(GTK_MENU(pMenu
), nullptr, nullptr, nullptr, nullptr, button
, event_time
);
6847 if (g_main_loop_is_running(pLoop
))
6848 main_loop_run(pLoop
);
6850 g_main_loop_unref(pLoop
);
6851 g_signal_handler_disconnect(pMenu
, nSignalId
);
6852 gtk_menu_detach(GTK_MENU(pMenu
));
6856 // open screenshot annotation dialog
6857 VclAbstractDialogFactory
* pFact
= VclAbstractDialogFactory::Create();
6858 VclPtr
<AbstractScreenshotAnnotationDlg
> xTmp
= pFact
->CreateScreenshotAnnotationDlg(*this);
6859 ScopedVclPtr
<AbstractScreenshotAnnotationDlg
> xDialog(xTmp
);
6867 static gboolean
signalScreenshotPopupMenu(GtkWidget
*, gpointer widget
)
6869 #if !GTK_CHECK_VERSION(4, 0, 0)
6870 GtkInstanceDialog
* pThis
= static_cast<GtkInstanceDialog
*>(widget
);
6871 return pThis
->signal_screenshot_popup_menu(nullptr);
6878 #if !GTK_CHECK_VERSION(4, 0, 0)
6879 static gboolean
signalScreenshotButton(GtkWidget
*, GdkEventButton
* pEvent
, gpointer widget
)
6881 GtkInstanceDialog
* pThis
= static_cast<GtkInstanceDialog
*>(widget
);
6882 SolarMutexGuard aGuard
;
6883 return pThis
->signal_screenshot_button(pEvent
);
6886 bool signal_screenshot_button(GdkEventButton
* pEvent
)
6888 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent
*>(pEvent
)) && pEvent
->type
== GDK_BUTTON_PRESS
)
6890 //if handled for context menu, stop processing
6891 return signal_screenshot_popup_menu(pEvent
);
6898 GtkInstanceDialog(GtkWindow
* pDialog
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
6899 : GtkInstanceWindow(pDialog
, pBuilder
, bTakeOwnership
)
6900 , m_pDialog(pDialog
)
6901 , m_aDialogRun(pDialog
, this)
6902 , m_nResponseSignalId(0)
6903 , m_nCancelSignalId(0)
6904 , m_nSignalDeleteId(0)
6905 , m_pRefEdit(nullptr)
6906 , m_nOldEditWidth(0)
6907 , m_nOldEditWidthReq(0)
6908 #if !GTK_CHECK_VERSION(4, 0, 0)
6909 , m_nOldBorderWidth(0)
6912 if (GTK_IS_DIALOG(m_pDialog
) || GTK_IS_ASSISTANT(m_pDialog
))
6913 m_nCloseSignalId
= g_signal_connect(m_pDialog
, "close", G_CALLBACK(signalClose
), this);
6915 m_nCloseSignalId
= 0;
6916 const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
6917 if (bScreenshotMode
)
6919 g_signal_connect(m_pDialog
, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu
), this);
6920 #if !GTK_CHECK_VERSION(4, 0, 0)
6921 g_signal_connect(m_pDialog
, "button-press-event", G_CALLBACK(signalScreenshotButton
), this);
6926 virtual bool runAsync(std::shared_ptr
<weld::DialogController
> rDialogController
, const std::function
<void(sal_Int32
)>& func
) override
6928 assert(!m_nResponseSignalId
&& !m_nCancelSignalId
&& !m_nSignalDeleteId
);
6930 m_xDialogController
= rDialogController
;
6934 m_aDialogRun
.inc_modal_count();
6937 m_nResponseSignalId
= GTK_IS_DIALOG(m_pDialog
) ? g_signal_connect(m_pDialog
, "response", G_CALLBACK(signalAsyncResponse
), this) : 0;
6938 m_nCancelSignalId
= GTK_IS_ASSISTANT(m_pDialog
) ? g_signal_connect(m_pDialog
, "cancel", G_CALLBACK(signalAsyncCancel
), this) : 0;
6939 #if !GTK_CHECK_VERSION(4, 0, 0)
6940 m_nSignalDeleteId
= g_signal_connect(m_pDialog
, "delete-event", G_CALLBACK(signalAsyncDelete
), this);
6946 virtual bool runAsync(std::shared_ptr
<Dialog
> const & rxSelf
, const std::function
<void(sal_Int32
)>& func
) override
6948 assert( rxSelf
.get() == this );
6949 assert(!m_nResponseSignalId
&& !m_nCancelSignalId
&& !m_nSignalDeleteId
);
6951 // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
6952 // which is that rxSelf enforces.
6953 m_xRunAsyncSelf
= rxSelf
;
6957 m_aDialogRun
.inc_modal_count();
6960 m_nResponseSignalId
= GTK_IS_DIALOG(m_pDialog
) ? g_signal_connect(m_pDialog
, "response", G_CALLBACK(signalAsyncResponse
), this) : 0;
6961 m_nCancelSignalId
= GTK_IS_ASSISTANT(m_pDialog
) ? g_signal_connect(m_pDialog
, "cancel", G_CALLBACK(signalAsyncCancel
), this) : 0;
6962 #if !GTK_CHECK_VERSION(4, 0, 0)
6963 m_nSignalDeleteId
= g_signal_connect(m_pDialog
, "delete-event", G_CALLBACK(signalAsyncDelete
), this);
6969 GtkInstanceButton
* has_click_handler(int nResponse
);
6971 virtual int run() override
;
6973 virtual void show() override
6975 if (gtk_widget_get_visible(m_pWidget
))
6977 #if !GTK_CHECK_VERSION(4, 0, 0)
6978 if (GTK_IS_DIALOG(m_pDialog
))
6979 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog
))));
6981 GtkInstanceWindow::show();
6984 virtual void set_modal(bool bModal
) override
6986 if (get_modal() == bModal
)
6988 GtkInstanceWindow::set_modal(bModal
);
6989 /* if change the dialog modality while its running, then also change the parent LibreOffice window
6990 modal count, we typically expect the dialog modality to be restored to its original state
6992 This change modality while running case is for...
6994 a) the calc/chart dialogs which put up an extra range chooser
6995 dialog, hides the original, the user can select a range of cells and
6996 on completion the original dialog is restored
6998 b) the validity dialog in calc
7000 // tdf#135567 we know we are running in the sync case if loop_is_running is true
7001 // but for the async case we instead check for m_xDialogController which is set in
7002 // runAsync and cleared in asyncresponse
7003 if (m_aDialogRun
.loop_is_running() || m_xDialogController
)
7006 m_aDialogRun
.inc_modal_count();
7008 m_aDialogRun
.dec_modal_count();
7012 virtual void response(int nResponse
) override
;
7014 virtual void add_button(const OUString
& rText
, int nResponse
, const OString
& rHelpId
) override
7016 GtkWidget
* pWidget
= gtk_dialog_add_button(GTK_DIALOG(m_pDialog
), MapToGtkAccelerator(rText
).getStr(), VclToGtk(nResponse
));
7017 if (!rHelpId
.isEmpty())
7018 ::set_help_id(pWidget
, rHelpId
);
7021 virtual void set_default_response(int nResponse
) override
7023 gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog
), VclToGtk(nResponse
));
7026 virtual GtkButton
* get_widget_for_response(int nGtkResponse
)
7028 return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog
), nGtkResponse
));
7031 virtual weld::Button
* weld_widget_for_response(int nVclResponse
) override
;
7033 virtual Container
* weld_content_area() override
7035 #if !GTK_CHECK_VERSION(4, 0, 0)
7036 return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog
))), m_pBuilder
, false);
7038 return new GtkInstanceContainer(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog
)), m_pBuilder
, false);
7042 virtual void collapse(weld::Widget
* pEdit
, weld::Widget
* pButton
) override
7044 GtkInstanceWidget
* pVclEdit
= dynamic_cast<GtkInstanceWidget
*>(pEdit
);
7046 GtkInstanceWidget
* pVclButton
= dynamic_cast<GtkInstanceWidget
*>(pButton
);
7048 GtkWidget
* pRefEdit
= pVclEdit
->getWidget();
7049 GtkWidget
* pRefBtn
= pVclButton
? pVclButton
->getWidget() : nullptr;
7051 m_nOldEditWidth
= gtk_widget_get_allocated_width(pRefEdit
);
7053 gtk_widget_get_size_request(pRefEdit
, &m_nOldEditWidthReq
, nullptr);
7055 //We want just pRefBtn and pRefEdit to be shown
7056 //mark widgets we want to be visible, starting with pRefEdit
7057 //and all its direct parents.
7058 winset aVisibleWidgets
;
7059 GtkWidget
*pContentArea
= gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog
));
7060 for (GtkWidget
*pCandidate
= pRefEdit
;
7061 pCandidate
&& pCandidate
!= pContentArea
&& gtk_widget_get_visible(pCandidate
);
7062 pCandidate
= gtk_widget_get_parent(pCandidate
))
7064 aVisibleWidgets
.insert(pCandidate
);
7066 #if GTK_CHECK_VERSION(4, 0, 0)
7067 collectVisibleChildren(pRefEdit
, aVisibleWidgets
);
7071 #if GTK_CHECK_VERSION(4, 0, 0)
7072 collectVisibleChildren(pRefBtn
, aVisibleWidgets
);
7074 //same again with pRefBtn, except stop if there's a
7075 //shared parent in the existing widgets
7076 for (GtkWidget
*pCandidate
= pRefBtn
;
7077 pCandidate
&& pCandidate
!= pContentArea
&& gtk_widget_get_visible(pCandidate
);
7078 pCandidate
= gtk_widget_get_parent(pCandidate
))
7080 if (aVisibleWidgets
.insert(pCandidate
).second
)
7085 //hide everything except the aVisibleWidgets
7086 hideUnless(pContentArea
, aVisibleWidgets
, m_aHiddenWidgets
);
7087 gtk_widget_set_size_request(pRefEdit
, m_nOldEditWidth
, -1);
7088 #if !GTK_CHECK_VERSION(4, 0, 0)
7089 m_nOldBorderWidth
= gtk_container_get_border_width(GTK_CONTAINER(m_pDialog
));
7090 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog
), 0);
7091 if (GtkWidget
* pActionArea
= gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog
)))
7092 gtk_widget_hide(pActionArea
);
7093 gtk_widget_show_all(pRefEdit
);
7095 gtk_widget_show_all(pRefBtn
);
7097 if (GtkWidget
* pActionArea
= gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog
)))
7098 gtk_widget_hide(pActionArea
);
7101 // calc's insert->function is springing back to its original size if the ref-button
7102 // is used to shrink the dialog down and then the user clicks in the calc area to do
7104 bool bWorkaroundSizeSpringingBack
= DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget
));
7105 if (bWorkaroundSizeSpringingBack
)
7106 gtk_widget_unmap(GTK_WIDGET(m_pDialog
));
7108 resize_to_request();
7110 if (bWorkaroundSizeSpringingBack
)
7111 gtk_widget_map(GTK_WIDGET(m_pDialog
));
7113 m_pRefEdit
= pRefEdit
;
7116 virtual void undo_collapse() override
7118 // All others: Show();
7119 for (GtkWidget
* pWindow
: m_aHiddenWidgets
)
7121 gtk_widget_show(pWindow
);
7122 g_object_unref(pWindow
);
7124 m_aHiddenWidgets
.clear();
7126 gtk_widget_set_size_request(m_pRefEdit
, m_nOldEditWidthReq
, -1);
7127 m_pRefEdit
= nullptr;
7128 #if !GTK_CHECK_VERSION(4, 0, 0)
7129 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog
), m_nOldBorderWidth
);
7130 if (GtkWidget
* pActionArea
= gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog
)))
7131 gtk_widget_show(pActionArea
);
7133 if (GtkWidget
* pActionArea
= gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog
)))
7134 gtk_widget_show(pActionArea
);
7136 resize_to_request();
7140 void close(bool bCloseSignal
);
7142 virtual void SetInstallLOKNotifierHdl(const Link
<void*, vcl::ILibreOfficeKitNotifier
*>&) override
7144 //not implemented for the gtk variant
7147 virtual ~GtkInstanceDialog() override
7149 if (!m_aHiddenWidgets
.empty())
7151 for (GtkWidget
* pWindow
: m_aHiddenWidgets
)
7152 g_object_unref(pWindow
);
7153 m_aHiddenWidgets
.clear();
7156 if (m_nCloseSignalId
)
7157 g_signal_handler_disconnect(m_pDialog
, m_nCloseSignalId
);
7158 assert(!m_nResponseSignalId
&& !m_nCancelSignalId
&& !m_nSignalDeleteId
);
7164 void DialogRunner::signal_response(GtkDialog
*, gint nResponseId
, gpointer data
)
7166 DialogRunner
* pThis
= static_cast<DialogRunner
*>(data
);
7168 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
7169 if (nResponseId
== GTK_RESPONSE_DELETE_EVENT
)
7171 pThis
->m_pInstance
->close(false);
7175 pThis
->m_nResponseId
= nResponseId
;
7179 void DialogRunner::signal_cancel(GtkAssistant
*, gpointer data
)
7181 DialogRunner
* pThis
= static_cast<DialogRunner
*>(data
);
7183 // make esc in an assistant act as if cancel button was pressed
7184 pThis
->m_pInstance
->close(false);
7189 class GtkInstanceMessageDialog
: public GtkInstanceDialog
, public virtual weld::MessageDialog
7192 GtkMessageDialog
* m_pMessageDialog
;
7194 GtkInstanceMessageDialog(GtkMessageDialog
* pMessageDialog
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
7195 : GtkInstanceDialog(GTK_WINDOW(pMessageDialog
), pBuilder
, bTakeOwnership
)
7196 , m_pMessageDialog(pMessageDialog
)
7200 virtual void set_primary_text(const OUString
& rText
) override
7202 ::set_primary_text(m_pMessageDialog
, rText
);
7205 virtual OUString
get_primary_text() const override
7207 return ::get_primary_text(m_pMessageDialog
);
7210 virtual void set_secondary_text(const OUString
& rText
) override
7212 ::set_secondary_text(m_pMessageDialog
, rText
);
7215 virtual OUString
get_secondary_text() const override
7217 return ::get_secondary_text(m_pMessageDialog
);
7220 virtual Container
* weld_message_area() override
7222 #if !GTK_CHECK_VERSION(4, 0, 0)
7223 return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog
)), m_pBuilder
, false);
7225 return new GtkInstanceContainer(gtk_message_dialog_get_message_area(m_pMessageDialog
), m_pBuilder
, false);
7230 void set_label_wrap(GtkLabel
* pLabel
, bool bWrap
)
7232 #if GTK_CHECK_VERSION(4, 0, 0)
7233 gtk_label_set_wrap(pLabel
, bWrap
);
7235 gtk_label_set_line_wrap(pLabel
, bWrap
);
7239 class GtkInstanceAssistant
: public GtkInstanceDialog
, public virtual weld::Assistant
7242 GtkAssistant
* m_pAssistant
;
7243 GtkWidget
* m_pSidebar
;
7244 GtkWidget
* m_pSidebarEventBox
;
7245 #if !GTK_CHECK_VERSION(4, 0, 0)
7246 GtkButtonBox
* m_pButtonBox
;
7248 GtkBox
* m_pButtonBox
;
7249 GtkEventController
* m_pSidebarClickController
;
7254 GtkButton
* m_pFinish
;
7255 GtkButton
* m_pCancel
;
7256 gulong m_nButtonPressSignalId
;
7257 std::vector
<std::unique_ptr
<GtkInstanceContainer
>> m_aPages
;
7258 std::map
<OString
, bool> m_aNotClickable
;
7260 int find_page(std::string_view ident
) const
7262 int nPages
= gtk_assistant_get_n_pages(m_pAssistant
);
7263 for (int i
= 0; i
< nPages
; ++i
)
7265 GtkWidget
* pPage
= gtk_assistant_get_nth_page(m_pAssistant
, i
);
7266 OString sBuildableName
= ::get_buildable_id(GTK_BUILDABLE(pPage
));
7267 if (sBuildableName
== ident
)
7273 static void wrap_sidebar_label(GtkWidget
*pWidget
, gpointer
/*user_data*/)
7275 if (GTK_IS_LABEL(pWidget
))
7277 ::set_label_wrap(GTK_LABEL(pWidget
), true);
7278 gtk_label_set_width_chars(GTK_LABEL(pWidget
), 22);
7279 gtk_label_set_max_width_chars(GTK_LABEL(pWidget
), 22);
7283 static void find_sidebar(GtkWidget
*pWidget
, gpointer user_data
)
7285 OString sBuildableName
= ::get_buildable_id(GTK_BUILDABLE(pWidget
));
7286 if (sBuildableName
== "sidebar")
7288 GtkWidget
**ppSidebar
= static_cast<GtkWidget
**>(user_data
);
7289 *ppSidebar
= pWidget
;
7291 #if !GTK_CHECK_VERSION(4, 0, 0)
7292 if (GTK_IS_CONTAINER(pWidget
))
7293 gtk_container_forall(GTK_CONTAINER(pWidget
), find_sidebar
, user_data
);
7297 static void signalHelpClicked(GtkButton
*, gpointer widget
)
7299 GtkInstanceAssistant
* pThis
= static_cast<GtkInstanceAssistant
*>(widget
);
7300 pThis
->signal_help_clicked();
7303 void signal_help_clicked()
7308 #if GTK_CHECK_VERSION(4, 0, 0)
7309 static void signalButton(GtkGestureClick
* /*pGesture*/, int /*n_press*/, gdouble x
, gdouble y
, gpointer widget
)
7311 GtkInstanceAssistant
* pThis
= static_cast<GtkInstanceAssistant
*>(widget
);
7312 SolarMutexGuard aGuard
;
7313 pThis
->signal_button(x
, y
);
7316 static gboolean
signalButton(GtkWidget
*, GdkEventButton
* pEvent
, gpointer widget
)
7318 GtkInstanceAssistant
* pThis
= static_cast<GtkInstanceAssistant
*>(widget
);
7319 SolarMutexGuard aGuard
;
7320 return pThis
->signal_button(pEvent
->x
, pEvent
->y
);
7324 bool signal_button(gtk_coord event_x
, gtk_coord event_y
)
7326 int nNewCurrentPage
= -1;
7328 GtkAllocation allocation
;
7332 #if GTK_CHECK_VERSION(4, 0, 0)
7333 for (GtkWidget
* pWidget
= gtk_widget_get_first_child(m_pSidebar
);
7334 pWidget
; pWidget
= gtk_widget_get_next_sibling(pWidget
))
7337 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(m_pSidebar
));
7338 for (GList
* pChild
= g_list_first(pChildren
); pChild
; pChild
= g_list_next(pChild
))
7340 GtkWidget
* pWidget
= static_cast<GtkWidget
*>(pChild
->data
);
7342 if (!gtk_widget_get_visible(pWidget
))
7345 gtk_widget_get_allocation(pWidget
, &allocation
);
7347 gtk_coord dest_x1
, dest_y1
;
7348 gtk_widget_translate_coordinates(pWidget
,
7355 gtk_coord dest_x2
, dest_y2
;
7356 gtk_widget_translate_coordinates(pWidget
,
7364 if (event_x
>= dest_x1
&& event_x
<= dest_x2
&& event_y
>= dest_y1
&& event_y
<= dest_y2
)
7366 nNewCurrentPage
= nPageIndex
;
7372 #if !GTK_CHECK_VERSION(4, 0, 0)
7373 g_list_free(pChildren
);
7376 if (nNewCurrentPage
!= -1 && nNewCurrentPage
!= get_current_page())
7378 OString sIdent
= get_page_ident(nNewCurrentPage
);
7379 if (!m_aNotClickable
[sIdent
] && !signal_jump_page(sIdent
))
7380 set_current_page(nNewCurrentPage
);
7387 GtkInstanceAssistant(GtkAssistant
* pAssistant
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
7388 : GtkInstanceDialog(GTK_WINDOW(pAssistant
), pBuilder
, bTakeOwnership
)
7389 , m_pAssistant(pAssistant
)
7390 , m_pSidebar(nullptr)
7391 #if GTK_CHECK_VERSION(4, 0, 0)
7392 , m_pSidebarClickController(nullptr)
7394 , m_nButtonPressSignalId(0)
7396 #if !GTK_CHECK_VERSION(4, 0, 0)
7397 m_pButtonBox
= GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL
));
7398 gtk_button_box_set_layout(m_pButtonBox
, GTK_BUTTONBOX_END
);
7399 gtk_box_set_spacing(GTK_BOX(m_pButtonBox
), 6);
7401 m_pButtonBox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6));
7404 m_pBack
= GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back
)).getStr()));
7405 #if !GTK_CHECK_VERSION(4, 0, 0)
7406 gtk_widget_set_can_default(GTK_WIDGET(m_pBack
), true);
7408 ::set_buildable_id(GTK_BUILDABLE(m_pBack
), "previous");
7409 #if GTK_CHECK_VERSION(4, 0, 0)
7410 gtk_box_append(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pBack
));
7412 gtk_box_pack_end(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pBack
), false, false, 0);
7415 m_pNext
= GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next
)).getStr()));
7416 #if !GTK_CHECK_VERSION(4, 0, 0)
7417 gtk_widget_set_can_default(GTK_WIDGET(m_pNext
), true);
7419 ::set_buildable_id(GTK_BUILDABLE(m_pNext
), "next");
7420 #if GTK_CHECK_VERSION(4, 0, 0)
7421 gtk_box_append(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pNext
));
7423 gtk_box_pack_end(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pNext
), false, false, 0);
7426 m_pCancel
= GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel
)).getStr()));
7427 #if !GTK_CHECK_VERSION(4, 0, 0)
7428 gtk_widget_set_can_default(GTK_WIDGET(m_pCancel
), true);
7430 #if GTK_CHECK_VERSION(4, 0, 0)
7431 gtk_box_append(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pCancel
));
7433 gtk_box_pack_end(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pCancel
), false, false, 0);
7436 m_pFinish
= GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish
)).getStr()));
7437 #if !GTK_CHECK_VERSION(4, 0, 0)
7438 gtk_widget_set_can_default(GTK_WIDGET(m_pFinish
), true);
7440 ::set_buildable_id(GTK_BUILDABLE(m_pFinish
), "finish");
7441 #if GTK_CHECK_VERSION(4, 0, 0)
7442 gtk_box_append(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pFinish
));
7444 gtk_box_pack_end(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pFinish
), false, false, 0);
7447 #if GTK_CHECK_VERSION(4, 0, 0)
7448 m_pHelp
= GTK_BUTTON(gtk_button_new_from_icon_name("help-browser-symbolic"));
7450 m_pHelp
= GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help
)).getStr()));
7452 #if !GTK_CHECK_VERSION(4, 0, 0)
7453 gtk_widget_set_can_default(GTK_WIDGET(m_pHelp
), true);
7455 g_signal_connect(m_pHelp
, "clicked", G_CALLBACK(signalHelpClicked
), this);
7456 #if GTK_CHECK_VERSION(4, 0, 0)
7457 gtk_box_prepend(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pHelp
));
7458 gtk_widget_set_hexpand(GTK_WIDGET(m_pHelp
), true);
7459 gtk_widget_set_halign(GTK_WIDGET(m_pHelp
), GTK_ALIGN_START
);
7461 gtk_box_pack_end(GTK_BOX(m_pButtonBox
), GTK_WIDGET(m_pHelp
), false, false, 0);
7464 gtk_assistant_add_action_widget(pAssistant
, GTK_WIDGET(m_pButtonBox
));
7465 #if !GTK_CHECK_VERSION(4, 0, 0)
7466 gtk_button_box_set_child_secondary(m_pButtonBox
, GTK_WIDGET(m_pHelp
), true);
7468 gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox
), true);
7470 GtkWidget
* pParent
= gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox
));
7471 #if !GTK_CHECK_VERSION(4, 0, 0)
7472 gtk_container_child_set(GTK_CONTAINER(pParent
), GTK_WIDGET(m_pButtonBox
), "expand", true, "fill", true, nullptr);
7474 gtk_widget_set_halign(pParent
, GTK_ALIGN_FILL
);
7476 // Hide the built-in ones early so we get a nice optimal size for the width without
7477 // including the unused contents
7478 #if GTK_CHECK_VERSION(4, 0, 0)
7479 for (GtkWidget
* pChild
= gtk_widget_get_first_child(pParent
);
7480 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
7482 gtk_widget_hide(pChild
);
7485 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(pParent
));
7486 for (GList
* pChild
= g_list_first(pChildren
); pChild
; pChild
= g_list_next(pChild
))
7488 GtkWidget
* pWidget
= static_cast<GtkWidget
*>(pChild
->data
);
7489 gtk_widget_hide(pWidget
);
7491 g_list_free(pChildren
);
7494 #if !GTK_CHECK_VERSION(4, 0, 0)
7495 gtk_widget_show_all(GTK_WIDGET(m_pButtonBox
));
7497 gtk_widget_show(GTK_WIDGET(m_pButtonBox
));
7500 find_sidebar(GTK_WIDGET(m_pAssistant
), &m_pSidebar
);
7502 m_pSidebarEventBox
= ::ensureEventWidget(m_pSidebar
);
7503 if (m_pSidebarEventBox
)
7505 #if GTK_CHECK_VERSION(4, 0, 0)
7506 GtkGesture
*pClick
= gtk_gesture_click_new();
7507 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick
), 0);
7508 m_pSidebarClickController
= GTK_EVENT_CONTROLLER(pClick
);
7509 gtk_widget_add_controller(m_pSidebarEventBox
, m_pSidebarClickController
);
7510 m_nButtonPressSignalId
= g_signal_connect(m_pSidebarClickController
, "pressed", G_CALLBACK(signalButton
), this);
7512 m_nButtonPressSignalId
= g_signal_connect(m_pSidebarEventBox
, "button-press-event", G_CALLBACK(signalButton
), this);
7517 virtual int get_current_page() const override
7519 return gtk_assistant_get_current_page(m_pAssistant
);
7522 virtual int get_n_pages() const override
7524 return gtk_assistant_get_n_pages(m_pAssistant
);
7527 virtual OString
get_page_ident(int nPage
) const override
7529 const GtkWidget
* pWidget
= gtk_assistant_get_nth_page(m_pAssistant
, nPage
);
7530 return ::get_buildable_id(GTK_BUILDABLE(pWidget
));
7533 virtual OString
get_current_page_ident() const override
7535 return get_page_ident(get_current_page());
7538 virtual void set_current_page(int nPage
) override
7540 OString
sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant
)));
7542 gtk_assistant_set_current_page(m_pAssistant
, nPage
);
7544 // if the page doesn't have a title, then the dialog will now have no
7545 // title, so restore the original title as a fallback
7546 GtkWidget
* pPage
= gtk_assistant_get_nth_page(m_pAssistant
, nPage
);
7547 if (!gtk_assistant_get_page_title(m_pAssistant
, pPage
))
7548 gtk_window_set_title(GTK_WINDOW(m_pAssistant
), sDialogTitle
.getStr());
7551 virtual void set_current_page(const OString
& rIdent
) override
7553 int nPage
= find_page(rIdent
);
7556 set_current_page(nPage
);
7559 virtual void set_page_title(const OString
& rIdent
, const OUString
& rTitle
) override
7561 int nIndex
= find_page(rIdent
);
7564 GtkWidget
* pPage
= gtk_assistant_get_nth_page(m_pAssistant
, nIndex
);
7565 gtk_assistant_set_page_title(m_pAssistant
, pPage
,
7566 OUStringToOString(rTitle
, RTL_TEXTENCODING_UTF8
).getStr());
7567 #if !GTK_CHECK_VERSION(4, 0, 0)
7568 gtk_container_forall(GTK_CONTAINER(m_pSidebar
), wrap_sidebar_label
, nullptr);
7572 virtual OUString
get_page_title(const OString
& rIdent
) const override
7574 int nIndex
= find_page(rIdent
);
7577 GtkWidget
* pPage
= gtk_assistant_get_nth_page(m_pAssistant
, nIndex
);
7578 const gchar
* pStr
= gtk_assistant_get_page_title(m_pAssistant
, pPage
);
7579 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
7582 virtual void set_page_sensitive(const OString
& rIdent
, bool bSensitive
) override
7584 m_aNotClickable
[rIdent
] = !bSensitive
;
7587 virtual void set_page_index(const OString
& rIdent
, int nNewIndex
) override
7589 int nOldIndex
= find_page(rIdent
);
7590 if (nOldIndex
== -1)
7593 if (nOldIndex
== nNewIndex
)
7596 GtkWidget
* pPage
= gtk_assistant_get_nth_page(m_pAssistant
, nOldIndex
);
7598 g_object_ref(pPage
);
7599 OString
sTitle(gtk_assistant_get_page_title(m_pAssistant
, pPage
));
7600 gtk_assistant_remove_page(m_pAssistant
, nOldIndex
);
7601 gtk_assistant_insert_page(m_pAssistant
, pPage
, nNewIndex
);
7602 gtk_assistant_set_page_type(m_pAssistant
, pPage
, GTK_ASSISTANT_PAGE_CUSTOM
);
7603 gtk_assistant_set_page_title(m_pAssistant
, pPage
, sTitle
.getStr());
7604 #if !GTK_CHECK_VERSION(4, 0, 0)
7605 gtk_container_forall(GTK_CONTAINER(m_pSidebar
), wrap_sidebar_label
, nullptr);
7607 g_object_unref(pPage
);
7610 virtual weld::Container
* append_page(const OString
& rIdent
) override
7612 disable_notify_events();
7614 GtkWidget
*pChild
= gtk_grid_new();
7615 ::set_buildable_id(GTK_BUILDABLE(pChild
), rIdent
);
7616 gtk_assistant_append_page(m_pAssistant
, pChild
);
7617 gtk_assistant_set_page_type(m_pAssistant
, pChild
, GTK_ASSISTANT_PAGE_CUSTOM
);
7618 gtk_widget_show(pChild
);
7620 enable_notify_events();
7622 #if !GTK_CHECK_VERSION(4, 0, 0)
7623 m_aPages
.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild
), m_pBuilder
, false));
7625 m_aPages
.emplace_back(new GtkInstanceContainer(pChild
, m_pBuilder
, false));
7628 return m_aPages
.back().get();
7631 virtual void set_page_side_help_id(const OString
& rHelpId
) override
7635 ::set_help_id(m_pSidebar
, rHelpId
);
7638 virtual GtkButton
* get_widget_for_response(int nGtkResponse
) override
7640 GtkButton
* pButton
= nullptr;
7641 if (nGtkResponse
== GTK_RESPONSE_YES
)
7643 else if (nGtkResponse
== GTK_RESPONSE_NO
)
7645 else if (nGtkResponse
== GTK_RESPONSE_OK
)
7646 pButton
= m_pFinish
;
7647 else if (nGtkResponse
== GTK_RESPONSE_CANCEL
)
7648 pButton
= m_pCancel
;
7649 else if (nGtkResponse
== GTK_RESPONSE_HELP
)
7654 virtual ~GtkInstanceAssistant() override
7656 if (m_nButtonPressSignalId
)
7658 #if GTK_CHECK_VERSION(4, 0, 0)
7659 g_signal_handler_disconnect(m_pSidebarClickController
, m_nButtonPressSignalId
);
7661 g_signal_handler_disconnect(m_pSidebarEventBox
, m_nButtonPressSignalId
);
7667 class GtkInstanceFrame
: public GtkInstanceContainer
, public virtual weld::Frame
7672 GtkInstanceFrame(GtkFrame
* pFrame
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
7673 #if !GTK_CHECK_VERSION(4, 0, 0)
7674 : GtkInstanceContainer(GTK_CONTAINER(pFrame
), pBuilder
, bTakeOwnership
)
7676 : GtkInstanceContainer(GTK_WIDGET(pFrame
), pBuilder
, bTakeOwnership
)
7682 virtual void set_label(const OUString
& rText
) override
7684 gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame
)), rText
.replaceFirst("~", "").toUtf8().getStr());
7687 virtual OUString
get_label() const override
7689 const gchar
* pStr
= gtk_frame_get_label(m_pFrame
);
7690 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
7693 virtual std::unique_ptr
<weld::Label
> weld_label_widget() const override
;
7696 class GtkInstancePaned
: public GtkInstanceContainer
, public virtual weld::Paned
7701 GtkInstancePaned(GtkPaned
* pPaned
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
7702 #if !GTK_CHECK_VERSION(4, 0, 0)
7703 : GtkInstanceContainer(GTK_CONTAINER(pPaned
), pBuilder
, bTakeOwnership
)
7705 : GtkInstanceContainer(GTK_WIDGET(pPaned
), pBuilder
, bTakeOwnership
)
7711 virtual void set_position(int nPos
) override
7713 gtk_paned_set_position(m_pPaned
, nPos
);
7716 virtual int get_position() const override
7718 return gtk_paned_get_position(m_pPaned
);
7724 static GType
immobilized_viewport_get_type();
7725 static gpointer immobilized_viewport_parent_class
;
7728 # define IMMOBILIZED_TYPE_VIEWPORT (immobilized_viewport_get_type())
7729 # define IMMOBILIZED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), IMMOBILIZED_TYPE_VIEWPORT))
7734 struct ImmobilizedViewportPrivate
7736 GtkAdjustment
*hadjustment
;
7737 GtkAdjustment
*vadjustment
;
7742 #define IMMOBILIZED_VIEWPORT_PRIVATE_DATA "ImmobilizedViewportPrivateData"
7749 PROP_HSCROLL_POLICY
,
7750 PROP_VSCROLL_POLICY
,
7754 static void viewport_set_adjustment(GtkViewport
*viewport
,
7755 GtkOrientation orientation
,
7756 GtkAdjustment
*adjustment
)
7758 ImmobilizedViewportPrivate
* priv
=
7759 static_cast<ImmobilizedViewportPrivate
*>(g_object_get_data(G_OBJECT(viewport
),
7760 IMMOBILIZED_VIEWPORT_PRIVATE_DATA
));
7763 adjustment
= gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
7765 if (orientation
== GTK_ORIENTATION_HORIZONTAL
)
7767 if (priv
->hadjustment
)
7768 g_object_unref(priv
->hadjustment
);
7769 priv
->hadjustment
= adjustment
;
7773 if (priv
->vadjustment
)
7774 g_object_unref(priv
->vadjustment
);
7775 priv
->vadjustment
= adjustment
;
7778 g_object_ref_sink(adjustment
);
7782 immobilized_viewport_set_property(GObject
* object
,
7784 const GValue
* value
,
7785 GParamSpec
* /*pspec*/)
7787 GtkViewport
*viewport
= GTK_VIEWPORT(object
);
7791 case PROP_HADJUSTMENT
:
7792 viewport_set_adjustment(viewport
, GTK_ORIENTATION_HORIZONTAL
, GTK_ADJUSTMENT(g_value_get_object(value
)));
7794 case PROP_VADJUSTMENT
:
7795 viewport_set_adjustment(viewport
, GTK_ORIENTATION_VERTICAL
, GTK_ADJUSTMENT(g_value_get_object(value
)));
7797 case PROP_HSCROLL_POLICY
:
7798 case PROP_VSCROLL_POLICY
:
7801 SAL_WARN( "vcl.gtk", "unknown property\n");
7807 immobilized_viewport_get_property(GObject
* object
,
7810 GParamSpec
* /*pspec*/)
7812 ImmobilizedViewportPrivate
* priv
=
7813 static_cast<ImmobilizedViewportPrivate
*>(g_object_get_data(object
,
7814 IMMOBILIZED_VIEWPORT_PRIVATE_DATA
));
7818 case PROP_HADJUSTMENT
:
7819 g_value_set_object(value
, priv
->hadjustment
);
7821 case PROP_VADJUSTMENT
:
7822 g_value_set_object(value
, priv
->vadjustment
);
7824 case PROP_HSCROLL_POLICY
:
7825 g_value_set_enum(value
, GTK_SCROLL_MINIMUM
);
7827 case PROP_VSCROLL_POLICY
:
7828 g_value_set_enum(value
, GTK_SCROLL_MINIMUM
);
7831 SAL_WARN( "vcl.gtk", "unknown property\n");
7836 static ImmobilizedViewportPrivate
*
7837 immobilized_viewport_new_private_data()
7839 ImmobilizedViewportPrivate
* priv
= g_slice_new0(ImmobilizedViewportPrivate
);
7840 priv
->hadjustment
= nullptr;
7841 priv
->vadjustment
= nullptr;
7846 immobilized_viewport_instance_init(GTypeInstance
*instance
, gpointer
/*klass*/)
7848 GObject
* object
= G_OBJECT(instance
);
7849 g_object_set_data(object
, IMMOBILIZED_VIEWPORT_PRIVATE_DATA
,
7850 immobilized_viewport_new_private_data());
7854 immobilized_viewport_finalize(GObject
* object
)
7856 void* priv
= g_object_get_data(object
, IMMOBILIZED_VIEWPORT_PRIVATE_DATA
);
7859 g_slice_free(ImmobilizedViewportPrivate
, priv
);
7860 g_object_set_data(object
, IMMOBILIZED_VIEWPORT_PRIVATE_DATA
, nullptr);
7862 G_OBJECT_CLASS(immobilized_viewport_parent_class
)->finalize(object
);
7865 static void immobilized_viewport_class_init(GtkWidgetClass
* klass
)
7867 immobilized_viewport_parent_class
= g_type_class_peek_parent(klass
);
7869 GObjectClass
* o_class
= G_OBJECT_CLASS(klass
);
7871 /* GObject signals */
7872 o_class
->finalize
= immobilized_viewport_finalize
;
7873 o_class
->set_property
= immobilized_viewport_set_property
;
7874 o_class
->get_property
= immobilized_viewport_get_property
;
7877 g_object_class_override_property(o_class
, PROP_HADJUSTMENT
, "hadjustment");
7878 g_object_class_override_property(o_class
, PROP_VADJUSTMENT
, "vadjustment");
7879 g_object_class_override_property(o_class
, PROP_HSCROLL_POLICY
, "hscroll-policy");
7880 g_object_class_override_property(o_class
, PROP_VSCROLL_POLICY
, "vscroll-policy");
7883 GType
immobilized_viewport_get_type()
7885 static GType type
= 0;
7890 g_type_query(gtk_viewport_get_type(), &query
);
7892 static const GTypeInfo tinfo
=
7894 static_cast<guint16
>(query
.class_size
),
7895 nullptr, /* base init */
7896 nullptr, /* base finalize */
7897 reinterpret_cast<GClassInitFunc
>(immobilized_viewport_class_init
), /* class init */
7898 nullptr, /* class finalize */
7899 nullptr, /* class data */
7900 static_cast<guint16
>(query
.instance_size
), /* instance size */
7901 0, /* nb preallocs */
7902 immobilized_viewport_instance_init
, /* instance init */
7903 nullptr /* value table */
7906 type
= g_type_register_static(GTK_TYPE_VIEWPORT
, "ImmobilizedViewport",
7907 &tinfo
, GTypeFlags(0));
7913 static VclPolicyType
GtkToVcl(GtkPolicyType eType
)
7915 VclPolicyType
eRet(VclPolicyType::NEVER
);
7918 case GTK_POLICY_ALWAYS
:
7919 eRet
= VclPolicyType::ALWAYS
;
7921 case GTK_POLICY_AUTOMATIC
:
7922 eRet
= VclPolicyType::AUTOMATIC
;
7924 case GTK_POLICY_EXTERNAL
:
7925 case GTK_POLICY_NEVER
:
7926 eRet
= VclPolicyType::NEVER
;
7932 static GtkPolicyType
VclToGtk(VclPolicyType eType
)
7934 GtkPolicyType
eRet(GTK_POLICY_ALWAYS
);
7937 case VclPolicyType::ALWAYS
:
7938 eRet
= GTK_POLICY_ALWAYS
;
7940 case VclPolicyType::AUTOMATIC
:
7941 eRet
= GTK_POLICY_AUTOMATIC
;
7943 case VclPolicyType::NEVER
:
7944 eRet
= GTK_POLICY_NEVER
;
7950 static GtkMessageType
VclToGtk(VclMessageType eType
)
7952 GtkMessageType
eRet(GTK_MESSAGE_INFO
);
7955 case VclMessageType::Info
:
7956 eRet
= GTK_MESSAGE_INFO
;
7958 case VclMessageType::Warning
:
7959 eRet
= GTK_MESSAGE_WARNING
;
7961 case VclMessageType::Question
:
7962 eRet
= GTK_MESSAGE_QUESTION
;
7964 case VclMessageType::Error
:
7965 eRet
= GTK_MESSAGE_ERROR
;
7967 case VclMessageType::Other
:
7968 eRet
= GTK_MESSAGE_OTHER
;
7974 static GtkButtonsType
VclToGtk(VclButtonsType eType
)
7976 GtkButtonsType
eRet(GTK_BUTTONS_NONE
);
7979 case VclButtonsType::NONE
:
7980 eRet
= GTK_BUTTONS_NONE
;
7982 case VclButtonsType::Ok
:
7983 eRet
= GTK_BUTTONS_OK
;
7985 case VclButtonsType::Close
:
7986 eRet
= GTK_BUTTONS_CLOSE
;
7988 case VclButtonsType::Cancel
:
7989 eRet
= GTK_BUTTONS_CANCEL
;
7991 case VclButtonsType::YesNo
:
7992 eRet
= GTK_BUTTONS_YES_NO
;
7994 case VclButtonsType::OkCancel
:
7995 eRet
= GTK_BUTTONS_OK_CANCEL
;
8001 static GtkSelectionMode
VclToGtk(SelectionMode eType
)
8003 GtkSelectionMode
eRet(GTK_SELECTION_NONE
);
8006 case SelectionMode::NONE
:
8007 eRet
= GTK_SELECTION_NONE
;
8009 case SelectionMode::Single
:
8010 eRet
= GTK_SELECTION_SINGLE
;
8012 case SelectionMode::Range
:
8013 eRet
= GTK_SELECTION_BROWSE
;
8015 case SelectionMode::Multiple
:
8016 eRet
= GTK_SELECTION_MULTIPLE
;
8024 class GtkInstanceScrolledWindow final
: public GtkInstanceContainer
, public virtual weld::ScrolledWindow
8027 GtkScrolledWindow
* m_pScrolledWindow
;
8028 GtkWidget
*m_pOrigViewport
;
8029 GtkCssProvider
* m_pScrollBarCssProvider
;
8030 GtkAdjustment
* m_pVAdjustment
;
8031 GtkAdjustment
* m_pHAdjustment
;
8032 gulong m_nVAdjustChangedSignalId
;
8033 gulong m_nHAdjustChangedSignalId
;
8035 static void signalVAdjustValueChanged(GtkAdjustment
*, gpointer widget
)
8037 GtkInstanceScrolledWindow
* pThis
= static_cast<GtkInstanceScrolledWindow
*>(widget
);
8038 SolarMutexGuard aGuard
;
8039 pThis
->signal_vadjustment_changed();
8042 static void signalHAdjustValueChanged(GtkAdjustment
*, gpointer widget
)
8044 GtkInstanceScrolledWindow
* pThis
= static_cast<GtkInstanceScrolledWindow
*>(widget
);
8045 SolarMutexGuard aGuard
;
8046 pThis
->signal_hadjustment_changed();
8050 GtkInstanceScrolledWindow(GtkScrolledWindow
* pScrolledWindow
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
, bool bUserManagedScrolling
)
8051 #if !GTK_CHECK_VERSION(4, 0, 0)
8052 : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow
), pBuilder
, bTakeOwnership
)
8054 : GtkInstanceContainer(GTK_WIDGET(pScrolledWindow
), pBuilder
, bTakeOwnership
)
8056 , m_pScrolledWindow(pScrolledWindow
)
8057 , m_pOrigViewport(nullptr)
8058 , m_pScrollBarCssProvider(nullptr)
8059 , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow
))
8060 , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow
))
8061 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment
, "value-changed", G_CALLBACK(signalVAdjustValueChanged
), this))
8062 , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment
, "value-changed", G_CALLBACK(signalHAdjustValueChanged
), this))
8064 if (bUserManagedScrolling
)
8065 set_user_managed_scrolling();
8068 void set_user_managed_scrolling()
8070 disable_notify_events();
8071 //remove the original viewport and replace it with our bodged one which
8072 //doesn't do any scrolling and expects its child to figure it out somehow
8073 assert(!m_pOrigViewport
);
8074 #if GTK_CHECK_VERSION(4, 0, 0)
8075 GtkWidget
*pViewport
= gtk_scrolled_window_get_child(m_pScrolledWindow
);
8077 GtkWidget
*pViewport
= gtk_bin_get_child(GTK_BIN(m_pScrolledWindow
));
8079 assert(GTK_IS_VIEWPORT(pViewport
));
8080 #if GTK_CHECK_VERSION(4, 0, 0)
8081 GtkWidget
*pChild
= gtk_viewport_get_child(GTK_VIEWPORT(pViewport
));
8083 GtkWidget
*pChild
= gtk_bin_get_child(GTK_BIN(pViewport
));
8085 g_object_ref(pChild
);
8086 #if GTK_CHECK_VERSION(4, 0, 0)
8087 gtk_viewport_set_child(GTK_VIEWPORT(pViewport
), nullptr);
8089 gtk_container_remove(GTK_CONTAINER(pViewport
), pChild
);
8091 g_object_ref(pViewport
);
8092 #if GTK_CHECK_VERSION(4, 0, 0)
8093 gtk_scrolled_window_set_child(m_pScrolledWindow
, nullptr);
8095 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow
), pViewport
);
8097 GtkWidget
* pNewViewport
= GTK_WIDGET(g_object_new(immobilized_viewport_get_type(), nullptr));
8098 gtk_widget_show(pNewViewport
);
8099 #if GTK_CHECK_VERSION(4, 0, 0)
8100 gtk_scrolled_window_set_child(m_pScrolledWindow
, pNewViewport
);
8101 gtk_viewport_set_child(GTK_VIEWPORT(pNewViewport
), pChild
);
8103 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow
), pNewViewport
);
8104 gtk_container_add(GTK_CONTAINER(pNewViewport
), pChild
);
8106 g_object_unref(pChild
);
8107 m_pOrigViewport
= pViewport
;
8108 enable_notify_events();
8111 virtual void hadjustment_configure(int value
, int lower
, int upper
,
8112 int step_increment
, int page_increment
,
8113 int page_size
) override
8115 disable_notify_events();
8117 value
= upper
- (value
- lower
+ page_size
);
8118 gtk_adjustment_configure(m_pHAdjustment
, value
, lower
, upper
, step_increment
, page_increment
, page_size
);
8119 enable_notify_events();
8122 virtual int hadjustment_get_value() const override
8124 int value
= gtk_adjustment_get_value(m_pHAdjustment
);
8128 int upper
= gtk_adjustment_get_upper(m_pHAdjustment
);
8129 int lower
= gtk_adjustment_get_lower(m_pHAdjustment
);
8130 int page_size
= gtk_adjustment_get_page_size(m_pHAdjustment
);
8131 value
= lower
+ (upper
- value
- page_size
);
8137 virtual void hadjustment_set_value(int value
) override
8139 disable_notify_events();
8143 int upper
= gtk_adjustment_get_upper(m_pHAdjustment
);
8144 int lower
= gtk_adjustment_get_lower(m_pHAdjustment
);
8145 int page_size
= gtk_adjustment_get_page_size(m_pHAdjustment
);
8146 value
= upper
- (value
- lower
+ page_size
);
8149 gtk_adjustment_set_value(m_pHAdjustment
, value
);
8150 enable_notify_events();
8153 virtual int hadjustment_get_upper() const override
8155 return gtk_adjustment_get_upper(m_pHAdjustment
);
8158 virtual void hadjustment_set_upper(int upper
) override
8160 disable_notify_events();
8161 gtk_adjustment_set_upper(m_pHAdjustment
, upper
);
8162 enable_notify_events();
8165 virtual int hadjustment_get_page_size() const override
8167 return gtk_adjustment_get_page_size(m_pHAdjustment
);
8170 virtual void hadjustment_set_page_size(int size
) override
8172 gtk_adjustment_set_page_size(m_pHAdjustment
, size
);
8175 virtual void hadjustment_set_page_increment(int size
) override
8177 gtk_adjustment_set_page_increment(m_pHAdjustment
, size
);
8180 virtual void hadjustment_set_step_increment(int size
) override
8182 gtk_adjustment_set_step_increment(m_pHAdjustment
, size
);
8185 virtual void set_hpolicy(VclPolicyType eHPolicy
) override
8187 GtkPolicyType eGtkVPolicy
;
8188 gtk_scrolled_window_get_policy(m_pScrolledWindow
, nullptr, &eGtkVPolicy
);
8189 gtk_scrolled_window_set_policy(m_pScrolledWindow
, VclToGtk(eHPolicy
), eGtkVPolicy
);
8192 virtual VclPolicyType
get_hpolicy() const override
8194 GtkPolicyType eGtkHPolicy
;
8195 gtk_scrolled_window_get_policy(m_pScrolledWindow
, &eGtkHPolicy
, nullptr);
8196 return GtkToVcl(eGtkHPolicy
);
8199 virtual void vadjustment_configure(int value
, int lower
, int upper
,
8200 int step_increment
, int page_increment
,
8201 int page_size
) override
8203 disable_notify_events();
8204 gtk_adjustment_configure(m_pVAdjustment
, value
, lower
, upper
, step_increment
, page_increment
, page_size
);
8205 enable_notify_events();
8208 virtual int vadjustment_get_value() const override
8210 return gtk_adjustment_get_value(m_pVAdjustment
);
8213 virtual void vadjustment_set_value(int value
) override
8215 disable_notify_events();
8216 gtk_adjustment_set_value(m_pVAdjustment
, value
);
8217 enable_notify_events();
8220 virtual int vadjustment_get_upper() const override
8222 return gtk_adjustment_get_upper(m_pVAdjustment
);
8225 virtual void vadjustment_set_upper(int upper
) override
8227 disable_notify_events();
8228 gtk_adjustment_set_upper(m_pVAdjustment
, upper
);
8229 enable_notify_events();
8232 virtual int vadjustment_get_lower() const override
8234 return gtk_adjustment_get_lower(m_pVAdjustment
);
8237 virtual void vadjustment_set_lower(int lower
) override
8239 disable_notify_events();
8240 gtk_adjustment_set_lower(m_pVAdjustment
, lower
);
8241 enable_notify_events();
8244 virtual int vadjustment_get_page_size() const override
8246 return gtk_adjustment_get_page_size(m_pVAdjustment
);
8249 virtual void vadjustment_set_page_size(int size
) override
8251 gtk_adjustment_set_page_size(m_pVAdjustment
, size
);
8254 virtual void vadjustment_set_page_increment(int size
) override
8256 gtk_adjustment_set_page_increment(m_pVAdjustment
, size
);
8259 virtual void vadjustment_set_step_increment(int size
) override
8261 gtk_adjustment_set_step_increment(m_pVAdjustment
, size
);
8264 virtual void set_vpolicy(VclPolicyType eVPolicy
) override
8266 GtkPolicyType eGtkHPolicy
;
8267 gtk_scrolled_window_get_policy(m_pScrolledWindow
, &eGtkHPolicy
, nullptr);
8268 gtk_scrolled_window_set_policy(m_pScrolledWindow
, eGtkHPolicy
, VclToGtk(eVPolicy
));
8271 virtual VclPolicyType
get_vpolicy() const override
8273 GtkPolicyType eGtkVPolicy
;
8274 gtk_scrolled_window_get_policy(m_pScrolledWindow
, nullptr, &eGtkVPolicy
);
8275 return GtkToVcl(eGtkVPolicy
);
8278 virtual int get_scroll_thickness() const override
8280 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow
))
8282 return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow
));
8285 virtual void set_scroll_thickness(int nThickness
) override
8287 GtkWidget
*pHorzBar
= gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow
);
8288 GtkWidget
*pVertBar
= gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow
);
8289 gtk_widget_set_size_request(pHorzBar
, -1, nThickness
);
8290 gtk_widget_set_size_request(pVertBar
, nThickness
, -1);
8293 virtual void disable_notify_events() override
8295 g_signal_handler_block(m_pVAdjustment
, m_nVAdjustChangedSignalId
);
8296 g_signal_handler_block(m_pHAdjustment
, m_nHAdjustChangedSignalId
);
8297 GtkInstanceContainer::disable_notify_events();
8300 virtual void enable_notify_events() override
8302 GtkInstanceContainer::enable_notify_events();
8303 g_signal_handler_unblock(m_pVAdjustment
, m_nVAdjustChangedSignalId
);
8304 g_signal_handler_unblock(m_pHAdjustment
, m_nHAdjustChangedSignalId
);
8307 virtual void customize_scrollbars(const Color
& rBackgroundColor
,
8308 const Color
& rShadowColor
,
8309 const Color
& rFaceColor
) override
8311 GtkWidget
*pHorzBar
= gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow
);
8312 GtkWidget
*pVertBar
= gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow
);
8313 GtkStyleContext
*pHorzContext
= gtk_widget_get_style_context(pHorzBar
);
8314 GtkStyleContext
*pVertContext
= gtk_widget_get_style_context(pVertBar
);
8315 if (m_pScrollBarCssProvider
)
8317 gtk_style_context_remove_provider(pHorzContext
, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider
));
8318 gtk_style_context_remove_provider(pVertContext
, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider
));
8321 m_pScrollBarCssProvider
= gtk_css_provider_new();
8322 // intentionally 'trough' a long, narrow open container.
8323 OUString aBuffer
= "scrollbar contents trough { background-color: #" + rBackgroundColor
.AsRGBHexString() + "; } "
8324 "scrollbar contents trough slider { background-color: #" + rShadowColor
.AsRGBHexString() + "; } "
8325 "scrollbar contents button { background-color: #" + rFaceColor
.AsRGBHexString() + "; } "
8326 "scrollbar contents button { color: #000000; } "
8327 "scrollbar contents button:disabled { color: #7f7f7f; }";
8328 OString aResult
= OUStringToOString(aBuffer
, RTL_TEXTENCODING_UTF8
);
8329 css_provider_load_from_data(m_pScrollBarCssProvider
, aResult
.getStr(), aResult
.getLength());
8331 gtk_style_context_add_provider(pHorzContext
, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider
),
8332 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8333 gtk_style_context_add_provider(pVertContext
, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider
),
8334 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8337 virtual ~GtkInstanceScrolledWindow() override
8339 // we use GtkInstanceContainer::[disable|enable]_notify_events later on
8340 // to avoid touching these removed handlers
8341 g_signal_handler_disconnect(m_pVAdjustment
, m_nVAdjustChangedSignalId
);
8342 g_signal_handler_disconnect(m_pHAdjustment
, m_nHAdjustChangedSignalId
);
8344 if (m_pScrollBarCssProvider
)
8346 GtkStyleContext
*pHorzContext
= gtk_widget_get_style_context(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow
));
8347 GtkStyleContext
*pVertContext
= gtk_widget_get_style_context(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow
));
8348 gtk_style_context_remove_provider(pHorzContext
, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider
));
8349 gtk_style_context_remove_provider(pVertContext
, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider
));
8350 m_pScrollBarCssProvider
= nullptr;
8353 //put it back the way it was
8354 if (!m_pOrigViewport
)
8357 GtkInstanceContainer::disable_notify_events();
8359 // force in new adjustment to drop the built-in handlers on value-changed
8360 // which are getting called eventually by the gtk_container_add call
8361 // and which access the scrolled window indicators which, in the case
8362 // of user-managed scrolling windows in toolbar popups during popdown
8363 // are nullptr causing crashes when the scrolling windows is not at its
8364 // initial 0,0 position
8365 GtkAdjustment
*pVAdjustment
= gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
8366 gtk_scrolled_window_set_vadjustment(m_pScrolledWindow
, pVAdjustment
);
8367 GtkAdjustment
*pHAdjustment
= gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
8368 gtk_scrolled_window_set_hadjustment(m_pScrolledWindow
, pHAdjustment
);
8370 #if GTK_CHECK_VERSION(4, 0, 0)
8371 GtkWidget
*pViewport
= gtk_scrolled_window_get_child(m_pScrolledWindow
);
8373 GtkWidget
*pViewport
= gtk_bin_get_child(GTK_BIN(m_pScrolledWindow
));
8375 assert(IMMOBILIZED_IS_VIEWPORT(pViewport
));
8376 #if GTK_CHECK_VERSION(4, 0, 0)
8377 GtkWidget
*pChild
= gtk_viewport_get_child(GTK_VIEWPORT(pViewport
));
8379 GtkWidget
*pChild
= gtk_bin_get_child(GTK_BIN(pViewport
));
8381 g_object_ref(pChild
);
8382 #if GTK_CHECK_VERSION(4, 0, 0)
8383 gtk_viewport_set_child(GTK_VIEWPORT(pViewport
), nullptr);
8385 gtk_container_remove(GTK_CONTAINER(pViewport
), pChild
);
8387 g_object_ref(pViewport
);
8388 #if GTK_CHECK_VERSION(4, 0, 0)
8389 gtk_scrolled_window_set_child(m_pScrolledWindow
, nullptr);
8391 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow
), pViewport
);
8394 #if GTK_CHECK_VERSION(4, 0, 0)
8395 gtk_scrolled_window_set_child(m_pScrolledWindow
, m_pOrigViewport
);
8397 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow
), m_pOrigViewport
);
8399 // coverity[freed_arg : FALSE] - this does not free m_pOrigViewport, it is reffed by m_pScrolledWindow
8400 g_object_unref(m_pOrigViewport
);
8401 #if GTK_CHECK_VERSION(4, 0, 0)
8402 gtk_viewport_set_child(GTK_VIEWPORT(m_pOrigViewport
), pChild
);
8404 gtk_container_add(GTK_CONTAINER(m_pOrigViewport
), pChild
);
8406 g_object_unref(pChild
);
8407 #if !GTK_CHECK_VERSION(4, 0, 0)
8408 gtk_widget_destroy(pViewport
);
8410 g_object_unref(pViewport
);
8411 m_pOrigViewport
= nullptr;
8412 GtkInstanceContainer::enable_notify_events();
8420 class GtkInstanceNotebook
: public GtkInstanceWidget
, public virtual weld::Notebook
8423 GtkNotebook
* m_pNotebook
;
8424 GtkBox
* m_pOverFlowBox
;
8425 GtkNotebook
* m_pOverFlowNotebook
;
8426 gulong m_nSwitchPageSignalId
;
8427 gulong m_nOverFlowSwitchPageSignalId
;
8428 #if GTK_CHECK_VERSION(4, 0, 0)
8429 NotifyingLayout
* m_pLayout
;
8431 gulong m_nNotebookSizeAllocateSignalId
;
8432 gulong m_nFocusSignalId
;
8434 gulong m_nChangeCurrentPageId
;
8435 guint m_nLaunchSplitTimeoutId
;
8436 bool m_bOverFlowBoxActive
;
8437 bool m_bOverFlowBoxIsStart
;
8438 bool m_bInternalPageChange
;
8439 int m_nStartTabCount
;
8441 mutable std::vector
<std::unique_ptr
<GtkInstanceContainer
>> m_aPages
;
8443 static void signalSwitchPage(GtkNotebook
*, GtkWidget
*, guint nNewPage
, gpointer widget
)
8445 GtkInstanceNotebook
* pThis
= static_cast<GtkInstanceNotebook
*>(widget
);
8446 SolarMutexGuard aGuard
;
8447 pThis
->signal_switch_page(nNewPage
);
8450 static gboolean
launch_overflow_switch_page(GtkInstanceNotebook
* pThis
)
8452 SolarMutexGuard aGuard
;
8453 pThis
->signal_overflow_switch_page();
8457 static void signalOverFlowSwitchPage(GtkNotebook
*, GtkWidget
*, guint
, gpointer widget
)
8459 g_timeout_add_full(G_PRIORITY_HIGH_IDLE
, 0, reinterpret_cast<GSourceFunc
>(launch_overflow_switch_page
), widget
, nullptr);
8462 void signal_switch_page(int nNewPage
)
8464 if (m_bOverFlowBoxIsStart
)
8466 auto nOverFlowLen
= m_bOverFlowBoxActive
? gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1 : 0;
8467 // add count of overflow pages, minus the extra tab
8468 nNewPage
+= nOverFlowLen
;
8471 bool bAllow
= m_bInternalPageChange
|| !m_aLeavePageHdl
.IsSet() || m_aLeavePageHdl
.Call(get_current_page_ident());
8474 g_signal_stop_emission_by_name(m_pNotebook
, "switch-page");
8477 if (m_bOverFlowBoxActive
)
8478 gtk_notebook_set_current_page(m_pOverFlowNotebook
, gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1);
8479 OString
sNewIdent(get_page_ident(nNewPage
));
8480 if (!m_bInternalPageChange
)
8481 m_aEnterPageHdl
.Call(sNewIdent
);
8484 void unsplit_notebooks()
8486 int nOverFlowPages
= gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1;
8487 int nMainPages
= gtk_notebook_get_n_pages(m_pNotebook
);
8489 if (!m_bOverFlowBoxIsStart
)
8490 nPageIndex
+= nMainPages
;
8492 // take the overflow pages, and put them back at the end of the normal one
8494 while (nOverFlowPages
)
8496 OString
sIdent(get_page_ident(m_pOverFlowNotebook
, 0));
8497 OUString
sLabel(get_tab_label_text(m_pOverFlowNotebook
, 0));
8498 remove_page(m_pOverFlowNotebook
, sIdent
);
8500 GtkWidget
* pPage
= m_aPages
[nPageIndex
]->getWidget();
8501 insert_page(m_pNotebook
, sIdent
, sLabel
, pPage
, -1);
8503 GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(m_pNotebook
,
8504 gtk_notebook_get_nth_page(m_pNotebook
, i
));
8505 gtk_widget_set_hexpand(pTabWidget
, true);
8511 // remove the dangling placeholder tab page
8512 remove_page(m_pOverFlowNotebook
, "useless");
8515 // a tab has been selected on the overflow notebook
8516 void signal_overflow_switch_page()
8518 int nNewPage
= gtk_notebook_get_current_page(m_pOverFlowNotebook
);
8519 int nOverFlowPages
= gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1;
8520 if (nNewPage
== nOverFlowPages
)
8522 // the useless tab which is there because there has to be an active tab
8526 // check if we are allowed leave before attempting to resplit the notebooks
8527 bool bAllow
= !m_aLeavePageHdl
.IsSet() || m_aLeavePageHdl
.Call(get_current_page_ident());
8531 disable_notify_events();
8533 // take the overflow pages, and put them back at the end of the normal one
8534 unsplit_notebooks();
8536 // now redo the split, the pages will be split the other way around this time
8537 std::swap(m_nStartTabCount
, m_nEndTabCount
);
8540 // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
8541 gtk_notebook_set_current_page(m_pNotebook
, nNewPage
);
8543 enable_notify_events();
8545 // trigger main notebook switch-page callback
8546 OString
sNewIdent(get_page_ident(m_pNotebook
, nNewPage
));
8547 m_aEnterPageHdl
.Call(sNewIdent
);
8550 static OString
get_page_ident(GtkNotebook
*pNotebook
, guint nPage
)
8552 const GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(pNotebook
, gtk_notebook_get_nth_page(pNotebook
, nPage
));
8553 return ::get_buildable_id(GTK_BUILDABLE(pTabWidget
));
8556 static gint
get_page_number(GtkNotebook
*pNotebook
, std::string_view ident
)
8558 gint nPages
= gtk_notebook_get_n_pages(pNotebook
);
8559 for (gint i
= 0; i
< nPages
; ++i
)
8561 const GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(pNotebook
, gtk_notebook_get_nth_page(pNotebook
, i
));
8562 OString sBuildableName
= ::get_buildable_id(GTK_BUILDABLE(pTabWidget
));
8563 if (sBuildableName
== ident
)
8569 int remove_page(GtkNotebook
*pNotebook
, std::string_view ident
)
8571 disable_notify_events();
8572 int nPageNumber
= get_page_number(pNotebook
, ident
);
8573 gtk_notebook_remove_page(pNotebook
, nPageNumber
);
8574 enable_notify_events();
8578 static OUString
get_tab_label_text(GtkNotebook
*pNotebook
, guint nPage
)
8580 const gchar
* pStr
= gtk_notebook_get_tab_label_text(pNotebook
, gtk_notebook_get_nth_page(pNotebook
, nPage
));
8581 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
8584 static void set_tab_label_text(GtkNotebook
*pNotebook
, guint nPage
, const OUString
& rText
)
8586 OString
sUtf8(rText
.toUtf8());
8588 GtkWidget
* pPage
= gtk_notebook_get_nth_page(pNotebook
, nPage
);
8590 // tdf#128241 if there's already a label here, reuse it so the buildable
8591 // name remains the same, gtk_notebook_set_tab_label_text will replace
8592 // the label widget with a new one
8593 GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(pNotebook
, pPage
);
8594 if (pTabWidget
&& GTK_IS_LABEL(pTabWidget
))
8596 gtk_label_set_label(GTK_LABEL(pTabWidget
), sUtf8
.getStr());
8600 gtk_notebook_set_tab_label_text(pNotebook
, pPage
, sUtf8
.getStr());
8603 void append_useless_page(GtkNotebook
*pNotebook
)
8605 disable_notify_events();
8607 GtkWidget
*pTabWidget
= gtk_fixed_new();
8608 ::set_buildable_id(GTK_BUILDABLE(pTabWidget
), "useless");
8610 GtkWidget
*pChild
= gtk_grid_new();
8611 gtk_notebook_append_page(pNotebook
, pChild
, pTabWidget
);
8612 gtk_widget_show(pChild
);
8613 gtk_widget_show(pTabWidget
);
8615 enable_notify_events();
8618 void insert_page(GtkNotebook
*pNotebook
, const OString
& rIdent
, const OUString
& rLabel
, GtkWidget
*pChild
, int nPos
)
8620 disable_notify_events();
8622 GtkWidget
*pTabWidget
= gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel
).getStr());
8623 ::set_buildable_id(GTK_BUILDABLE(pTabWidget
), rIdent
);
8624 gtk_notebook_insert_page(pNotebook
, pChild
, pTabWidget
, nPos
);
8625 gtk_widget_show(pChild
);
8626 gtk_widget_show(pTabWidget
);
8630 unsigned int nPageIndex
= static_cast<unsigned int>(nPos
);
8631 if (nPageIndex
< m_aPages
.size())
8632 m_aPages
.insert(m_aPages
.begin() + nPageIndex
, nullptr);
8635 enable_notify_events();
8638 void make_overflow_boxes()
8640 m_pOverFlowBox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0));
8641 GtkWidget
* pParent
= gtk_widget_get_parent(GTK_WIDGET(m_pNotebook
));
8642 container_add(pParent
, GTK_WIDGET(m_pOverFlowBox
));
8643 #if GTK_CHECK_VERSION(4, 0, 0)
8644 gtk_box_append(m_pOverFlowBox
, GTK_WIDGET(m_pOverFlowNotebook
));
8646 gtk_box_pack_start(m_pOverFlowBox
, GTK_WIDGET(m_pOverFlowNotebook
), false, false, 0);
8648 g_object_ref(m_pNotebook
);
8649 container_remove(pParent
, GTK_WIDGET(m_pNotebook
));
8650 #if GTK_CHECK_VERSION(4, 0, 0)
8651 gtk_box_append(m_pOverFlowBox
, GTK_WIDGET(m_pNotebook
));
8653 gtk_box_pack_start(m_pOverFlowBox
, GTK_WIDGET(m_pNotebook
), true, true, 0);
8655 // coverity[freed_arg : FALSE] - this does not free m_pNotebook , it is reffed by pParent
8656 g_object_unref(m_pNotebook
);
8657 gtk_widget_show(GTK_WIDGET(m_pOverFlowBox
));
8660 void split_notebooks()
8662 // get the original preferred size for the notebook, the sane width
8663 // expected here depends on the notebooks all initially having
8664 // scrollable tabs enabled
8665 GtkAllocation alloc
;
8666 gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook
), &alloc
);
8668 // toggle the direction of the split since the last time
8669 m_bOverFlowBoxIsStart
= !m_bOverFlowBoxIsStart
;
8670 if (!m_pOverFlowBox
)
8671 make_overflow_boxes();
8673 // don't scroll the tabs anymore
8674 // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
8675 gtk_notebook_set_scrollable(m_pNotebook
, false);
8677 #if !GTK_CHECK_VERSION(4, 0, 0)
8678 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook
));
8679 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook
));
8681 g_object_freeze_notify(G_OBJECT(m_pNotebook
));
8682 g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook
));
8685 gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook
));
8689 GtkRequisition size1
, size2
;
8691 if (!m_nStartTabCount
&& !m_nEndTabCount
)
8693 nPages
= gtk_notebook_get_n_pages(m_pNotebook
);
8695 std::vector
<int> aLabelWidths
;
8696 //move tabs to the overflow notebook
8697 for (int i
= 0; i
< nPages
; ++i
)
8699 OUString
sLabel(get_tab_label_text(m_pNotebook
, i
));
8700 aLabelWidths
.push_back(get_pixel_size(sLabel
).Width());
8702 int row_width
= std::accumulate(aLabelWidths
.begin(), aLabelWidths
.end(), 0) / 2;
8704 for (int i
= 0; i
< nPages
; ++i
)
8706 count
+= aLabelWidths
[i
];
8707 if (count
>= row_width
)
8709 m_nStartTabCount
= i
;
8714 m_nEndTabCount
= nPages
- m_nStartTabCount
;
8717 //move the tabs to the overflow notebook
8719 int nOverFlowPages
= m_nStartTabCount
;
8720 while (nOverFlowPages
)
8722 OString
sIdent(get_page_ident(m_pNotebook
, 0));
8723 OUString
sLabel(get_tab_label_text(m_pNotebook
, 0));
8724 remove_page(m_pNotebook
, sIdent
);
8725 insert_page(m_pOverFlowNotebook
, sIdent
, sLabel
, gtk_grid_new(), -1);
8726 GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(m_pOverFlowNotebook
,
8727 gtk_notebook_get_nth_page(m_pOverFlowNotebook
, i
));
8728 gtk_widget_set_hexpand(pTabWidget
, true);
8734 for (i
= 0; i
< m_nEndTabCount
; ++i
)
8736 GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(m_pNotebook
,
8737 gtk_notebook_get_nth_page(m_pNotebook
, i
));
8738 gtk_widget_set_hexpand(pTabWidget
, true);
8741 // have to have some tab as the active tab of the overflow notebook
8742 append_useless_page(m_pOverFlowNotebook
);
8743 gtk_notebook_set_current_page(m_pOverFlowNotebook
, gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1);
8744 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook
)))
8745 gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook
));
8747 // add this temporarily to the normal notebook to measure how wide
8748 // the row would be if switched to the other notebook
8749 append_useless_page(m_pNotebook
);
8751 gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook
), nullptr, &size1
);
8752 gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook
), nullptr, &size2
);
8754 auto nWidth
= std::max(size1
.width
, size2
.width
);
8755 gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook
), nWidth
, alloc
.height
);
8756 gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook
), nWidth
, -1);
8758 // remove it once we've measured it
8759 remove_page(m_pNotebook
, "useless");
8761 #if !GTK_CHECK_VERSION(4, 0, 0)
8762 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook
));
8763 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook
));
8765 g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook
));
8766 g_object_thaw_notify(G_OBJECT(m_pNotebook
));
8769 m_bOverFlowBoxActive
= true;
8772 static gboolean
launch_split_notebooks(GtkInstanceNotebook
* pThis
)
8774 int nCurrentPage
= pThis
->get_current_page();
8775 pThis
->split_notebooks();
8776 pThis
->set_current_page(nCurrentPage
);
8777 pThis
->m_nLaunchSplitTimeoutId
= 0;
8782 // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
8783 // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
8784 // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
8785 // tabs in a single row when they would fit
8786 void signal_notebook_size_allocate()
8788 if (m_bOverFlowBoxActive
|| m_nLaunchSplitTimeoutId
)
8790 disable_notify_events();
8791 gint nPages
= gtk_notebook_get_n_pages(m_pNotebook
);
8792 if (nPages
> 6 && gtk_notebook_get_tab_pos(m_pNotebook
) == GTK_POS_TOP
)
8794 for (gint i
= 0; i
< nPages
; ++i
)
8796 GtkWidget
* pTabWidget
= gtk_notebook_get_tab_label(m_pNotebook
, gtk_notebook_get_nth_page(m_pNotebook
, i
));
8797 #if GTK_CHECK_VERSION(4, 0, 0)
8798 bool bTabVisible
= gtk_widget_get_child_visible(gtk_widget_get_parent(pTabWidget
));
8800 bool bTabVisible
= gtk_widget_get_child_visible(pTabWidget
);
8804 m_nLaunchSplitTimeoutId
= g_timeout_add_full(G_PRIORITY_HIGH_IDLE
, 0, reinterpret_cast<GSourceFunc
>(launch_split_notebooks
), this, nullptr);
8809 enable_notify_events();
8812 #if GTK_CHECK_VERSION(4, 0, 0)
8813 DECL_LINK(SizeAllocateHdl
, void*, void);
8815 static void signalSizeAllocate(GtkWidget
*, GdkRectangle
*, gpointer widget
)
8817 GtkInstanceNotebook
* pThis
= static_cast<GtkInstanceNotebook
*>(widget
);
8818 pThis
->signal_notebook_size_allocate();
8822 bool signal_focus(GtkDirectionType direction
)
8824 if (!m_bOverFlowBoxActive
)
8827 int nPage
= gtk_notebook_get_current_page(m_pNotebook
);
8828 if (direction
== GTK_DIR_LEFT
&& nPage
== 0)
8830 auto nOverFlowLen
= gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1;
8831 gtk_notebook_set_current_page(m_pOverFlowNotebook
, nOverFlowLen
- 1);
8834 else if (direction
== GTK_DIR_RIGHT
&& nPage
== gtk_notebook_get_n_pages(m_pNotebook
) - 1)
8836 gtk_notebook_set_current_page(m_pOverFlowNotebook
, 0);
8843 #if !GTK_CHECK_VERSION(4, 0, 0)
8844 static gboolean
signalFocus(GtkNotebook
* notebook
, GtkDirectionType direction
, gpointer widget
)
8846 // if the notebook widget itself has focus
8847 if (gtk_widget_is_focus(GTK_WIDGET(notebook
)))
8849 GtkInstanceNotebook
* pThis
= static_cast<GtkInstanceNotebook
*>(widget
);
8850 return pThis
->signal_focus(direction
);
8856 // ctrl + page_up/ page_down
8857 bool signal_change_current_page(gint arg1
)
8859 bool bHandled
= signal_focus(arg1
< 0 ? GTK_DIR_LEFT
: GTK_DIR_RIGHT
);
8861 g_signal_stop_emission_by_name(m_pNotebook
, "change-current-page");
8865 static gboolean
signalChangeCurrentPage(GtkNotebook
*, gint arg1
, gpointer widget
)
8869 GtkInstanceNotebook
* pThis
= static_cast<GtkInstanceNotebook
*>(widget
);
8870 return pThis
->signal_change_current_page(arg1
);
8874 GtkInstanceNotebook(GtkNotebook
* pNotebook
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
8875 : GtkInstanceWidget(GTK_WIDGET(pNotebook
), pBuilder
, bTakeOwnership
)
8876 , m_pNotebook(pNotebook
)
8877 , m_pOverFlowBox(nullptr)
8878 , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
8879 , m_nSwitchPageSignalId(g_signal_connect(pNotebook
, "switch-page", G_CALLBACK(signalSwitchPage
), this))
8880 , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook
, "switch-page", G_CALLBACK(signalOverFlowSwitchPage
), this))
8881 #if GTK_CHECK_VERSION(4, 0, 0)
8882 , m_pLayout(nullptr)
8884 , m_nNotebookSizeAllocateSignalId(0)
8885 , m_nFocusSignalId(g_signal_connect(pNotebook
, "focus", G_CALLBACK(signalFocus
), this))
8887 , m_nChangeCurrentPageId(g_signal_connect(pNotebook
, "change-current-page", G_CALLBACK(signalChangeCurrentPage
), this))
8888 , m_nLaunchSplitTimeoutId(0)
8889 , m_bOverFlowBoxActive(false)
8890 , m_bOverFlowBoxIsStart(false)
8891 , m_bInternalPageChange(false)
8892 , m_nStartTabCount(0)
8895 #if !GTK_CHECK_VERSION(4, 0, 0)
8896 gtk_widget_add_events(GTK_WIDGET(pNotebook
), GDK_SCROLL_MASK
);
8898 gint nPages
= gtk_notebook_get_n_pages(m_pNotebook
);
8901 #if !GTK_CHECK_VERSION(4, 0, 0)
8902 m_nNotebookSizeAllocateSignalId
= g_signal_connect_after(pNotebook
, "size-allocate", G_CALLBACK(signalSizeAllocate
), this);
8904 m_pLayout
= NOTIFYING_LAYOUT(g_object_new(notifying_layout_get_type(), nullptr));
8905 notifying_layout_start_watch(m_pLayout
, GTK_WIDGET(pNotebook
), LINK(this, GtkInstanceNotebook
, SizeAllocateHdl
));
8908 gtk_notebook_set_show_border(m_pOverFlowNotebook
, false);
8910 // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
8911 // the unwanted tab into invisibility via the 'overflow' class themed by global CreateStyleProvider
8912 GtkStyleContext
*pNotebookContext
= gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook
));
8913 gtk_style_context_add_class(pNotebookContext
, "overflow");
8916 virtual int get_current_page() const override
8918 int nPage
= gtk_notebook_get_current_page(m_pNotebook
);
8921 if (m_bOverFlowBoxIsStart
)
8923 auto nOverFlowLen
= m_bOverFlowBoxActive
? gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1 : 0;
8924 // add count of overflow pages, minus the extra tab
8925 nPage
+= nOverFlowLen
;
8930 virtual OString
get_page_ident(int nPage
) const override
8932 auto nMainLen
= gtk_notebook_get_n_pages(m_pNotebook
);
8933 auto nOverFlowLen
= m_bOverFlowBoxActive
? gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1 : 0;
8934 if (m_bOverFlowBoxIsStart
)
8936 if (nPage
< nOverFlowLen
)
8937 return get_page_ident(m_pOverFlowNotebook
, nPage
);
8938 nPage
-= nOverFlowLen
;
8939 return get_page_ident(m_pNotebook
, nPage
);
8943 if (nPage
< nMainLen
)
8944 return get_page_ident(m_pNotebook
, nPage
);
8946 return get_page_ident(m_pOverFlowNotebook
, nPage
);
8950 virtual OString
get_current_page_ident() const override
8952 const int nPage
= get_current_page();
8953 return nPage
!= -1 ? get_page_ident(nPage
) : OString();
8956 virtual int get_page_index(const OString
& rIdent
) const override
8958 auto nMainIndex
= get_page_number(m_pNotebook
, rIdent
);
8959 auto nOverFlowIndex
= get_page_number(m_pOverFlowNotebook
, rIdent
);
8961 if (nMainIndex
== -1 && nOverFlowIndex
== -1)
8964 if (m_bOverFlowBoxIsStart
)
8966 if (nOverFlowIndex
!= -1)
8967 return nOverFlowIndex
;
8970 auto nOverFlowLen
= m_bOverFlowBoxActive
? gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1 : 0;
8971 return nMainIndex
+ nOverFlowLen
;
8976 if (nMainIndex
!= -1)
8980 auto nMainLen
= gtk_notebook_get_n_pages(m_pNotebook
);
8981 return nOverFlowIndex
+ nMainLen
;
8986 virtual weld::Container
* get_page(const OString
& rIdent
) const override
8988 int nPage
= get_page_index(rIdent
);
8993 if (m_bOverFlowBoxIsStart
)
8995 auto nOverFlowLen
= m_bOverFlowBoxActive
? gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1 : 0;
8996 if (nPage
< nOverFlowLen
)
8997 pChild
= gtk_notebook_get_nth_page(m_pOverFlowNotebook
, nPage
);
9000 nPage
-= nOverFlowLen
;
9001 pChild
= gtk_notebook_get_nth_page(m_pNotebook
, nPage
);
9006 auto nMainLen
= gtk_notebook_get_n_pages(m_pNotebook
);
9007 if (nPage
< nMainLen
)
9008 pChild
= gtk_notebook_get_nth_page(m_pNotebook
, nPage
);
9012 pChild
= gtk_notebook_get_nth_page(m_pOverFlowNotebook
, nPage
);
9016 unsigned int nPageIndex
= static_cast<unsigned int>(nPage
);
9017 if (m_aPages
.size() < nPageIndex
+ 1)
9018 m_aPages
.resize(nPageIndex
+ 1);
9019 #if !GTK_CHECK_VERSION(4, 0, 0)
9020 if (!m_aPages
[nPageIndex
])
9021 m_aPages
[nPageIndex
].reset(new GtkInstanceContainer(GTK_CONTAINER(pChild
), m_pBuilder
, false));
9023 if (!m_aPages
[nPageIndex
])
9024 m_aPages
[nPageIndex
].reset(new GtkInstanceContainer(pChild
, m_pBuilder
, false));
9026 return m_aPages
[nPageIndex
].get();
9029 virtual void set_current_page(int nPage
) override
9031 // normally we'd call disable_notify_events/enable_notify_events here,
9032 // but the notebook is complicated by the need to support the
9033 // double-decker hackery so for simplicity just flag that the page
9034 // change is not a directly user-triggered one
9035 bool bInternalPageChange
= m_bInternalPageChange
;
9036 m_bInternalPageChange
= true;
9038 if (m_bOverFlowBoxIsStart
)
9040 auto nOverFlowLen
= m_bOverFlowBoxActive
? gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1 : 0;
9041 if (nPage
< nOverFlowLen
)
9042 gtk_notebook_set_current_page(m_pOverFlowNotebook
, nPage
);
9045 nPage
-= nOverFlowLen
;
9046 gtk_notebook_set_current_page(m_pNotebook
, nPage
);
9051 auto nMainLen
= gtk_notebook_get_n_pages(m_pNotebook
);
9052 if (nPage
< nMainLen
)
9053 gtk_notebook_set_current_page(m_pNotebook
, nPage
);
9057 gtk_notebook_set_current_page(m_pOverFlowNotebook
, nPage
);
9061 m_bInternalPageChange
= bInternalPageChange
;
9064 virtual void set_current_page(const OString
& rIdent
) override
9066 gint nPage
= get_page_index(rIdent
);
9067 set_current_page(nPage
);
9070 virtual int get_n_pages() const override
9072 int nLen
= gtk_notebook_get_n_pages(m_pNotebook
);
9073 if (m_bOverFlowBoxActive
)
9074 nLen
+= gtk_notebook_get_n_pages(m_pOverFlowNotebook
) - 1;
9078 virtual OUString
get_tab_label_text(const OString
& rIdent
) const override
9080 gint nPageNum
= get_page_number(m_pNotebook
, rIdent
);
9082 return get_tab_label_text(m_pNotebook
, nPageNum
);
9083 nPageNum
= get_page_number(m_pOverFlowNotebook
, rIdent
);
9085 return get_tab_label_text(m_pOverFlowNotebook
, nPageNum
);
9089 virtual void set_tab_label_text(const OString
& rIdent
, const OUString
& rText
) override
9091 gint nPageNum
= get_page_number(m_pNotebook
, rIdent
);
9094 set_tab_label_text(m_pNotebook
, nPageNum
, rText
);
9097 nPageNum
= get_page_number(m_pOverFlowNotebook
, rIdent
);
9100 set_tab_label_text(m_pOverFlowNotebook
, nPageNum
, rText
);
9104 virtual void disable_notify_events() override
9106 g_signal_handler_block(m_pNotebook
, m_nSwitchPageSignalId
);
9107 #if !GTK_CHECK_VERSION(4, 0, 0)
9108 g_signal_handler_block(m_pNotebook
, m_nFocusSignalId
);
9110 g_signal_handler_block(m_pNotebook
, m_nChangeCurrentPageId
);
9111 g_signal_handler_block(m_pOverFlowNotebook
, m_nOverFlowSwitchPageSignalId
);
9112 #if !GTK_CHECK_VERSION(4, 0, 0)
9113 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook
));
9115 g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook
));
9116 GtkInstanceWidget::disable_notify_events();
9119 virtual void enable_notify_events() override
9121 GtkInstanceWidget::enable_notify_events();
9122 g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook
));
9123 #if !GTK_CHECK_VERSION(4, 0, 0)
9124 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook
));
9126 g_signal_handler_unblock(m_pOverFlowNotebook
, m_nOverFlowSwitchPageSignalId
);
9127 g_signal_handler_unblock(m_pNotebook
, m_nSwitchPageSignalId
);
9128 #if !GTK_CHECK_VERSION(4, 0, 0)
9129 g_signal_handler_unblock(m_pNotebook
, m_nFocusSignalId
);
9131 g_signal_handler_unblock(m_pNotebook
, m_nChangeCurrentPageId
);
9134 void reset_split_data()
9136 // reset overflow and allow it to be recalculated if necessary
9137 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook
));
9138 m_bOverFlowBoxActive
= false;
9139 m_nStartTabCount
= 0;
9143 virtual void remove_page(const OString
& rIdent
) override
9145 if (m_bOverFlowBoxActive
)
9147 unsplit_notebooks();
9151 unsigned int nPageIndex
= remove_page(m_pNotebook
, rIdent
);
9152 if (nPageIndex
< m_aPages
.size())
9153 m_aPages
.erase(m_aPages
.begin() + nPageIndex
);
9156 virtual void insert_page(const OString
& rIdent
, const OUString
& rLabel
, int nPos
) override
9158 if (m_bOverFlowBoxActive
)
9160 unsplit_notebooks();
9164 // reset overflow and allow it to be recalculated if necessary
9165 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook
));
9166 m_bOverFlowBoxActive
= false;
9168 insert_page(m_pNotebook
, rIdent
, rLabel
, gtk_grid_new(), nPos
);
9171 virtual ~GtkInstanceNotebook() override
9173 if (m_nLaunchSplitTimeoutId
)
9174 g_source_remove(m_nLaunchSplitTimeoutId
);
9175 #if !GTK_CHECK_VERSION(4, 0, 0)
9176 if (m_nNotebookSizeAllocateSignalId
)
9177 g_signal_handler_disconnect(m_pNotebook
, m_nNotebookSizeAllocateSignalId
);
9181 // put it back how we found it initially
9182 notifying_layout_stop_watch(m_pLayout
);
9185 g_signal_handler_disconnect(m_pNotebook
, m_nSwitchPageSignalId
);
9186 #if !GTK_CHECK_VERSION(4, 0, 0)
9187 g_signal_handler_disconnect(m_pNotebook
, m_nFocusSignalId
);
9189 g_signal_handler_disconnect(m_pNotebook
, m_nChangeCurrentPageId
);
9190 g_signal_handler_disconnect(m_pOverFlowNotebook
, m_nOverFlowSwitchPageSignalId
);
9191 #if !GTK_CHECK_VERSION(4, 0, 0)
9192 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook
));
9194 GtkWidget
* pOverFlowWidget
= GTK_WIDGET(m_pOverFlowNotebook
);
9195 g_clear_pointer(&pOverFlowWidget
, gtk_widget_unparent
);
9197 if (!m_pOverFlowBox
)
9200 // put it back to how we found it initially
9201 GtkWidget
* pParent
= gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox
));
9202 g_object_ref(m_pNotebook
);
9203 container_remove(GTK_WIDGET(m_pOverFlowBox
), GTK_WIDGET(m_pNotebook
));
9204 container_add(GTK_WIDGET(pParent
), GTK_WIDGET(m_pNotebook
));
9205 g_object_unref(m_pNotebook
);
9207 #if !GTK_CHECK_VERSION(4, 0, 0)
9208 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox
));
9210 GtkWidget
* pOverFlowBox
= GTK_WIDGET(m_pOverFlowBox
);
9211 g_clear_pointer(&pOverFlowBox
, gtk_widget_unparent
);
9216 #if GTK_CHECK_VERSION(4, 0, 0)
9217 IMPL_LINK_NOARG(GtkInstanceNotebook
, SizeAllocateHdl
, void*, void)
9219 signal_notebook_size_allocate();
9224 OUString
vcl_font_to_css(const vcl::Font
& rFont
)
9226 OUStringBuffer sCSS
;
9227 sCSS
.append("font-family: \"" + rFont
.GetFamilyName() + "\"; ");
9228 sCSS
.append("font-size: " + OUString::number(rFont
.GetFontSize().Height()) + "pt; ");
9229 switch (rFont
.GetItalic())
9232 sCSS
.append("font-style: normal; ");
9235 sCSS
.append("font-style: italic; ");
9237 case ITALIC_OBLIQUE
:
9238 sCSS
.append("font-style: oblique; ");
9243 switch (rFont
.GetWeight())
9245 case WEIGHT_ULTRALIGHT
:
9246 sCSS
.append("font-weight: 200; ");
9249 sCSS
.append("font-weight: 300; ");
9252 sCSS
.append("font-weight: 400; ");
9255 sCSS
.append("font-weight: 700; ");
9257 case WEIGHT_ULTRABOLD
:
9258 sCSS
.append("font-weight: 800; ");
9263 switch (rFont
.GetWidthType())
9265 case WIDTH_ULTRA_CONDENSED
:
9266 sCSS
.append("font-stretch: ultra-condensed; ");
9268 case WIDTH_EXTRA_CONDENSED
:
9269 sCSS
.append("font-stretch: extra-condensed; ");
9271 case WIDTH_CONDENSED
:
9272 sCSS
.append("font-stretch: condensed; ");
9274 case WIDTH_SEMI_CONDENSED
:
9275 sCSS
.append("font-stretch: semi-condensed; ");
9278 sCSS
.append("font-stretch: normal; ");
9280 case WIDTH_SEMI_EXPANDED
:
9281 sCSS
.append("font-stretch: semi-expanded; ");
9283 case WIDTH_EXPANDED
:
9284 sCSS
.append("font-stretch: expanded; ");
9286 case WIDTH_EXTRA_EXPANDED
:
9287 sCSS
.append("font-stretch: extra-expanded; ");
9289 case WIDTH_ULTRA_EXPANDED
:
9290 sCSS
.append("font-stretch: ultra-expanded; ");
9295 return sCSS
.toString();
9298 void update_attr_list(PangoAttrList
* pAttrList
, const vcl::Font
& rFont
)
9300 pango_attr_list_change(pAttrList
, pango_attr_family_new(OUStringToOString(rFont
.GetFamilyName(), RTL_TEXTENCODING_UTF8
).getStr()));
9301 pango_attr_list_change(pAttrList
, pango_attr_size_new(rFont
.GetFontSize().Height() * PANGO_SCALE
));
9303 switch (rFont
.GetItalic())
9306 pango_attr_list_change(pAttrList
, pango_attr_style_new(PANGO_STYLE_NORMAL
));
9309 pango_attr_list_change(pAttrList
, pango_attr_style_new(PANGO_STYLE_ITALIC
));
9311 case ITALIC_OBLIQUE
:
9312 pango_attr_list_change(pAttrList
, pango_attr_style_new(PANGO_STYLE_OBLIQUE
));
9317 switch (rFont
.GetWeight())
9319 case WEIGHT_ULTRALIGHT
:
9320 pango_attr_list_change(pAttrList
, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT
));
9323 pango_attr_list_change(pAttrList
, pango_attr_weight_new(PANGO_WEIGHT_LIGHT
));
9326 pango_attr_list_change(pAttrList
, pango_attr_weight_new(PANGO_WEIGHT_NORMAL
));
9329 pango_attr_list_change(pAttrList
, pango_attr_weight_new(PANGO_WEIGHT_BOLD
));
9331 case WEIGHT_ULTRABOLD
:
9332 pango_attr_list_change(pAttrList
, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD
));
9337 switch (rFont
.GetWidthType())
9339 case WIDTH_ULTRA_CONDENSED
:
9340 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED
));
9342 case WIDTH_EXTRA_CONDENSED
:
9343 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED
));
9345 case WIDTH_CONDENSED
:
9346 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED
));
9348 case WIDTH_SEMI_CONDENSED
:
9349 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED
));
9352 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_NORMAL
));
9354 case WIDTH_SEMI_EXPANDED
:
9355 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED
));
9357 case WIDTH_EXPANDED
:
9358 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED
));
9360 case WIDTH_EXTRA_EXPANDED
:
9361 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED
));
9363 case WIDTH_ULTRA_EXPANDED
:
9364 pango_attr_list_change(pAttrList
, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED
));
9371 gboolean
filter_pango_attrs(PangoAttribute
*attr
, gpointer data
)
9373 PangoAttrType
* pFilterAttrs
= static_cast<PangoAttrType
*>(data
);
9374 while (*pFilterAttrs
)
9376 if (attr
->klass
->type
== *pFilterAttrs
)
9383 void set_font(GtkLabel
* pLabel
, const vcl::Font
& rFont
)
9385 PangoAttrList
* pOrigList
= gtk_label_get_attributes(pLabel
);
9386 PangoAttrList
* pAttrList
= pOrigList
? pango_attr_list_copy(pOrigList
) : pango_attr_list_new();
9390 // tdf#143443 remove both PANGO_ATTR_ABSOLUTE_SIZE and PANGO_ATTR_SIZE
9391 // because pango_attr_list_change(..., pango_attr_size_new...) isn't
9392 // sufficient on its own to ensure a new size sticks.
9393 PangoAttrType aFilterAttrs
[] = {PANGO_ATTR_ABSOLUTE_SIZE
, PANGO_ATTR_SIZE
, PANGO_ATTR_INVALID
};
9394 PangoAttrList
* pRemovedAttrs
= pango_attr_list_filter(pAttrList
, filter_pango_attrs
, &aFilterAttrs
);
9395 pango_attr_list_unref(pRemovedAttrs
);
9398 update_attr_list(pAttrList
, rFont
);
9399 gtk_label_set_attributes(pLabel
, pAttrList
);
9400 pango_attr_list_unref(pAttrList
);
9407 class WidgetBackground
9410 GtkWidget
* m_pWidget
;
9411 GtkCssProvider
* m_pCustomCssProvider
;
9412 std::unique_ptr
<utl::TempFile
> m_xCustomImage
;
9415 // See: https://developer.gnome.org/Buttons/
9416 void use_custom_content(const VirtualDevice
* pDevice
)
9418 GtkStyleContext
*pWidgetContext
= gtk_widget_get_style_context(m_pWidget
);
9420 if (m_pCustomCssProvider
)
9422 gtk_style_context_remove_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pCustomCssProvider
));
9423 m_pCustomCssProvider
= nullptr;
9426 m_xCustomImage
.reset();
9431 m_xCustomImage
.reset(new utl::TempFile
);
9432 m_xCustomImage
->EnableKillingFile(true);
9434 cairo_surface_t
* surface
= get_underlying_cairo_surface(*pDevice
);
9435 Size aSize
= pDevice
->GetOutputSizePixel();
9436 cairo_surface_write_to_png(surface
, OUStringToOString(m_xCustomImage
->GetFileName(), osl_getThreadTextEncoding()).getStr());
9438 m_pCustomCssProvider
= gtk_css_provider_new();
9439 OUString aBuffer
= "* { background-image: url(\"" + m_xCustomImage
->GetURL() + "\"); "
9440 "background-size: " + OUString::number(aSize
.Width()) + "px " + OUString::number(aSize
.Height()) + "px; "
9441 "border-radius: 0; border-width: 0; }";
9442 OString aResult
= OUStringToOString(aBuffer
, RTL_TEXTENCODING_UTF8
);
9443 css_provider_load_from_data(m_pCustomCssProvider
, aResult
.getStr(), aResult
.getLength());
9444 gtk_style_context_add_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pCustomCssProvider
),
9445 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
9449 WidgetBackground(GtkWidget
* pWidget
)
9450 : m_pWidget(pWidget
)
9451 , m_pCustomCssProvider(nullptr)
9457 if (m_pCustomCssProvider
)
9458 use_custom_content(nullptr);
9459 assert(!m_pCustomCssProvider
);
9466 GtkWidget
* m_pWidget
;
9467 GtkCssProvider
* m_pFontCssProvider
;
9468 std::unique_ptr
<vcl::Font
> m_xFont
;
9470 WidgetFont(GtkWidget
* pWidget
)
9471 : m_pWidget(pWidget
)
9472 , m_pFontCssProvider(nullptr)
9476 void use_custom_font(const vcl::Font
* pFont
, std::u16string_view rCSSSelector
)
9478 GtkStyleContext
*pWidgetContext
= gtk_widget_get_style_context(m_pWidget
);
9479 if (m_pFontCssProvider
)
9481 gtk_style_context_remove_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pFontCssProvider
));
9482 m_pFontCssProvider
= nullptr;
9490 m_xFont
.reset(new vcl::Font(*pFont
));
9491 m_pFontCssProvider
= gtk_css_provider_new();
9492 OUString aBuffer
= rCSSSelector
+ OUString::Concat(" { ") + vcl_font_to_css(*pFont
) + OUString::Concat(" }");
9493 OString aResult
= OUStringToOString(aBuffer
, RTL_TEXTENCODING_UTF8
);
9494 css_provider_load_from_data(m_pFontCssProvider
, aResult
.getStr(), aResult
.getLength());
9495 gtk_style_context_add_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pFontCssProvider
),
9496 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
9499 const vcl::Font
* get_custom_font() const
9501 return m_xFont
.get();
9506 if (m_pFontCssProvider
)
9507 use_custom_font(nullptr, u
"");
9508 assert(!m_pFontCssProvider
);
9512 class GtkInstanceButton
: public GtkInstanceWidget
, public virtual weld::Button
9515 GtkButton
* m_pButton
;
9517 std::optional
<vcl::Font
> m_xFont
;
9518 WidgetBackground m_aCustomBackground
;
9520 static void signalClicked(GtkButton
*, gpointer widget
)
9522 GtkInstanceButton
* pThis
= static_cast<GtkInstanceButton
*>(widget
);
9523 SolarMutexGuard aGuard
;
9524 pThis
->signal_clicked();
9527 virtual void ensureMouseEventWidget() override
9529 // The GtkButton is sufficient to get mouse events without an intermediate GtkEventBox
9530 if (!m_pMouseEventBox
)
9531 m_pMouseEventBox
= m_pWidget
;
9535 GtkInstanceButton(GtkButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
9536 : GtkInstanceWidget(GTK_WIDGET(pButton
), pBuilder
, bTakeOwnership
)
9537 , m_pButton(pButton
)
9538 , m_nSignalId(g_signal_connect(pButton
, "clicked", G_CALLBACK(signalClicked
), this))
9539 , m_aCustomBackground(GTK_WIDGET(pButton
))
9541 g_object_set_data(G_OBJECT(m_pButton
), "g-lo-GtkInstanceButton", this);
9544 virtual void set_label(const OUString
& rText
) override
9546 ::button_set_label(m_pButton
, rText
);
9549 virtual void set_image(VirtualDevice
* pDevice
) override
9551 ::button_set_image(m_pButton
, pDevice
);
9554 virtual void set_from_icon_name(const OUString
& rIconName
) override
9556 ::button_set_from_icon_name(m_pButton
, rIconName
);
9559 virtual void set_image(const css::uno::Reference
<css::graphic::XGraphic
>& rImage
) override
9561 ::button_set_image(m_pButton
, rImage
);
9564 virtual void set_custom_button(VirtualDevice
* pDevice
) override
9566 m_aCustomBackground
.use_custom_content(pDevice
);
9569 virtual OUString
get_label() const override
9571 return ::button_get_label(m_pButton
);
9574 virtual void set_font(const vcl::Font
& rFont
) override
9577 GtkLabel
* pChild
= ::get_label_widget(GTK_WIDGET(m_pButton
));
9578 ::set_font(pChild
, rFont
);
9581 virtual vcl::Font
get_font() override
9585 return GtkInstanceWidget::get_font();
9588 // allow us to block buttons with click handlers making dialogs return a response
9589 bool has_click_handler() const
9591 return m_aClickHdl
.IsSet();
9594 void clear_click_handler()
9596 m_aClickHdl
= Link
<Button
&, void>();
9599 virtual void disable_notify_events() override
9601 g_signal_handler_block(m_pButton
, m_nSignalId
);
9602 GtkInstanceWidget::disable_notify_events();
9605 virtual void enable_notify_events() override
9607 GtkInstanceWidget::enable_notify_events();
9608 g_signal_handler_unblock(m_pButton
, m_nSignalId
);
9611 virtual ~GtkInstanceButton() override
9613 g_object_steal_data(G_OBJECT(m_pButton
), "g-lo-GtkInstanceButton");
9614 g_signal_handler_disconnect(m_pButton
, m_nSignalId
);
9620 void GtkInstanceDialog::asyncresponse(gint ret
)
9622 SolarMutexGuard aGuard
;
9624 if (ret
== GTK_RESPONSE_HELP
)
9630 GtkInstanceButton
* pClickHandler
= has_click_handler(ret
);
9633 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
9634 if (ret
== GTK_RESPONSE_DELETE_EVENT
)
9640 m_aDialogRun
.dec_modal_count();
9643 // move the self pointer, otherwise it might be de-allocated by time we try to reset it
9644 auto xRunAsyncSelf
= std::move(m_xRunAsyncSelf
);
9645 auto xDialogController
= std::move(m_xDialogController
);
9646 auto aFunc
= std::move(m_aFunc
);
9648 auto nResponseSignalId
= m_nResponseSignalId
;
9649 auto nCancelSignalId
= m_nCancelSignalId
;
9650 auto nSignalDeleteId
= m_nSignalDeleteId
;
9651 m_nResponseSignalId
= 0;
9652 m_nCancelSignalId
= 0;
9653 m_nSignalDeleteId
= 0;
9656 aFunc(GtkToVcl(ret
));
9658 if (nResponseSignalId
)
9659 g_signal_handler_disconnect(m_pDialog
, nResponseSignalId
);
9660 if (nCancelSignalId
)
9661 g_signal_handler_disconnect(m_pDialog
, nCancelSignalId
);
9662 if (nSignalDeleteId
)
9663 g_signal_handler_disconnect(m_pDialog
, nSignalDeleteId
);
9665 xDialogController
.reset();
9666 xRunAsyncSelf
.reset();
9669 int GtkInstanceDialog::run()
9671 #if !GTK_CHECK_VERSION(4, 0, 0)
9672 if (GTK_IS_DIALOG(m_pDialog
))
9673 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog
))));
9678 ret
= m_aDialogRun
.run();
9679 if (ret
== GTK_RESPONSE_HELP
)
9684 else if (has_click_handler(ret
))
9689 return GtkToVcl(ret
);
9692 weld::Button
* GtkInstanceDialog::weld_widget_for_response(int nVclResponse
)
9694 GtkButton
* pButton
= get_widget_for_response(VclToGtk(nVclResponse
));
9697 return new GtkInstanceButton(pButton
, m_pBuilder
, false);
9700 void GtkInstanceDialog::response(int nResponse
)
9702 int nGtkResponse
= VclToGtk(nResponse
);
9703 //unblock this response now when activated through code
9704 if (GtkButton
* pWidget
= get_widget_for_response(nGtkResponse
))
9706 void* pData
= g_object_get_data(G_OBJECT(pWidget
), "g-lo-GtkInstanceButton");
9707 GtkInstanceButton
* pButton
= static_cast<GtkInstanceButton
*>(pData
);
9709 pButton
->clear_click_handler();
9711 if (GTK_IS_DIALOG(m_pDialog
))
9712 gtk_dialog_response(GTK_DIALOG(m_pDialog
), nGtkResponse
);
9713 else if (GTK_IS_ASSISTANT(m_pDialog
))
9715 if (!m_aDialogRun
.loop_is_running())
9716 asyncresponse(nGtkResponse
);
9719 m_aDialogRun
.m_nResponseId
= nGtkResponse
;
9720 m_aDialogRun
.loop_quit();
9725 void GtkInstanceDialog::close(bool bCloseSignal
)
9727 GtkInstanceButton
* pClickHandler
= has_click_handler(GTK_RESPONSE_CANCEL
);
9731 g_signal_stop_emission_by_name(m_pDialog
, "close");
9732 // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
9733 // act as if cancel button was pressed
9734 pClickHandler
->clicked();
9737 response(RET_CANCEL
);
9740 GtkInstanceButton
* GtkInstanceDialog::has_click_handler(int nResponse
)
9742 GtkInstanceButton
* pButton
= nullptr;
9743 // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
9744 nResponse
= VclToGtk(GtkToVcl(nResponse
));
9745 if (GtkButton
* pWidget
= get_widget_for_response(nResponse
))
9747 void* pData
= g_object_get_data(G_OBJECT(pWidget
), "g-lo-GtkInstanceButton");
9748 pButton
= static_cast<GtkInstanceButton
*>(pData
);
9749 if (pButton
&& !pButton
->has_click_handler())
9757 class GtkInstanceToggleButton
: public GtkInstanceButton
, public virtual weld::ToggleButton
9760 GtkToggleButton
* m_pToggleButton
;
9761 gulong m_nToggledSignalId
;
9763 static void signalToggled(GtkToggleButton
*, gpointer widget
)
9765 GtkInstanceToggleButton
* pThis
= static_cast<GtkInstanceToggleButton
*>(widget
);
9766 SolarMutexGuard aGuard
;
9767 pThis
->signal_toggled();
9770 GtkInstanceToggleButton(GtkToggleButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
9771 : GtkInstanceButton(GTK_BUTTON(pButton
), pBuilder
, bTakeOwnership
)
9772 , m_pToggleButton(pButton
)
9773 , m_nToggledSignalId(g_signal_connect(m_pToggleButton
, "toggled", G_CALLBACK(signalToggled
), this))
9777 virtual void set_active(bool active
) override
9779 disable_notify_events();
9780 set_inconsistent(false);
9781 gtk_toggle_button_set_active(m_pToggleButton
, active
);
9782 enable_notify_events();
9785 virtual bool get_active() const override
9787 return gtk_toggle_button_get_active(m_pToggleButton
);
9790 virtual void set_inconsistent(bool inconsistent
) override
9792 #if GTK_CHECK_VERSION(4, 0, 0)
9794 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleButton
), GTK_STATE_FLAG_INCONSISTENT
, false);
9796 gtk_widget_unset_state_flags(GTK_WIDGET(m_pToggleButton
), GTK_STATE_FLAG_INCONSISTENT
);
9798 gtk_toggle_button_set_inconsistent(m_pToggleButton
, inconsistent
);
9802 virtual bool get_inconsistent() const override
9804 #if GTK_CHECK_VERSION(4, 0, 0)
9805 return gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton
)) & GTK_STATE_FLAG_INCONSISTENT
;
9807 return gtk_toggle_button_get_inconsistent(m_pToggleButton
);
9811 virtual void disable_notify_events() override
9813 g_signal_handler_block(m_pToggleButton
, m_nToggledSignalId
);
9814 GtkInstanceButton::disable_notify_events();
9817 virtual void enable_notify_events() override
9819 GtkInstanceButton::enable_notify_events();
9820 g_signal_handler_unblock(m_pToggleButton
, m_nToggledSignalId
);
9823 virtual ~GtkInstanceToggleButton() override
9825 g_signal_handler_disconnect(m_pToggleButton
, m_nToggledSignalId
);
9831 #if !GTK_CHECK_VERSION(4, 0, 0)
9835 void do_grab(GtkWidget
* pWidget
)
9837 GdkDisplay
*pDisplay
= gtk_widget_get_display(pWidget
);
9838 GdkSeat
* pSeat
= gdk_display_get_default_seat(pDisplay
);
9839 gdk_seat_grab(pSeat
, widget_get_surface(pWidget
),
9840 GDK_SEAT_CAPABILITY_KEYBOARD
, true, nullptr, nullptr, nullptr, nullptr);
9843 void do_ungrab(GtkWidget
* pWidget
)
9845 GdkDisplay
*pDisplay
= gtk_widget_get_display(pWidget
);
9846 GdkSeat
* pSeat
= gdk_display_get_default_seat(pDisplay
);
9847 gdk_seat_ungrab(pSeat
);
9850 GtkPositionType
show_menu_older_gtk(GtkWidget
* pMenuButton
, GtkWindow
* pMenu
, const GdkRectangle
& rAnchor
,
9851 weld::Placement ePlace
, bool bTryShrink
)
9853 //place the toplevel just below its launcher button
9854 GtkWidget
* pToplevel
= widget_get_toplevel(pMenuButton
);
9855 gtk_coord x
, y
, absx
, absy
;
9856 gtk_widget_translate_coordinates(pMenuButton
, pToplevel
, rAnchor
.x
, rAnchor
.y
, &x
, &y
);
9857 GdkSurface
* pWindow
= widget_get_surface(pToplevel
);
9858 gdk_window_get_position(pWindow
, &absx
, &absy
);
9863 gint nButtonHeight
= rAnchor
.height
;
9864 gint nButtonWidth
= rAnchor
.width
;
9865 if (ePlace
== weld::Placement::Under
)
9870 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel
)), pMenu
);
9871 gtk_window_set_transient_for(pMenu
, GTK_WINDOW(pToplevel
));
9873 gint nMenuWidth
, nMenuHeight
;
9874 gtk_widget_get_size_request(GTK_WIDGET(pMenu
), &nMenuWidth
, &nMenuHeight
);
9876 if (nMenuWidth
== -1 || nMenuHeight
== -1)
9879 gtk_widget_get_preferred_size(GTK_WIDGET(pMenu
), nullptr, &req
);
9880 if (nMenuWidth
== -1)
9881 nMenuWidth
= req
.width
;
9882 if (nMenuHeight
== -1)
9883 nMenuHeight
= req
.height
;
9886 bool bSwapForRTL
= SwapForRTL(pMenuButton
);
9889 if (ePlace
== weld::Placement::Under
)
9896 tools::Rectangle
aWorkArea(::get_monitor_workarea(pMenuButton
));
9898 // shrink it a little, I find it reassuring to see a little margin with a
9899 // long menu to know the menu is fully on screen
9900 aWorkArea
.AdjustTop(8);
9901 aWorkArea
.AdjustBottom(-8);
9902 aWorkArea
.AdjustLeft(8);
9903 aWorkArea
.AdjustRight(-8);
9905 GtkPositionType ePosUsed
;
9907 if (ePlace
== weld::Placement::Under
)
9909 gint endx
= x
+ nMenuWidth
;
9910 if (endx
> aWorkArea
.Right())
9911 x
-= endx
- aWorkArea
.Right();
9915 ePosUsed
= GTK_POS_BOTTOM
;
9916 gint endy
= y
+ nMenuHeight
;
9917 gint nMissingBelow
= endy
- aWorkArea
.Bottom();
9918 if (nMissingBelow
> 0)
9920 gint nNewY
= y
- (nButtonHeight
+ nMenuHeight
);
9921 gint nMissingAbove
= aWorkArea
.Top() - nNewY
;
9922 if (nMissingAbove
> 0)
9926 if (nMissingBelow
<= nMissingAbove
)
9927 nMenuHeight
-= nMissingBelow
;
9930 nMenuHeight
-= nMissingAbove
;
9931 y
= aWorkArea
.Top();
9932 ePosUsed
= GTK_POS_TOP
;
9934 gtk_widget_set_size_request(GTK_WIDGET(pMenu
), nMenuWidth
, nMenuHeight
);
9938 if (nMissingBelow
<= nMissingAbove
)
9942 y
= aWorkArea
.Top();
9943 ePosUsed
= GTK_POS_TOP
;
9950 ePosUsed
= GTK_POS_TOP
;
9958 ePosUsed
= GTK_POS_RIGHT
;
9959 gint endx
= x
+ nMenuWidth
;
9960 gint nMissingAfter
= endx
- aWorkArea
.Right();
9961 if (nMissingAfter
> 0)
9963 gint nNewX
= x
- (nButtonWidth
+ nMenuWidth
);
9964 if (nNewX
>= aWorkArea
.Left())
9967 ePosUsed
= GTK_POS_LEFT
;
9973 ePosUsed
= GTK_POS_LEFT
;
9975 gint nMissingBefore
= aWorkArea
.Left() - startx
;
9976 if (nMissingBefore
> 0)
9978 gint nNewX
= x
+ (nButtonWidth
+ nMenuWidth
);
9979 if (nNewX
+ nMenuWidth
< aWorkArea
.Right())
9982 ePosUsed
= GTK_POS_RIGHT
;
9988 gtk_window_move(pMenu
, x
, y
);
9993 bool show_menu_newer_gtk(GtkWidget
* pComboBox
, GtkWindow
* pMenu
, const GdkRectangle
&rAnchor
,
9994 weld::Placement ePlace
, bool bTryShrink
)
9996 static auto window_move_to_rect
= reinterpret_cast<void (*) (GdkWindow
*, const GdkRectangle
*, GdkGravity
,
9997 GdkGravity
, GdkAnchorHints
, gint
, gint
)>(
9998 dlsym(nullptr, "gdk_window_move_to_rect"));
9999 if (!window_move_to_rect
)
10002 // under wayland gdk_window_move_to_rect works great for me, but in my current
10003 // gtk 3.24 under X it leaves part of long menus outside the work area
10004 GdkDisplay
*pDisplay
= gtk_widget_get_display(pComboBox
);
10005 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay
))
10008 //place the toplevel just below its launcher button
10009 GtkWidget
* pToplevel
= widget_get_toplevel(pComboBox
);
10011 gtk_widget_translate_coordinates(pComboBox
, pToplevel
, rAnchor
.x
, rAnchor
.y
, &x
, &y
);
10013 gtk_widget_realize(GTK_WIDGET(pMenu
));
10014 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel
)), pMenu
);
10015 gtk_window_set_transient_for(pMenu
, GTK_WINDOW(pToplevel
));
10017 bool bSwapForRTL
= SwapForRTL(GTK_WIDGET(pComboBox
));
10019 GdkGravity rect_anchor
;
10020 GdkGravity menu_anchor
;
10022 if (ePlace
== weld::Placement::Under
)
10024 rect_anchor
= !bSwapForRTL
? GDK_GRAVITY_SOUTH_WEST
: GDK_GRAVITY_SOUTH_EAST
;
10025 menu_anchor
= !bSwapForRTL
? GDK_GRAVITY_NORTH_WEST
: GDK_GRAVITY_NORTH_EAST
;
10029 rect_anchor
= !bSwapForRTL
? GDK_GRAVITY_NORTH_EAST
: GDK_GRAVITY_NORTH_WEST
;
10030 menu_anchor
= !bSwapForRTL
? GDK_GRAVITY_NORTH_WEST
: GDK_GRAVITY_NORTH_EAST
;
10033 GdkAnchorHints anchor_hints
= static_cast<GdkAnchorHints
>(GDK_ANCHOR_FLIP
| GDK_ANCHOR_SLIDE
);
10035 anchor_hints
= static_cast<GdkAnchorHints
>(anchor_hints
| GDK_ANCHOR_RESIZE
);
10036 GdkRectangle rect
{x
, y
, rAnchor
.width
, rAnchor
.height
};
10037 GdkSurface
* toplevel
= widget_get_surface(GTK_WIDGET(pMenu
));
10039 window_move_to_rect(toplevel
, &rect
, rect_anchor
, menu_anchor
, anchor_hints
,
10045 GtkPositionType
show_menu(GtkWidget
* pMenuButton
, GtkWindow
* pMenu
, const GdkRectangle
& rAnchor
,
10046 weld::Placement ePlace
, bool bTryShrink
)
10048 // we only use ePosUsed in the replacement-for-X-popover case of a
10049 // MenuButton, so we only need it when show_menu_older_gtk is used
10050 GtkPositionType ePosUsed
= GTK_POS_BOTTOM
;
10052 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
10053 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
10054 // implemented as popups, which means that we cannot show any popup if there is a
10055 // visible tooltip.
10056 GtkWidget
* pParent
= widget_get_toplevel(pMenuButton
);
10057 GtkSalFrame
* pFrame
= pParent
? GtkSalFrame::getFromWindow(pParent
) : nullptr;
10060 // hide any current tooltip
10061 pFrame
->HideTooltip();
10062 // don't allow any more to appear until menu is dismissed
10063 pFrame
->BlockTooltip();
10066 // try with gdk_window_move_to_rect, but if that's not available, try without
10067 if (!show_menu_newer_gtk(pMenuButton
, pMenu
, rAnchor
, ePlace
, bTryShrink
))
10068 ePosUsed
= show_menu_older_gtk(pMenuButton
, pMenu
, rAnchor
, ePlace
, bTryShrink
);
10069 gtk_widget_show_all(GTK_WIDGET(pMenu
));
10070 gtk_widget_grab_focus(GTK_WIDGET(pMenu
));
10071 do_grab(GTK_WIDGET(pMenu
));
10081 #if !GTK_CHECK_VERSION(4, 0, 0)
10082 bool button_event_is_outside(GtkWidget
* pMenuHack
, GdkEventButton
* pEvent
)
10084 //we want to pop down if the button was released outside our popup
10085 gdouble x
= pEvent
->x_root
;
10086 gdouble y
= pEvent
->y_root
;
10088 gint window_x
, window_y
;
10089 GdkSurface
* pWindow
= widget_get_surface(pMenuHack
);
10090 gdk_window_get_position(pWindow
, &window_x
, &window_y
);
10092 GtkAllocation alloc
;
10093 gtk_widget_get_allocation(pMenuHack
, &alloc
);
10094 gint x1
= window_x
;
10095 gint y1
= window_y
;
10096 gint x2
= x1
+ alloc
.width
;
10097 gint y2
= y1
+ alloc
.height
;
10099 if (x
> x1
&& x
< x2
&& y
> y1
&& y
< y2
)
10105 GtkPositionType
MovePopoverContentsToWindow(GtkWidget
* pPopover
, GtkWindow
* pMenuHack
, GtkWidget
* pAnchor
,
10106 const GdkRectangle
& rAnchor
, weld::Placement ePlace
)
10109 gtk_container_set_border_width(GTK_CONTAINER(pMenuHack
), gtk_container_get_border_width(GTK_CONTAINER(pPopover
)));
10111 //steal popover contents and smuggle into toplevel display window
10112 GtkWidget
* pChild
= gtk_bin_get_child(GTK_BIN(pPopover
));
10113 g_object_ref(pChild
);
10114 gtk_container_remove(GTK_CONTAINER(pPopover
), pChild
);
10115 gtk_container_add(GTK_CONTAINER(pMenuHack
), pChild
);
10116 g_object_unref(pChild
);
10118 GtkPositionType eRet
= show_menu(pAnchor
, pMenuHack
, rAnchor
, ePlace
, false);
10120 gtk_grab_add(GTK_WIDGET(pMenuHack
));
10122 GdkSurface
* pSurface
= widget_get_surface(GTK_WIDGET(pMenuHack
));
10123 g_object_set_data(G_OBJECT(pSurface
), "g-lo-InstancePopup", GINT_TO_POINTER(true));
10128 void MoveWindowContentsToPopover(GtkWindow
* pMenuHack
, GtkWidget
* pPopover
, GtkWidget
* pAnchor
)
10130 bool bHadFocus
= gtk_window_has_toplevel_focus(pMenuHack
);
10132 do_ungrab(GTK_WIDGET(pMenuHack
));
10134 gtk_grab_remove(GTK_WIDGET(pMenuHack
));
10136 gtk_widget_hide(GTK_WIDGET(pMenuHack
));
10137 //put contents back from where the came from
10138 GtkWidget
* pChild
= gtk_bin_get_child(GTK_BIN(pMenuHack
));
10139 g_object_ref(pChild
);
10140 gtk_container_remove(GTK_CONTAINER(pMenuHack
), pChild
);
10141 gtk_container_add(GTK_CONTAINER(pPopover
), pChild
);
10142 g_object_unref(pChild
);
10144 GdkSurface
* pSurface
= widget_get_surface(GTK_WIDGET(pMenuHack
));
10145 g_object_set_data(G_OBJECT(pSurface
), "g-lo-InstancePopup", GINT_TO_POINTER(false));
10147 // so gdk_window_move_to_rect will work again the next time
10148 gtk_widget_unrealize(GTK_WIDGET(pMenuHack
));
10150 gtk_widget_set_size_request(GTK_WIDGET(pMenuHack
), -1, -1);
10152 // undo show_menu tooltip blocking
10153 GtkWidget
* pParent
= widget_get_toplevel(pAnchor
);
10154 GtkSalFrame
* pFrame
= pParent
? GtkSalFrame::getFromWindow(pParent
) : nullptr;
10156 pFrame
->UnblockTooltip();
10160 GdkSurface
* pParentSurface
= pParent
? widget_get_surface(pParent
) : nullptr;
10161 void* pParentIsPopover
= pParentSurface
? g_object_get_data(G_OBJECT(pParentSurface
), "g-lo-InstancePopup") : nullptr;
10162 if (pParentIsPopover
)
10164 gtk_widget_grab_focus(pAnchor
);
10170 /* four types of uses of this
10171 a) textual menubutton, always with pan-down symbol, e.g. math, format, font, modify
10172 b) image + text, always with additional pan-down symbol, e.g. writer, format, watermark
10173 c) gear menu, never with text and without pan-down symbol where there is a replacement
10174 icon for pan-down, e.g. file, new, templates
10175 d) image, always with additional pan-down symbol, e.g. calc, insert, header/footer */
10176 #if !GTK_CHECK_VERSION(4, 0, 0)
10177 class GtkInstanceMenuButton
: public GtkInstanceToggleButton
, public MenuHelper
, public virtual weld::MenuButton
10179 class GtkInstanceMenuButton
: public GtkInstanceWidget
, public MenuHelper
, public virtual weld::MenuButton
10183 GtkMenuButton
* m_pMenuButton
;
10186 #if !GTK_CHECK_VERSION(4, 0, 0)
10187 GtkImage
* m_pImage
;
10189 GtkPicture
* m_pImage
;
10190 GtkToggleButton
* m_pMenuButtonToggleButton
;
10192 GtkWidget
* m_pLabel
;
10193 #if !GTK_CHECK_VERSION(4, 0, 0)
10194 //popover cannot escape dialog under X so stick up own window instead
10195 GtkWindow
* m_pMenuHack
;
10196 //when doing so, if it's a toolbar menubutton align the menu to the full toolitem
10197 GtkWidget
* m_pMenuHackAlign
;
10198 bool m_nButtonPressSeen
;
10199 gulong m_nSignalId
;
10201 GtkWidget
* m_pPopover
;
10202 #if GTK_CHECK_VERSION(4, 0, 0)
10203 gulong m_nToggledSignalId
;
10204 std::optional
<vcl::Font
> m_xFont
;
10205 WidgetBackground m_aCustomBackground
;
10208 #if !GTK_CHECK_VERSION(4, 0, 0)
10209 static void signalMenuButtonToggled(GtkWidget
*, gpointer widget
)
10211 GtkInstanceMenuButton
* pThis
= static_cast<GtkInstanceMenuButton
*>(widget
);
10212 SolarMutexGuard aGuard
;
10213 pThis
->menu_toggled();
10217 #if !GTK_CHECK_VERSION(4, 0, 0)
10218 void menu_toggled()
10224 m_nButtonPressSeen
= false;
10225 MoveWindowContentsToPopover(m_pMenuHack
, m_pPopover
, GTK_WIDGET(m_pMenuButton
));
10229 GtkWidget
* pAnchor
= m_pMenuHackAlign
? m_pMenuHackAlign
: GTK_WIDGET(m_pMenuButton
);
10230 GdkRectangle aAnchor
{0, 0, gtk_widget_get_allocated_width(pAnchor
), gtk_widget_get_allocated_height(pAnchor
) };
10231 GtkPositionType ePosUsed
= MovePopoverContentsToWindow(m_pPopover
, m_pMenuHack
, pAnchor
, aAnchor
, weld::Placement::Under
);
10232 // tdf#132540 keep the placeholder popover on this same side as the replacement menu
10233 gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton
), ePosUsed
);
10238 #if !GTK_CHECK_VERSION(4, 0, 0)
10239 static void signalGrabBroken(GtkWidget
*, GdkEventGrabBroken
*pEvent
, gpointer widget
)
10241 GtkInstanceMenuButton
* pThis
= static_cast<GtkInstanceMenuButton
*>(widget
);
10242 pThis
->grab_broken(pEvent
);
10245 void grab_broken(const GdkEventGrabBroken
*event
)
10247 if (event
->grab_window
== nullptr)
10251 else if (!g_object_get_data(G_OBJECT(event
->grab_window
), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
10253 //try and regrab, so when we lose the grab to the menu of the color palette
10254 //combobox we regain it so the color palette doesn't itself disappear on next
10255 //click on the color palette combobox
10256 do_grab(GTK_WIDGET(m_pMenuHack
));
10260 static gboolean
signalButtonPress(GtkWidget
* /*pWidget*/, GdkEventButton
* /*pEvent*/, gpointer widget
)
10262 GtkInstanceMenuButton
* pThis
= static_cast<GtkInstanceMenuButton
*>(widget
);
10263 pThis
->m_nButtonPressSeen
= true;
10267 static gboolean
signalButtonRelease(GtkWidget
* /*pWidget*/, GdkEventButton
* pEvent
, gpointer widget
)
10269 GtkInstanceMenuButton
* pThis
= static_cast<GtkInstanceMenuButton
*>(widget
);
10270 if (pThis
->m_nButtonPressSeen
&& button_event_is_outside(GTK_WIDGET(pThis
->m_pMenuHack
), pEvent
))
10271 pThis
->set_active(false);
10275 static gboolean
keyPress(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
10277 GtkInstanceMenuButton
* pThis
= static_cast<GtkInstanceMenuButton
*>(widget
);
10278 return pThis
->key_press(pEvent
);
10281 bool key_press(const GdkEventKey
* pEvent
)
10283 if (pEvent
->keyval
== GDK_KEY_Escape
)
10292 void ensure_image_widget()
10297 #if !GTK_CHECK_VERSION(4, 0, 0)
10298 m_pImage
= GTK_IMAGE(gtk_image_new());
10299 gtk_box_pack_start(m_pBox
, GTK_WIDGET(m_pImage
), false, false, 0);
10300 gtk_box_reorder_child(m_pBox
, GTK_WIDGET(m_pImage
), 0);
10302 m_pImage
= GTK_PICTURE(gtk_picture_new());
10303 gtk_widget_set_halign(GTK_WIDGET(m_pImage
), GTK_ALIGN_CENTER
);
10304 gtk_widget_set_valign(GTK_WIDGET(m_pImage
), GTK_ALIGN_CENTER
);
10305 gtk_box_prepend(m_pBox
, GTK_WIDGET(m_pImage
));
10306 gtk_widget_set_halign(m_pLabel
, GTK_ALIGN_START
);
10308 gtk_widget_show(GTK_WIDGET(m_pImage
));
10311 static void signalFlagsChanged(GtkToggleButton
* pToggleButton
, GtkStateFlags flags
, gpointer widget
)
10313 GtkInstanceMenuButton
* pThis
= static_cast<GtkInstanceMenuButton
*>(widget
);
10314 bool bOldChecked
= flags
& GTK_STATE_FLAG_CHECKED
;
10315 bool bNewChecked
= gtk_widget_get_state_flags(GTK_WIDGET(pToggleButton
)) & GTK_STATE_FLAG_CHECKED
;
10316 if (bOldChecked
== bNewChecked
)
10318 if (bOldChecked
&& gtk_widget_get_focus_on_click(GTK_WIDGET(pToggleButton
)))
10320 // grab focus back to the toggle button if the menu was popped down
10321 gtk_widget_grab_focus(GTK_WIDGET(pToggleButton
));
10323 SolarMutexGuard aGuard
;
10324 pThis
->signal_toggled();
10328 #if !GTK_CHECK_VERSION(4, 0, 0)
10329 GtkInstanceMenuButton(GtkMenuButton
* pMenuButton
, GtkWidget
* pMenuAlign
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
10330 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton
), pBuilder
, bTakeOwnership
)
10331 , MenuHelper(gtk_menu_button_get_popup(pMenuButton
), false)
10333 GtkInstanceMenuButton(GtkMenuButton
* pMenuButton
, GtkWidget
* pMenuAlign
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
10334 : GtkInstanceWidget(GTK_WIDGET(pMenuButton
), pBuilder
, bTakeOwnership
)
10335 , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton
)), false)
10337 , m_pMenuButton(pMenuButton
)
10338 , m_pImage(nullptr)
10339 #if !GTK_CHECK_VERSION(4, 0, 0)
10340 , m_pMenuHack(nullptr)
10341 , m_pMenuHackAlign(pMenuAlign
)
10342 , m_nButtonPressSeen(true)
10345 , m_pPopover(nullptr)
10346 #if GTK_CHECK_VERSION(4, 0, 0)
10347 , m_aCustomBackground(GTK_WIDGET(pMenuButton
))
10350 #if !GTK_CHECK_VERSION(4, 0, 0)
10351 // tdf#142924 "toggled" is too late to use to populate changes to the menu,
10352 // so use "state-flag-changed" on GTK_STATE_FLAG_CHECKED instead which
10353 // happens before "toggled"
10354 g_signal_handler_disconnect(m_pToggleButton
, m_nToggledSignalId
);
10355 m_nToggledSignalId
= g_signal_connect(m_pToggleButton
, "state-flags-changed", G_CALLBACK(signalFlagsChanged
), this);
10357 m_pLabel
= gtk_bin_get_child(GTK_BIN(m_pMenuButton
));
10358 m_pImage
= get_image_widget(GTK_WIDGET(m_pMenuButton
));
10359 m_pBox
= formatMenuButton(m_pLabel
);
10361 GtkWidget
* pToggleButton
= gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton
));
10362 assert(GTK_IS_TOGGLE_BUTTON(pToggleButton
));
10363 m_pMenuButtonToggleButton
= GTK_TOGGLE_BUTTON(pToggleButton
);
10364 m_nToggledSignalId
= g_signal_connect(m_pMenuButtonToggleButton
, "state-flags-changed", G_CALLBACK(signalFlagsChanged
), this);
10365 GtkWidget
* pChild
= gtk_button_get_child(GTK_BUTTON(pToggleButton
));
10366 m_pBox
= GTK_IS_BOX(pChild
) ? GTK_BOX(pChild
) : nullptr;
10367 m_pLabel
= m_pBox
? gtk_widget_get_first_child(GTK_WIDGET(m_pBox
)) : nullptr;
10371 #if GTK_CHECK_VERSION(4, 0, 0)
10372 gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton
), "menu", m_pActionGroup
);
10374 update_action_group_from_popover_model();
10378 virtual void set_size_request(int nWidth
, int nHeight
) override
10380 // tweak the label to get a narrower size to stick
10381 if (GTK_IS_LABEL(m_pLabel
))
10382 gtk_label_set_ellipsize(GTK_LABEL(m_pLabel
), PANGO_ELLIPSIZE_MIDDLE
);
10383 gtk_widget_set_size_request(m_pWidget
, nWidth
, nHeight
);
10386 virtual void set_label(const OUString
& rText
) override
10388 ::set_label(GTK_LABEL(m_pLabel
), rText
);
10391 virtual OUString
get_label() const override
10393 return ::get_label(GTK_LABEL(m_pLabel
));
10396 virtual void set_image(VirtualDevice
* pDevice
) override
10398 ensure_image_widget();
10399 #if GTK_CHECK_VERSION(4, 0, 0)
10400 picture_set_from_virtual_device(m_pImage
, pDevice
);
10402 image_set_from_virtual_device(m_pImage
, pDevice
);
10406 virtual void set_image(const css::uno::Reference
<css::graphic::XGraphic
>& rImage
) override
10408 ensure_image_widget();
10409 #if GTK_CHECK_VERSION(4, 0, 0)
10410 picture_set_from_xgraphic(m_pImage
, rImage
);
10412 image_set_from_xgraphic(m_pImage
, rImage
);
10416 #if GTK_CHECK_VERSION(4, 0, 0)
10417 virtual void set_from_icon_name(const OUString
& rIconName
) override
10419 ensure_image_widget();
10420 picture_set_from_icon_name(m_pImage
, rIconName
);
10423 virtual void set_custom_button(VirtualDevice
* pDevice
) override
10425 m_aCustomBackground
.use_custom_content(pDevice
);
10428 virtual void set_inconsistent(bool inconsistent
) override
10431 gtk_widget_set_state_flags(GTK_WIDGET(m_pMenuButton
), GTK_STATE_FLAG_INCONSISTENT
, false);
10433 gtk_widget_unset_state_flags(GTK_WIDGET(m_pMenuButton
), GTK_STATE_FLAG_INCONSISTENT
);
10436 virtual bool get_inconsistent() const override
10438 return gtk_widget_get_state_flags(GTK_WIDGET(m_pMenuButton
)) & GTK_STATE_FLAG_INCONSISTENT
;
10441 virtual void set_active(bool active
) override
10443 disable_notify_events();
10444 set_inconsistent(false);
10446 gtk_menu_button_popup(m_pMenuButton
);
10448 gtk_menu_button_popdown(m_pMenuButton
);
10449 enable_notify_events();
10452 virtual bool get_active() const override
10454 GtkPopover
* pPopover
= gtk_menu_button_get_popover(m_pMenuButton
);
10455 return pPopover
&& gtk_widget_get_visible(GTK_WIDGET(pPopover
));
10458 virtual void set_font(const vcl::Font
& rFont
) override
10461 GtkLabel
* pChild
= ::get_label_widget(GTK_WIDGET(m_pMenuButton
));
10462 ::set_font(pChild
, rFont
);
10465 virtual vcl::Font
get_font() override
10469 return GtkInstanceWidget::get_font();
10472 virtual void set_active(bool bActive
) override
10474 bool bWasActive
= get_active();
10475 GtkInstanceToggleButton::set_active(bActive
);
10476 if (bWasActive
&& !bActive
&& gtk_widget_get_focus_on_click(GTK_WIDGET(m_pMenuButton
)))
10478 // grab focus back to the toggle button if the menu was popped down
10479 gtk_widget_grab_focus(GTK_WIDGET(m_pMenuButton
));
10484 virtual void insert_item(int pos
, const OUString
& rId
, const OUString
& rStr
,
10485 const OUString
* pIconName
, VirtualDevice
* pImageSurface
, TriState eCheckRadioFalse
) override
10487 MenuHelper::insert_item(pos
, rId
, rStr
, pIconName
, pImageSurface
, eCheckRadioFalse
);
10490 virtual void insert_separator(int pos
, const OUString
& rId
) override
10492 MenuHelper::insert_separator(pos
, rId
);
10495 virtual void remove_item(const OString
& rId
) override
10497 MenuHelper::remove_item(rId
);
10500 virtual void clear() override
10502 MenuHelper::clear_items();
10505 virtual void set_item_active(const OString
& rIdent
, bool bActive
) override
10507 MenuHelper::set_item_active(rIdent
, bActive
);
10510 virtual void set_item_sensitive(const OString
& rIdent
, bool bSensitive
) override
10512 MenuHelper::set_item_sensitive(rIdent
, bSensitive
);
10515 virtual void set_item_label(const OString
& rIdent
, const OUString
& rLabel
) override
10517 MenuHelper::set_item_label(rIdent
, rLabel
);
10520 virtual OUString
get_item_label(const OString
& rIdent
) const override
10522 return MenuHelper::get_item_label(rIdent
);
10525 virtual void set_item_visible(const OString
& rIdent
, bool bVisible
) override
10527 MenuHelper::set_item_visible(rIdent
, bVisible
);
10530 virtual void signal_item_activate(const OString
& rIdent
) override
10532 signal_selected(rIdent
);
10535 virtual void set_popover(weld::Widget
* pPopover
) override
10537 GtkInstanceWidget
* pPopoverWidget
= dynamic_cast<GtkInstanceWidget
*>(pPopover
);
10538 m_pPopover
= pPopoverWidget
? pPopoverWidget
->getWidget() : nullptr;
10540 #if GTK_CHECK_VERSION(4, 0, 0)
10541 gtk_menu_button_set_popover(m_pMenuButton
, m_pPopover
);
10542 update_action_group_from_popover_model();
10548 gtk_menu_button_set_popover(m_pMenuButton
, nullptr);
10554 //under wayland a Popover will work to "escape" the parent dialog, not
10555 //so under X, so come up with this hack to use a raw GtkWindow
10556 GdkDisplay
*pDisplay
= gtk_widget_get_display(m_pWidget
);
10557 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay
) && gtk_popover_get_constrain_to(GTK_POPOVER(m_pPopover
)) == GTK_POPOVER_CONSTRAINT_NONE
)
10559 m_pMenuHack
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP
));
10560 gtk_window_set_type_hint(m_pMenuHack
, GDK_WINDOW_TYPE_HINT_COMBO
);
10561 // See writer "format, watermark" for true here. Can't interact with the replacement popover otherwise.
10562 gtk_window_set_modal(m_pMenuHack
, true);
10563 gtk_window_set_resizable(m_pMenuHack
, false);
10564 m_nSignalId
= g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton
), "toggled", G_CALLBACK(signalMenuButtonToggled
), this);
10565 g_signal_connect(m_pMenuHack
, "key-press-event", G_CALLBACK(keyPress
), this);
10566 g_signal_connect(m_pMenuHack
, "grab-broken-event", G_CALLBACK(signalGrabBroken
), this);
10567 g_signal_connect(m_pMenuHack
, "button-press-event", G_CALLBACK(signalButtonPress
), this);
10568 g_signal_connect(m_pMenuHack
, "button-release-event", G_CALLBACK(signalButtonRelease
), this);
10574 GtkWidget
* pPlaceHolder
= gtk_popover_new(GTK_WIDGET(m_pMenuButton
));
10575 gtk_popover_set_transitions_enabled(GTK_POPOVER(pPlaceHolder
), false);
10577 // tdf#132540 theme the unwanted popover into invisibility
10578 GtkStyleContext
*pPopoverContext
= gtk_widget_get_style_context(pPlaceHolder
);
10579 GtkCssProvider
*pProvider
= gtk_css_provider_new();
10580 static const gchar data
[] = "popover { box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0; border-image: none; border-image-width: 0 0 0 0; background-image: none; background-color: transparent; border-radius: 0 0 0 0; border-width: 0 0 0 0; border-style: none; border-color: transparent; opacity: 0; min-height: 0; min-width: 0; }";
10581 css_provider_load_from_data(pProvider
, data
, -1);
10582 gtk_style_context_add_provider(pPopoverContext
, GTK_STYLE_PROVIDER(pProvider
),
10583 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
10585 gtk_menu_button_set_popover(m_pMenuButton
, pPlaceHolder
);
10589 gtk_menu_button_set_popover(m_pMenuButton
, m_pPopover
);
10590 gtk_widget_show_all(m_pPopover
);
10595 void set_menu(weld::Menu
* pMenu
);
10597 static GtkBox
* formatMenuButton(GtkWidget
* pLabel
)
10599 // format the GtkMenuButton "manually" so we can have the dropdown image in GtkMenuButtons shown
10600 // on the right at the same time as an image is shown on the left
10601 g_object_ref(pLabel
);
10602 GtkWidget
* pContainer
= gtk_widget_get_parent(pLabel
);
10603 #if !GTK_CHECK_VERSION(4, 0, 0)
10604 gtk_container_remove(GTK_CONTAINER(pContainer
), pLabel
);
10606 gtk_box_remove(GTK_BOX(pContainer
), pLabel
);
10609 gint
nImageSpacing(2);
10610 #if !GTK_CHECK_VERSION(4, 0, 0)
10611 GtkStyleContext
*pContext
= gtk_widget_get_style_context(pContainer
);
10612 gtk_style_context_get_style(pContext
, "image-spacing", &nImageSpacing
, nullptr);
10614 GtkBox
* pBox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, nImageSpacing
));
10616 #if !GTK_CHECK_VERSION(4, 0, 0)
10617 gtk_box_pack_start(pBox
, pLabel
, true, true, 0);
10619 gtk_widget_set_halign(pLabel
, GTK_ALIGN_START
);
10620 gtk_box_prepend(pBox
, pLabel
);
10622 g_object_unref(pLabel
);
10624 #if !GTK_CHECK_VERSION(4, 0, 0)
10625 if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(pContainer
)))
10626 gtk_box_pack_end(pBox
, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON
), false, false, 0);
10629 #if !GTK_CHECK_VERSION(4, 0, 0)
10630 gtk_container_add(GTK_CONTAINER(pContainer
), GTK_WIDGET(pBox
));
10632 gtk_box_prepend(GTK_BOX(pContainer
), GTK_WIDGET(pBox
));
10634 #if !GTK_CHECK_VERSION(4, 0, 0)
10635 gtk_widget_show_all(GTK_WIDGET(pBox
));
10637 gtk_widget_show(GTK_WIDGET(pBox
));
10643 #if GTK_CHECK_VERSION(4, 0, 0)
10644 virtual void disable_notify_events() override
10646 g_signal_handler_block(m_pMenuButtonToggleButton
, m_nToggledSignalId
);
10647 GtkInstanceWidget::disable_notify_events();
10650 virtual void enable_notify_events() override
10652 GtkInstanceWidget::enable_notify_events();
10653 g_signal_handler_unblock(m_pMenuButtonToggleButton
, m_nToggledSignalId
);
10657 virtual ~GtkInstanceMenuButton() override
10659 #if GTK_CHECK_VERSION(4, 0, 0)
10660 g_signal_handler_disconnect(m_pMenuButtonToggleButton
, m_nToggledSignalId
);
10664 g_signal_handler_disconnect(m_pMenuButton
, m_nSignalId
);
10665 gtk_menu_button_set_popover(m_pMenuButton
, nullptr);
10666 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack
));
10672 class GtkInstanceMenuToggleButton
: public GtkInstanceToggleButton
, public MenuHelper
10673 , public virtual weld::MenuToggleButton
10676 GtkBox
* m_pContainer
;
10677 GtkButton
* m_pToggleMenuButton
;
10678 GtkMenuButton
* m_pMenuButton
;
10679 gulong m_nMenuBtnClickedId
;
10680 gulong m_nToggleStateFlagsChangedId
;
10681 gulong m_nMenuBtnStateFlagsChangedId
;
10683 static void signalToggleStateFlagsChanged(GtkWidget
* pWidget
, GtkStateFlags
/*eFlags*/, gpointer widget
)
10685 GtkInstanceMenuToggleButton
* pThis
= static_cast<GtkInstanceMenuToggleButton
*>(widget
);
10686 // mirror togglebutton state to menubutton
10687 gtk_widget_set_state_flags(GTK_WIDGET(pThis
->m_pToggleMenuButton
), gtk_widget_get_state_flags(pWidget
), true);
10690 static void signalMenuBtnStateFlagsChanged(GtkWidget
* pWidget
, GtkStateFlags
/*eFlags*/, gpointer widget
)
10692 GtkInstanceMenuToggleButton
* pThis
= static_cast<GtkInstanceMenuToggleButton
*>(widget
);
10693 // mirror menubutton to togglebutton, keeping depressed state of menubutton
10694 GtkStateFlags eToggleFlags
= gtk_widget_get_state_flags(GTK_WIDGET(pThis
->m_pToggleButton
));
10695 GtkStateFlags eFlags
= gtk_widget_get_state_flags(pWidget
);
10696 GtkStateFlags eFinalFlags
= static_cast<GtkStateFlags
>((eFlags
& ~GTK_STATE_FLAG_ACTIVE
) |
10697 (eToggleFlags
& GTK_STATE_FLAG_ACTIVE
));
10698 gtk_widget_set_state_flags(GTK_WIDGET(pThis
->m_pToggleButton
), eFinalFlags
, true);
10701 static void signalMenuBtnClicked(GtkButton
*, gpointer widget
)
10703 GtkInstanceMenuToggleButton
* pThis
= static_cast<GtkInstanceMenuToggleButton
*>(widget
);
10704 pThis
->launch_menu();
10709 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleMenuButton
), gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton
)), true);
10710 GtkWidget
* pWidget
= GTK_WIDGET(m_pToggleButton
);
10712 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
10713 //it during DispatchCommand, returning now to the outer loop causes the
10714 //launching PopupMenu to be destroyed, instead run the subloop here
10715 //until the gtk menu is destroyed
10716 GMainLoop
* pLoop
= g_main_loop_new(nullptr, true);
10718 #if GTK_CHECK_VERSION(4, 0, 0)
10719 gulong nSignalId
= g_signal_connect_swapped(G_OBJECT(m_pMenu
), "closed", G_CALLBACK(g_main_loop_quit
), pLoop
);
10721 g_object_ref(m_pMenu
);
10722 gtk_menu_button_set_popover(m_pMenuButton
, nullptr);
10723 gtk_widget_set_parent(GTK_WIDGET(m_pMenu
), pWidget
);
10724 gtk_popover_set_position(GTK_POPOVER(m_pMenu
), GTK_POS_BOTTOM
);
10725 gtk_popover_popup(GTK_POPOVER(m_pMenu
));
10727 gulong nSignalId
= g_signal_connect_swapped(G_OBJECT(m_pMenu
), "deactivate", G_CALLBACK(g_main_loop_quit
), pLoop
);
10729 #if GTK_CHECK_VERSION(3,22,0)
10730 if (gtk_check_version(3, 22, 0) == nullptr)
10732 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
10733 // before trying to launch the menu
10734 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
10735 // Fixed in GTK 2.34
10736 GdkEvent
*pKeyEvent
= GtkSalFrame::makeFakeKeyPress(pWidget
);
10737 gtk_main_do_event(pKeyEvent
);
10739 GdkEvent
*pTriggerEvent
= gtk_get_current_event();
10740 if (!pTriggerEvent
)
10741 pTriggerEvent
= pKeyEvent
;
10743 gtk_menu_popup_at_widget(m_pMenu
, pWidget
, GDK_GRAVITY_SOUTH_WEST
, GDK_GRAVITY_NORTH_WEST
, pTriggerEvent
);
10745 gdk_event_free(pKeyEvent
);
10753 //typically there is an event, and we can then distinguish if this was
10754 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
10756 GdkEvent
*pEvent
= gtk_get_current_event();
10759 gdk_event_get_button(pEvent
, &nButton
);
10760 nTime
= gdk_event_get_time(pEvent
);
10765 nTime
= GtkSalFrame::GetLastInputEventTime();
10768 gtk_menu_popup(m_pMenu
, nullptr, nullptr, nullptr, nullptr, nButton
, nTime
);
10772 if (g_main_loop_is_running(pLoop
))
10773 main_loop_run(pLoop
);
10775 g_main_loop_unref(pLoop
);
10776 g_signal_handler_disconnect(m_pMenu
, nSignalId
);
10778 #if GTK_CHECK_VERSION(4, 0, 0)
10779 gtk_widget_unparent(GTK_WIDGET(m_pMenu
));
10780 gtk_menu_button_set_popover(m_pMenuButton
, GTK_WIDGET(m_pMenu
));
10781 g_object_unref(m_pMenu
);
10786 static gboolean
signalMenuToggleButton(GtkWidget
*, gboolean bGroupCycling
, gpointer widget
)
10788 GtkInstanceMenuToggleButton
* pThis
= static_cast<GtkInstanceMenuToggleButton
*>(widget
);
10789 return gtk_widget_mnemonic_activate(GTK_WIDGET(pThis
->m_pToggleButton
), bGroupCycling
);
10793 GtkInstanceMenuToggleButton(GtkBuilder
* pMenuToggleButtonBuilder
, GtkMenuButton
* pMenuButton
,
10794 GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
10795 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder
, "togglebutton")),
10796 pBuilder
, bTakeOwnership
)
10797 #if !GTK_CHECK_VERSION(4, 0, 0)
10798 , MenuHelper(gtk_menu_button_get_popup(pMenuButton
), false)
10800 , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton
)), false)
10802 , m_pContainer(GTK_BOX(gtk_builder_get_object(pMenuToggleButtonBuilder
, "box")))
10803 , m_pToggleMenuButton(GTK_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder
, "menubutton")))
10804 , m_pMenuButton(pMenuButton
)
10805 , m_nMenuBtnClickedId(g_signal_connect(m_pToggleMenuButton
, "clicked", G_CALLBACK(signalMenuBtnClicked
), this))
10806 , m_nToggleStateFlagsChangedId(g_signal_connect(m_pToggleButton
, "state-flags-changed", G_CALLBACK(signalToggleStateFlagsChanged
), this))
10807 , m_nMenuBtnStateFlagsChangedId(g_signal_connect(m_pToggleMenuButton
, "state-flags-changed", G_CALLBACK(signalMenuBtnStateFlagsChanged
), this))
10809 #if !GTK_CHECK_VERSION(4, 0, 0)
10810 GtkInstanceMenuButton::formatMenuButton(gtk_bin_get_child(GTK_BIN(m_pMenuButton
)));
10813 insertAsParent(GTK_WIDGET(m_pMenuButton
), GTK_WIDGET(m_pContainer
));
10814 gtk_widget_hide(GTK_WIDGET(m_pMenuButton
));
10816 // move the first GtkMenuButton child, as created by GtkInstanceMenuButton ctor, into the GtkToggleButton
10817 // instead, leaving just the indicator behind in the GtkMenuButton
10818 #if !GTK_CHECK_VERSION(4, 0, 0)
10819 GtkWidget
* pButtonBox
= gtk_bin_get_child(GTK_BIN(m_pMenuButton
));
10820 GList
* pChildren
= gtk_container_get_children(GTK_CONTAINER(pButtonBox
));
10822 for (GList
* pChild
= g_list_first(pChildren
); pChild
&& nGroup
< 2; pChild
= g_list_next(pChild
), ++nGroup
)
10824 GtkWidget
* pWidget
= static_cast<GtkWidget
*>(pChild
->data
);
10825 g_object_ref(pWidget
);
10826 gtk_container_remove(GTK_CONTAINER(pButtonBox
), pWidget
);
10828 gtk_container_add(GTK_CONTAINER(m_pToggleButton
), pWidget
);
10830 gtk_container_add(GTK_CONTAINER(m_pToggleMenuButton
), pWidget
);
10831 gtk_widget_show_all(pWidget
);
10832 g_object_unref(pWidget
);
10834 g_list_free(pChildren
);
10837 if (gtk_check_version(4, 5, 0) == nullptr)
10839 pChild
= gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton
));
10840 pChild
= gtk_widget_get_first_child(pChild
);
10841 pChild
= gtk_widget_get_first_child(pChild
);
10844 pChild
= gtk_widget_get_last_child(GTK_WIDGET(m_pMenuButton
));
10845 g_object_ref(pChild
);
10846 gtk_widget_unparent(pChild
);
10847 gtk_button_set_child(GTK_BUTTON(m_pToggleButton
), pChild
);
10848 g_object_unref(pChild
);
10851 // match the GtkToggleButton relief to the GtkMenuButton
10852 #if !GTK_CHECK_VERSION(4, 0, 0)
10853 const GtkReliefStyle eStyle
= gtk_button_get_relief(GTK_BUTTON(m_pMenuButton
));
10854 gtk_button_set_relief(GTK_BUTTON(m_pToggleButton
), eStyle
);
10855 gtk_button_set_relief(GTK_BUTTON(m_pToggleMenuButton
), eStyle
);
10857 const bool bStyle
= gtk_menu_button_get_has_frame(GTK_MENU_BUTTON(m_pMenuButton
));
10858 gtk_button_set_has_frame(GTK_BUTTON(m_pToggleButton
), bStyle
);
10859 gtk_button_set_has_frame(GTK_BUTTON(m_pToggleMenuButton
), bStyle
);
10862 // move the GtkMenuButton margins up to the new parent
10863 gtk_widget_set_margin_top(GTK_WIDGET(m_pContainer
),
10864 gtk_widget_get_margin_top(GTK_WIDGET(m_pMenuButton
)));
10865 gtk_widget_set_margin_bottom(GTK_WIDGET(m_pContainer
),
10866 gtk_widget_get_margin_bottom(GTK_WIDGET(m_pMenuButton
)));
10867 gtk_widget_set_margin_start(GTK_WIDGET(m_pContainer
),
10868 gtk_widget_get_margin_start(GTK_WIDGET(m_pMenuButton
)));
10869 gtk_widget_set_margin_end(GTK_WIDGET(m_pContainer
),
10870 gtk_widget_get_margin_end(GTK_WIDGET(m_pMenuButton
)));
10872 #if !GTK_CHECK_VERSION(4, 0, 0)
10873 gtk_menu_detach(m_pMenu
);
10874 gtk_menu_attach_to_widget(m_pMenu
, GTK_WIDGET(m_pToggleButton
), nullptr);
10876 gtk_widget_insert_action_group(GTK_WIDGET(m_pContainer
), "menu", m_pActionGroup
);
10878 update_action_group_from_popover_model();
10881 g_signal_connect(m_pContainer
, "mnemonic-activate", G_CALLBACK(signalMenuToggleButton
), this);
10884 virtual void disable_notify_events() override
10886 g_signal_handler_block(m_pToggleMenuButton
, m_nMenuBtnClickedId
);
10887 GtkInstanceToggleButton::disable_notify_events();
10890 virtual void enable_notify_events() override
10892 GtkInstanceToggleButton::enable_notify_events();
10893 g_signal_handler_unblock(m_pToggleMenuButton
, m_nMenuBtnClickedId
);
10896 virtual ~GtkInstanceMenuToggleButton()
10898 g_signal_handler_disconnect(m_pToggleButton
, m_nToggleStateFlagsChangedId
);
10899 g_signal_handler_disconnect(m_pToggleMenuButton
, m_nMenuBtnStateFlagsChangedId
);
10900 g_signal_handler_disconnect(m_pToggleMenuButton
, m_nMenuBtnClickedId
);
10902 #if GTK_CHECK_VERSION(4, 0, 0)
10903 GtkWidget
* pChild
= gtk_button_get_child(GTK_BUTTON(m_pToggleButton
));
10904 g_object_ref(pChild
);
10905 gtk_button_set_child(GTK_BUTTON(m_pToggleButton
), nullptr);
10906 gtk_widget_unparent(pChild
);
10907 gtk_widget_set_parent(pChild
, GTK_WIDGET(m_pMenuButton
));
10908 g_object_unref(pChild
);
10912 virtual void insert_item(int pos
, const OUString
& rId
, const OUString
& rStr
,
10913 const OUString
* pIconName
, VirtualDevice
* pImageSurface
, TriState eCheckRadioFalse
) override
10915 MenuHelper::insert_item(pos
, rId
, rStr
, pIconName
, pImageSurface
, eCheckRadioFalse
);
10918 virtual void insert_separator(int pos
, const OUString
& rId
) override
10920 MenuHelper::insert_separator(pos
, rId
);
10923 virtual void remove_item(const OString
& rId
) override
10925 MenuHelper::remove_item(rId
);
10928 virtual void clear() override
10930 MenuHelper::clear_items();
10933 virtual void set_item_active(const OString
& rIdent
, bool bActive
) override
10935 MenuHelper::set_item_active(rIdent
, bActive
);
10938 virtual void set_item_sensitive(const OString
& rIdent
, bool bSensitive
) override
10940 MenuHelper::set_item_sensitive(rIdent
, bSensitive
);
10943 virtual void set_item_label(const OString
& rIdent
, const OUString
& rLabel
) override
10945 MenuHelper::set_item_label(rIdent
, rLabel
);
10948 virtual OUString
get_item_label(const OString
& rIdent
) const override
10950 return MenuHelper::get_item_label(rIdent
);
10953 virtual void set_item_visible(const OString
& rIdent
, bool bVisible
) override
10955 MenuHelper::set_item_visible(rIdent
, bVisible
);
10958 virtual void signal_item_activate(const OString
& rIdent
) override
10960 signal_selected(rIdent
);
10963 virtual void set_popover(weld::Widget
* /*pPopover*/) override
10965 assert(false && "not implemented");
10969 class GtkInstanceMenu
: public MenuHelper
, public virtual weld::Menu
10972 #if !GTK_CHECK_VERSION(4, 0, 0)
10973 std::vector
<GtkMenuItem
*> m_aExtraItems
;
10975 OString m_sActivated
;
10976 #if !GTK_CHECK_VERSION(4, 0, 0)
10977 MenuHelper
* m_pTopLevelMenuHelper
;
10981 virtual void signal_item_activate(const OString
& rIdent
) override
10983 m_sActivated
= rIdent
;
10984 weld::Menu::signal_activate(m_sActivated
);
10987 #if !GTK_CHECK_VERSION(4, 0, 0)
10988 void clear_extras()
10990 if (m_aExtraItems
.empty())
10992 if (m_pTopLevelMenuHelper
)
10994 for (auto a
: m_aExtraItems
)
10995 m_pTopLevelMenuHelper
->remove_from_map(a
);
10997 m_aExtraItems
.clear();
11002 #if !GTK_CHECK_VERSION(4, 0, 0)
11003 GtkInstanceMenu(GtkMenu
* pMenu
, bool bTakeOwnership
)
11005 GtkInstanceMenu(GtkPopoverMenu
* pMenu
, bool bTakeOwnership
)
11007 : MenuHelper(pMenu
, bTakeOwnership
)
11008 #if !GTK_CHECK_VERSION(4, 0, 0)
11009 , m_pTopLevelMenuHelper(nullptr)
11012 g_object_set_data(G_OBJECT(m_pMenu
), "g-lo-GtkInstanceMenu", this);
11013 #if !GTK_CHECK_VERSION(4, 0, 0)
11014 // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
11015 // then find that MenuButton parent so that when adding items to this
11016 // menu we can inform the MenuButton of their addition
11017 GtkMenu
* pTopLevelMenu
= pMenu
;
11020 GtkWidget
* pAttached
= gtk_menu_get_attach_widget(pTopLevelMenu
);
11021 if (!pAttached
|| !GTK_IS_MENU_ITEM(pAttached
))
11023 GtkWidget
* pParent
= gtk_widget_get_parent(pAttached
);
11024 if (!pParent
|| !GTK_IS_MENU(pParent
))
11026 pTopLevelMenu
= GTK_MENU(pParent
);
11028 if (pTopLevelMenu
== pMenu
)
11031 // maybe the toplevel is a menubutton
11032 GtkWidget
* pAttached
= gtk_menu_get_attach_widget(pTopLevelMenu
);
11033 if (pAttached
&& GTK_IS_MENU_BUTTON(pAttached
))
11035 void* pData
= g_object_get_data(G_OBJECT(pAttached
), "g-lo-GtkInstanceButton");
11036 m_pTopLevelMenuHelper
= dynamic_cast<GtkInstanceMenuButton
*>(static_cast<GtkInstanceButton
*>(pData
));
11039 if (!m_pTopLevelMenuHelper
)
11041 void* pData
= g_object_get_data(G_OBJECT(pTopLevelMenu
), "g-lo-GtkInstanceMenu");
11042 m_pTopLevelMenuHelper
= static_cast<GtkInstanceMenu
*>(pData
);
11045 update_action_group_from_popover_model();
11049 virtual OString
popup_at_rect(weld::Widget
* pParent
, const tools::Rectangle
& rRect
, weld::Placement ePlace
) override
11051 m_sActivated
.clear();
11053 GtkInstanceWidget
* pGtkWidget
= dynamic_cast<GtkInstanceWidget
*>(pParent
);
11054 assert(pGtkWidget
);
11055 GtkWidget
* pWidget
= pGtkWidget
->getWidget();
11057 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
11058 //it during DispatchCommand, returning now to the outer loop causes the
11059 //launching PopupMenu to be destroyed, instead run the subloop here
11060 //until the gtk menu is destroyed
11061 GMainLoop
* pLoop
= g_main_loop_new(nullptr, true);
11063 #if GTK_CHECK_VERSION(4, 0, 0)
11064 gtk_widget_insert_action_group(pWidget
, "menu", m_pActionGroup
);
11066 gulong nSignalId
= g_signal_connect_swapped(G_OBJECT(m_pMenu
), "closed", G_CALLBACK(g_main_loop_quit
), pLoop
);
11068 GdkRectangle aRect
;
11069 pWidget
= getPopupRect(pWidget
, rRect
, aRect
);
11071 GtkWidget
* pOrigParent
= gtk_widget_get_parent(GTK_WIDGET(m_pMenu
));
11072 gtk_widget_set_parent(GTK_WIDGET(m_pMenu
), pWidget
);
11073 gtk_popover_set_pointing_to(GTK_POPOVER(m_pMenu
), &aRect
);
11074 if (ePlace
== weld::Placement::Under
)
11075 gtk_popover_set_position(GTK_POPOVER(m_pMenu
), GTK_POS_BOTTOM
);
11078 if (SwapForRTL(pWidget
))
11079 gtk_popover_set_position(GTK_POPOVER(m_pMenu
), GTK_POS_LEFT
);
11081 gtk_popover_set_position(GTK_POPOVER(m_pMenu
), GTK_POS_RIGHT
);
11083 gtk_popover_popup(GTK_POPOVER(m_pMenu
));
11085 gulong nSignalId
= g_signal_connect_swapped(G_OBJECT(m_pMenu
), "deactivate", G_CALLBACK(g_main_loop_quit
), pLoop
);
11087 #if GTK_CHECK_VERSION(3,22,0)
11088 if (gtk_check_version(3, 22, 0) == nullptr)
11090 GdkRectangle aRect
;
11091 pWidget
= getPopupRect(pWidget
, rRect
, aRect
);
11092 gtk_menu_attach_to_widget(m_pMenu
, pWidget
, nullptr);
11094 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
11095 // before trying to launch the menu
11096 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
11097 // Fixed in GTK 2.34
11098 GdkEvent
*pKeyEvent
= GtkSalFrame::makeFakeKeyPress(pWidget
);
11099 gtk_main_do_event(pKeyEvent
);
11101 GdkEvent
*pTriggerEvent
= gtk_get_current_event();
11102 if (!pTriggerEvent
)
11103 pTriggerEvent
= pKeyEvent
;
11105 bool bSwapForRTL
= SwapForRTL(pWidget
);
11107 if (ePlace
== weld::Placement::Under
)
11110 gtk_menu_popup_at_rect(m_pMenu
, widget_get_surface(pWidget
), &aRect
, GDK_GRAVITY_SOUTH_EAST
, GDK_GRAVITY_NORTH_EAST
, pTriggerEvent
);
11112 gtk_menu_popup_at_rect(m_pMenu
, widget_get_surface(pWidget
), &aRect
, GDK_GRAVITY_SOUTH_WEST
, GDK_GRAVITY_NORTH_WEST
, pTriggerEvent
);
11117 gtk_menu_popup_at_rect(m_pMenu
, widget_get_surface(pWidget
), &aRect
, GDK_GRAVITY_NORTH_WEST
, GDK_GRAVITY_NORTH_EAST
, pTriggerEvent
);
11119 gtk_menu_popup_at_rect(m_pMenu
, widget_get_surface(pWidget
), &aRect
, GDK_GRAVITY_NORTH_EAST
, GDK_GRAVITY_NORTH_WEST
, pTriggerEvent
);
11122 gdk_event_free(pKeyEvent
);
11129 gtk_menu_attach_to_widget(m_pMenu
, pWidget
, nullptr);
11134 //typically there is an event, and we can then distinguish if this was
11135 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
11137 GdkEvent
*pEvent
= gtk_get_current_event();
11140 if (!gdk_event_get_button(pEvent
, &nButton
))
11142 nTime
= gdk_event_get_time(pEvent
);
11147 nTime
= GtkSalFrame::GetLastInputEventTime();
11150 gtk_menu_popup(m_pMenu
, nullptr, nullptr, nullptr, nullptr, nButton
, nTime
);
11154 if (g_main_loop_is_running(pLoop
))
11155 main_loop_run(pLoop
);
11157 g_main_loop_unref(pLoop
);
11158 g_signal_handler_disconnect(m_pMenu
, nSignalId
);
11160 #if GTK_CHECK_VERSION(4, 0, 0)
11162 gtk_widget_unparent(GTK_WIDGET(m_pMenu
));
11164 gtk_widget_set_parent(GTK_WIDGET(m_pMenu
), pOrigParent
);
11166 gtk_widget_insert_action_group(pWidget
, "menu", nullptr);
11168 gtk_menu_detach(m_pMenu
);
11171 return m_sActivated
;
11174 virtual void set_sensitive(const OString
& rIdent
, bool bSensitive
) override
11176 set_item_sensitive(rIdent
, bSensitive
);
11179 virtual bool get_sensitive(const OString
& rIdent
) const override
11181 return get_item_sensitive(rIdent
);
11184 virtual void set_active(const OString
& rIdent
, bool bActive
) override
11186 set_item_active(rIdent
, bActive
);
11189 virtual bool get_active(const OString
& rIdent
) const override
11191 return get_item_active(rIdent
);
11194 virtual void set_visible(const OString
& rIdent
, bool bShow
) override
11196 set_item_visible(rIdent
, bShow
);
11199 virtual void set_label(const OString
& rIdent
, const OUString
& rLabel
) override
11201 set_item_label(rIdent
, rLabel
);
11204 virtual OUString
get_label(const OString
& rIdent
) const override
11206 return get_item_label(rIdent
);
11209 virtual void insert_separator(int pos
, const OUString
& rId
) override
11211 MenuHelper::insert_separator(pos
, rId
);
11214 virtual void clear() override
11216 #if !GTK_CHECK_VERSION(4, 0, 0)
11219 MenuHelper::clear_items();
11222 virtual void insert(int pos
, const OUString
& rId
, const OUString
& rStr
,
11223 const OUString
* pIconName
, VirtualDevice
* pImageSurface
,
11224 const css::uno::Reference
<css::graphic::XGraphic
>& rGraphic
,
11225 TriState eCheckRadioFalse
) override
11227 #if !GTK_CHECK_VERSION(4, 0, 0)
11228 GtkWidget
* pImage
= nullptr;
11231 if (GdkPixbuf
* pixbuf
= load_icon_by_name(*pIconName
))
11233 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
11234 g_object_unref(pixbuf
);
11237 else if (pImageSurface
)
11239 pImage
= image_new_from_virtual_device(*pImageSurface
);
11243 if (GdkPixbuf
* pixbuf
= getPixbuf(rGraphic
))
11245 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
11246 g_object_unref(pixbuf
);
11253 GtkBox
*pBox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6));
11254 GtkWidget
*pLabel
= gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr
).getStr());
11255 gtk_label_set_xalign(GTK_LABEL(pLabel
), 0.0);
11256 pItem
= eCheckRadioFalse
!= TRISTATE_INDET
? gtk_check_menu_item_new() : gtk_menu_item_new();
11257 gtk_box_pack_start(pBox
, pImage
, false, true, 0);
11258 gtk_box_pack_start(pBox
, pLabel
, true, true, 0);
11259 gtk_container_add(GTK_CONTAINER(pItem
), GTK_WIDGET(pBox
));
11260 gtk_widget_show_all(pItem
);
11264 pItem
= eCheckRadioFalse
!= TRISTATE_INDET
? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr
).getStr())
11265 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr
).getStr());
11268 if (eCheckRadioFalse
== TRISTATE_FALSE
)
11269 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem
), true);
11271 ::set_buildable_id(GTK_BUILDABLE(pItem
), OUStringToOString(rId
, RTL_TEXTENCODING_UTF8
));
11272 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu
), pItem
);
11273 gtk_widget_show(pItem
);
11274 GtkMenuItem
* pMenuItem
= GTK_MENU_ITEM(pItem
);
11275 m_aExtraItems
.push_back(pMenuItem
);
11276 add_to_map(pMenuItem
);
11277 if (m_pTopLevelMenuHelper
)
11278 m_pTopLevelMenuHelper
->add_to_map(pMenuItem
);
11280 gtk_menu_reorder_child(m_pMenu
, pItem
, pos
);
11282 SAL_WARN("vcl.gtk", "needs to be implemented for gtk4");
11287 (void)pImageSurface
;
11289 (void)eCheckRadioFalse
;
11293 virtual OString
get_id(int pos
) const override
11295 return get_item_id(pos
);
11298 virtual int n_children() const override
11300 return get_n_children();
11303 void remove(const OString
& rIdent
) override
11305 #if !GTK_CHECK_VERSION(4, 0, 0)
11306 if (!m_aExtraItems
.empty())
11308 GtkMenuItem
* pMenuItem
= m_aMap
[rIdent
];
11309 auto iter
= std::find(m_aExtraItems
.begin(), m_aExtraItems
.end(), pMenuItem
);
11310 if (iter
!= m_aExtraItems
.end())
11312 if (m_pTopLevelMenuHelper
)
11313 m_pTopLevelMenuHelper
->remove_from_map(pMenuItem
);
11314 m_aExtraItems
.erase(iter
);
11318 MenuHelper::remove_item(rIdent
);
11321 virtual ~GtkInstanceMenu() override
11323 #if !GTK_CHECK_VERSION(4, 0, 0)
11326 g_object_steal_data(G_OBJECT(m_pMenu
), "g-lo-GtkInstanceMenu");
11330 #if !GTK_CHECK_VERSION(4, 0, 0)
11331 vcl::ImageType
GtkToVcl(GtkIconSize eSize
)
11333 vcl::ImageType eRet
;
11336 #if !GTK_CHECK_VERSION(4, 0, 0)
11337 case GTK_ICON_SIZE_MENU
:
11338 case GTK_ICON_SIZE_SMALL_TOOLBAR
:
11339 case GTK_ICON_SIZE_BUTTON
:
11340 eRet
= vcl::ImageType::Size16
;
11342 case GTK_ICON_SIZE_LARGE_TOOLBAR
:
11343 eRet
= vcl::ImageType::Size26
;
11345 case GTK_ICON_SIZE_DND
:
11346 case GTK_ICON_SIZE_DIALOG
:
11347 eRet
= vcl::ImageType::Size32
;
11350 case GTK_ICON_SIZE_INVALID
:
11351 eRet
= vcl::ImageType::Small
;
11354 case GTK_ICON_SIZE_LARGE
:
11355 eRet
= vcl::ImageType::Size32
;
11357 case GTK_ICON_SIZE_NORMAL
:
11359 eRet
= vcl::ImageType::Size16
;
11366 GtkIconSize
VclToGtk(vcl::ImageType eSize
)
11369 #if !GTK_CHECK_VERSION(4, 0, 0)
11372 case vcl::ImageType::Size16
:
11373 eRet
= GTK_ICON_SIZE_SMALL_TOOLBAR
;
11375 case vcl::ImageType::Size26
:
11376 eRet
= GTK_ICON_SIZE_LARGE_TOOLBAR
;
11378 case vcl::ImageType::Size32
:
11379 eRet
= GTK_ICON_SIZE_DIALOG
;
11387 case vcl::ImageType::Size26
:
11388 case vcl::ImageType::Size32
:
11389 eRet
= GTK_ICON_SIZE_LARGE
;
11391 case vcl::ImageType::Size16
:
11393 eRet
= GTK_ICON_SIZE_NORMAL
;
11402 void GtkInstanceMenuButton::set_menu(weld::Menu
* pMenu
)
11404 GtkInstanceMenu
* pPopoverWidget
= dynamic_cast<GtkInstanceMenu
*>(pMenu
);
11405 m_pPopover
= nullptr;
11406 m_pMenu
= pPopoverWidget
? pPopoverWidget
->getMenu() : nullptr;
11408 #if !GTK_CHECK_VERSION(4, 0, 0)
11409 gtk_menu_button_set_popup(m_pMenuButton
, GTK_WIDGET(m_pMenu
));
11411 gtk_menu_button_set_popover(m_pMenuButton
, GTK_WIDGET(m_pMenu
));
11412 update_action_group_from_popover_model();
11418 class GtkInstanceToolbar
: public GtkInstanceWidget
, public virtual weld::Toolbar
11421 #if !GTK_CHECK_VERSION(4, 0, 0)
11422 GtkToolbar
* m_pToolbar
;
11424 GtkBox
* m_pToolbar
;
11425 vcl::ImageType m_eImageType
;
11427 GtkCssProvider
*m_pMenuButtonProvider
;
11429 std::map
<OString
, GtkWidget
*> m_aMap
;
11430 std::map
<OString
, std::unique_ptr
<GtkInstanceMenuButton
>> m_aMenuButtonMap
;
11432 #if !GTK_CHECK_VERSION(4, 0, 0)
11433 // at the time of writing there is no gtk_menu_tool_button_set_popover available
11434 // though there will be in the future
11435 // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
11436 static void find_menu_button(GtkWidget
*pWidget
, gpointer user_data
)
11438 if (g_strcmp0(gtk_widget_get_name(pWidget
), "GtkMenuButton") == 0)
11440 GtkWidget
**ppToggleButton
= static_cast<GtkWidget
**>(user_data
);
11441 *ppToggleButton
= pWidget
;
11443 else if (GTK_IS_CONTAINER(pWidget
))
11444 gtk_container_forall(GTK_CONTAINER(pWidget
), find_menu_button
, user_data
);
11447 static void find_menupeer_button(GtkWidget
*pWidget
, gpointer user_data
)
11449 if (g_strcmp0(gtk_widget_get_name(pWidget
), "GtkButton") == 0)
11451 GtkWidget
**ppButton
= static_cast<GtkWidget
**>(user_data
);
11452 *ppButton
= pWidget
;
11454 else if (GTK_IS_CONTAINER(pWidget
))
11455 gtk_container_forall(GTK_CONTAINER(pWidget
), find_menupeer_button
, user_data
);
11459 static void collect(GtkWidget
* pItem
, gpointer widget
)
11461 #if !GTK_CHECK_VERSION(4, 0, 0)
11462 if (!GTK_IS_TOOL_ITEM(pItem
))
11465 GtkInstanceToolbar
* pThis
= static_cast<GtkInstanceToolbar
*>(widget
);
11467 GtkMenuButton
* pMenuButton
= nullptr;
11468 #if !GTK_CHECK_VERSION(4, 0, 0)
11469 if (GTK_IS_MENU_TOOL_BUTTON(pItem
))
11470 find_menu_button(pItem
, &pMenuButton
);
11472 if (GTK_IS_MENU_BUTTON(pItem
))
11473 pMenuButton
= GTK_MENU_BUTTON(pItem
);
11476 pThis
->add_to_map(pItem
, pMenuButton
);
11479 void add_to_map(GtkWidget
* pToolItem
, GtkMenuButton
* pMenuButton
)
11481 OString id
= ::get_buildable_id(GTK_BUILDABLE(pToolItem
));
11482 m_aMap
[id
] = pToolItem
;
11485 m_aMenuButtonMap
[id
] = std::make_unique
<GtkInstanceMenuButton
>(pMenuButton
, GTK_WIDGET(pToolItem
), m_pBuilder
, false);
11486 // so that, e.g. with focus initially in writer main document then
11487 // after clicking the heading menu in the writer navigator focus is
11488 // left in the main document and not in the toolbar
11489 #if !GTK_CHECK_VERSION(4, 0, 0)
11490 gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton
), false);
11491 g_signal_connect(pMenuButton
, "toggled", G_CALLBACK(signalItemToggled
), this);
11493 gtk_widget_set_focus_on_click(GTK_WIDGET(pMenuButton
), false);
11495 GtkWidget
* pToggleButton
= gtk_widget_get_first_child(GTK_WIDGET(pMenuButton
));
11496 assert(GTK_IS_TOGGLE_BUTTON(pToggleButton
));
11497 g_signal_connect(pToggleButton
, "toggled", G_CALLBACK(signalItemToggled
), this);
11500 // by default the GtkMenuButton down arrow button is as wide as
11501 // a normal button and LibreOffice's original ones are very
11502 // narrow, that assumption is fairly baked into the toolbar and
11503 // sidebar designs, try and minimize the width of the dropdown
11505 GtkStyleContext
*pButtonContext
= gtk_widget_get_style_context(GTK_WIDGET(pMenuButton
));
11507 if (!m_pMenuButtonProvider
)
11509 m_pMenuButtonProvider
= gtk_css_provider_new();
11510 static const gchar data
[] = "* { "
11512 "margin-left: 0px;"
11513 "margin-right: 0px;"
11516 css_provider_load_from_data(m_pMenuButtonProvider
, data
, -1);
11519 gtk_style_context_add_provider(pButtonContext
,
11520 GTK_STYLE_PROVIDER(m_pMenuButtonProvider
),
11521 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
11523 #if !GTK_CHECK_VERSION(4, 0, 0)
11524 if (!GTK_IS_TOOL_BUTTON(pToolItem
))
11526 if (!GTK_IS_BUTTON(pToolItem
))
11531 g_signal_connect(pToolItem
, "clicked", G_CALLBACK(signalItemClicked
), this);
11534 #if !GTK_CHECK_VERSION(4, 0, 0)
11535 static void signalItemClicked(GtkToolButton
* pItem
, gpointer widget
)
11537 static void signalItemClicked(GtkButton
* pItem
, gpointer widget
)
11540 GtkInstanceToolbar
* pThis
= static_cast<GtkInstanceToolbar
*>(widget
);
11541 SolarMutexGuard aGuard
;
11542 pThis
->signal_item_clicked(pItem
);
11545 #if !GTK_CHECK_VERSION(4, 0, 0)
11546 void signal_item_clicked(GtkToolButton
* pItem
)
11548 void signal_item_clicked(GtkButton
* pItem
)
11551 signal_clicked(::get_buildable_id(GTK_BUILDABLE(pItem
)));
11554 static void signalItemToggled(GtkToggleButton
* pItem
, gpointer widget
)
11556 GtkInstanceToolbar
* pThis
= static_cast<GtkInstanceToolbar
*>(widget
);
11557 SolarMutexGuard aGuard
;
11558 pThis
->signal_item_toggled(pItem
);
11561 void signal_item_toggled(GtkToggleButton
* pItem
)
11563 for (const auto& a
: m_aMenuButtonMap
)
11565 #if !GTK_CHECK_VERSION(4, 0, 0)
11566 if (a
.second
->getWidget() == GTK_WIDGET(pItem
))
11568 if (a
.second
->getWidget() == gtk_widget_get_parent(GTK_WIDGET(pItem
)))
11571 signal_toggle_menu(a
.first
);
11577 #if GTK_CHECK_VERSION(4, 0, 0)
11578 static void set_item_image(GtkWidget
* pItem
, GtkWidget
* pImage
)
11580 if (GTK_IS_BUTTON(pItem
))
11581 gtk_button_set_child(GTK_BUTTON(pItem
), pImage
);
11582 else if (GTK_IS_MENU_BUTTON(pItem
))
11584 // TODO after gtk 4.6 is released require that version and drop this
11585 static auto menu_button_set_child
= reinterpret_cast<void (*) (GtkMenuButton
*, GtkWidget
*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
11586 if (menu_button_set_child
)
11587 menu_button_set_child(GTK_MENU_BUTTON(pItem
), pImage
);
11589 // versions of gtk4 > 4.2.1 might do this on their own
11590 gtk_widget_remove_css_class(pItem
, "text-button");
11594 #if !GTK_CHECK_VERSION(4, 0, 0)
11595 static void set_item_image(GtkToolButton
* pItem
, const css::uno::Reference
<css::graphic::XGraphic
>& rIcon
)
11597 static void set_item_image(GtkWidget
* pItem
, const css::uno::Reference
<css::graphic::XGraphic
>& rIcon
)
11600 GtkWidget
* pImage
= nullptr;
11602 if (GdkPixbuf
* pixbuf
= getPixbuf(rIcon
))
11604 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
11605 g_object_unref(pixbuf
);
11606 gtk_widget_show(pImage
);
11609 #if !GTK_CHECK_VERSION(4, 0, 0)
11610 gtk_tool_button_set_icon_widget(pItem
, pImage
);
11612 set_item_image(pItem
, pImage
);
11616 #if !GTK_CHECK_VERSION(4, 0, 0)
11617 void set_item_image(GtkToolButton
* pItem
, const VirtualDevice
* pDevice
)
11619 void set_item_image(GtkWidget
* pItem
, const VirtualDevice
* pDevice
)
11622 GtkWidget
* pImage
= nullptr;
11626 #if GTK_CHECK_VERSION(4, 0, 0)
11627 pImage
= picture_new_from_virtual_device(*pDevice
);
11629 pImage
= image_new_from_virtual_device(*pDevice
);
11631 gtk_widget_show(pImage
);
11634 #if !GTK_CHECK_VERSION(4, 0, 0)
11635 gtk_tool_button_set_icon_widget(pItem
, pImage
);
11637 set_item_image(pItem
, pImage
);
11639 gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar
));
11642 #if !GTK_CHECK_VERSION(4, 0, 0)
11643 GtkWidget
* toolbar_get_nth_item(int nIndex
) const
11645 return GTK_WIDGET(gtk_toolbar_get_nth_item(m_pToolbar
, nIndex
));
11648 GtkWidget
* toolbar_get_nth_item(int nIndex
) const
11651 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar
));
11652 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
11662 #if !GTK_CHECK_VERSION(4, 0, 0)
11663 GtkInstanceToolbar(GtkToolbar
* pToolbar
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
11665 GtkInstanceToolbar(GtkBox
* pToolbar
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
11667 : GtkInstanceWidget(GTK_WIDGET(pToolbar
), pBuilder
, bTakeOwnership
)
11668 , m_pToolbar(pToolbar
)
11669 #if GTK_CHECK_VERSION(4, 0, 0)
11670 , m_eImageType(vcl::ImageType::Size16
)
11672 , m_pMenuButtonProvider(nullptr)
11674 #if GTK_CHECK_VERSION(4, 0, 0)
11675 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(pToolbar
));
11676 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
11678 collect(pChild
, this);
11681 gtk_container_foreach(GTK_CONTAINER(pToolbar
), collect
, this);
11685 void disable_item_notify_events()
11687 for (auto& a
: m_aMap
)
11689 g_signal_handlers_block_by_func(a
.second
, reinterpret_cast<void*>(signalItemClicked
), this);
11693 void enable_item_notify_events()
11695 for (auto& a
: m_aMap
)
11697 g_signal_handlers_unblock_by_func(a
.second
, reinterpret_cast<void*>(signalItemClicked
), this);
11701 virtual void set_item_sensitive(const OString
& rIdent
, bool bSensitive
) override
11703 disable_item_notify_events();
11704 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap
[rIdent
]), bSensitive
);
11705 enable_item_notify_events();
11708 virtual bool get_item_sensitive(const OString
& rIdent
) const override
11710 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap
.find(rIdent
)->second
));
11713 virtual void set_item_visible(const OString
& rIdent
, bool bVisible
) override
11715 disable_item_notify_events();
11716 gtk_widget_set_visible(GTK_WIDGET(m_aMap
[rIdent
]), bVisible
);
11717 enable_item_notify_events();
11720 virtual void set_item_help_id(const OString
& rIdent
, const OString
& rHelpId
) override
11722 ::set_help_id(GTK_WIDGET(m_aMap
[rIdent
]), rHelpId
);
11725 virtual bool get_item_visible(const OString
& rIdent
) const override
11727 return gtk_widget_get_visible(GTK_WIDGET(m_aMap
.find(rIdent
)->second
));
11730 virtual void set_item_active(const OString
& rIdent
, bool bActive
) override
11732 disable_item_notify_events();
11734 GtkWidget
* pToolButton
= m_aMap
.find(rIdent
)->second
;
11736 #if !GTK_CHECK_VERSION(4, 0, 0)
11737 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton
))
11738 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton
), bActive
);
11741 GtkButton
* pButton
= nullptr;
11742 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
11744 find_menupeer_button(GTK_WIDGET(pToolButton
), &pButton
);
11747 auto eState
= gtk_widget_get_state_flags(GTK_WIDGET(pButton
)) & ~GTK_STATE_FLAG_CHECKED
;
11749 eState
|= GTK_STATE_FLAG_CHECKED
;
11750 gtk_widget_set_state_flags(GTK_WIDGET(pButton
), static_cast<GtkStateFlags
>(eState
), true);
11754 GtkWidget
* pWidget
;
11755 if (GTK_IS_MENU_BUTTON(pToolButton
))
11757 pWidget
= gtk_widget_get_first_child(pToolButton
);
11758 assert(GTK_IS_TOGGLE_BUTTON(pWidget
));
11761 pWidget
= pToolButton
;
11762 auto eState
= gtk_widget_get_state_flags(pWidget
) & ~GTK_STATE_FLAG_CHECKED
;
11764 eState
|= GTK_STATE_FLAG_CHECKED
;
11765 gtk_widget_set_state_flags(pWidget
, static_cast<GtkStateFlags
>(eState
), true);
11768 enable_item_notify_events();
11771 virtual bool get_item_active(const OString
& rIdent
) const override
11773 GtkWidget
* pToolButton
= m_aMap
.find(rIdent
)->second
;
11775 #if !GTK_CHECK_VERSION(4, 0, 0)
11776 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton
))
11777 return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton
));
11780 GtkButton
* pButton
= nullptr;
11781 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
11783 find_menupeer_button(GTK_WIDGET(pToolButton
), &pButton
);
11786 return gtk_widget_get_state_flags(GTK_WIDGET(pButton
)) & GTK_STATE_FLAG_CHECKED
;
11790 GtkWidget
* pWidget
;
11791 if (GTK_IS_MENU_BUTTON(pToolButton
))
11793 pWidget
= gtk_widget_get_first_child(pToolButton
);
11794 assert(GTK_IS_TOGGLE_BUTTON(pWidget
));
11797 pWidget
= pToolButton
;
11798 return gtk_widget_get_state_flags(pWidget
) & GTK_STATE_FLAG_CHECKED
;
11804 virtual void set_menu_item_active(const OString
& rIdent
, bool bActive
) override
11806 disable_item_notify_events();
11808 auto aFind
= m_aMenuButtonMap
.find(rIdent
);
11809 assert (aFind
!= m_aMenuButtonMap
.end());
11810 aFind
->second
->set_active(bActive
);
11812 enable_item_notify_events();
11815 virtual bool get_menu_item_active(const OString
& rIdent
) const override
11817 auto aFind
= m_aMenuButtonMap
.find(rIdent
);
11818 assert (aFind
!= m_aMenuButtonMap
.end());
11819 return aFind
->second
->get_active();
11822 virtual void insert_item(int pos
, const OUString
& rId
) override
11824 OString sId
= OUStringToOString(rId
, RTL_TEXTENCODING_UTF8
);
11825 #if !GTK_CHECK_VERSION(4, 0, 0)
11826 GtkToolItem
* pItem
= gtk_tool_button_new(nullptr, sId
.getStr());
11828 GtkWidget
* pItem
= gtk_button_new();
11830 ::set_buildable_id(GTK_BUILDABLE(pItem
), sId
);
11831 #if !GTK_CHECK_VERSION(4, 0, 0)
11832 gtk_toolbar_insert(m_pToolbar
, pItem
, pos
);
11834 gtk_box_insert_child_after(m_pToolbar
, pItem
, toolbar_get_nth_item(pos
- 1));
11836 gtk_widget_show(GTK_WIDGET(pItem
));
11837 add_to_map(GTK_WIDGET(pItem
), nullptr);
11840 virtual void insert_separator(int pos
, const OUString
& rId
) override
11842 OString sId
= OUStringToOString(rId
, RTL_TEXTENCODING_UTF8
);
11843 #if !GTK_CHECK_VERSION(4, 0, 0)
11844 GtkToolItem
* pItem
= gtk_separator_tool_item_new();
11846 GtkWidget
* pItem
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
11848 ::set_buildable_id(GTK_BUILDABLE(pItem
), sId
);
11849 #if !GTK_CHECK_VERSION(4, 0, 0)
11850 gtk_toolbar_insert(m_pToolbar
, pItem
, pos
);
11852 gtk_box_insert_child_after(m_pToolbar
, pItem
, toolbar_get_nth_item(pos
- 1));
11854 gtk_widget_show(GTK_WIDGET(pItem
));
11857 virtual void set_item_popover(const OString
& rIdent
, weld::Widget
* pPopover
) override
11859 m_aMenuButtonMap
[rIdent
]->set_popover(pPopover
);
11862 virtual void set_item_menu(const OString
& rIdent
, weld::Menu
* pMenu
) override
11864 m_aMenuButtonMap
[rIdent
]->set_menu(pMenu
);
11867 virtual int get_n_items() const override
11869 #if !GTK_CHECK_VERSION(4, 0, 0)
11870 return gtk_toolbar_get_n_items(m_pToolbar
);
11873 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar
));
11874 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
11882 virtual OString
get_item_ident(int nIndex
) const override
11884 auto* pItem
= toolbar_get_nth_item(nIndex
);
11885 return ::get_buildable_id(GTK_BUILDABLE(pItem
));
11888 virtual void set_item_ident(int nIndex
, const OString
& rIdent
) override
11890 OString
sOldIdent(get_item_ident(nIndex
));
11891 m_aMap
.erase(m_aMap
.find(sOldIdent
));
11893 auto* pItem
= toolbar_get_nth_item(nIndex
);
11894 ::set_buildable_id(GTK_BUILDABLE(pItem
), rIdent
);
11896 // to keep the ids unique, if the new id is already in use by an item,
11897 // change the id of that item to the now unused old ident of this item
11898 auto aFind
= m_aMap
.find(rIdent
);
11899 if (aFind
!= m_aMap
.end())
11901 GtkWidget
* pDupIdItem
= aFind
->second
;
11902 ::set_buildable_id(GTK_BUILDABLE(pDupIdItem
), sOldIdent
);
11903 m_aMap
[sOldIdent
] = pDupIdItem
;
11906 m_aMap
[rIdent
] = pItem
;
11909 virtual void set_item_label(int nIndex
, const OUString
& rLabel
) override
11911 auto* pItem
= toolbar_get_nth_item(nIndex
);
11912 #if !GTK_CHECK_VERSION(4, 0, 0)
11913 if (!GTK_IS_TOOL_BUTTON(pItem
))
11915 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem
), MapToGtkAccelerator(rLabel
).getStr());
11917 if (!GTK_IS_BUTTON(pItem
))
11919 ::button_set_label(GTK_BUTTON(pItem
), rLabel
);
11923 virtual void set_item_label(const OString
& rIdent
, const OUString
& rLabel
) override
11925 GtkWidget
* pItem
= m_aMap
[rIdent
];
11926 #if !GTK_CHECK_VERSION(4, 0, 0)
11927 if (!pItem
|| !GTK_IS_TOOL_BUTTON(pItem
))
11929 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem
), MapToGtkAccelerator(rLabel
).getStr());
11931 if (!pItem
|| !GTK_IS_BUTTON(pItem
))
11933 ::button_set_label(GTK_BUTTON(pItem
), rLabel
);
11937 OUString
get_item_label(const OString
& rIdent
) const override
11939 #if !GTK_CHECK_VERSION(4, 0, 0)
11940 const gchar
* pText
= gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap
.find(rIdent
)->second
));
11942 const gchar
* pText
= gtk_button_get_label(GTK_BUTTON(m_aMap
.find(rIdent
)->second
));
11944 return OUString(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
11947 virtual void set_item_icon_name(const OString
& rIdent
, const OUString
& rIconName
) override
11949 GtkWidget
* pItem
= m_aMap
[rIdent
];
11950 #if !GTK_CHECK_VERSION(4, 0, 0)
11951 if (!pItem
|| !GTK_IS_TOOL_BUTTON(pItem
))
11954 if (!pItem
|| !GTK_IS_BUTTON(pItem
))
11958 GtkWidget
* pImage
= nullptr;
11960 if (GdkPixbuf
* pixbuf
= getPixbuf(rIconName
))
11962 pImage
= gtk_image_new_from_pixbuf(pixbuf
);
11963 g_object_unref(pixbuf
);
11964 gtk_widget_show(pImage
);
11967 #if !GTK_CHECK_VERSION(4, 0, 0)
11968 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem
), pImage
);
11970 gtk_button_set_child(GTK_BUTTON(pItem
), pImage
);
11971 // versions of gtk4 > 4.2.1 might do this on their own
11972 gtk_widget_remove_css_class(GTK_WIDGET(pItem
), "text-button");
11976 virtual void set_item_image(const OString
& rIdent
, const css::uno::Reference
<css::graphic::XGraphic
>& rIcon
) override
11978 GtkWidget
* pItem
= m_aMap
[rIdent
];
11979 #if !GTK_CHECK_VERSION(4, 0, 0)
11980 if (!pItem
|| !GTK_IS_TOOL_BUTTON(pItem
))
11982 set_item_image(GTK_TOOL_BUTTON(pItem
), rIcon
);
11986 set_item_image(pItem
, rIcon
);
11990 virtual void set_item_image(const OString
& rIdent
, VirtualDevice
* pDevice
) override
11992 GtkWidget
* pItem
= m_aMap
[rIdent
];
11993 #if !GTK_CHECK_VERSION(4, 0, 0)
11994 if (!pItem
|| !GTK_IS_TOOL_BUTTON(pItem
))
11996 set_item_image(GTK_TOOL_BUTTON(pItem
), pDevice
);
12000 set_item_image(pItem
, pDevice
);
12004 virtual void set_item_image(int nIndex
, const css::uno::Reference
<css::graphic::XGraphic
>& rIcon
) override
12006 auto* pItem
= toolbar_get_nth_item(nIndex
);
12007 #if !GTK_CHECK_VERSION(4, 0, 0)
12008 if (!GTK_IS_TOOL_BUTTON(pItem
))
12010 set_item_image(GTK_TOOL_BUTTON(pItem
), rIcon
);
12012 set_item_image(pItem
, rIcon
);
12016 virtual void set_item_tooltip_text(int nIndex
, const OUString
& rTip
) override
12018 auto* pItem
= toolbar_get_nth_item(nIndex
);
12019 gtk_widget_set_tooltip_text(GTK_WIDGET(pItem
), OUStringToOString(rTip
, RTL_TEXTENCODING_UTF8
).getStr());
12022 virtual void set_item_tooltip_text(const OString
& rIdent
, const OUString
& rTip
) override
12024 GtkWidget
* pItem
= GTK_WIDGET(m_aMap
[rIdent
]);
12025 gtk_widget_set_tooltip_text(pItem
, OUStringToOString(rTip
, RTL_TEXTENCODING_UTF8
).getStr());
12028 virtual OUString
get_item_tooltip_text(const OString
& rIdent
) const override
12030 GtkWidget
* pItem
= GTK_WIDGET(m_aMap
.find(rIdent
)->second
);
12031 const gchar
* pStr
= gtk_widget_get_tooltip_text(pItem
);
12032 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
12035 virtual vcl::ImageType
get_icon_size() const override
12037 #if GTK_CHECK_VERSION(4, 0, 0)
12038 return m_eImageType
;
12040 return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar
));
12044 virtual void set_icon_size(vcl::ImageType eType
) override
12046 #if GTK_CHECK_VERSION(4, 0, 0)
12047 m_eImageType
= eType
;
12049 gtk_toolbar_set_icon_size(m_pToolbar
, VclToGtk(eType
));
12053 virtual sal_uInt16
get_modifier_state() const override
12055 #if GTK_CHECK_VERSION(4, 0, 0)
12056 GdkDisplay
* pDisplay
= gtk_widget_get_display(GTK_WIDGET(m_pToolbar
));
12057 GdkSeat
* pSeat
= gdk_display_get_default_seat(pDisplay
);
12058 GdkDevice
* pDevice
= gdk_seat_get_keyboard(pSeat
);
12059 guint nState
= gdk_device_get_modifier_state(pDevice
);
12061 GdkKeymap
* pKeymap
= gdk_keymap_get_default();
12062 guint nState
= gdk_keymap_get_modifier_state(pKeymap
);
12064 return GtkSalFrame::GetKeyModCode(nState
);
12067 virtual int get_drop_index(const Point
& rPoint
) const override
12069 #if !GTK_CHECK_VERSION(4, 0, 0)
12070 return gtk_toolbar_get_drop_index(m_pToolbar
, rPoint
.X(), rPoint
.Y());
12072 GtkWidget
* pToolbar
= GTK_WIDGET(m_pToolbar
);
12073 GtkWidget
* pTarget
= gtk_widget_pick(pToolbar
, rPoint
.X(), rPoint
.Y(), GTK_PICK_DEFAULT
);
12074 if (!pTarget
|| pTarget
== pToolbar
)
12077 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar
));
12078 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
12080 if (pChild
== pTarget
)
12088 virtual bool has_focus() const override
12090 if (gtk_widget_has_focus(m_pWidget
))
12093 GtkWidget
* pTopLevel
= widget_get_toplevel(m_pWidget
);
12094 if (!GTK_IS_WINDOW(pTopLevel
))
12096 GtkWidget
* pFocus
= gtk_window_get_focus(GTK_WINDOW(pTopLevel
));
12099 return gtk_widget_is_ancestor(pFocus
, m_pWidget
);
12102 virtual void grab_focus() override
12106 gtk_widget_grab_focus(m_pWidget
);
12107 #if GTK_CHECK_VERSION(4, 0, 0)
12108 bool bHasFocusChild
= gtk_widget_get_focus_child(m_pWidget
);
12110 bool bHasFocusChild
= gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget
));
12112 if (!bHasFocusChild
)
12114 if (auto* pItem
= toolbar_get_nth_item(0))
12116 #if GTK_CHECK_VERSION(4, 0, 0)
12117 gtk_widget_set_focus_child(m_pWidget
, GTK_WIDGET(pItem
));
12119 gtk_container_set_focus_child(GTK_CONTAINER(m_pWidget
), GTK_WIDGET(pItem
));
12121 bHasFocusChild
= true;
12124 if (bHasFocusChild
)
12126 #if GTK_CHECK_VERSION(4, 0, 0)
12127 gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget
), GTK_DIR_TAB_FORWARD
);
12129 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget
)), GTK_DIR_TAB_FORWARD
);
12134 virtual ~GtkInstanceToolbar() override
12136 for (auto& a
: m_aMap
)
12137 g_signal_handlers_disconnect_by_data(a
.second
, this);
12145 class GtkInstanceLinkButton
: public GtkInstanceWidget
, public virtual weld::LinkButton
12148 GtkLinkButton
* m_pButton
;
12149 gulong m_nSignalId
;
12151 static bool signalActivateLink(GtkButton
*, gpointer widget
)
12153 GtkInstanceLinkButton
* pThis
= static_cast<GtkInstanceLinkButton
*>(widget
);
12154 SolarMutexGuard aGuard
;
12155 return pThis
->signal_activate_link();
12159 GtkInstanceLinkButton(GtkLinkButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12160 : GtkInstanceWidget(GTK_WIDGET(pButton
), pBuilder
, bTakeOwnership
)
12161 , m_pButton(pButton
)
12162 , m_nSignalId(g_signal_connect(pButton
, "activate-link", G_CALLBACK(signalActivateLink
), this))
12166 virtual void set_label(const OUString
& rText
) override
12168 ::button_set_label(GTK_BUTTON(m_pButton
), rText
);
12171 virtual OUString
get_label() const override
12173 return ::button_get_label(GTK_BUTTON(m_pButton
));
12176 virtual void set_uri(const OUString
& rText
) override
12178 gtk_link_button_set_uri(m_pButton
, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
12181 virtual OUString
get_uri() const override
12183 const gchar
* pStr
= gtk_link_button_get_uri(m_pButton
);
12184 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
12187 virtual void disable_notify_events() override
12189 g_signal_handler_block(m_pButton
, m_nSignalId
);
12190 GtkInstanceWidget::disable_notify_events();
12193 virtual void enable_notify_events() override
12195 GtkInstanceWidget::enable_notify_events();
12196 g_signal_handler_unblock(m_pButton
, m_nSignalId
);
12199 virtual ~GtkInstanceLinkButton() override
12201 g_signal_handler_disconnect(m_pButton
, m_nSignalId
);
12209 class GtkInstanceCheckButton
: public GtkInstanceWidget
, public virtual weld::CheckButton
12212 GtkCheckButton
* m_pCheckButton
;
12213 gulong m_nSignalId
;
12215 static void signalToggled(void*, gpointer widget
)
12217 GtkInstanceCheckButton
* pThis
= static_cast<GtkInstanceCheckButton
*>(widget
);
12218 SolarMutexGuard aGuard
;
12219 pThis
->signal_toggled();
12223 GtkInstanceCheckButton(GtkCheckButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12224 : GtkInstanceWidget(GTK_WIDGET(pButton
), pBuilder
, bTakeOwnership
)
12225 , m_pCheckButton(pButton
)
12226 , m_nSignalId(g_signal_connect(m_pCheckButton
, "toggled", G_CALLBACK(signalToggled
), this))
12230 virtual void set_active(bool active
) override
12232 disable_notify_events();
12233 #if GTK_CHECK_VERSION(4, 0, 0)
12234 gtk_check_button_set_inconsistent(m_pCheckButton
, false);
12235 gtk_check_button_set_active(m_pCheckButton
, active
);
12237 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton
), false);
12238 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pCheckButton
), active
);
12240 enable_notify_events();
12243 virtual bool get_active() const override
12245 #if GTK_CHECK_VERSION(4, 0, 0)
12246 return gtk_check_button_get_active(m_pCheckButton
);
12248 return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pCheckButton
));
12252 virtual void set_inconsistent(bool inconsistent
) override
12254 #if GTK_CHECK_VERSION(4, 0, 0)
12255 gtk_check_button_set_inconsistent(m_pCheckButton
, inconsistent
);
12257 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton
), inconsistent
);
12261 virtual bool get_inconsistent() const override
12263 #if GTK_CHECK_VERSION(4, 0, 0)
12264 return gtk_check_button_get_inconsistent(m_pCheckButton
);
12266 return gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton
));
12270 virtual void set_label(const OUString
& rText
) override
12272 #if GTK_CHECK_VERSION(4, 0, 0)
12273 gtk_check_button_set_label(m_pCheckButton
, MapToGtkAccelerator(rText
).getStr());
12275 ::button_set_label(GTK_BUTTON(m_pCheckButton
), rText
);
12279 virtual OUString
get_label() const override
12281 #if GTK_CHECK_VERSION(4, 0, 0)
12282 const gchar
* pStr
= gtk_check_button_get_label(m_pCheckButton
);
12283 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
12285 return ::button_get_label(GTK_BUTTON(m_pCheckButton
));
12289 virtual void set_label_wrap(bool bWrap
) override
12291 GtkLabel
* pChild
= ::get_label_widget(GTK_WIDGET(m_pCheckButton
));
12292 ::set_label_wrap(pChild
, bWrap
);
12295 virtual void disable_notify_events() override
12297 g_signal_handler_block(m_pCheckButton
, m_nSignalId
);
12298 GtkInstanceWidget::disable_notify_events();
12301 virtual void enable_notify_events() override
12303 GtkInstanceWidget::enable_notify_events();
12304 g_signal_handler_unblock(m_pCheckButton
, m_nSignalId
);
12307 virtual ~GtkInstanceCheckButton() override
12309 g_signal_handler_disconnect(m_pCheckButton
, m_nSignalId
);
12313 class GtkInstanceRadioButton
: public GtkInstanceCheckButton
, public virtual weld::RadioButton
12316 #if GTK_CHECK_VERSION(4, 0, 0)
12317 GtkInstanceRadioButton(GtkCheckButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12318 : GtkInstanceCheckButton(pButton
, pBuilder
, bTakeOwnership
)
12320 GtkInstanceRadioButton(GtkRadioButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12321 : GtkInstanceCheckButton(GTK_CHECK_BUTTON(pButton
), pBuilder
, bTakeOwnership
)
12331 class GtkInstanceScale
: public GtkInstanceWidget
, public virtual weld::Scale
12334 GtkScale
* m_pScale
;
12335 gulong m_nValueChangedSignalId
;
12337 static void signalValueChanged(GtkScale
*, gpointer widget
)
12339 GtkInstanceScale
* pThis
= static_cast<GtkInstanceScale
*>(widget
);
12340 SolarMutexGuard aGuard
;
12341 pThis
->signal_value_changed();
12345 GtkInstanceScale(GtkScale
* pScale
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12346 : GtkInstanceWidget(GTK_WIDGET(pScale
), pBuilder
, bTakeOwnership
)
12348 , m_nValueChangedSignalId(g_signal_connect(m_pScale
, "value-changed", G_CALLBACK(signalValueChanged
), this))
12352 virtual void disable_notify_events() override
12354 g_signal_handler_block(m_pScale
, m_nValueChangedSignalId
);
12355 GtkInstanceWidget::disable_notify_events();
12358 virtual void enable_notify_events() override
12360 GtkInstanceWidget::enable_notify_events();
12361 g_signal_handler_unblock(m_pScale
, m_nValueChangedSignalId
);
12364 virtual void set_value(int value
) override
12366 disable_notify_events();
12367 gtk_range_set_value(GTK_RANGE(m_pScale
), value
);
12368 enable_notify_events();
12371 virtual void set_range(int min
, int max
) override
12373 disable_notify_events();
12374 gtk_range_set_range(GTK_RANGE(m_pScale
), min
, max
);
12375 enable_notify_events();
12378 virtual void set_increments(int step
, int page
) override
12380 disable_notify_events();
12381 gtk_range_set_increments(GTK_RANGE(m_pScale
), step
, page
);
12382 enable_notify_events();
12385 virtual void get_increments(int& step
, int& page
) const override
12387 GtkAdjustment
* pAdjustment
= gtk_range_get_adjustment(GTK_RANGE(m_pScale
));
12388 step
= gtk_adjustment_get_step_increment(pAdjustment
);
12389 page
= gtk_adjustment_get_page_increment(pAdjustment
);
12392 virtual int get_value() const override
12394 return gtk_range_get_value(GTK_RANGE(m_pScale
));
12397 virtual ~GtkInstanceScale() override
12399 g_signal_handler_disconnect(m_pScale
, m_nValueChangedSignalId
);
12403 class GtkInstanceProgressBar
: public GtkInstanceWidget
, public virtual weld::ProgressBar
12406 GtkProgressBar
* m_pProgressBar
;
12409 GtkInstanceProgressBar(GtkProgressBar
* pProgressBar
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12410 : GtkInstanceWidget(GTK_WIDGET(pProgressBar
), pBuilder
, bTakeOwnership
)
12411 , m_pProgressBar(pProgressBar
)
12415 virtual void set_percentage(int value
) override
12417 gtk_progress_bar_set_fraction(m_pProgressBar
, value
/ 100.0);
12420 virtual OUString
get_text() const override
12422 const gchar
* pText
= gtk_progress_bar_get_text(m_pProgressBar
);
12423 OUString
sRet(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
12427 virtual void set_text(const OUString
& rText
) override
12429 gtk_progress_bar_set_text(m_pProgressBar
, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
12433 class GtkInstanceSpinner
: public GtkInstanceWidget
, public virtual weld::Spinner
12436 GtkSpinner
* m_pSpinner
;
12439 GtkInstanceSpinner(GtkSpinner
* pSpinner
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12440 : GtkInstanceWidget(GTK_WIDGET(pSpinner
), pBuilder
, bTakeOwnership
)
12441 , m_pSpinner(pSpinner
)
12445 virtual void start() override
12447 gtk_spinner_start(m_pSpinner
);
12450 virtual void stop() override
12452 gtk_spinner_stop(m_pSpinner
);
12456 class GtkInstanceImage
: public GtkInstanceWidget
, public virtual weld::Image
12459 GtkImage
* m_pImage
;
12462 GtkInstanceImage(GtkImage
* pImage
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12463 : GtkInstanceWidget(GTK_WIDGET(pImage
), pBuilder
, bTakeOwnership
)
12468 virtual void set_from_icon_name(const OUString
& rIconName
) override
12470 image_set_from_icon_name(m_pImage
, rIconName
);
12473 virtual void set_image(VirtualDevice
* pDevice
) override
12475 image_set_from_virtual_device(m_pImage
, pDevice
);
12478 virtual void set_image(const css::uno::Reference
<css::graphic::XGraphic
>& rImage
) override
12480 image_set_from_xgraphic(m_pImage
, rImage
);
12484 #if GTK_CHECK_VERSION(4, 0, 0)
12485 class GtkInstancePicture
: public GtkInstanceWidget
, public virtual weld::Image
12488 GtkPicture
* m_pPicture
;
12491 GtkInstancePicture(GtkPicture
* pPicture
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12492 : GtkInstanceWidget(GTK_WIDGET(pPicture
), pBuilder
, bTakeOwnership
)
12493 , m_pPicture(pPicture
)
12495 gtk_picture_set_can_shrink(m_pPicture
, true);
12498 virtual void set_from_icon_name(const OUString
& rIconName
) override
12500 picture_set_from_icon_name(m_pPicture
, rIconName
);
12503 virtual void set_image(VirtualDevice
* pDevice
) override
12505 picture_set_from_virtual_device(m_pPicture
, pDevice
);
12508 virtual void set_image(const css::uno::Reference
<css::graphic::XGraphic
>& rPicture
) override
12510 picture_set_from_xgraphic(m_pPicture
, rPicture
);
12515 class GtkInstanceCalendar
: public GtkInstanceWidget
, public virtual weld::Calendar
12518 GtkCalendar
* m_pCalendar
;
12519 #if GTK_CHECK_VERSION(4, 0, 0)
12520 GtkEventController
* m_pKeyController
;
12522 gulong m_nDaySelectedSignalId
;
12523 gulong m_nDaySelectedDoubleClickSignalId
;
12524 gulong m_nKeyPressEventSignalId
;
12525 #if !GTK_CHECK_VERSION(4, 0, 0)
12526 gulong m_nButtonPressEventSignalId
;
12529 static void signalDaySelected(GtkCalendar
*, gpointer widget
)
12531 GtkInstanceCalendar
* pThis
= static_cast<GtkInstanceCalendar
*>(widget
);
12532 SolarMutexGuard aGuard
;
12533 pThis
->signal_selected();
12536 static void signalDaySelectedDoubleClick(GtkCalendar
*, gpointer widget
)
12538 GtkInstanceCalendar
* pThis
= static_cast<GtkInstanceCalendar
*>(widget
);
12539 SolarMutexGuard aGuard
;
12540 pThis
->signal_activated();
12543 bool signal_key_press(guint nKeyVal
)
12545 if (nKeyVal
== GDK_KEY_Return
|| nKeyVal
== GDK_KEY_KP_Enter
)
12547 SolarMutexGuard aGuard
;
12548 signal_activated();
12554 #if GTK_CHECK_VERSION(4, 0, 0)
12555 static gboolean
signalKeyPress(GtkEventControllerKey
*, guint nKeyVal
, guint
/*nKeyCode*/, GdkModifierType
, gpointer widget
)
12557 GtkInstanceCalendar
* pThis
= static_cast<GtkInstanceCalendar
*>(widget
);
12558 return pThis
->signal_key_press(nKeyVal
);
12561 static gboolean
signalKeyPress(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
12563 GtkInstanceCalendar
* pThis
= static_cast<GtkInstanceCalendar
*>(widget
);
12564 return pThis
->signal_key_press(pEvent
->keyval
);
12568 #if !GTK_CHECK_VERSION(4, 0, 0)
12569 static gboolean
signalButton(GtkWidget
*, GdkEventButton
*, gpointer
)
12571 // don't let button press get to parent window, for the case of the
12572 // ImplCFieldFloatWin floating window belonging to CalendarField where
12573 // the click on the calendar continues to the parent GtkWindow and
12574 // closePopup is called by GtkSalFrame::signalButton because the click
12575 // window isn't that of the floating parent GtkWindow
12581 GtkInstanceCalendar(GtkCalendar
* pCalendar
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12582 : GtkInstanceWidget(GTK_WIDGET(pCalendar
), pBuilder
, bTakeOwnership
)
12583 , m_pCalendar(pCalendar
)
12584 #if GTK_CHECK_VERSION(4, 0, 0)
12585 , m_pKeyController(gtk_event_controller_key_new())
12587 , m_nDaySelectedSignalId(g_signal_connect(pCalendar
, "day-selected", G_CALLBACK(signalDaySelected
), this))
12588 , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar
, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick
), this))
12589 #if GTK_CHECK_VERSION(4, 0, 0)
12590 , m_nKeyPressEventSignalId(g_signal_connect(m_pKeyController
, "key-pressed", G_CALLBACK(signalKeyPress
), this))
12592 , m_nKeyPressEventSignalId(g_signal_connect(pCalendar
, "key-press-event", G_CALLBACK(signalKeyPress
), this))
12593 , m_nButtonPressEventSignalId(g_signal_connect_after(pCalendar
, "button-press-event", G_CALLBACK(signalButton
), this))
12596 #if GTK_CHECK_VERSION(4, 0, 0)
12597 gtk_widget_add_controller(GTK_WIDGET(m_pCalendar
), m_pKeyController
);
12601 virtual void set_date(const Date
& rDate
) override
12603 if (!rDate
.IsValidAndGregorian())
12606 disable_notify_events();
12607 #if GTK_CHECK_VERSION(4, 0, 0)
12608 GDateTime
* pDateTime
= g_date_time_new_local(rDate
.GetYear(), rDate
.GetMonth(), rDate
.GetDay(), 0, 0, 0);
12609 gtk_calendar_select_day(m_pCalendar
, pDateTime
);
12610 g_date_time_unref(pDateTime
);
12612 gtk_calendar_select_month(m_pCalendar
, rDate
.GetMonth() - 1, rDate
.GetYear());
12613 gtk_calendar_select_day(m_pCalendar
, rDate
.GetDay());
12615 enable_notify_events();
12618 virtual Date
get_date() const override
12620 #if GTK_CHECK_VERSION(4, 0, 0)
12621 GDateTime
* pDateTime
= gtk_calendar_get_date(m_pCalendar
);
12622 Date
aDate(g_date_time_get_day_of_month(pDateTime
),
12623 g_date_time_get_month(pDateTime
),
12624 g_date_time_get_year(pDateTime
));
12625 g_date_time_unref(pDateTime
);
12628 guint year
, month
, day
;
12629 gtk_calendar_get_date(m_pCalendar
, &year
, &month
, &day
);
12630 return Date(day
, month
+ 1, year
);
12634 virtual void disable_notify_events() override
12636 g_signal_handler_block(m_pCalendar
, m_nDaySelectedDoubleClickSignalId
);
12637 g_signal_handler_block(m_pCalendar
, m_nDaySelectedSignalId
);
12638 GtkInstanceWidget::disable_notify_events();
12641 virtual void enable_notify_events() override
12643 GtkInstanceWidget::enable_notify_events();
12644 g_signal_handler_unblock(m_pCalendar
, m_nDaySelectedSignalId
);
12645 g_signal_handler_unblock(m_pCalendar
, m_nDaySelectedDoubleClickSignalId
);
12648 virtual ~GtkInstanceCalendar() override
12650 #if GTK_CHECK_VERSION(4, 0, 0)
12651 g_signal_handler_disconnect(m_pKeyController
, m_nKeyPressEventSignalId
);
12653 g_signal_handler_disconnect(m_pCalendar
, m_nButtonPressEventSignalId
);
12654 g_signal_handler_disconnect(m_pCalendar
, m_nKeyPressEventSignalId
);
12656 g_signal_handler_disconnect(m_pCalendar
, m_nDaySelectedDoubleClickSignalId
);
12657 g_signal_handler_disconnect(m_pCalendar
, m_nDaySelectedSignalId
);
12665 // CSS nodes: entry[.flat][.warning][.error]
12666 void set_widget_css_message_type(GtkWidget
* pWidget
, weld::EntryMessageType eType
)
12668 #if GTK_CHECK_VERSION(4, 0, 0)
12669 gtk_widget_remove_css_class(pWidget
, "error");
12670 gtk_widget_remove_css_class(pWidget
, "warning");
12672 GtkStyleContext
*pWidgetContext
= gtk_widget_get_style_context(pWidget
);
12673 gtk_style_context_remove_class(pWidgetContext
, "error");
12674 gtk_style_context_remove_class(pWidgetContext
, "warning");
12679 case weld::EntryMessageType::Normal
:
12681 case weld::EntryMessageType::Warning
:
12682 #if GTK_CHECK_VERSION(4, 0, 0)
12683 gtk_widget_add_css_class(pWidget
, "warning");
12685 gtk_style_context_add_class(pWidgetContext
, "warning");
12688 case weld::EntryMessageType::Error
:
12689 #if GTK_CHECK_VERSION(4, 0, 0)
12690 gtk_widget_add_css_class(pWidget
, "error");
12692 gtk_style_context_add_class(pWidgetContext
, "error");
12698 void set_entry_message_type(GtkEntry
* pEntry
, weld::EntryMessageType eType
)
12700 set_widget_css_message_type(GTK_WIDGET(pEntry
), eType
);
12703 case weld::EntryMessageType::Normal
:
12704 gtk_entry_set_icon_from_icon_name(pEntry
, GTK_ENTRY_ICON_SECONDARY
, nullptr);
12706 case weld::EntryMessageType::Warning
:
12707 gtk_entry_set_icon_from_icon_name(pEntry
, GTK_ENTRY_ICON_SECONDARY
, "dialog-warning");
12709 case weld::EntryMessageType::Error
:
12710 gtk_entry_set_icon_from_icon_name(pEntry
, GTK_ENTRY_ICON_SECONDARY
, "dialog-error");
12719 class GtkInstanceEditable
: public GtkInstanceWidget
, public virtual weld::Entry
12722 GtkEditable
* m_pEditable
;
12723 GtkWidget
* m_pDelegate
;
12724 WidgetFont m_aCustomFont
;
12726 gulong m_nChangedSignalId
;
12727 gulong m_nInsertTextSignalId
;
12728 gulong m_nCursorPosSignalId
;
12729 gulong m_nSelectionPosSignalId
;
12730 gulong m_nActivateSignalId
;
12732 static void signalChanged(GtkEditable
*, gpointer widget
)
12734 GtkInstanceEditable
* pThis
= static_cast<GtkInstanceEditable
*>(widget
);
12735 SolarMutexGuard aGuard
;
12736 pThis
->signal_changed();
12739 static void signalInsertText(GtkEditable
* pEditable
, const gchar
* pNewText
, gint nNewTextLength
,
12740 gint
* position
, gpointer widget
)
12742 GtkInstanceEditable
* pThis
= static_cast<GtkInstanceEditable
*>(widget
);
12743 SolarMutexGuard aGuard
;
12744 pThis
->signal_insert_text(pEditable
, pNewText
, nNewTextLength
, position
);
12747 void signal_insert_text(GtkEditable
* pEditable
, const gchar
* pNewText
, gint nNewTextLength
, gint
* position
)
12749 if (!m_aInsertTextHdl
.IsSet())
12751 OUString
sText(pNewText
, nNewTextLength
, RTL_TEXTENCODING_UTF8
);
12752 const bool bContinue
= m_aInsertTextHdl
.Call(sText
);
12753 if (bContinue
&& !sText
.isEmpty())
12755 OString
sFinalText(OUStringToOString(sText
, RTL_TEXTENCODING_UTF8
));
12756 g_signal_handlers_block_by_func(pEditable
, reinterpret_cast<gpointer
>(signalInsertText
), this);
12757 gtk_editable_insert_text(pEditable
, sFinalText
.getStr(), sFinalText
.getLength(), position
);
12758 g_signal_handlers_unblock_by_func(pEditable
, reinterpret_cast<gpointer
>(signalInsertText
), this);
12760 g_signal_stop_emission_by_name(pEditable
, "insert-text");
12763 static void signalCursorPosition(void*, GParamSpec
*, gpointer widget
)
12765 GtkInstanceEditable
* pThis
= static_cast<GtkInstanceEditable
*>(widget
);
12766 pThis
->signal_cursor_position();
12769 static void signalActivate(void*, gpointer widget
)
12771 GtkInstanceEditable
* pThis
= static_cast<GtkInstanceEditable
*>(widget
);
12772 pThis
->signal_activate();
12775 virtual void ensureMouseEventWidget() override
12777 // The GtkEntry is sufficient to get mouse events without an intermediate GtkEventBox
12778 if (!m_pMouseEventBox
)
12779 m_pMouseEventBox
= m_pDelegate
;
12784 virtual void signal_activate()
12786 if (m_aActivateHdl
.IsSet())
12788 SolarMutexGuard aGuard
;
12789 if (m_aActivateHdl
.Call(*this))
12790 g_signal_stop_emission_by_name(m_pDelegate
, "activate");
12794 PangoAttrList
* get_attributes()
12796 #if GTK_CHECK_VERSION(4, 0, 0)
12797 return gtk_text_get_attributes(GTK_TEXT(m_pDelegate
));
12799 return gtk_entry_get_attributes(GTK_ENTRY(m_pDelegate
));
12803 void set_attributes(PangoAttrList
* pAttrs
)
12805 #if GTK_CHECK_VERSION(4, 0, 0)
12806 gtk_text_set_attributes(GTK_TEXT(m_pDelegate
), pAttrs
);
12808 gtk_entry_set_attributes(GTK_ENTRY(m_pDelegate
), pAttrs
);
12813 GtkInstanceEditable(GtkWidget
* pWidget
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
12814 : GtkInstanceWidget(pWidget
, pBuilder
, bTakeOwnership
)
12815 , m_pEditable(GTK_EDITABLE(pWidget
))
12816 #if GTK_CHECK_VERSION(4, 0, 0)
12817 , m_pDelegate(GTK_WIDGET(gtk_editable_get_delegate(m_pEditable
)))
12819 , m_pDelegate(pWidget
)
12821 , m_aCustomFont(m_pWidget
)
12822 , m_nChangedSignalId(g_signal_connect(m_pEditable
, "changed", G_CALLBACK(signalChanged
), this))
12823 , m_nInsertTextSignalId(g_signal_connect(m_pEditable
, "insert-text", G_CALLBACK(signalInsertText
), this))
12824 , m_nCursorPosSignalId(g_signal_connect(m_pEditable
, "notify::cursor-position", G_CALLBACK(signalCursorPosition
), this))
12825 , m_nSelectionPosSignalId(g_signal_connect(m_pEditable
, "notify::selection-bound", G_CALLBACK(signalCursorPosition
), this))
12826 , m_nActivateSignalId(g_signal_connect(m_pDelegate
, "activate", G_CALLBACK(signalActivate
), this))
12830 virtual void set_text(const OUString
& rText
) override
12832 disable_notify_events();
12833 #if GTK_CHECK_VERSION(4, 0, 0)
12834 gtk_editable_set_text(m_pEditable
, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
12836 gtk_entry_set_text(GTK_ENTRY(m_pDelegate
), OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
12838 enable_notify_events();
12841 virtual OUString
get_text() const override
12843 #if GTK_CHECK_VERSION(4, 0, 0)
12844 const gchar
* pText
= gtk_editable_get_text(m_pEditable
);
12846 const gchar
* pText
= gtk_entry_get_text(GTK_ENTRY(m_pDelegate
));
12848 OUString
sRet(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
12852 virtual void set_width_chars(int nChars
) override
12854 disable_notify_events();
12855 #if GTK_CHECK_VERSION(4, 0, 0)
12856 gtk_editable_set_width_chars(m_pEditable
, nChars
);
12857 gtk_editable_set_max_width_chars(m_pEditable
, nChars
);
12859 gtk_entry_set_width_chars(GTK_ENTRY(m_pDelegate
), nChars
);
12860 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pDelegate
), nChars
);
12862 enable_notify_events();
12865 virtual int get_width_chars() const override
12867 #if GTK_CHECK_VERSION(4, 0, 0)
12868 return gtk_editable_get_width_chars(m_pEditable
);
12870 return gtk_entry_get_width_chars(GTK_ENTRY(m_pDelegate
));
12874 virtual void set_max_length(int nChars
) override
12876 disable_notify_events();
12877 #if GTK_CHECK_VERSION(4, 0, 0)
12878 gtk_text_set_max_length(GTK_TEXT(m_pDelegate
), nChars
);
12880 gtk_entry_set_max_length(GTK_ENTRY(m_pDelegate
), nChars
);
12882 enable_notify_events();
12885 virtual void select_region(int nStartPos
, int nEndPos
) override
12887 disable_notify_events();
12888 gtk_editable_select_region(m_pEditable
, nStartPos
, nEndPos
);
12889 enable_notify_events();
12892 bool get_selection_bounds(int& rStartPos
, int& rEndPos
) override
12894 return gtk_editable_get_selection_bounds(m_pEditable
, &rStartPos
, &rEndPos
);
12897 virtual void replace_selection(const OUString
& rText
) override
12899 disable_notify_events();
12900 gtk_editable_delete_selection(m_pEditable
);
12901 OString
sText(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
12902 gint position
= gtk_editable_get_position(m_pEditable
);
12903 gtk_editable_insert_text(m_pEditable
, sText
.getStr(), sText
.getLength(),
12905 enable_notify_events();
12908 virtual void set_position(int nCursorPos
) override
12910 disable_notify_events();
12911 gtk_editable_set_position(m_pEditable
, nCursorPos
);
12912 enable_notify_events();
12915 virtual int get_position() const override
12917 return gtk_editable_get_position(m_pEditable
);
12920 virtual void set_editable(bool bEditable
) override
12922 gtk_editable_set_editable(m_pEditable
, bEditable
);
12925 virtual bool get_editable() const override
12927 return gtk_editable_get_editable(m_pEditable
);
12930 virtual void set_overwrite_mode(bool bOn
) override
12932 #if GTK_CHECK_VERSION(4, 0, 0)
12933 gtk_text_set_overwrite_mode(GTK_TEXT(m_pDelegate
), bOn
);
12935 gtk_entry_set_overwrite_mode(GTK_ENTRY(m_pDelegate
), bOn
);
12939 virtual bool get_overwrite_mode() const override
12941 #if GTK_CHECK_VERSION(4, 0, 0)
12942 return gtk_text_get_overwrite_mode(GTK_TEXT(m_pDelegate
));
12944 return gtk_entry_get_overwrite_mode(GTK_ENTRY(m_pDelegate
));
12948 virtual void set_message_type(weld::EntryMessageType eType
) override
12950 #if GTK_CHECK_VERSION(4, 0, 0)
12951 if (!GTK_IS_ENTRY(m_pDelegate
))
12953 ::set_widget_css_message_type(m_pDelegate
, eType
);
12957 ::set_entry_message_type(GTK_ENTRY(m_pDelegate
), eType
);
12960 virtual void disable_notify_events() override
12962 g_signal_handler_block(m_pDelegate
, m_nActivateSignalId
);
12963 g_signal_handler_block(m_pEditable
, m_nSelectionPosSignalId
);
12964 g_signal_handler_block(m_pEditable
, m_nCursorPosSignalId
);
12965 g_signal_handler_block(m_pEditable
, m_nInsertTextSignalId
);
12966 g_signal_handler_block(m_pEditable
, m_nChangedSignalId
);
12967 GtkInstanceWidget::disable_notify_events();
12970 virtual void enable_notify_events() override
12972 GtkInstanceWidget::enable_notify_events();
12973 g_signal_handler_unblock(m_pEditable
, m_nChangedSignalId
);
12974 g_signal_handler_unblock(m_pEditable
, m_nInsertTextSignalId
);
12975 g_signal_handler_unblock(m_pEditable
, m_nCursorPosSignalId
);
12976 g_signal_handler_unblock(m_pEditable
, m_nSelectionPosSignalId
);
12977 g_signal_handler_unblock(m_pDelegate
, m_nActivateSignalId
);
12980 virtual vcl::Font
get_font() override
12982 if (const vcl::Font
* pFont
= m_aCustomFont
.get_custom_font())
12984 return GtkInstanceWidget::get_font();
12987 void set_font_color(const Color
& rColor
) override
12989 PangoAttrList
* pOrigList
= get_attributes();
12990 if (rColor
== COL_AUTO
&& !pOrigList
) // nothing to do
12993 PangoAttrType aFilterAttrs
[] = {PANGO_ATTR_FOREGROUND
, PANGO_ATTR_INVALID
};
12995 PangoAttrList
* pAttrs
= pOrigList
? pango_attr_list_copy(pOrigList
) : pango_attr_list_new();
12996 PangoAttrList
* pRemovedAttrs
= pOrigList
? pango_attr_list_filter(pAttrs
, filter_pango_attrs
, &aFilterAttrs
) : nullptr;
12998 if (rColor
!= COL_AUTO
)
12999 pango_attr_list_insert(pAttrs
, pango_attr_foreground_new(rColor
.GetRed()/255.0, rColor
.GetGreen()/255.0, rColor
.GetBlue()/255.0));
13001 set_attributes(pAttrs
);
13002 pango_attr_list_unref(pAttrs
);
13003 pango_attr_list_unref(pRemovedAttrs
);
13006 void fire_signal_changed()
13011 virtual void cut_clipboard() override
13013 #if GTK_CHECK_VERSION(4, 0, 0)
13014 gtk_widget_activate_action(m_pDelegate
, "cut.clipboard", nullptr);
13016 gtk_editable_cut_clipboard(m_pEditable
);
13020 virtual void copy_clipboard() override
13022 #if GTK_CHECK_VERSION(4, 0, 0)
13023 gtk_widget_activate_action(m_pDelegate
, "copy.clipboard", nullptr);
13025 gtk_editable_copy_clipboard(m_pEditable
);
13029 virtual void paste_clipboard() override
13031 #if GTK_CHECK_VERSION(4, 0, 0)
13032 gtk_widget_activate_action(m_pDelegate
, "paste.clipboard", nullptr);
13034 gtk_editable_paste_clipboard(m_pEditable
);
13038 virtual void set_placeholder_text(const OUString
& rText
) override
13040 #if GTK_CHECK_VERSION(4, 0, 0)
13041 gtk_text_set_placeholder_text(GTK_TEXT(m_pDelegate
), rText
.toUtf8().getStr());
13043 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pDelegate
), rText
.toUtf8().getStr());
13047 virtual void grab_focus() override
13051 #if GTK_CHECK_VERSION(4, 0, 0)
13052 gtk_text_grab_focus_without_selecting(GTK_TEXT(m_pDelegate
));
13054 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(m_pDelegate
));
13058 virtual void set_alignment(TxtAlign eXAlign
) override
13063 case TxtAlign::Left
:
13066 case TxtAlign::Center
:
13069 case TxtAlign::Right
:
13073 #if GTK_CHECK_VERSION(4, 0, 0)
13074 gtk_editable_set_alignment(m_pEditable
, xalign
);
13076 gtk_entry_set_alignment(GTK_ENTRY(m_pDelegate
), xalign
);
13080 virtual ~GtkInstanceEditable() override
13082 g_signal_handler_disconnect(m_pDelegate
, m_nActivateSignalId
);
13083 g_signal_handler_disconnect(m_pEditable
, m_nSelectionPosSignalId
);
13084 g_signal_handler_disconnect(m_pEditable
, m_nCursorPosSignalId
);
13085 g_signal_handler_disconnect(m_pEditable
, m_nInsertTextSignalId
);
13086 g_signal_handler_disconnect(m_pEditable
, m_nChangedSignalId
);
13090 class GtkInstanceEntry
: public GtkInstanceEditable
13093 GtkInstanceEntry(GtkEntry
* pEntry
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
13094 : GtkInstanceEditable(GTK_WIDGET(pEntry
), pBuilder
, bTakeOwnership
)
13098 virtual void set_font(const vcl::Font
& rFont
) override
13100 m_aCustomFont
.use_custom_font(&rFont
, u
"entry");
13114 Search(std::u16string_view rText
, int nCol
)
13115 : str(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
))
13122 gboolean
foreach_find(GtkTreeModel
* model
, GtkTreePath
* path
, GtkTreeIter
* iter
, gpointer data
)
13124 Search
* search
= static_cast<Search
*>(data
);
13125 gchar
*pStr
= nullptr;
13126 gtk_tree_model_get(model
, iter
, search
->col
, &pStr
, -1);
13127 bool found
= strcmp(pStr
, search
->str
.getStr()) == 0;
13131 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
13132 search
->index
= indices
[depth
-1];
13138 void insert_row(GtkListStore
* pListStore
, GtkTreeIter
& iter
, int pos
, const OUString
* pId
, std::u16string_view rText
, const OUString
* pIconName
, const VirtualDevice
* pDevice
)
13140 if (!pIconName
&& !pDevice
)
13142 gtk_list_store_insert_with_values(pListStore
, &iter
, pos
,
13143 0, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr(),
13144 1, !pId
? nullptr : OUStringToOString(*pId
, RTL_TEXTENCODING_UTF8
).getStr(),
13151 GdkPixbuf
* pixbuf
= getPixbuf(*pIconName
);
13153 gtk_list_store_insert_with_values(pListStore
, &iter
, pos
,
13154 0, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr(),
13155 1, !pId
? nullptr : OUStringToOString(*pId
, RTL_TEXTENCODING_UTF8
).getStr(),
13160 g_object_unref(pixbuf
);
13164 cairo_surface_t
* surface
= get_underlying_cairo_surface(*pDevice
);
13166 Size
aSize(pDevice
->GetOutputSizePixel());
13167 cairo_surface_t
* target
= cairo_surface_create_similar(surface
,
13168 cairo_surface_get_content(surface
),
13172 cairo_t
* cr
= cairo_create(target
);
13173 cairo_set_source_surface(cr
, surface
, 0, 0);
13177 gtk_list_store_insert_with_values(pListStore
, &iter
, pos
,
13178 0, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr(),
13179 1, !pId
? nullptr : OUStringToOString(*pId
, RTL_TEXTENCODING_UTF8
).getStr(),
13182 cairo_surface_destroy(target
);
13190 gint
default_sort_func(GtkTreeModel
* pModel
, GtkTreeIter
* a
, GtkTreeIter
* b
, gpointer data
)
13192 comphelper::string::NaturalStringSorter
* pSorter
= static_cast<comphelper::string::NaturalStringSorter
*>(data
);
13195 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(pModel
);
13196 gint
sort_column_id(0);
13197 gtk_tree_sortable_get_sort_column_id(pSortable
, &sort_column_id
, nullptr);
13198 gtk_tree_model_get(pModel
, a
, sort_column_id
, &pName1
, -1);
13199 gtk_tree_model_get(pModel
, b
, sort_column_id
, &pName2
, -1);
13200 gint ret
= pSorter
->compare(OUString(pName1
, pName1
? strlen(pName1
) : 0, RTL_TEXTENCODING_UTF8
),
13201 OUString(pName2
, pName2
? strlen(pName2
) : 0, RTL_TEXTENCODING_UTF8
));
13207 int starts_with(GtkTreeModel
* pTreeModel
, const OUString
& rStr
, int col
, int nStartRow
, bool bCaseSensitive
)
13210 if (!gtk_tree_model_iter_nth_child(pTreeModel
, &iter
, nullptr, nStartRow
))
13213 const vcl::I18nHelper
& rI18nHelper
= Application::GetSettings().GetUILocaleI18nHelper();
13214 int nRet
= nStartRow
;
13218 gtk_tree_model_get(pTreeModel
, &iter
, col
, &pStr
, -1);
13219 OUString
aStr(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
13221 const bool bMatch
= !bCaseSensitive
? rI18nHelper
.MatchString(rStr
, aStr
) : aStr
.startsWith(rStr
);
13225 } while (gtk_tree_model_iter_next(pTreeModel
, &iter
));
13230 struct GtkInstanceTreeIter
: public weld::TreeIter
13232 GtkInstanceTreeIter(const GtkInstanceTreeIter
* pOrig
)
13235 iter
= pOrig
->iter
;
13237 memset(&iter
, 0, sizeof(iter
));
13239 GtkInstanceTreeIter(const GtkTreeIter
& rOrig
)
13241 memcpy(&iter
, &rOrig
, sizeof(iter
));
13243 virtual bool equal(const TreeIter
& rOther
) const override
13245 return memcmp(&iter
, &static_cast<const GtkInstanceTreeIter
&>(rOther
).iter
, sizeof(GtkTreeIter
)) == 0;
13250 class GtkInstanceTreeView
;
13254 static GtkInstanceTreeView
* g_DragSource
;
13258 struct CompareGtkTreePath
13260 bool operator()(const GtkTreePath
* lhs
, const GtkTreePath
* rhs
) const
13262 return gtk_tree_path_compare(lhs
, rhs
) < 0;
13266 int get_height_row(GtkTreeView
* pTreeView
, GList
* pColumns
)
13268 gint nMaxRowHeight
= 0;
13269 for (GList
* pEntry
= g_list_first(pColumns
); pEntry
; pEntry
= g_list_next(pEntry
))
13271 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
13272 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
13273 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
13275 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
13277 gtk_cell_renderer_get_preferred_height(pCellRenderer
, GTK_WIDGET(pTreeView
), nullptr, &nRowHeight
);
13278 nMaxRowHeight
= std::max(nMaxRowHeight
, nRowHeight
);
13280 g_list_free(pRenderers
);
13282 return nMaxRowHeight
;
13285 int get_height_row_separator(GtkTreeView
* pTreeView
)
13287 // gtk4: _TREE_VIEW_VERTICAL_SEPARATOR define in gtk/gtktreeview.c
13288 gint nVerticalSeparator
= 2;
13289 #if !GTK_CHECK_VERSION(4, 0, 0)
13290 gtk_widget_style_get(GTK_WIDGET(pTreeView
), "vertical-separator", &nVerticalSeparator
, nullptr);
13294 return nVerticalSeparator
;
13297 int get_height_rows(GtkTreeView
* pTreeView
, GList
* pColumns
, int nRows
)
13299 gint nMaxRowHeight
= get_height_row(pTreeView
, pColumns
);
13300 gint nVerticalSeparator
= get_height_row_separator(pTreeView
);
13301 return (nMaxRowHeight
* nRows
) + (nVerticalSeparator
* nRows
) / 2;
13304 #if !GTK_CHECK_VERSION(4, 0, 0)
13305 int get_height_rows(int nRowHeight
, int nSeparatorHeight
, int nRows
)
13307 return (nRowHeight
* nRows
) + (nSeparatorHeight
* (nRows
+ 1));
13311 tools::Rectangle
get_row_area(GtkTreeView
* pTreeView
, GList
* pColumns
, GtkTreePath
* pPath
)
13313 tools::Rectangle aRet
;
13315 GdkRectangle aRect
;
13316 for (GList
* pEntry
= g_list_last(pColumns
); pEntry
; pEntry
= g_list_previous(pEntry
))
13318 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
13319 gtk_tree_view_get_cell_area(pTreeView
, pPath
, pColumn
, &aRect
);
13320 aRet
.Union(tools::Rectangle(aRect
.x
, aRect
.y
, aRect
.x
+ aRect
.width
, aRect
.y
+ aRect
.height
));
13326 struct GtkTreeRowReferenceDeleter
13328 void operator()(GtkTreeRowReference
* p
) const
13330 gtk_tree_row_reference_free(p
);
13334 bool separator_function(const GtkTreePath
* path
, const std::vector
<std::unique_ptr
<GtkTreeRowReference
, GtkTreeRowReferenceDeleter
>>& rSeparatorRows
)
13336 bool bFound
= false;
13337 for (auto& a
: rSeparatorRows
)
13339 GtkTreePath
* seppath
= gtk_tree_row_reference_get_path(a
.get());
13342 bFound
= gtk_tree_path_compare(path
, seppath
) == 0;
13343 gtk_tree_path_free(seppath
);
13351 void tree_store_set(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, ...)
13355 va_start(args
, pIter
);
13356 gtk_tree_store_set_valist(GTK_TREE_STORE(pTreeModel
), pIter
, args
);
13360 void list_store_set(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, ...)
13364 va_start(args
, pIter
);
13365 gtk_list_store_set_valist(GTK_LIST_STORE(pTreeModel
), pIter
, args
);
13369 void tree_store_insert_with_values(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, GtkTreeIter
*pParent
, gint nPos
,
13370 gint nTextCol
, const gchar
* pText
,
13371 gint nIdCol
, const gchar
* pId
)
13373 gtk_tree_store_insert_with_values(GTK_TREE_STORE(pTreeModel
), pIter
, pParent
, nPos
,
13374 nTextCol
, pText
, nIdCol
, pId
, -1);
13377 void list_store_insert_with_values(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, GtkTreeIter
*pParent
, gint nPos
,
13378 gint nTextCol
, const gchar
* pText
,
13379 gint nIdCol
, const gchar
* pId
)
13381 assert(!pParent
); (void)pParent
;
13382 gtk_list_store_insert_with_values(GTK_LIST_STORE(pTreeModel
), pIter
, nPos
,
13383 nTextCol
, pText
, nIdCol
, pId
, -1);
13386 void tree_store_prepend(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, GtkTreeIter
*pParent
)
13388 gtk_tree_store_prepend(GTK_TREE_STORE(pTreeModel
), pIter
, pParent
);
13391 void list_store_prepend(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, GtkTreeIter
*pParent
)
13393 assert(!pParent
); (void)pParent
;
13394 gtk_list_store_prepend(GTK_LIST_STORE(pTreeModel
), pIter
);
13397 void tree_store_insert(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, GtkTreeIter
*pParent
, gint nPosition
)
13399 gtk_tree_store_insert(GTK_TREE_STORE(pTreeModel
), pIter
, pParent
, nPosition
);
13402 void list_store_insert(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
, GtkTreeIter
*pParent
, gint nPosition
)
13404 assert(!pParent
); (void)pParent
;
13405 gtk_list_store_insert(GTK_LIST_STORE(pTreeModel
), pIter
, nPosition
);
13408 void tree_store_clear(GtkTreeModel
* pTreeModel
)
13410 gtk_tree_store_clear(GTK_TREE_STORE(pTreeModel
));
13413 void list_store_clear(GtkTreeModel
* pTreeModel
)
13415 gtk_list_store_clear(GTK_LIST_STORE(pTreeModel
));
13418 bool tree_store_remove(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
)
13420 return gtk_tree_store_remove(GTK_TREE_STORE(pTreeModel
), pIter
);
13423 bool list_store_remove(GtkTreeModel
* pTreeModel
, GtkTreeIter
*pIter
)
13425 return gtk_list_store_remove(GTK_LIST_STORE(pTreeModel
), pIter
);
13428 void tree_store_swap(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter1
, GtkTreeIter
* pIter2
)
13430 gtk_tree_store_swap(GTK_TREE_STORE(pTreeModel
), pIter1
, pIter2
);
13433 void list_store_swap(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter1
, GtkTreeIter
* pIter2
)
13435 gtk_list_store_swap(GTK_LIST_STORE(pTreeModel
), pIter1
, pIter2
);
13438 void tree_store_set_value(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter
, gint nColumn
, GValue
* pValue
)
13440 gtk_tree_store_set_value(GTK_TREE_STORE(pTreeModel
), pIter
, nColumn
, pValue
);
13443 void list_store_set_value(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter
, gint nColumn
, GValue
* pValue
)
13445 gtk_list_store_set_value(GTK_LIST_STORE(pTreeModel
), pIter
, nColumn
, pValue
);
13448 int promote_arg(bool bArg
)
13450 return static_cast<int>(bArg
);
13453 class GtkInstanceTreeView
: public GtkInstanceWidget
, public virtual weld::TreeView
13456 GtkTreeView
* m_pTreeView
;
13457 GtkTreeModel
* m_pTreeModel
;
13459 typedef void(*setterFnc
)(GtkTreeModel
*, GtkTreeIter
*, ...);
13460 setterFnc m_Setter
;
13462 typedef void(*insertWithValuesFnc
)(GtkTreeModel
*, GtkTreeIter
*, GtkTreeIter
*, gint
, gint
, const gchar
*, gint
, const gchar
*);
13463 insertWithValuesFnc m_InsertWithValues
;
13465 typedef void(*insertFnc
)(GtkTreeModel
*, GtkTreeIter
*, GtkTreeIter
*, gint
);
13466 insertFnc m_Insert
;
13468 typedef void(*prependFnc
)(GtkTreeModel
*, GtkTreeIter
*, GtkTreeIter
*);
13469 prependFnc m_Prepend
;
13471 typedef void(*clearFnc
)(GtkTreeModel
*);
13474 typedef bool(*removeFnc
)(GtkTreeModel
*, GtkTreeIter
*);
13475 removeFnc m_Remove
;
13477 typedef void(*swapFnc
)(GtkTreeModel
*, GtkTreeIter
*, GtkTreeIter
*);
13480 typedef void(*setValueFnc
)(GtkTreeModel
*, GtkTreeIter
*, gint
, GValue
*);
13481 setValueFnc m_SetValue
;
13483 std::unique_ptr
<comphelper::string::NaturalStringSorter
> m_xSorter
;
13485 std::vector
<gulong
> m_aColumnSignalIds
;
13486 // map from toggle column to toggle visibility column
13487 std::map
<int, int> m_aToggleVisMap
;
13488 // map from toggle column to tristate column
13489 std::map
<int, int> m_aToggleTriStateMap
;
13490 // map from text column to text weight column
13491 std::map
<int, int> m_aWeightMap
;
13492 // map from text column to sensitive column
13493 std::map
<int, int> m_aSensitiveMap
;
13494 // map from text column to indent column
13495 std::map
<int, int> m_aIndentMap
;
13496 // map from text column to text align column
13497 std::map
<int, int> m_aAlignMap
;
13498 // currently expanding parent that logically, but not currently physically,
13499 // contain placeholders
13500 o3tl::sorted_vector
<GtkTreePath
*, CompareGtkTreePath
> m_aExpandingPlaceHolderParents
;
13501 // which rows are separators (rare)
13502 std::vector
<std::unique_ptr
<GtkTreeRowReference
, GtkTreeRowReferenceDeleter
>> m_aSeparatorRows
;
13503 std::vector
<GtkSortType
> m_aSavedSortTypes
;
13504 std::vector
<int> m_aSavedSortColumns
;
13505 bool m_bWorkAroundBadDragRegion
;
13507 bool m_bChangedByMouse
;
13511 gint m_nExpanderToggleCol
;
13512 gint m_nExpanderImageCol
;
13514 int m_nPendingVAdjustment
;
13515 gulong m_nChangedSignalId
;
13516 gulong m_nRowActivatedSignalId
;
13517 gulong m_nTestExpandRowSignalId
;
13518 gulong m_nTestCollapseRowSignalId
;
13519 gulong m_nVAdjustmentChangedSignalId
;
13520 gulong m_nRowDeletedSignalId
;
13521 gulong m_nRowInsertedSignalId
;
13522 #if !GTK_CHECK_VERSION(4, 0, 0)
13523 gulong m_nPopupMenuSignalId
;
13524 gulong m_nKeyPressSignalId
;
13526 gulong m_nQueryTooltipSignalId
;
13527 GtkAdjustment
* m_pVAdjustment
;
13528 ImplSVEvent
* m_pChangeEvent
;
13530 DECL_LINK(async_signal_changed
, void*, void);
13532 void launch_signal_changed()
13534 //tdf#117991 selection change is sent before the focus change, and focus change
13535 //is what will cause a spinbutton that currently has the focus to set its contents
13536 //as the spin button value. So any LibreOffice callbacks on
13537 //signal-change would happen before the spinbutton value-change occurs.
13538 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
13539 //has been processed
13540 if (m_pChangeEvent
)
13541 Application::RemoveUserEvent(m_pChangeEvent
);
13543 #if !GTK_CHECK_VERSION(4, 0, 0)
13544 GdkEvent
*pEvent
= gtk_get_current_event();
13545 m_bChangedByMouse
= pEvent
&& categorizeEvent(pEvent
) == VclInputFlags::MOUSE
;
13547 //TODO maybe iterate over gtk_widget_observe_controllers looking for a motion controller
13550 m_pChangeEvent
= Application::PostUserEvent(LINK(this, GtkInstanceTreeView
, async_signal_changed
));
13553 static void signalChanged(GtkTreeView
*, gpointer widget
)
13555 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13556 pThis
->launch_signal_changed();
13559 void handle_row_activated()
13561 if (signal_row_activated())
13563 GtkInstanceTreeIter
aIter(nullptr);
13564 if (!get_cursor(&aIter
))
13566 if (gtk_tree_model_iter_has_child(m_pTreeModel
, &aIter
.iter
))
13567 get_row_expanded(aIter
) ? collapse_row(aIter
) : expand_row(aIter
);
13570 static void signalRowActivated(GtkTreeView
*, GtkTreePath
*, GtkTreeViewColumn
*, gpointer widget
)
13572 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13573 SolarMutexGuard aGuard
;
13574 pThis
->handle_row_activated();
13577 virtual bool signal_popup_menu(const CommandEvent
& rCEvt
) override
13579 return m_aPopupMenuHdl
.Call(rCEvt
);
13582 void insert_row(GtkTreeIter
& iter
, const GtkTreeIter
* parent
, int pos
, const OUString
* pId
, const OUString
* pText
,
13583 const OUString
* pIconName
, const VirtualDevice
* pDevice
)
13585 m_InsertWithValues(m_pTreeModel
, &iter
, const_cast<GtkTreeIter
*>(parent
), pos
,
13586 m_nTextCol
, !pText
? nullptr : OUStringToOString(*pText
, RTL_TEXTENCODING_UTF8
).getStr(),
13587 m_nIdCol
, !pId
? nullptr : OUStringToOString(*pId
, RTL_TEXTENCODING_UTF8
).getStr());
13591 GdkPixbuf
* pixbuf
= getPixbuf(*pIconName
);
13592 m_Setter(m_pTreeModel
, &iter
, m_nImageCol
, pixbuf
, -1);
13594 g_object_unref(pixbuf
);
13598 cairo_surface_t
* surface
= get_underlying_cairo_surface(*pDevice
);
13600 Size
aSize(pDevice
->GetOutputSizePixel());
13601 cairo_surface_t
* target
= cairo_surface_create_similar(surface
,
13602 cairo_surface_get_content(surface
),
13606 cairo_t
* cr
= cairo_create(target
);
13607 cairo_set_source_surface(cr
, surface
, 0, 0);
13611 m_Setter(m_pTreeModel
, &iter
, m_nImageCol
, target
, -1);
13612 cairo_surface_destroy(target
);
13616 bool separator_function(const GtkTreePath
* path
)
13618 return ::separator_function(path
, m_aSeparatorRows
);
13621 static gboolean
separatorFunction(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter
, gpointer widget
)
13623 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13624 GtkTreePath
* path
= gtk_tree_model_get_path(pTreeModel
, pIter
);
13625 bool bRet
= pThis
->separator_function(path
);
13626 gtk_tree_path_free(path
);
13630 OUString
get(const GtkTreeIter
& iter
, int col
) const
13633 gtk_tree_model_get(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, &pStr
, -1);
13634 OUString
sRet(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
13639 OUString
get(int pos
, int col
) const
13643 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13644 sRet
= get(iter
, col
);
13648 gint
get_int(const GtkTreeIter
& iter
, int col
) const
13651 gtk_tree_model_get(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, &nRet
, -1);
13655 gint
get_int(int pos
, int col
) const
13659 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13660 nRet
= get_int(iter
, col
);
13661 gtk_tree_model_get(m_pTreeModel
, &iter
, col
, &nRet
, -1);
13665 bool get_bool(const GtkTreeIter
& iter
, int col
) const
13667 gboolean
bRet(false);
13668 gtk_tree_model_get(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, &bRet
, -1);
13672 bool get_bool(int pos
, int col
) const
13676 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13677 bRet
= get_bool(iter
, col
);
13681 void set_toggle(const GtkTreeIter
& iter
, TriState eState
, int col
)
13684 col
= m_nExpanderToggleCol
;
13686 col
= to_internal_model(col
);
13688 if (eState
== TRISTATE_INDET
)
13690 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
),
13691 m_aToggleVisMap
[col
], promote_arg(true), // checkbuttons are invisible until toggled on or off
13692 m_aToggleTriStateMap
[col
], promote_arg(true), // tristate on
13697 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
),
13698 m_aToggleVisMap
[col
], promote_arg(true), // checkbuttons are invisible until toggled on or off
13699 m_aToggleTriStateMap
[col
], promote_arg(false), // tristate off
13700 col
, promote_arg(eState
== TRISTATE_TRUE
), // set toggle state
13705 void set(const GtkTreeIter
& iter
, int col
, std::u16string_view rText
)
13707 OString
aStr(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
13708 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, aStr
.getStr(), -1);
13711 void set(int pos
, int col
, std::u16string_view rText
)
13714 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13715 set(iter
, col
, rText
);
13718 void set(const GtkTreeIter
& iter
, int col
, bool bOn
)
13720 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, promote_arg(bOn
), -1);
13723 void set(int pos
, int col
, bool bOn
)
13726 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13727 set(iter
, col
, bOn
);
13730 void set(const GtkTreeIter
& iter
, int col
, gint bInt
)
13732 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, bInt
, -1);
13735 void set(int pos
, int col
, gint bInt
)
13738 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13739 set(iter
, col
, bInt
);
13742 void set(const GtkTreeIter
& iter
, int col
, double fValue
)
13744 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, fValue
, -1);
13747 void set(int pos
, int col
, double fValue
)
13750 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
13751 set(iter
, col
, fValue
);
13754 static gboolean
signalTestExpandRow(GtkTreeView
*, GtkTreeIter
* iter
, GtkTreePath
*, gpointer widget
)
13756 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13757 return !pThis
->signal_test_expand_row(*iter
);
13760 static gboolean
signalTestCollapseRow(GtkTreeView
*, GtkTreeIter
* iter
, GtkTreePath
*, gpointer widget
)
13762 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13763 return !pThis
->signal_test_collapse_row(*iter
);
13766 bool child_is_placeholder(GtkInstanceTreeIter
& rGtkIter
) const
13768 GtkTreePath
* pPath
= gtk_tree_model_get_path(m_pTreeModel
, &rGtkIter
.iter
);
13769 bool bExpanding
= m_aExpandingPlaceHolderParents
.count(pPath
);
13770 gtk_tree_path_free(pPath
);
13774 bool bPlaceHolder
= false;
13776 if (gtk_tree_model_iter_children(m_pTreeModel
, &tmp
, &rGtkIter
.iter
))
13778 rGtkIter
.iter
= tmp
;
13779 if (get_text(rGtkIter
, -1) == "<dummy>")
13781 bPlaceHolder
= true;
13784 return bPlaceHolder
;
13787 bool signal_test_expand_row(GtkTreeIter
& iter
)
13789 disable_notify_events();
13791 // if there's a preexisting placeholder child, required to make this
13792 // potentially expandable in the first place, now we remove it
13793 GtkInstanceTreeIter
aIter(iter
);
13794 GtkTreePath
* pPlaceHolderPath
= nullptr;
13795 bool bPlaceHolder
= child_is_placeholder(aIter
);
13798 m_Remove(m_pTreeModel
, &aIter
.iter
);
13800 pPlaceHolderPath
= gtk_tree_model_get_path(m_pTreeModel
, &iter
);
13801 m_aExpandingPlaceHolderParents
.insert(pPlaceHolderPath
);
13805 bool bRet
= signal_expanding(aIter
);
13809 //expand disallowed, restore placeholder
13812 GtkTreeIter subiter
;
13813 OUString
sDummy("<dummy>");
13814 insert_row(subiter
, &iter
, -1, nullptr, &sDummy
, nullptr, nullptr);
13816 m_aExpandingPlaceHolderParents
.erase(pPlaceHolderPath
);
13817 gtk_tree_path_free(pPlaceHolderPath
);
13820 enable_notify_events();
13824 bool signal_test_collapse_row(const GtkTreeIter
& iter
)
13826 disable_notify_events();
13828 GtkInstanceTreeIter
aIter(iter
);
13829 bool bRet
= signal_collapsing(aIter
);
13831 enable_notify_events();
13835 static void signalCellToggled(GtkCellRendererToggle
* pCell
, const gchar
*path
, gpointer widget
)
13837 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13838 void* pData
= g_object_get_data(G_OBJECT(pCell
), "g-lo-CellIndex");
13839 pThis
->signal_cell_toggled(path
, reinterpret_cast<sal_IntPtr
>(pData
));
13842 void signal_cell_toggled(const gchar
*path
, int nCol
)
13844 GtkTreePath
*tree_path
= gtk_tree_path_new_from_string(path
);
13846 // additionally set the cursor into the row the toggled element is in
13847 gtk_tree_view_set_cursor(m_pTreeView
, tree_path
, nullptr, false);
13850 gtk_tree_model_get_iter(m_pTreeModel
, &iter
, tree_path
);
13852 gboolean
bRet(false);
13853 gtk_tree_model_get(m_pTreeModel
, &iter
, nCol
, &bRet
, -1);
13855 m_Setter(m_pTreeModel
, &iter
, nCol
, bRet
, -1);
13857 set(iter
, m_aToggleTriStateMap
[nCol
], false);
13859 signal_toggled(iter_col(GtkInstanceTreeIter(iter
), to_external_model(nCol
)));
13861 gtk_tree_path_free(tree_path
);
13864 DECL_LINK(async_stop_cell_editing
, void*, void);
13866 static void signalCellEditingStarted(GtkCellRenderer
*, GtkCellEditable
*, const gchar
*path
, gpointer widget
)
13868 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13869 if (!pThis
->signal_cell_editing_started(path
))
13870 Application::PostUserEvent(LINK(pThis
, GtkInstanceTreeView
, async_stop_cell_editing
));
13873 bool signal_cell_editing_started(const gchar
*path
)
13875 GtkTreePath
*tree_path
= gtk_tree_path_new_from_string(path
);
13877 GtkInstanceTreeIter
aGtkIter(nullptr);
13878 gtk_tree_model_get_iter(m_pTreeModel
, &aGtkIter
.iter
, tree_path
);
13879 gtk_tree_path_free(tree_path
);
13881 return signal_editing_started(aGtkIter
);
13884 static void signalCellEdited(GtkCellRendererText
* pCell
, const gchar
*path
, const gchar
*pNewText
, gpointer widget
)
13886 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13887 pThis
->signal_cell_edited(pCell
, path
, pNewText
);
13890 static void restoreNonEditable(GObject
* pCell
)
13892 if (g_object_get_data(pCell
, "g-lo-RestoreNonEditable"))
13894 g_object_set(pCell
, "editable", false, "editable-set", false, nullptr);
13895 g_object_set_data(pCell
, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer
>(false));
13899 void signal_cell_edited(GtkCellRendererText
* pCell
, const gchar
*path
, const gchar
* pNewText
)
13901 GtkTreePath
*tree_path
= gtk_tree_path_new_from_string(path
);
13903 GtkInstanceTreeIter
aGtkIter(nullptr);
13904 gtk_tree_model_get_iter(m_pTreeModel
, &aGtkIter
.iter
, tree_path
);
13905 gtk_tree_path_free(tree_path
);
13907 OUString
sText(pNewText
, pNewText
? strlen(pNewText
) : 0, RTL_TEXTENCODING_UTF8
);
13908 if (signal_editing_done(iter_string(aGtkIter
, sText
)))
13910 void* pData
= g_object_get_data(G_OBJECT(pCell
), "g-lo-CellIndex");
13911 set(aGtkIter
.iter
, reinterpret_cast<sal_IntPtr
>(pData
), sText
);
13914 restoreNonEditable(G_OBJECT(pCell
));
13917 static void signalCellEditingCanceled(GtkCellRenderer
* pCell
, gpointer
/*widget*/)
13919 restoreNonEditable(G_OBJECT(pCell
));
13922 void signal_column_clicked(GtkTreeViewColumn
* pClickedColumn
)
13925 for (GList
* pEntry
= g_list_first(m_pColumns
); pEntry
; pEntry
= g_list_next(pEntry
))
13927 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
13928 if (pColumn
== pClickedColumn
)
13930 TreeView::signal_column_clicked(nIndex
);
13937 static void signalColumnClicked(GtkTreeViewColumn
* pColumn
, gpointer widget
)
13939 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13940 pThis
->signal_column_clicked(pColumn
);
13943 static void signalVAdjustmentChanged(GtkAdjustment
*, gpointer widget
)
13945 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13946 pThis
->signal_visible_range_changed();
13949 // The outside concept of a column maps to a gtk CellRenderer, rather than
13950 // a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
13951 // and/or a leading Image Renderer, those are considered special expander
13952 // columns and precede index 0 and can be accessed via outside index -1
13953 int to_external_model(int modelcol
) const
13955 if (m_nExpanderToggleCol
!= -1)
13957 if (m_nExpanderImageCol
!= -1)
13962 int to_internal_model(int modelcol
) const
13964 if (m_nExpanderToggleCol
!= -1)
13966 if (m_nExpanderImageCol
!= -1)
13971 void set_column_editable(int nCol
, bool bEditable
)
13973 nCol
= to_internal_model(nCol
);
13975 for (GList
* pEntry
= g_list_first(m_pColumns
); pEntry
; pEntry
= g_list_next(pEntry
))
13977 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
13978 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
13979 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
13981 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
13982 void* pData
= g_object_get_data(G_OBJECT(pCellRenderer
), "g-lo-CellIndex");
13983 if (reinterpret_cast<sal_IntPtr
>(pData
) == nCol
)
13985 g_object_set(G_OBJECT(pCellRenderer
), "editable", bEditable
, "editable-set", true, nullptr);
13989 g_list_free(pRenderers
);
13993 static void signalRowDeleted(GtkTreeModel
*, GtkTreePath
*, gpointer widget
)
13995 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
13996 pThis
->signal_model_changed();
13999 static void signalRowInserted(GtkTreeModel
*, GtkTreePath
*, GtkTreeIter
*, gpointer widget
)
14001 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
14002 pThis
->signal_model_changed();
14005 static gint
sortFunc(GtkTreeModel
* pModel
, GtkTreeIter
* a
, GtkTreeIter
* b
, gpointer widget
)
14007 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
14008 return pThis
->sort_func(pModel
, a
, b
);
14011 gint
sort_func(GtkTreeModel
* pModel
, GtkTreeIter
* a
, GtkTreeIter
* b
)
14014 return m_aCustomSort(GtkInstanceTreeIter(*a
), GtkInstanceTreeIter(*b
));
14015 return default_sort_func(pModel
, a
, b
, m_xSorter
.get());
14018 #if !GTK_CHECK_VERSION(4, 0, 0)
14019 bool signal_key_press(GdkEventKey
* pEvent
)
14021 if (pEvent
->keyval
!= GDK_KEY_Left
&& pEvent
->keyval
!= GDK_KEY_Right
)
14024 GtkInstanceTreeIter
aIter(nullptr);
14025 if (!get_cursor(&aIter
))
14028 bool bHasChild
= gtk_tree_model_iter_has_child(m_pTreeModel
, &aIter
.iter
);
14030 if (pEvent
->keyval
== GDK_KEY_Right
)
14032 if (bHasChild
&& !get_row_expanded(aIter
))
14040 if (bHasChild
&& get_row_expanded(aIter
))
14042 collapse_row(aIter
);
14046 if (iter_parent(aIter
))
14057 static gboolean
signalKeyPress(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
14059 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
14060 return pThis
->signal_key_press(pEvent
);
14064 static gboolean
signalQueryTooltip(GtkWidget
* /*pGtkWidget*/, gint x
, gint y
,
14065 gboolean keyboard_tip
, GtkTooltip
*tooltip
,
14068 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
14070 GtkTreeView
*pTreeView
= pThis
->m_pTreeView
;
14071 GtkTreeModel
*pModel
= gtk_tree_view_get_model(pTreeView
);
14072 GtkTreePath
*pPath
= nullptr;
14073 #if GTK_CHECK_VERSION(4, 0, 0)
14074 if (!gtk_tree_view_get_tooltip_context(pTreeView
, x
, y
, keyboard_tip
, &pModel
, &pPath
, &iter
))
14077 if (!gtk_tree_view_get_tooltip_context(pTreeView
, &x
, &y
, keyboard_tip
, &pModel
, &pPath
, &iter
))
14080 OUString aTooltip
= pThis
->signal_query_tooltip(GtkInstanceTreeIter(iter
));
14081 if (!aTooltip
.isEmpty())
14083 gtk_tooltip_set_text(tooltip
, OUStringToOString(aTooltip
, RTL_TEXTENCODING_UTF8
).getStr());
14084 gtk_tree_view_set_tooltip_row(pTreeView
, tooltip
, pPath
);
14086 gtk_tree_path_free(pPath
);
14087 return !aTooltip
.isEmpty();
14090 void last_child(GtkTreeModel
* pModel
, GtkTreeIter
* result
, GtkTreeIter
* pParent
, int nChildren
) const
14092 gtk_tree_model_iter_nth_child(pModel
, result
, pParent
, nChildren
- 1);
14093 nChildren
= gtk_tree_model_iter_n_children(pModel
, result
);
14096 GtkTreeIter
newparent(*result
);
14097 last_child(pModel
, result
, &newparent
, nChildren
);
14101 GtkTreePath
* get_path_of_last_entry(GtkTreeModel
*pModel
)
14103 GtkTreePath
*lastpath
;
14104 // find the last entry in the model for comparison
14105 int nChildren
= gtk_tree_model_iter_n_children(pModel
, nullptr);
14107 lastpath
= gtk_tree_path_new_from_indices(0, -1);
14111 last_child(pModel
, &iter
, nullptr, nChildren
);
14112 lastpath
= gtk_tree_model_get_path(pModel
, &iter
);
14117 void set_font_color(const GtkTreeIter
& iter
, const Color
& rColor
)
14119 if (rColor
== COL_AUTO
)
14120 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), m_nIdCol
+ 1, nullptr, -1);
14123 GdkRGBA aColor
{rColor
.GetRed()/255.0f
, rColor
.GetGreen()/255.0f
, rColor
.GetBlue()/255.0f
, 0};
14124 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), m_nIdCol
+ 1, &aColor
, -1);
14128 int get_expander_size() const
14130 // gtk4: _TREE_VIEW_EXPANDER_SIZE define in gtk/gtktreeview.c
14131 gint nExpanderSize
= 16;
14132 // gtk4: _TREE_VIEW_HORIZONTAL_SEPARATOR define in gtk/gtktreeview.c
14133 gint nHorizontalSeparator
= 4;
14135 #if !GTK_CHECK_VERSION(4, 0, 0)
14136 gtk_widget_style_get(GTK_WIDGET(m_pTreeView
),
14137 "expander-size", &nExpanderSize
,
14138 "horizontal-separator", &nHorizontalSeparator
,
14142 return nExpanderSize
+ (nHorizontalSeparator
/ 2);
14145 void real_vadjustment_set_value(int value
)
14147 disable_notify_events();
14148 gtk_adjustment_set_value(m_pVAdjustment
, value
);
14149 enable_notify_events();
14152 static gboolean
setAdjustmentCallback(GtkWidget
*, GdkFrameClock
*, gpointer widget
)
14154 GtkInstanceTreeView
* pThis
= static_cast<GtkInstanceTreeView
*>(widget
);
14155 if (pThis
->m_nPendingVAdjustment
!= -1)
14157 pThis
->real_vadjustment_set_value(pThis
->m_nPendingVAdjustment
);
14158 pThis
->m_nPendingVAdjustment
= -1;
14163 bool iter_next(weld::TreeIter
& rIter
, bool bOnlyExpanded
) const
14165 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
14167 GtkTreeIter iter
= rGtkIter
.iter
;
14169 bool ret
= gtk_tree_model_iter_children(m_pTreeModel
, &tmp
, &iter
);
14170 if (ret
&& bOnlyExpanded
&& !get_row_expanded(rGtkIter
))
14172 rGtkIter
.iter
= tmp
;
14175 //on-demand dummy entry doesn't count
14176 if (get_text(rGtkIter
, -1) == "<dummy>")
14177 return iter_next(rGtkIter
, bOnlyExpanded
);
14182 if (gtk_tree_model_iter_next(m_pTreeModel
, &tmp
))
14184 rGtkIter
.iter
= tmp
;
14185 //on-demand dummy entry doesn't count
14186 if (get_text(rGtkIter
, -1) == "<dummy>")
14187 return iter_next(rGtkIter
, bOnlyExpanded
);
14190 // Move up level(s) until we find the level where the next node exists.
14191 while (gtk_tree_model_iter_parent(m_pTreeModel
, &tmp
, &iter
))
14194 if (gtk_tree_model_iter_next(m_pTreeModel
, &tmp
))
14196 rGtkIter
.iter
= tmp
;
14197 //on-demand dummy entry doesn't count
14198 if (get_text(rGtkIter
, -1) == "<dummy>")
14199 return iter_next(rGtkIter
, bOnlyExpanded
);
14207 GtkInstanceTreeView(GtkTreeView
* pTreeView
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
14208 : GtkInstanceWidget(GTK_WIDGET(pTreeView
), pBuilder
, bTakeOwnership
)
14209 , m_pTreeView(pTreeView
)
14210 , m_pTreeModel(gtk_tree_view_get_model(m_pTreeView
))
14211 , m_bWorkAroundBadDragRegion(false)
14213 , m_bChangedByMouse(false)
14217 , m_nExpanderToggleCol(-1)
14218 , m_nExpanderImageCol(-1)
14219 , m_nPendingVAdjustment(-1)
14220 , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView
), "changed",
14221 G_CALLBACK(signalChanged
), this))
14222 , m_nRowActivatedSignalId(g_signal_connect(pTreeView
, "row-activated", G_CALLBACK(signalRowActivated
), this))
14223 , m_nTestExpandRowSignalId(g_signal_connect(pTreeView
, "test-expand-row", G_CALLBACK(signalTestExpandRow
), this))
14224 , m_nTestCollapseRowSignalId(g_signal_connect(pTreeView
, "test-collapse-row", G_CALLBACK(signalTestCollapseRow
), this))
14225 , m_nVAdjustmentChangedSignalId(0)
14226 #if !GTK_CHECK_VERSION(4, 0, 0)
14227 , m_nPopupMenuSignalId(g_signal_connect(pTreeView
, "popup-menu", G_CALLBACK(signalPopupMenu
), this))
14228 , m_nKeyPressSignalId(g_signal_connect(pTreeView
, "key-press-event", G_CALLBACK(signalKeyPress
), this))
14230 , m_nQueryTooltipSignalId(0)
14231 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView
)))
14232 , m_pChangeEvent(nullptr)
14234 if (GTK_IS_TREE_STORE(m_pTreeModel
))
14236 m_Setter
= tree_store_set
;
14237 m_InsertWithValues
= tree_store_insert_with_values
;
14238 m_Insert
= tree_store_insert
;
14239 m_Prepend
= tree_store_prepend
;
14240 m_Remove
= tree_store_remove
;
14241 m_Swap
= tree_store_swap
;
14242 m_SetValue
= tree_store_set_value
;
14243 m_Clear
= tree_store_clear
;
14248 tdf#136559 see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2693
14249 If we only need a list and not a tree we can get a performance boost from using a ListStore
14251 assert(!gtk_tree_view_get_show_expanders(m_pTreeView
) && "a liststore can only be used if no tree structure is needed");
14252 m_Setter
= list_store_set
;
14253 m_InsertWithValues
= list_store_insert_with_values
;
14254 m_Insert
= list_store_insert
;
14255 m_Prepend
= list_store_prepend
;
14256 m_Remove
= list_store_remove
;
14257 m_Swap
= list_store_swap
;
14258 m_SetValue
= list_store_set_value
;
14259 m_Clear
= list_store_clear
;
14262 /* The outside concept of a column maps to a gtk CellRenderer, rather than
14263 a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
14264 and/or a leading Image Renderer, those are considered special expander
14265 columns and precede index 0 and can be accessed via outside index -1
14267 m_pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
14269 int nViewColumn(0);
14270 for (GList
* pEntry
= g_list_first(m_pColumns
); pEntry
; pEntry
= g_list_next(pEntry
))
14272 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
14273 m_aColumnSignalIds
.push_back(g_signal_connect(pColumn
, "clicked", G_CALLBACK(signalColumnClicked
), this));
14274 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
14275 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
14277 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
14278 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer
))
14280 if (m_nTextCol
== -1)
14282 m_nTextCol
= nIndex
;
14283 m_nTextView
= nViewColumn
;
14285 m_aWeightMap
[nIndex
] = -1;
14286 m_aSensitiveMap
[nIndex
] = -1;
14287 m_aIndentMap
[nIndex
] = -1;
14288 m_aAlignMap
[nIndex
] = -1;
14289 g_signal_connect(G_OBJECT(pCellRenderer
), "editing-started", G_CALLBACK(signalCellEditingStarted
), this);
14290 g_signal_connect(G_OBJECT(pCellRenderer
), "editing-canceled", G_CALLBACK(signalCellEditingCanceled
), this);
14291 g_signal_connect(G_OBJECT(pCellRenderer
), "edited", G_CALLBACK(signalCellEdited
), this);
14293 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer
))
14295 const bool bExpander
= nIndex
== 0 || (nIndex
== 1 && m_nExpanderImageCol
== 0);
14297 m_nExpanderToggleCol
= nIndex
;
14298 g_signal_connect(G_OBJECT(pCellRenderer
), "toggled", G_CALLBACK(signalCellToggled
), this);
14299 m_aToggleVisMap
[nIndex
] = -1;
14300 m_aToggleTriStateMap
[nIndex
] = -1;
14302 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer
))
14304 const bool bExpander
= g_list_next(pRenderer
) != nullptr;
14305 if (bExpander
&& m_nExpanderImageCol
== -1)
14306 m_nExpanderImageCol
= nIndex
;
14307 else if (m_nImageCol
== -1)
14308 m_nImageCol
= nIndex
;
14310 g_object_set_data(G_OBJECT(pCellRenderer
), "g-lo-CellIndex", reinterpret_cast<gpointer
>(nIndex
));
14313 g_list_free(pRenderers
);
14317 m_nIdCol
= nIndex
++;
14319 for (auto& a
: m_aToggleVisMap
)
14320 a
.second
= nIndex
++;
14321 for (auto& a
: m_aToggleTriStateMap
)
14322 a
.second
= nIndex
++;
14323 for (auto& a
: m_aWeightMap
)
14324 a
.second
= nIndex
++;
14325 for (auto& a
: m_aSensitiveMap
)
14326 a
.second
= nIndex
++;
14327 for (auto& a
: m_aIndentMap
)
14328 a
.second
= nIndex
++;
14329 for (auto& a
: m_aAlignMap
)
14330 a
.second
= nIndex
++;
14332 ensure_drag_begin_end();
14334 m_nRowDeletedSignalId
= g_signal_connect(m_pTreeModel
, "row-deleted", G_CALLBACK(signalRowDeleted
), this);
14335 m_nRowInsertedSignalId
= g_signal_connect(m_pTreeModel
, "row-inserted", G_CALLBACK(signalRowInserted
), this);
14338 virtual void connect_query_tooltip(const Link
<const weld::TreeIter
&, OUString
>& rLink
) override
14340 weld::TreeView::connect_query_tooltip(rLink
);
14341 m_nQueryTooltipSignalId
= g_signal_connect(m_pTreeView
, "query-tooltip", G_CALLBACK(signalQueryTooltip
), this);
14344 virtual void columns_autosize() override
14346 gtk_tree_view_columns_autosize(m_pTreeView
);
14349 virtual void set_column_fixed_widths(const std::vector
<int>& rWidths
) override
14351 GList
* pEntry
= g_list_first(m_pColumns
);
14352 for (auto nWidth
: rWidths
)
14354 assert(pEntry
&& "wrong count");
14355 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
14356 gtk_tree_view_column_set_fixed_width(pColumn
, nWidth
);
14357 pEntry
= g_list_next(pEntry
);
14361 virtual void set_column_editables(const std::vector
<bool>& rEditables
) override
14363 size_t nTabCount
= rEditables
.size();
14364 for (size_t i
= 0 ; i
< nTabCount
; ++i
)
14365 set_column_editable(i
, rEditables
[i
]);
14368 virtual void set_centered_column(int nCol
) override
14370 for (GList
* pEntry
= g_list_first(m_pColumns
); pEntry
; pEntry
= g_list_next(pEntry
))
14372 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
14373 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
14374 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
14376 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
14377 void* pData
= g_object_get_data(G_OBJECT(pCellRenderer
), "g-lo-CellIndex");
14378 if (reinterpret_cast<sal_IntPtr
>(pData
) == nCol
)
14380 g_object_set(G_OBJECT(pCellRenderer
), "xalign", 0.5, nullptr);
14384 g_list_free(pRenderers
);
14388 virtual int get_column_width(int nColumn
) const override
14390 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, nColumn
));
14391 assert(pColumn
&& "wrong count");
14392 int nWidth
= gtk_tree_view_column_get_width(pColumn
);
14393 // https://github.com/exaile/exaile/issues/580
14394 // after setting fixed_width on a column and requesting width before
14395 // gtk has a chance to do its layout of the column means that the width
14396 // request hasn't come into effect
14398 nWidth
= gtk_tree_view_column_get_fixed_width(pColumn
);
14402 virtual OUString
get_column_title(int nColumn
) const override
14404 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, nColumn
));
14405 assert(pColumn
&& "wrong count");
14406 const gchar
* pTitle
= gtk_tree_view_column_get_title(pColumn
);
14407 OUString
sRet(pTitle
, pTitle
? strlen(pTitle
) : 0, RTL_TEXTENCODING_UTF8
);
14411 virtual void set_column_title(int nColumn
, const OUString
& rTitle
) override
14413 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, nColumn
));
14414 assert(pColumn
&& "wrong count");
14415 gtk_tree_view_column_set_title(pColumn
, OUStringToOString(rTitle
, RTL_TEXTENCODING_UTF8
).getStr());
14418 virtual void set_column_custom_renderer(int nColumn
, bool bEnable
) override
14420 assert(n_children() == 0 && "tree must be empty");
14421 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, nColumn
));
14422 assert(pColumn
&& "wrong count");
14424 GtkCellRenderer
* pExpander
= nullptr;
14425 GtkCellRenderer
* pToggle
= nullptr;
14427 // migrate existing editable setting to the new renderer
14428 gboolean
is_editable(false);
14429 void* pEditCellData(nullptr);
14430 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
14431 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
14433 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
14435 void* pData
= g_object_get_data(G_OBJECT(pCellRenderer
), "g-lo-CellIndex");
14436 auto nCellIndex
= reinterpret_cast<sal_IntPtr
>(pData
);
14438 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer
))
14440 g_object_get(pCellRenderer
, "editable", &is_editable
, nullptr);
14441 pEditCellData
= pData
;
14444 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer
))
14446 if (nCellIndex
== m_nExpanderToggleCol
)
14448 pToggle
= pCellRenderer
;
14449 g_object_ref(pToggle
);
14452 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer
))
14454 if (nCellIndex
== m_nExpanderImageCol
)
14456 pExpander
= pCellRenderer
;
14457 g_object_ref(pExpander
);
14462 g_list_free(pRenderers
);
14464 GtkCellRenderer
* pRenderer
;
14466 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn
));
14469 gtk_tree_view_column_pack_start(pColumn
, pExpander
, false);
14470 gtk_tree_view_column_add_attribute(pColumn
, pExpander
, "pixbuf", m_nExpanderImageCol
);
14471 g_object_unref(pExpander
);
14475 gtk_tree_view_column_pack_start(pColumn
, pToggle
, false);
14476 gtk_tree_view_column_add_attribute(pColumn
, pToggle
, "active", m_nExpanderToggleCol
);
14477 gtk_tree_view_column_add_attribute(pColumn
, pToggle
, "active", m_nExpanderToggleCol
);
14478 gtk_tree_view_column_add_attribute(pColumn
, pToggle
, "visible", m_aToggleTriStateMap
[m_nExpanderToggleCol
]);
14479 g_object_unref(pToggle
);
14484 pRenderer
= custom_cell_renderer_new();
14485 GValue value
= G_VALUE_INIT
;
14486 g_value_init(&value
, G_TYPE_POINTER
);
14487 g_value_set_pointer(&value
, static_cast<gpointer
>(this));
14488 g_object_set_property(G_OBJECT(pRenderer
), "instance", &value
);
14489 gtk_tree_view_column_pack_start(pColumn
, pRenderer
, true);
14490 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "text", m_nTextCol
);
14491 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "id", m_nIdCol
);
14495 pRenderer
= gtk_cell_renderer_text_new();
14496 gtk_tree_view_column_pack_start(pColumn
, pRenderer
, true);
14497 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "text", m_nTextCol
);
14502 g_object_set(pRenderer
, "editable", true, "editable-set", true, nullptr);
14503 g_object_set_data(G_OBJECT(pRenderer
), "g-lo-CellIndex", pEditCellData
);
14504 g_signal_connect(pRenderer
, "editing-started", G_CALLBACK(signalCellEditingStarted
), this);
14505 g_signal_connect(pRenderer
, "editing-canceled", G_CALLBACK(signalCellEditingCanceled
), this);
14506 g_signal_connect(pRenderer
, "edited", G_CALLBACK(signalCellEdited
), this);
14510 virtual void queue_draw() override
14512 gtk_widget_queue_draw(GTK_WIDGET(m_pTreeView
));
14515 virtual void insert(const weld::TreeIter
* pParent
, int pos
, const OUString
* pText
, const OUString
* pId
, const OUString
* pIconName
,
14516 VirtualDevice
* pImageSurface
,
14517 bool bChildrenOnDemand
, weld::TreeIter
* pRet
) override
14519 disable_notify_events();
14521 const GtkInstanceTreeIter
* pGtkIter
= static_cast<const GtkInstanceTreeIter
*>(pParent
);
14522 insert_row(iter
, pGtkIter
? &pGtkIter
->iter
: nullptr, pos
, pId
, pText
, pIconName
, pImageSurface
);
14523 if (bChildrenOnDemand
)
14525 GtkTreeIter subiter
;
14526 OUString
sDummy("<dummy>");
14527 insert_row(subiter
, &iter
, -1, nullptr, &sDummy
, nullptr, nullptr);
14531 GtkInstanceTreeIter
* pGtkRetIter
= static_cast<GtkInstanceTreeIter
*>(pRet
);
14532 pGtkRetIter
->iter
= iter
;
14534 enable_notify_events();
14537 virtual void insert_separator(int pos
, const OUString
& rId
) override
14539 disable_notify_events();
14541 if (!gtk_tree_view_get_row_separator_func(m_pTreeView
))
14542 gtk_tree_view_set_row_separator_func(m_pTreeView
, separatorFunction
, this, nullptr);
14543 insert_row(iter
, nullptr, pos
, &rId
, nullptr, nullptr, nullptr);
14544 GtkTreePath
* pPath
= gtk_tree_model_get_path(m_pTreeModel
, &iter
);
14545 m_aSeparatorRows
.emplace_back(gtk_tree_row_reference_new(m_pTreeModel
, pPath
));
14546 gtk_tree_path_free(pPath
);
14547 enable_notify_events();
14550 virtual void set_font_color(int pos
, const Color
& rColor
) override
14553 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
);
14554 set_font_color(iter
, rColor
);
14557 virtual void set_font_color(const weld::TreeIter
& rIter
, const Color
& rColor
) override
14559 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
14560 set_font_color(rGtkIter
.iter
, rColor
);
14563 virtual void remove(int pos
) override
14565 disable_notify_events();
14567 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
);
14568 m_Remove(m_pTreeModel
, &iter
);
14569 enable_notify_events();
14572 virtual int find_text(const OUString
& rText
) const override
14574 Search
aSearch(rText
, m_nTextCol
);
14575 gtk_tree_model_foreach(m_pTreeModel
, foreach_find
, &aSearch
);
14576 return aSearch
.index
;
14579 virtual int find_id(const OUString
& rId
) const override
14581 Search
aSearch(rId
, m_nIdCol
);
14582 gtk_tree_model_foreach(m_pTreeModel
, foreach_find
, &aSearch
);
14583 return aSearch
.index
;
14586 virtual void bulk_insert_for_each(int nSourceCount
, const std::function
<void(weld::TreeIter
&, int nSourceIndex
)>& func
,
14587 const weld::TreeIter
* pParent
,
14588 const std::vector
<int>* pFixedWidths
) override
14590 GtkInstanceTreeIter
* pGtkIter
= const_cast<GtkInstanceTreeIter
*>(static_cast<const GtkInstanceTreeIter
*>(pParent
));
14597 GtkTreeIter
restore(pGtkIter
->iter
);
14599 if (iter_children(*pGtkIter
))
14600 while (m_Remove(m_pTreeModel
, &pGtkIter
->iter
));
14602 pGtkIter
->iter
= restore
;
14604 GtkInstanceTreeIter
aGtkIter(nullptr);
14607 set_column_fixed_widths(*pFixedWidths
);
14609 while (nSourceCount
)
14611 // tdf#125241 inserting backwards is massively faster
14612 m_Prepend(m_pTreeModel
, &aGtkIter
.iter
, pGtkIter
? &pGtkIter
->iter
: nullptr);
14613 func(aGtkIter
, --nSourceCount
);
14619 virtual void swap(int pos1
, int pos2
) override
14621 disable_notify_events();
14624 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter1
, nullptr, pos1
);
14627 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter2
, nullptr, pos2
);
14629 m_Swap(m_pTreeModel
, &iter1
, &iter2
);
14631 enable_notify_events();
14634 virtual void clear() override
14636 disable_notify_events();
14637 gtk_tree_view_set_row_separator_func(m_pTreeView
, nullptr, nullptr, nullptr);
14638 m_aSeparatorRows
.clear();
14639 m_Clear(m_pTreeModel
);
14640 enable_notify_events();
14643 virtual void make_sorted() override
14645 // thaw wants to restore sort state of freeze
14646 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14647 m_xSorter
.reset(new comphelper::string::NaturalStringSorter(
14648 ::comphelper::getProcessComponentContext(),
14649 Application::GetSettings().GetUILanguageTag().getLocale()));
14650 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14651 gtk_tree_sortable_set_sort_func(pSortable
, m_nTextCol
, sortFunc
, this, nullptr);
14652 gtk_tree_sortable_set_sort_column_id(pSortable
, m_nTextCol
, GTK_SORT_ASCENDING
);
14655 virtual void make_unsorted() override
14659 GtkSortType eSortType
;
14660 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14661 gtk_tree_sortable_get_sort_column_id(pSortable
, &nSortColumn
, &eSortType
);
14662 gtk_tree_sortable_set_sort_column_id(pSortable
, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
, eSortType
);
14665 virtual void set_sort_order(bool bAscending
) override
14667 GtkSortType eSortType
= bAscending
? GTK_SORT_ASCENDING
: GTK_SORT_DESCENDING
;
14669 gint
sort_column_id(0);
14670 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14671 gtk_tree_sortable_get_sort_column_id(pSortable
, &sort_column_id
, nullptr);
14672 gtk_tree_sortable_set_sort_column_id(pSortable
, sort_column_id
, eSortType
);
14675 virtual bool get_sort_order() const override
14678 GtkSortType eSortType
;
14680 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14681 gtk_tree_sortable_get_sort_column_id(pSortable
, &nSortColumn
, &eSortType
);
14682 return nSortColumn
!= GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
&& eSortType
== GTK_SORT_ASCENDING
;
14685 virtual void set_sort_indicator(TriState eState
, int col
) override
14687 assert(col
>= 0 && "cannot sort on expander column");
14689 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, col
));
14690 assert(pColumn
&& "wrong count");
14691 if (eState
== TRISTATE_INDET
)
14692 gtk_tree_view_column_set_sort_indicator(pColumn
, false);
14695 gtk_tree_view_column_set_sort_indicator(pColumn
, true);
14696 GtkSortType eSortType
= eState
== TRISTATE_TRUE
? GTK_SORT_ASCENDING
: GTK_SORT_DESCENDING
;
14697 gtk_tree_view_column_set_sort_order(pColumn
, eSortType
);
14701 virtual TriState
get_sort_indicator(int col
) const override
14703 assert(col
>= 0 && "cannot sort on expander column");
14705 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, col
));
14706 if (!gtk_tree_view_column_get_sort_indicator(pColumn
))
14707 return TRISTATE_INDET
;
14708 return gtk_tree_view_column_get_sort_order(pColumn
) == GTK_SORT_ASCENDING
? TRISTATE_TRUE
: TRISTATE_FALSE
;
14711 virtual int get_sort_column() const override
14713 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14714 gint
sort_column_id(0);
14715 if (!gtk_tree_sortable_get_sort_column_id(pSortable
, &sort_column_id
, nullptr))
14717 return to_external_model(sort_column_id
);
14720 virtual void set_sort_column(int nColumn
) override
14727 GtkSortType eSortType
;
14728 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14729 gtk_tree_sortable_get_sort_column_id(pSortable
, nullptr, &eSortType
);
14730 int nSortCol
= to_internal_model(nColumn
);
14731 gtk_tree_sortable_set_sort_func(pSortable
, nSortCol
, sortFunc
, this, nullptr);
14732 gtk_tree_sortable_set_sort_column_id(pSortable
, nSortCol
, eSortType
);
14735 virtual void set_sort_func(const std::function
<int(const weld::TreeIter
&, const weld::TreeIter
&)>& func
) override
14737 weld::TreeView::set_sort_func(func
);
14738 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
14739 gtk_tree_sortable_sort_column_changed(pSortable
);
14742 virtual int n_children() const override
14744 return gtk_tree_model_iter_n_children(m_pTreeModel
, nullptr);
14747 virtual int iter_n_children(const weld::TreeIter
& rIter
) const override
14749 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
14750 return gtk_tree_model_iter_n_children(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
14753 virtual void select(int pos
) override
14755 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14756 disable_notify_events();
14757 if (pos
== -1 || (pos
== 0 && n_children() == 0))
14759 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView
));
14763 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
14764 gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView
), path
);
14765 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, nullptr, false, 0, 0);
14766 gtk_tree_path_free(path
);
14768 enable_notify_events();
14771 virtual void set_cursor(int pos
) override
14773 disable_notify_events();
14777 path
= gtk_tree_path_new_from_indices(pos
, -1);
14778 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, nullptr, false, 0, 0);
14781 path
= gtk_tree_path_new();
14782 gtk_tree_view_set_cursor(m_pTreeView
, path
, nullptr, false);
14783 gtk_tree_path_free(path
);
14784 enable_notify_events();
14787 virtual void scroll_to_row(int pos
) override
14789 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14790 disable_notify_events();
14791 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
14792 gtk_tree_view_expand_to_path(m_pTreeView
, path
);
14793 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, nullptr, false, 0, 0);
14794 gtk_tree_path_free(path
);
14795 enable_notify_events();
14798 virtual bool is_selected(int pos
) const override
14801 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
);
14802 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView
), &iter
);
14805 virtual void unselect(int pos
) override
14807 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14808 disable_notify_events();
14809 if (pos
== -1 || (pos
== 0 && n_children() == 0))
14811 gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView
));
14815 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
14816 gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView
), path
);
14817 gtk_tree_path_free(path
);
14819 enable_notify_events();
14822 virtual std::vector
<int> get_selected_rows() const override
14824 std::vector
<int> aRows
;
14826 GList
* pList
= gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView
), nullptr);
14827 for (GList
* pItem
= g_list_first(pList
); pItem
; pItem
= g_list_next(pItem
))
14829 GtkTreePath
* path
= static_cast<GtkTreePath
*>(pItem
->data
);
14832 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
14833 int nRow
= indices
[depth
-1];
14835 aRows
.push_back(nRow
);
14837 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
14842 virtual void all_foreach(const std::function
<bool(weld::TreeIter
&)>& func
) override
14844 g_object_freeze_notify(G_OBJECT(m_pTreeModel
));
14846 GtkInstanceTreeIter
aGtkIter(nullptr);
14847 if (get_iter_first(aGtkIter
))
14851 if (func(aGtkIter
))
14853 } while (iter_next(aGtkIter
));
14856 g_object_thaw_notify(G_OBJECT(m_pTreeModel
));
14859 virtual void selected_foreach(const std::function
<bool(weld::TreeIter
&)>& func
) override
14861 g_object_freeze_notify(G_OBJECT(m_pTreeModel
));
14863 GtkInstanceTreeIter
aGtkIter(nullptr);
14865 GtkTreeModel
* pModel
;
14866 GList
* pList
= gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView
), &pModel
);
14867 for (GList
* pItem
= g_list_first(pList
); pItem
; pItem
= g_list_next(pItem
))
14869 GtkTreePath
* path
= static_cast<GtkTreePath
*>(pItem
->data
);
14870 gtk_tree_model_get_iter(pModel
, &aGtkIter
.iter
, path
);
14871 if (func(aGtkIter
))
14874 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
14876 g_object_thaw_notify(G_OBJECT(m_pTreeModel
));
14879 virtual void visible_foreach(const std::function
<bool(weld::TreeIter
&)>& func
) override
14881 g_object_freeze_notify(G_OBJECT(m_pTreeModel
));
14883 GtkTreePath
* start_path
;
14884 GtkTreePath
* end_path
;
14886 if (!gtk_tree_view_get_visible_range(m_pTreeView
, &start_path
, &end_path
))
14889 GtkInstanceTreeIter
aGtkIter(nullptr);
14890 gtk_tree_model_get_iter(m_pTreeModel
, &aGtkIter
.iter
, start_path
);
14894 if (func(aGtkIter
))
14896 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, &aGtkIter
.iter
);
14897 bool bContinue
= gtk_tree_path_compare(path
, end_path
) != 0;
14898 gtk_tree_path_free(path
);
14901 if (!iter_next(aGtkIter
))
14905 gtk_tree_path_free(start_path
);
14906 gtk_tree_path_free(end_path
);
14908 g_object_thaw_notify(G_OBJECT(m_pTreeModel
));
14911 virtual void connect_visible_range_changed(const Link
<weld::TreeView
&, void>& rLink
) override
14913 weld::TreeView::connect_visible_range_changed(rLink
);
14914 if (!m_nVAdjustmentChangedSignalId
)
14916 GtkAdjustment
* pVAdjustment
= gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView
));
14917 m_nVAdjustmentChangedSignalId
= g_signal_connect(pVAdjustment
, "value-changed", G_CALLBACK(signalVAdjustmentChanged
), this);
14921 virtual bool is_selected(const weld::TreeIter
& rIter
) const override
14923 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
14924 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView
), const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
14927 virtual OUString
get_text(int pos
, int col
) const override
14932 col
= to_internal_model(col
);
14933 return get(pos
, col
);
14936 virtual void set_text(int pos
, const OUString
& rText
, int col
) override
14941 col
= to_internal_model(col
);
14942 set(pos
, col
, rText
);
14945 virtual TriState
get_toggle(int pos
, int col
) const override
14948 col
= m_nExpanderToggleCol
;
14950 col
= to_internal_model(col
);
14952 if (get_bool(pos
, m_aToggleTriStateMap
.find(col
)->second
))
14953 return TRISTATE_INDET
;
14954 return get_bool(pos
, col
) ? TRISTATE_TRUE
: TRISTATE_FALSE
;
14957 virtual TriState
get_toggle(const weld::TreeIter
& rIter
, int col
) const override
14960 col
= m_nExpanderToggleCol
;
14962 col
= to_internal_model(col
);
14964 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
14965 if (get_bool(rGtkIter
.iter
, m_aToggleTriStateMap
.find(col
)->second
))
14966 return TRISTATE_INDET
;
14967 return get_bool(rGtkIter
.iter
, col
) ? TRISTATE_TRUE
: TRISTATE_FALSE
;
14970 virtual void set_toggle(const weld::TreeIter
& rIter
, TriState eState
, int col
) override
14972 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
14973 set_toggle(rGtkIter
.iter
, eState
, col
);
14976 virtual void set_toggle(int pos
, TriState eState
, int col
) override
14979 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
14980 set_toggle(iter
, eState
, col
);
14983 virtual void enable_toggle_buttons(weld::ColumnToggleType eType
) override
14985 for (GList
* pEntry
= g_list_first(m_pColumns
); pEntry
; pEntry
= g_list_next(pEntry
))
14987 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
14988 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
14989 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
14991 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
14992 if (!GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer
))
14994 GtkCellRendererToggle
* pToggle
= GTK_CELL_RENDERER_TOGGLE(pCellRenderer
);
14995 gtk_cell_renderer_toggle_set_radio(pToggle
, eType
== weld::ColumnToggleType::Radio
);
14997 g_list_free(pRenderers
);
15001 virtual void set_clicks_to_toggle(int /*nToggleBehavior*/) override
15005 virtual void set_extra_row_indent(const weld::TreeIter
& rIter
, int nIndentLevel
) override
15007 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15008 set(rGtkIter
.iter
, m_aIndentMap
[m_nTextCol
], nIndentLevel
* get_expander_size());
15011 virtual void set_text_emphasis(const weld::TreeIter
& rIter
, bool bOn
, int col
) override
15013 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15014 col
= to_internal_model(col
);
15015 set(rGtkIter
.iter
, m_aWeightMap
[col
], bOn
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
);
15018 virtual void set_text_emphasis(int pos
, bool bOn
, int col
) override
15020 col
= to_internal_model(col
);
15021 set(pos
, m_aWeightMap
[col
], bOn
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
);
15024 virtual bool get_text_emphasis(const weld::TreeIter
& rIter
, int col
) const override
15026 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15027 col
= to_internal_model(col
);
15028 return get_int(rGtkIter
.iter
, m_aWeightMap
.find(col
)->second
) == PANGO_WEIGHT_BOLD
;
15031 virtual bool get_text_emphasis(int pos
, int col
) const override
15033 col
= to_internal_model(col
);
15034 return get_int(pos
, m_aWeightMap
.find(col
)->second
) == PANGO_WEIGHT_BOLD
;
15037 virtual void set_text_align(const weld::TreeIter
& rIter
, double fAlign
, int col
) override
15039 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15040 col
= to_internal_model(col
);
15041 set(rGtkIter
.iter
, m_aAlignMap
[col
], fAlign
);
15044 virtual void set_text_align(int pos
, double fAlign
, int col
) override
15046 col
= to_internal_model(col
);
15047 set(pos
, m_aAlignMap
[col
], fAlign
);
15050 using GtkInstanceWidget::set_sensitive
;
15052 virtual void set_sensitive(int pos
, bool bSensitive
, int col
) override
15057 col
= to_internal_model(col
);
15058 set(pos
, m_aSensitiveMap
[col
], bSensitive
);
15061 virtual void set_sensitive(const weld::TreeIter
& rIter
, bool bSensitive
, int col
) override
15066 col
= to_internal_model(col
);
15067 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15068 set(rGtkIter
.iter
, m_aSensitiveMap
[col
], bSensitive
);
15071 void set_image(const GtkTreeIter
& iter
, int col
, GdkPixbuf
* pixbuf
)
15074 col
= m_nExpanderImageCol
;
15076 col
= to_internal_model(col
);
15077 m_Setter(m_pTreeModel
, const_cast<GtkTreeIter
*>(&iter
), col
, pixbuf
, -1);
15079 g_object_unref(pixbuf
);
15082 void set_image(int pos
, GdkPixbuf
* pixbuf
, int col
)
15085 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
15087 set_image(iter
, col
, pixbuf
);
15091 virtual void set_image(int pos
, const css::uno::Reference
<css::graphic::XGraphic
>& rImage
, int col
) override
15093 set_image(pos
, getPixbuf(rImage
), col
);
15096 virtual void set_image(int pos
, const OUString
& rImage
, int col
) override
15098 set_image(pos
, getPixbuf(rImage
), col
);
15101 virtual void set_image(int pos
, VirtualDevice
& rImage
, int col
) override
15103 set_image(pos
, getPixbuf(rImage
), col
);
15106 virtual void set_image(const weld::TreeIter
& rIter
, const css::uno::Reference
<css::graphic::XGraphic
>& rImage
, int col
) override
15108 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15109 set_image(rGtkIter
.iter
, col
, getPixbuf(rImage
));
15112 virtual void set_image(const weld::TreeIter
& rIter
, const OUString
& rImage
, int col
) override
15114 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15115 set_image(rGtkIter
.iter
, col
, getPixbuf(rImage
));
15118 virtual void set_image(const weld::TreeIter
& rIter
, VirtualDevice
& rImage
, int col
) override
15120 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15121 set_image(rGtkIter
.iter
, col
, getPixbuf(rImage
));
15124 virtual OUString
get_id(int pos
) const override
15126 return get(pos
, m_nIdCol
);
15129 virtual void set_id(int pos
, const OUString
& rId
) override
15131 return set(pos
, m_nIdCol
, rId
);
15134 virtual int get_iter_index_in_parent(const weld::TreeIter
& rIter
) const override
15136 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15138 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15141 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
15142 int nRet
= indices
[depth
-1];
15144 gtk_tree_path_free(path
);
15149 virtual int iter_compare(const weld::TreeIter
& a
, const weld::TreeIter
& b
) const override
15151 const GtkInstanceTreeIter
& rGtkIterA
= static_cast<const GtkInstanceTreeIter
&>(a
);
15152 const GtkInstanceTreeIter
& rGtkIterB
= static_cast<const GtkInstanceTreeIter
&>(b
);
15154 GtkTreePath
* pathA
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIterA
.iter
));
15155 GtkTreePath
* pathB
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIterB
.iter
));
15157 int nRet
= gtk_tree_path_compare(pathA
, pathB
);
15159 gtk_tree_path_free(pathB
);
15160 gtk_tree_path_free(pathA
);
15165 // by copy and delete of old copy
15166 void move_subtree(GtkTreeIter
& rFromIter
, GtkTreeIter
* pGtkParentIter
, int nIndexInNewParent
)
15168 int nCols
= gtk_tree_model_get_n_columns(m_pTreeModel
);
15171 GtkTreeIter toiter
;
15172 m_Insert(m_pTreeModel
, &toiter
, pGtkParentIter
, nIndexInNewParent
);
15174 for (int i
= 0; i
< nCols
; ++i
)
15176 memset(&value
, 0, sizeof(GValue
));
15177 gtk_tree_model_get_value(m_pTreeModel
, &rFromIter
, i
, &value
);
15178 m_SetValue(m_pTreeModel
, &toiter
, i
, &value
);
15179 g_value_unset(&value
);
15182 GtkTreeIter tmpfromiter
;
15183 if (gtk_tree_model_iter_children(m_pTreeModel
, &tmpfromiter
, &rFromIter
))
15188 move_subtree(tmpfromiter
, &toiter
, j
++);
15189 } while (gtk_tree_model_iter_next(m_pTreeModel
, &tmpfromiter
));
15192 m_Remove(m_pTreeModel
, &rFromIter
);
15195 virtual void move_subtree(weld::TreeIter
& rNode
, const weld::TreeIter
* pNewParent
, int nIndexInNewParent
) override
15197 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rNode
);
15198 const GtkInstanceTreeIter
* pGtkParentIter
= static_cast<const GtkInstanceTreeIter
*>(pNewParent
);
15199 move_subtree(rGtkIter
.iter
, pGtkParentIter
? const_cast<GtkTreeIter
*>(&pGtkParentIter
->iter
) : nullptr, nIndexInNewParent
);
15202 virtual int get_selected_index() const override
15204 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't request selection when frozen");
15206 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(m_pTreeView
);
15207 if (gtk_tree_selection_get_mode(selection
) != GTK_SELECTION_MULTIPLE
)
15210 GtkTreeModel
* pModel
;
15211 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView
), &pModel
, &iter
))
15213 GtkTreePath
* path
= gtk_tree_model_get_path(pModel
, &iter
);
15216 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
15217 nRet
= indices
[depth
-1];
15219 gtk_tree_path_free(path
);
15224 auto vec
= get_selected_rows();
15225 return vec
.empty() ? -1 : vec
[0];
15230 bool get_selected_iterator(GtkTreeIter
* pIter
) const
15232 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't request selection when frozen");
15234 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(m_pTreeView
);
15235 if (gtk_tree_selection_get_mode(selection
) != GTK_SELECTION_MULTIPLE
)
15236 bRet
= gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView
), nullptr, pIter
);
15239 GtkTreeModel
* pModel
;
15240 GList
* pList
= gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView
), &pModel
);
15241 for (GList
* pItem
= g_list_first(pList
); pItem
; pItem
= g_list_next(pItem
))
15245 GtkTreePath
* path
= static_cast<GtkTreePath
*>(pItem
->data
);
15246 gtk_tree_model_get_iter(pModel
, pIter
, path
);
15251 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
15256 virtual OUString
get_selected_text() const override
15258 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't request selection when frozen");
15260 if (get_selected_iterator(&iter
))
15261 return get(iter
, m_nTextCol
);
15265 virtual OUString
get_selected_id() const override
15267 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't request selection when frozen");
15269 if (get_selected_iterator(&iter
))
15270 return get(iter
, m_nIdCol
);
15274 virtual std::unique_ptr
<weld::TreeIter
> make_iterator(const weld::TreeIter
* pOrig
) const override
15276 return std::unique_ptr
<weld::TreeIter
>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter
*>(pOrig
)));
15279 virtual void copy_iterator(const weld::TreeIter
& rSource
, weld::TreeIter
& rDest
) const override
15281 const GtkInstanceTreeIter
& rGtkSource(static_cast<const GtkInstanceTreeIter
&>(rSource
));
15282 GtkInstanceTreeIter
& rGtkDest(static_cast<GtkInstanceTreeIter
&>(rDest
));
15283 rGtkDest
.iter
= rGtkSource
.iter
;
15286 virtual bool get_selected(weld::TreeIter
* pIter
) const override
15288 GtkInstanceTreeIter
* pGtkIter
= static_cast<GtkInstanceTreeIter
*>(pIter
);
15289 return get_selected_iterator(pGtkIter
? &pGtkIter
->iter
: nullptr);
15292 virtual bool get_cursor(weld::TreeIter
* pIter
) const override
15294 GtkInstanceTreeIter
* pGtkIter
= static_cast<GtkInstanceTreeIter
*>(pIter
);
15296 gtk_tree_view_get_cursor(m_pTreeView
, &path
, nullptr);
15297 if (pGtkIter
&& path
)
15299 gtk_tree_model_get_iter(m_pTreeModel
, &pGtkIter
->iter
, path
);
15303 gtk_tree_path_free(path
);
15307 virtual int get_cursor_index() const override
15312 gtk_tree_view_get_cursor(m_pTreeView
, &path
, nullptr);
15316 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
15317 nRet
= indices
[depth
-1];
15318 gtk_tree_path_free(path
);
15324 virtual void set_cursor(const weld::TreeIter
& rIter
) override
15326 disable_notify_events();
15327 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15329 if (gtk_tree_model_iter_parent(m_pTreeModel
, &Iter
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
)))
15331 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, &Iter
);
15332 if (!gtk_tree_view_row_expanded(m_pTreeView
, path
))
15333 gtk_tree_view_expand_to_path(m_pTreeView
, path
);
15334 gtk_tree_path_free(path
);
15336 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15337 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, nullptr, false, 0, 0);
15338 gtk_tree_view_set_cursor(m_pTreeView
, path
, nullptr, false);
15339 gtk_tree_path_free(path
);
15340 enable_notify_events();
15343 virtual bool get_iter_first(weld::TreeIter
& rIter
) const override
15345 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
15346 return gtk_tree_model_get_iter_first(m_pTreeModel
, &rGtkIter
.iter
);
15349 virtual bool iter_next_sibling(weld::TreeIter
& rIter
) const override
15351 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
15352 return gtk_tree_model_iter_next(m_pTreeModel
, &rGtkIter
.iter
);
15355 virtual bool iter_previous_sibling(weld::TreeIter
& rIter
) const override
15357 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
15358 return gtk_tree_model_iter_previous(m_pTreeModel
, &rGtkIter
.iter
);
15361 virtual bool iter_next(weld::TreeIter
& rIter
) const override
15363 return iter_next(rIter
, false);
15366 virtual bool iter_previous(weld::TreeIter
& rIter
) const override
15369 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
15370 GtkTreeIter iter
= rGtkIter
.iter
;
15371 GtkTreeIter tmp
= iter
;
15372 if (gtk_tree_model_iter_previous(m_pTreeModel
, &tmp
))
15374 // Move down level(s) until we find the level where the last node exists.
15375 int nChildren
= gtk_tree_model_iter_n_children(m_pTreeModel
, &tmp
);
15377 rGtkIter
.iter
= tmp
;
15379 last_child(m_pTreeModel
, &rGtkIter
.iter
, &tmp
, nChildren
);
15385 if (gtk_tree_model_iter_parent(m_pTreeModel
, &tmp
, &iter
))
15387 rGtkIter
.iter
= tmp
;
15394 //on-demand dummy entry doesn't count
15395 if (get_text(rGtkIter
, -1) == "<dummy>")
15396 return iter_previous(rGtkIter
);
15403 virtual bool iter_children(weld::TreeIter
& rIter
) const override
15405 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
15407 bool ret
= gtk_tree_model_iter_children(m_pTreeModel
, &tmp
, &rGtkIter
.iter
);
15408 rGtkIter
.iter
= tmp
;
15411 //on-demand dummy entry doesn't count
15412 return get_text(rGtkIter
, -1) != "<dummy>";
15417 virtual bool iter_parent(weld::TreeIter
& rIter
) const override
15419 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
15421 bool ret
= gtk_tree_model_iter_parent(m_pTreeModel
, &tmp
, &rGtkIter
.iter
);
15422 rGtkIter
.iter
= tmp
;
15426 virtual void remove(const weld::TreeIter
& rIter
) override
15428 disable_notify_events();
15429 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15430 m_Remove(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15431 enable_notify_events();
15434 virtual void remove_selection() override
15436 disable_notify_events();
15438 std::vector
<GtkTreeIter
> aIters
;
15439 GtkTreeModel
* pModel
;
15440 GList
* pList
= gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView
), &pModel
);
15441 for (GList
* pItem
= g_list_first(pList
); pItem
; pItem
= g_list_next(pItem
))
15443 GtkTreePath
* path
= static_cast<GtkTreePath
*>(pItem
->data
);
15444 aIters
.emplace_back();
15445 gtk_tree_model_get_iter(pModel
, &aIters
.back(), path
);
15447 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
15449 for (auto& iter
: aIters
)
15450 m_Remove(m_pTreeModel
, &iter
);
15452 enable_notify_events();
15455 virtual void select(const weld::TreeIter
& rIter
) override
15457 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15458 disable_notify_events();
15459 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15460 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView
), const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15461 enable_notify_events();
15464 virtual void scroll_to_row(const weld::TreeIter
& rIter
) override
15466 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15467 disable_notify_events();
15468 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15469 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15470 gtk_tree_view_expand_to_path(m_pTreeView
, path
);
15471 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, nullptr, false, 0, 0);
15472 gtk_tree_path_free(path
);
15473 enable_notify_events();
15476 virtual void unselect(const weld::TreeIter
& rIter
) override
15478 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15479 disable_notify_events();
15480 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15481 gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView
), const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15482 enable_notify_events();
15485 virtual int get_iter_depth(const weld::TreeIter
& rIter
) const override
15487 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15488 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15489 int ret
= gtk_tree_path_get_depth(path
) - 1;
15490 gtk_tree_path_free(path
);
15494 virtual bool iter_has_child(const weld::TreeIter
& rIter
) const override
15496 GtkInstanceTreeIter
aTempCopy(static_cast<const GtkInstanceTreeIter
*>(&rIter
));
15497 return iter_children(aTempCopy
);
15500 virtual bool get_row_expanded(const weld::TreeIter
& rIter
) const override
15502 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15503 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15504 bool ret
= gtk_tree_view_row_expanded(m_pTreeView
, path
);
15505 gtk_tree_path_free(path
);
15509 virtual bool get_children_on_demand(const weld::TreeIter
& rIter
) const override
15511 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15512 GtkInstanceTreeIter
aIter(&rGtkIter
);
15513 return child_is_placeholder(aIter
);
15516 virtual void set_children_on_demand(const weld::TreeIter
& rIter
, bool bChildrenOnDemand
) override
15518 disable_notify_events();
15520 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15521 GtkInstanceTreeIter
aPlaceHolderIter(&rGtkIter
);
15523 bool bPlaceHolder
= child_is_placeholder(aPlaceHolderIter
);
15525 if (bChildrenOnDemand
&& !bPlaceHolder
)
15527 GtkTreeIter subiter
;
15528 OUString
sDummy("<dummy>");
15529 insert_row(subiter
, &rGtkIter
.iter
, -1, nullptr, &sDummy
, nullptr, nullptr);
15531 else if (!bChildrenOnDemand
&& bPlaceHolder
)
15532 remove(aPlaceHolderIter
);
15534 enable_notify_events();
15537 virtual void expand_row(const weld::TreeIter
& rIter
) override
15539 assert(gtk_tree_view_get_model(m_pTreeView
) && "don't expand when frozen");
15541 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15542 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15543 if (!gtk_tree_view_row_expanded(m_pTreeView
, path
))
15544 gtk_tree_view_expand_to_path(m_pTreeView
, path
);
15545 gtk_tree_path_free(path
);
15548 virtual void collapse_row(const weld::TreeIter
& rIter
) override
15550 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15551 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15552 if (gtk_tree_view_row_expanded(m_pTreeView
, path
))
15553 gtk_tree_view_collapse_row(m_pTreeView
, path
);
15554 gtk_tree_path_free(path
);
15557 virtual OUString
get_text(const weld::TreeIter
& rIter
, int col
) const override
15559 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15563 col
= to_internal_model(col
);
15564 return get(rGtkIter
.iter
, col
);
15567 virtual void set_text(const weld::TreeIter
& rIter
, const OUString
& rText
, int col
) override
15569 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15573 col
= to_internal_model(col
);
15574 set(rGtkIter
.iter
, col
, rText
);
15577 virtual OUString
get_id(const weld::TreeIter
& rIter
) const override
15579 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15580 return get(rGtkIter
.iter
, m_nIdCol
);
15583 virtual void set_id(const weld::TreeIter
& rIter
, const OUString
& rId
) override
15585 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15586 set(rGtkIter
.iter
, m_nIdCol
, rId
);
15589 virtual void freeze() override
15591 disable_notify_events();
15592 bool bIsFirstFreeze
= IsFirstFreeze();
15593 GtkInstanceWidget::freeze();
15594 if (bIsFirstFreeze
)
15596 g_object_ref(m_pTreeModel
);
15597 gtk_tree_view_set_model(m_pTreeView
, nullptr);
15598 g_object_freeze_notify(G_OBJECT(m_pTreeModel
));
15602 GtkSortType eSortType
;
15603 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
15604 gtk_tree_sortable_get_sort_column_id(pSortable
, &nSortColumn
, &eSortType
);
15605 gtk_tree_sortable_set_sort_column_id(pSortable
, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
, eSortType
);
15607 m_aSavedSortColumns
.push_back(nSortColumn
);
15608 m_aSavedSortTypes
.push_back(eSortType
);
15611 enable_notify_events();
15614 virtual void thaw() override
15616 disable_notify_events();
15621 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
15622 gtk_tree_sortable_set_sort_column_id(pSortable
, m_aSavedSortColumns
.back(), m_aSavedSortTypes
.back());
15623 m_aSavedSortTypes
.pop_back();
15624 m_aSavedSortColumns
.pop_back();
15626 g_object_thaw_notify(G_OBJECT(m_pTreeModel
));
15627 gtk_tree_view_set_model(m_pTreeView
, GTK_TREE_MODEL(m_pTreeModel
));
15628 g_object_unref(m_pTreeModel
);
15630 GtkInstanceWidget::thaw();
15631 enable_notify_events();
15634 virtual int get_height_rows(int nRows
) const override
15636 return ::get_height_rows(m_pTreeView
, m_pColumns
, nRows
);
15639 virtual Size
get_size_request() const override
15641 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
15642 if (GTK_IS_SCROLLED_WINDOW(pParent
))
15644 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent
)),
15645 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent
)));
15647 int nWidth
, nHeight
;
15648 gtk_widget_get_size_request(m_pWidget
, &nWidth
, &nHeight
);
15649 return Size(nWidth
, nHeight
);
15652 virtual Size
get_preferred_size() const override
15655 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
15656 if (GTK_IS_SCROLLED_WINDOW(pParent
))
15658 aRet
= Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent
)),
15659 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent
)));
15661 GtkRequisition size
;
15662 #if !GTK_CHECK_VERSION(4, 0, 0)
15663 // sometimes gtk gives a bad outcome for gtk_widget_get_preferred_size if GtkTreeView's
15664 // do_validate_rows hasn't been run before querying the preferred size, if we call
15665 // gtk_widget_get_preferred_width first, we can guarantee do_validate_rows gets called
15666 gtk_widget_get_preferred_width(m_pWidget
, nullptr, &size
.width
);
15668 gtk_widget_get_preferred_size(m_pWidget
, nullptr, &size
);
15669 if (aRet
.Width() == -1)
15670 aRet
.setWidth(size
.width
);
15671 if (aRet
.Height() == -1)
15672 aRet
.setHeight(size
.height
);
15676 virtual void show() override
15678 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
15679 if (GTK_IS_SCROLLED_WINDOW(pParent
))
15680 gtk_widget_show(pParent
);
15681 gtk_widget_show(m_pWidget
);
15684 virtual void hide() override
15686 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
15687 if (GTK_IS_SCROLLED_WINDOW(pParent
))
15688 gtk_widget_hide(pParent
);
15689 gtk_widget_hide(m_pWidget
);
15692 virtual void enable_drag_source(rtl::Reference
<TransferDataContainer
>& rHelper
, sal_uInt8 eDNDConstants
) override
15694 do_enable_drag_source(rHelper
, eDNDConstants
);
15697 #if !GTK_CHECK_VERSION(4, 0, 0)
15698 virtual void drag_source_set(const std::vector
<GtkTargetEntry
>& rGtkTargets
, GdkDragAction eDragAction
) override
15700 if (rGtkTargets
.empty() && !eDragAction
)
15701 gtk_tree_view_unset_rows_drag_source(m_pTreeView
);
15703 gtk_tree_view_enable_model_drag_source(m_pTreeView
, GDK_BUTTON1_MASK
, rGtkTargets
.data(), rGtkTargets
.size(), eDragAction
);
15707 virtual void set_selection_mode(SelectionMode eMode
) override
15709 disable_notify_events();
15710 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView
), VclToGtk(eMode
));
15711 enable_notify_events();
15714 virtual int count_selected_rows() const override
15716 return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView
));
15719 int starts_with(const OUString
& rStr
, int nStartRow
, bool bCaseSensitive
)
15721 return ::starts_with(m_pTreeModel
, rStr
, m_nTextCol
, nStartRow
, bCaseSensitive
);
15724 virtual void disable_notify_events() override
15726 g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView
), m_nChangedSignalId
);
15727 g_signal_handler_block(m_pTreeView
, m_nRowActivatedSignalId
);
15729 g_signal_handler_block(m_pTreeModel
, m_nRowDeletedSignalId
);
15730 g_signal_handler_block(m_pTreeModel
, m_nRowInsertedSignalId
);
15732 GtkInstanceWidget::disable_notify_events();
15735 virtual void enable_notify_events() override
15737 GtkInstanceWidget::enable_notify_events();
15739 g_signal_handler_unblock(m_pTreeModel
, m_nRowDeletedSignalId
);
15740 g_signal_handler_unblock(m_pTreeModel
, m_nRowInsertedSignalId
);
15742 g_signal_handler_unblock(m_pTreeView
, m_nRowActivatedSignalId
);
15743 g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView
), m_nChangedSignalId
);
15746 virtual void connect_popup_menu(const Link
<const CommandEvent
&, bool>& rLink
) override
15748 ensureButtonPressSignal();
15749 weld::TreeView::connect_popup_menu(rLink
);
15752 virtual bool get_dest_row_at_pos(const Point
&rPos
, weld::TreeIter
* pResult
, bool bDnDMode
) override
15754 if (rPos
.X() < 0 || rPos
.Y() < 0)
15756 // short-circuit to avoid "gtk_tree_view_get_dest_row_at_pos: assertion 'drag_x >= 0'" g_assert
15760 const bool bAsTree
= gtk_tree_view_get_enable_tree_lines(m_pTreeView
);
15762 // to keep it simple we'll default to always drop before the current row
15763 // except for the special edge cases
15764 GtkTreeViewDropPosition pos
= bAsTree
? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
: GTK_TREE_VIEW_DROP_BEFORE
;
15766 // unhighlight current highlighted row
15767 gtk_tree_view_set_drag_dest_row(m_pTreeView
, nullptr, pos
);
15769 if (m_bWorkAroundBadDragRegion
)
15771 #if GTK_CHECK_VERSION(4, 0, 0)
15772 gtk_widget_unset_state_flags(GTK_WIDGET(m_pTreeView
), GTK_STATE_FLAG_DROP_ACTIVE
);
15774 gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView
));
15778 GtkTreePath
*path
= nullptr;
15779 GtkTreeViewDropPosition gtkpos
= bAsTree
? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
: GTK_TREE_VIEW_DROP_BEFORE
;
15780 bool ret
= gtk_tree_view_get_dest_row_at_pos(m_pTreeView
, rPos
.X(), rPos
.Y(),
15783 // find the last entry in the model for comparison
15784 GtkTreePath
*lastpath
= get_path_of_last_entry(m_pTreeModel
);
15788 // empty space, draw an indicator at the last entry
15790 path
= gtk_tree_path_copy(lastpath
);
15791 pos
= GTK_TREE_VIEW_DROP_AFTER
;
15793 else if (bDnDMode
&& gtk_tree_path_compare(path
, lastpath
) == 0)
15795 // if we're on the last entry, see if gtk thinks
15796 // the drop should be before or after it, and if
15797 // its after, treat it like a drop into empty
15798 // space, i.e. append it
15799 if (gtkpos
== GTK_TREE_VIEW_DROP_AFTER
||
15800 gtkpos
== GTK_TREE_VIEW_DROP_INTO_OR_AFTER
)
15803 pos
= bAsTree
? gtkpos
: GTK_TREE_VIEW_DROP_AFTER
;
15807 if (ret
&& pResult
)
15809 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(*pResult
);
15810 gtk_tree_model_get_iter(m_pTreeModel
, &rGtkIter
.iter
, path
);
15813 if (m_bInDrag
&& bDnDMode
)
15815 // highlight the row
15816 gtk_tree_view_set_drag_dest_row(m_pTreeView
, path
, pos
);
15820 gtk_tree_path_free(path
);
15821 gtk_tree_path_free(lastpath
);
15823 // auto scroll if we're close to the edges
15824 GtkAdjustment
* pVAdjustment
= gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView
));
15825 double fStep
= gtk_adjustment_get_step_increment(pVAdjustment
);
15826 if (rPos
.Y() < fStep
)
15828 double fValue
= gtk_adjustment_get_value(pVAdjustment
) - fStep
;
15831 gtk_adjustment_set_value(pVAdjustment
, fValue
);
15835 GdkRectangle aRect
;
15836 gtk_tree_view_get_visible_rect(m_pTreeView
, &aRect
);
15837 if (rPos
.Y() > aRect
.height
- fStep
)
15839 double fValue
= gtk_adjustment_get_value(pVAdjustment
) + fStep
;
15840 double fMax
= gtk_adjustment_get_upper(pVAdjustment
);
15843 gtk_adjustment_set_value(pVAdjustment
, fValue
);
15850 virtual void unset_drag_dest_row() override
15852 gtk_tree_view_set_drag_dest_row(m_pTreeView
, nullptr, GTK_TREE_VIEW_DROP_BEFORE
);
15855 virtual tools::Rectangle
get_row_area(const weld::TreeIter
& rIter
) const override
15857 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15858 GtkTreePath
* pPath
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15859 tools::Rectangle aRet
= ::get_row_area(m_pTreeView
, m_pColumns
, pPath
);
15860 gtk_tree_path_free(pPath
);
15864 virtual void start_editing(const weld::TreeIter
& rIter
) override
15866 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns
, m_nTextView
));
15867 assert(pColumn
&& "wrong column");
15869 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
15870 GtkTreePath
* path
= gtk_tree_model_get_path(m_pTreeModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
15872 // allow editing of cells which are not usually editable, so we can have double click
15873 // do its usual row-activate but if we explicitly want to edit (remote files dialog)
15874 // we can still do that
15875 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
15876 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
15878 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
15879 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer
))
15881 gboolean
is_editable(false);
15882 g_object_get(pCellRenderer
, "editable", &is_editable
, nullptr);
15885 g_object_set(pCellRenderer
, "editable", true, "editable-set", true, nullptr);
15886 g_object_set_data(G_OBJECT(pCellRenderer
), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer
>(true));
15891 g_list_free(pRenderers
);
15893 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, pColumn
, false, 0, 0);
15894 gtk_tree_view_set_cursor(m_pTreeView
, path
, pColumn
, true);
15896 gtk_tree_path_free(path
);
15899 virtual void end_editing() override
15901 GtkTreeViewColumn
*focus_column
= nullptr;
15902 gtk_tree_view_get_cursor(m_pTreeView
, nullptr, &focus_column
);
15904 gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column
)), true);
15907 virtual TreeView
* get_drag_source() const override
15909 return g_DragSource
;
15912 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon
) override
15914 if (m_aDragBeginHdl
.Call(rUnsetDragIcon
))
15916 g_DragSource
= this;
15920 virtual void do_signal_drag_end() override
15922 g_DragSource
= nullptr;
15925 // Under gtk 3.24.8 dragging into the TreeView is not highlighting
15926 // entire TreeView widget, just the rectangle which has no entries
15927 // in it, so as a workaround highlight the parent container
15928 // on drag start, and undo it on drag end, and trigger removal
15929 // of the treeview's highlight effort
15930 virtual void drag_started() override
15933 GtkWidget
* pWidget
= GTK_WIDGET(m_pTreeView
);
15934 GtkWidget
* pParent
= gtk_widget_get_parent(pWidget
);
15935 if (GTK_IS_SCROLLED_WINDOW(pParent
))
15937 #if GTK_CHECK_VERSION(4, 0, 0)
15938 gtk_widget_unset_state_flags(pWidget
, GTK_STATE_FLAG_DROP_ACTIVE
);
15939 gtk_widget_set_state_flags(pParent
, GTK_STATE_FLAG_DROP_ACTIVE
, false);
15941 gtk_drag_unhighlight(pWidget
);
15942 gtk_drag_highlight(pParent
);
15944 m_bWorkAroundBadDragRegion
= true;
15948 virtual void drag_ended() override
15951 if (m_bWorkAroundBadDragRegion
)
15953 GtkWidget
* pWidget
= GTK_WIDGET(m_pTreeView
);
15954 GtkWidget
* pParent
= gtk_widget_get_parent(pWidget
);
15955 #if GTK_CHECK_VERSION(4, 0, 0)
15956 gtk_widget_unset_state_flags(pParent
, GTK_STATE_FLAG_DROP_ACTIVE
);
15958 gtk_drag_unhighlight(pParent
);
15960 m_bWorkAroundBadDragRegion
= false;
15962 // unhighlight the row
15963 gtk_tree_view_set_drag_dest_row(m_pTreeView
, nullptr, GTK_TREE_VIEW_DROP_BEFORE
);
15966 virtual int vadjustment_get_value() const override
15968 if (m_nPendingVAdjustment
!= -1)
15969 return m_nPendingVAdjustment
;
15970 return gtk_adjustment_get_value(m_pVAdjustment
);
15973 virtual void vadjustment_set_value(int value
) override
15975 disable_notify_events();
15977 /* This rube goldberg device is to remove flicker from setting the
15978 scroll position of a GtkTreeView directly after clearing it and
15979 filling it. As a specific example the writer navigator with ~100
15980 tables, scroll to the end, right click on an entry near the end
15981 and rename it, the tree is cleared and refilled and an attempt
15982 made to set the scroll position of the freshly refilled tree to
15983 the same point as before the clear.
15986 // This forces the tree to recalculate now its preferred size
15987 // after being cleared
15988 GtkRequisition size
;
15989 gtk_widget_get_preferred_size(GTK_WIDGET(m_pTreeView
), nullptr, &size
);
15991 m_nPendingVAdjustment
= value
;
15993 // The value set here just has to be different to the final value
15994 // set later so that isn't a no-op
15995 gtk_adjustment_set_value(m_pVAdjustment
, value
- 0.0001);
15997 // This will set the desired m_nPendingVAdjustment value right
15998 // before the tree gets drawn
15999 gtk_widget_add_tick_callback(GTK_WIDGET(m_pTreeView
), setAdjustmentCallback
, this, nullptr);
16001 enable_notify_events();
16004 void call_signal_custom_render(VirtualDevice
& rOutput
, const tools::Rectangle
& rRect
, bool bSelected
, const OUString
& rId
)
16006 signal_custom_render(rOutput
, rRect
, bSelected
, rId
);
16009 Size
call_signal_custom_get_size(VirtualDevice
& rOutput
, const OUString
& rId
)
16011 return signal_custom_get_size(rOutput
, rId
);
16014 virtual void set_show_expanders(bool bShow
) override
16016 gtk_tree_view_set_show_expanders(m_pTreeView
, bShow
);
16019 virtual bool changed_by_hover() const override
16021 return m_bChangedByMouse
;
16024 virtual ~GtkInstanceTreeView() override
16026 if (m_pChangeEvent
)
16027 Application::RemoveUserEvent(m_pChangeEvent
);
16028 if (m_nQueryTooltipSignalId
)
16029 g_signal_handler_disconnect(m_pTreeView
, m_nQueryTooltipSignalId
);
16030 #if !GTK_CHECK_VERSION(4, 0, 0)
16031 g_signal_handler_disconnect(m_pTreeView
, m_nKeyPressSignalId
);
16032 g_signal_handler_disconnect(m_pTreeView
, m_nPopupMenuSignalId
);
16034 g_signal_handler_disconnect(m_pTreeModel
, m_nRowDeletedSignalId
);
16035 g_signal_handler_disconnect(m_pTreeModel
, m_nRowInsertedSignalId
);
16037 if (m_nVAdjustmentChangedSignalId
)
16039 GtkAdjustment
* pVAdjustment
= gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView
));
16040 g_signal_handler_disconnect(pVAdjustment
, m_nVAdjustmentChangedSignalId
);
16043 g_signal_handler_disconnect(m_pTreeView
, m_nTestCollapseRowSignalId
);
16044 g_signal_handler_disconnect(m_pTreeView
, m_nTestExpandRowSignalId
);
16045 g_signal_handler_disconnect(m_pTreeView
, m_nRowActivatedSignalId
);
16046 g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView
), m_nChangedSignalId
);
16048 GValue value
= G_VALUE_INIT
;
16049 g_value_init(&value
, G_TYPE_POINTER
);
16050 g_value_set_pointer(&value
, static_cast<gpointer
>(nullptr));
16052 for (GList
* pEntry
= g_list_last(m_pColumns
); pEntry
; pEntry
= g_list_previous(pEntry
))
16054 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pEntry
->data
);
16055 g_signal_handler_disconnect(pColumn
, m_aColumnSignalIds
.back());
16056 m_aColumnSignalIds
.pop_back();
16058 // unset "instance" to avoid dangling "instance" points in any CustomCellRenderers
16059 GList
*pRenderers
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn
));
16060 for (GList
* pRenderer
= g_list_first(pRenderers
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
16062 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
16063 if (!CUSTOM_IS_CELL_RENDERER(pCellRenderer
))
16065 g_object_set_property(G_OBJECT(pCellRenderer
), "instance", &value
);
16067 g_list_free(pRenderers
);
16069 g_list_free(m_pColumns
);
16075 IMPL_LINK_NOARG(GtkInstanceTreeView
, async_signal_changed
, void*, void)
16077 m_pChangeEvent
= nullptr;
16079 m_bChangedByMouse
= false;
16082 IMPL_LINK_NOARG(GtkInstanceTreeView
, async_stop_cell_editing
, void*, void)
16089 class GtkInstanceIconView
: public GtkInstanceWidget
, public virtual weld::IconView
16092 GtkIconView
* m_pIconView
;
16093 GtkTreeStore
* m_pTreeStore
;
16097 gulong m_nSelectionChangedSignalId
;
16098 gulong m_nItemActivatedSignalId
;
16099 #if !GTK_CHECK_VERSION(4, 0, 0)
16100 gulong m_nPopupMenu
;
16102 gulong m_nQueryTooltipSignalId
= 0;
16103 ImplSVEvent
* m_pSelectionChangeEvent
;
16105 DECL_LINK(async_signal_selection_changed
, void*, void);
16107 bool signal_command(const CommandEvent
& rCEvt
)
16109 return m_aCommandHdl
.Call(rCEvt
);
16112 virtual bool signal_popup_menu(const CommandEvent
& rCEvt
) override
16114 return signal_command(rCEvt
);
16117 void launch_signal_selection_changed()
16119 //tdf#117991 selection change is sent before the focus change, and focus change
16120 //is what will cause a spinbutton that currently has the focus to set its contents
16121 //as the spin button value. So any LibreOffice callbacks on
16122 //signal-change would happen before the spinbutton value-change occurs.
16123 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
16124 //has been processed
16125 if (m_pSelectionChangeEvent
)
16126 Application::RemoveUserEvent(m_pSelectionChangeEvent
);
16127 m_pSelectionChangeEvent
= Application::PostUserEvent(LINK(this, GtkInstanceIconView
, async_signal_selection_changed
));
16130 static void signalSelectionChanged(GtkIconView
*, gpointer widget
)
16132 GtkInstanceIconView
* pThis
= static_cast<GtkInstanceIconView
*>(widget
);
16133 pThis
->launch_signal_selection_changed();
16136 void handle_item_activated()
16138 if (signal_item_activated())
16142 static void signalItemActivated(GtkIconView
*, GtkTreePath
*, gpointer widget
)
16144 GtkInstanceIconView
* pThis
= static_cast<GtkInstanceIconView
*>(widget
);
16145 SolarMutexGuard aGuard
;
16146 pThis
->handle_item_activated();
16149 static gboolean
signalQueryTooltip(GtkWidget
* /*pGtkWidget*/, gint x
, gint y
,
16150 gboolean keyboard_tip
, GtkTooltip
* tooltip
,
16153 GtkInstanceIconView
* pThis
= static_cast<GtkInstanceIconView
*>(widget
);
16155 GtkIconView
* pIconView
= pThis
->m_pIconView
;
16156 GtkTreeModel
* pModel
= gtk_icon_view_get_model(pIconView
);
16157 GtkTreePath
* pPath
= nullptr;
16158 #if GTK_CHECK_VERSION(4, 0, 0)
16159 if (!gtk_icon_view_get_tooltip_context(pIconView
, x
, y
, keyboard_tip
, &pModel
, &pPath
, &iter
))
16162 if (!gtk_icon_view_get_tooltip_context(pIconView
, &x
, &y
, keyboard_tip
, &pModel
, &pPath
, &iter
))
16165 OUString aTooltip
= pThis
->signal_query_tooltip(GtkInstanceTreeIter(iter
));
16166 if (!aTooltip
.isEmpty())
16168 gtk_tooltip_set_text(tooltip
, OUStringToOString(aTooltip
, RTL_TEXTENCODING_UTF8
).getStr());
16169 gtk_icon_view_set_tooltip_item(pIconView
, tooltip
, pPath
);
16171 gtk_tree_path_free(pPath
);
16172 return !aTooltip
.isEmpty();
16175 void insert_item(GtkTreeIter
& iter
, int pos
, const OUString
* pId
, const OUString
* pText
, const OUString
* pIconName
)
16177 gtk_tree_store_insert_with_values(m_pTreeStore
, &iter
, nullptr, pos
,
16178 m_nTextCol
, !pText
? nullptr : OUStringToOString(*pText
, RTL_TEXTENCODING_UTF8
).getStr(),
16179 m_nIdCol
, !pId
? nullptr : OUStringToOString(*pId
, RTL_TEXTENCODING_UTF8
).getStr(),
16183 GdkPixbuf
* pixbuf
= getPixbuf(*pIconName
);
16184 gtk_tree_store_set(m_pTreeStore
, &iter
, m_nImageCol
, pixbuf
, -1);
16186 g_object_unref(pixbuf
);
16190 void insert_item(GtkTreeIter
& iter
, int pos
, const OUString
* pId
, const OUString
* pText
, const VirtualDevice
* pIcon
)
16192 gtk_tree_store_insert_with_values(m_pTreeStore
, &iter
, nullptr, pos
,
16193 m_nTextCol
, !pText
? nullptr : OUStringToOString(*pText
, RTL_TEXTENCODING_UTF8
).getStr(),
16194 m_nIdCol
, !pId
? nullptr : OUStringToOString(*pId
, RTL_TEXTENCODING_UTF8
).getStr(),
16198 GdkPixbuf
* pixbuf
= getPixbuf(*pIcon
);
16199 gtk_tree_store_set(m_pTreeStore
, &iter
, m_nImageCol
, pixbuf
, -1);
16201 g_object_unref(pixbuf
);
16205 OUString
get(const GtkTreeIter
& iter
, int col
) const
16207 GtkTreeModel
*pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16209 gtk_tree_model_get(pModel
, const_cast<GtkTreeIter
*>(&iter
), col
, &pStr
, -1);
16210 OUString
sRet(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
16215 bool get_selected_iterator(GtkTreeIter
* pIter
) const
16217 assert(gtk_icon_view_get_model(m_pIconView
) && "don't request selection when frozen");
16220 GtkTreeModel
* pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16221 GList
* pList
= gtk_icon_view_get_selected_items(m_pIconView
);
16222 for (GList
* pItem
= g_list_first(pList
); pItem
; pItem
= g_list_next(pItem
))
16226 GtkTreePath
* path
= static_cast<GtkTreePath
*>(pItem
->data
);
16227 gtk_tree_model_get_iter(pModel
, pIter
, path
);
16232 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
16238 GtkInstanceIconView(GtkIconView
* pIconView
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
16239 : GtkInstanceWidget(GTK_WIDGET(pIconView
), pBuilder
, bTakeOwnership
)
16240 , m_pIconView(pIconView
)
16241 , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView
)))
16242 , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView
))
16243 , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView
))
16244 , m_nSelectionChangedSignalId(g_signal_connect(pIconView
, "selection-changed",
16245 G_CALLBACK(signalSelectionChanged
), this))
16246 , m_nItemActivatedSignalId(g_signal_connect(pIconView
, "item-activated", G_CALLBACK(signalItemActivated
), this))
16247 #if !GTK_CHECK_VERSION(4, 0, 0)
16248 , m_nPopupMenu(g_signal_connect(pIconView
, "popup-menu", G_CALLBACK(signalPopupMenu
), this))
16250 , m_pSelectionChangeEvent(nullptr)
16252 m_nIdCol
= m_nTextCol
+ 1;
16255 virtual int get_item_width() const override
16257 return gtk_icon_view_get_item_width(m_pIconView
);
16260 virtual void set_item_width(int width
) override
16262 gtk_icon_view_set_item_width(m_pIconView
, width
);
16265 virtual void insert(int pos
, const OUString
* pText
, const OUString
* pId
, const OUString
* pIconName
, weld::TreeIter
* pRet
) override
16267 disable_notify_events();
16269 insert_item(iter
, pos
, pId
, pText
, pIconName
);
16272 GtkInstanceTreeIter
* pGtkRetIter
= static_cast<GtkInstanceTreeIter
*>(pRet
);
16273 pGtkRetIter
->iter
= iter
;
16275 enable_notify_events();
16278 virtual void insert(int pos
, const OUString
* pText
, const OUString
* pId
, const VirtualDevice
* pIcon
, weld::TreeIter
* pRet
) override
16280 disable_notify_events();
16282 insert_item(iter
, pos
, pId
, pText
, pIcon
);
16285 GtkInstanceTreeIter
* pGtkRetIter
= static_cast<GtkInstanceTreeIter
*>(pRet
);
16286 pGtkRetIter
->iter
= iter
;
16288 enable_notify_events();
16291 virtual void insert_separator(int /* pos */, const OUString
* /* pId */) override
16293 // TODO: can't just copy from GtkInstanceTreeView, since there's
16294 // no IconView analog for gtk_tree_view_get_row_separator_func
16297 virtual void connect_query_tooltip(const Link
<const weld::TreeIter
&, OUString
>& rLink
) override
16299 weld::IconView::connect_query_tooltip(rLink
);
16300 m_nQueryTooltipSignalId
= g_signal_connect(m_pIconView
, "query-tooltip", G_CALLBACK(signalQueryTooltip
), this);
16303 virtual OUString
get_selected_id() const override
16305 assert(gtk_icon_view_get_model(m_pIconView
) && "don't request selection when frozen");
16307 if (get_selected_iterator(&iter
))
16308 return get(iter
, m_nIdCol
);
16312 virtual void clear() override
16314 disable_notify_events();
16315 gtk_tree_store_clear(m_pTreeStore
);
16316 enable_notify_events();
16319 virtual void freeze() override
16321 disable_notify_events();
16322 bool bIsFirstFreeze
= IsFirstFreeze();
16323 GtkInstanceWidget::freeze();
16324 if (bIsFirstFreeze
)
16326 g_object_ref(m_pTreeStore
);
16327 gtk_icon_view_set_model(m_pIconView
, nullptr);
16328 g_object_freeze_notify(G_OBJECT(m_pTreeStore
));
16330 enable_notify_events();
16333 virtual void thaw() override
16335 disable_notify_events();
16338 g_object_thaw_notify(G_OBJECT(m_pTreeStore
));
16339 gtk_icon_view_set_model(m_pIconView
, GTK_TREE_MODEL(m_pTreeStore
));
16340 g_object_unref(m_pTreeStore
);
16342 GtkInstanceWidget::thaw();
16343 enable_notify_events();
16346 virtual Size
get_size_request() const override
16348 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
16349 if (GTK_IS_SCROLLED_WINDOW(pParent
))
16351 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent
)),
16352 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent
)));
16354 int nWidth
, nHeight
;
16355 gtk_widget_get_size_request(m_pWidget
, &nWidth
, &nHeight
);
16356 return Size(nWidth
, nHeight
);
16359 virtual Size
get_preferred_size() const override
16362 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
16363 if (GTK_IS_SCROLLED_WINDOW(pParent
))
16365 aRet
= Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent
)),
16366 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent
)));
16368 GtkRequisition size
;
16369 gtk_widget_get_preferred_size(m_pWidget
, nullptr, &size
);
16370 if (aRet
.Width() == -1)
16371 aRet
.setWidth(size
.width
);
16372 if (aRet
.Height() == -1)
16373 aRet
.setHeight(size
.height
);
16377 virtual void show() override
16379 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
16380 if (GTK_IS_SCROLLED_WINDOW(pParent
))
16381 gtk_widget_show(pParent
);
16382 gtk_widget_show(m_pWidget
);
16385 virtual void hide() override
16387 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
16388 if (GTK_IS_SCROLLED_WINDOW(pParent
))
16389 gtk_widget_hide(pParent
);
16390 gtk_widget_hide(m_pWidget
);
16393 virtual OUString
get_selected_text() const override
16395 assert(gtk_icon_view_get_model(m_pIconView
) && "don't request selection when frozen");
16397 if (get_selected_iterator(&iter
))
16398 return get(iter
, m_nTextCol
);
16402 virtual int count_selected_items() const override
16404 GList
* pList
= gtk_icon_view_get_selected_items(m_pIconView
);
16405 int nRet
= g_list_length(pList
);
16406 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
16410 virtual void select(int pos
) override
16412 assert(gtk_icon_view_get_model(m_pIconView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16413 disable_notify_events();
16414 if (pos
== -1 || (pos
== 0 && n_children() == 0))
16416 gtk_icon_view_unselect_all(m_pIconView
);
16420 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
16421 gtk_icon_view_select_path(m_pIconView
, path
);
16422 gtk_icon_view_scroll_to_path(m_pIconView
, path
, false, 0, 0);
16423 gtk_tree_path_free(path
);
16425 enable_notify_events();
16428 virtual void unselect(int pos
) override
16430 assert(gtk_icon_view_get_model(m_pIconView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16431 disable_notify_events();
16432 if (pos
== -1 || (pos
== 0 && n_children() == 0))
16434 gtk_icon_view_select_all(m_pIconView
);
16438 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
16439 gtk_icon_view_select_path(m_pIconView
, path
);
16440 gtk_tree_path_free(path
);
16442 enable_notify_events();
16445 virtual bool get_selected(weld::TreeIter
* pIter
) const override
16447 GtkInstanceTreeIter
* pGtkIter
= static_cast<GtkInstanceTreeIter
*>(pIter
);
16448 return get_selected_iterator(pGtkIter
? &pGtkIter
->iter
: nullptr);
16451 virtual bool get_cursor(weld::TreeIter
* pIter
) const override
16453 GtkInstanceTreeIter
* pGtkIter
= static_cast<GtkInstanceTreeIter
*>(pIter
);
16455 gtk_icon_view_get_cursor(m_pIconView
, &path
, nullptr);
16456 if (pGtkIter
&& path
)
16458 GtkTreeModel
*pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16459 gtk_tree_model_get_iter(pModel
, &pGtkIter
->iter
, path
);
16461 return path
!= nullptr;
16464 virtual void set_cursor(const weld::TreeIter
& rIter
) override
16466 disable_notify_events();
16467 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
16468 GtkTreeModel
*pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16469 GtkTreePath
* path
= gtk_tree_model_get_path(pModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
16470 gtk_icon_view_set_cursor(m_pIconView
, path
, nullptr, false);
16471 gtk_tree_path_free(path
);
16472 enable_notify_events();
16475 virtual bool get_iter_first(weld::TreeIter
& rIter
) const override
16477 GtkInstanceTreeIter
& rGtkIter
= static_cast<GtkInstanceTreeIter
&>(rIter
);
16478 GtkTreeModel
*pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16479 return gtk_tree_model_get_iter_first(pModel
, &rGtkIter
.iter
);
16482 virtual void scroll_to_item(const weld::TreeIter
& rIter
) override
16484 assert(gtk_icon_view_get_model(m_pIconView
) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16485 disable_notify_events();
16486 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
16487 GtkTreeModel
*pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16488 GtkTreePath
* path
= gtk_tree_model_get_path(pModel
, const_cast<GtkTreeIter
*>(&rGtkIter
.iter
));
16489 gtk_icon_view_scroll_to_path(m_pIconView
, path
, false, 0, 0);
16490 gtk_tree_path_free(path
);
16491 enable_notify_events();
16494 virtual std::unique_ptr
<weld::TreeIter
> make_iterator(const weld::TreeIter
* pOrig
) const override
16496 return std::unique_ptr
<weld::TreeIter
>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter
*>(pOrig
)));
16499 virtual void selected_foreach(const std::function
<bool(weld::TreeIter
&)>& func
) override
16501 GtkInstanceTreeIter
aGtkIter(nullptr);
16503 GtkTreeModel
*pModel
= GTK_TREE_MODEL(m_pTreeStore
);
16504 GList
* pList
= gtk_icon_view_get_selected_items(m_pIconView
);
16505 for (GList
* pItem
= g_list_first(pList
); pItem
; pItem
= g_list_next(pItem
))
16507 GtkTreePath
* path
= static_cast<GtkTreePath
*>(pItem
->data
);
16508 gtk_tree_model_get_iter(pModel
, &aGtkIter
.iter
, path
);
16509 if (func(aGtkIter
))
16512 g_list_free_full(pList
, reinterpret_cast<GDestroyNotify
>(gtk_tree_path_free
));
16515 virtual int n_children() const override
16517 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore
), nullptr);
16520 virtual OUString
get_id(const weld::TreeIter
& rIter
) const override
16522 const GtkInstanceTreeIter
& rGtkIter
= static_cast<const GtkInstanceTreeIter
&>(rIter
);
16523 return get(rGtkIter
.iter
, m_nIdCol
);
16526 virtual void disable_notify_events() override
16528 g_signal_handler_block(m_pIconView
, m_nSelectionChangedSignalId
);
16529 g_signal_handler_block(m_pIconView
, m_nItemActivatedSignalId
);
16531 GtkInstanceWidget::disable_notify_events();
16534 virtual void enable_notify_events() override
16536 GtkInstanceWidget::enable_notify_events();
16538 g_signal_handler_unblock(m_pIconView
, m_nItemActivatedSignalId
);
16539 g_signal_handler_unblock(m_pIconView
, m_nSelectionChangedSignalId
);
16542 virtual ~GtkInstanceIconView() override
16544 if (m_pSelectionChangeEvent
)
16545 Application::RemoveUserEvent(m_pSelectionChangeEvent
);
16547 if (m_nQueryTooltipSignalId
)
16548 g_signal_handler_disconnect(m_pIconView
, m_nQueryTooltipSignalId
);
16550 g_signal_handler_disconnect(m_pIconView
, m_nItemActivatedSignalId
);
16551 g_signal_handler_disconnect(m_pIconView
, m_nSelectionChangedSignalId
);
16552 #if !GTK_CHECK_VERSION(4, 0, 0)
16553 g_signal_handler_disconnect(m_pIconView
, m_nPopupMenu
);
16560 IMPL_LINK_NOARG(GtkInstanceIconView
, async_signal_selection_changed
, void*, void)
16562 m_pSelectionChangeEvent
= nullptr;
16563 signal_selection_changed();
16568 class GtkInstanceSpinButton
: public GtkInstanceEditable
, public virtual weld::SpinButton
16571 GtkSpinButton
* m_pButton
;
16572 gulong m_nValueChangedSignalId
;
16573 gulong m_nOutputSignalId
;
16574 gulong m_nInputSignalId
;
16575 bool m_bFormatting
;
16576 bool m_bBlockOutput
;
16579 static void signalValueChanged(GtkSpinButton
*, gpointer widget
)
16581 GtkInstanceSpinButton
* pThis
= static_cast<GtkInstanceSpinButton
*>(widget
);
16582 SolarMutexGuard aGuard
;
16583 pThis
->m_bBlank
= false;
16584 pThis
->signal_value_changed();
16587 bool guarded_signal_output()
16589 if (m_bBlockOutput
)
16591 m_bFormatting
= true;
16592 bool bRet
= signal_output();
16593 m_bFormatting
= false;
16597 static gboolean
signalOutput(GtkSpinButton
*, gpointer widget
)
16599 GtkInstanceSpinButton
* pThis
= static_cast<GtkInstanceSpinButton
*>(widget
);
16600 SolarMutexGuard aGuard
;
16601 return pThis
->guarded_signal_output();
16604 static gint
signalInput(GtkSpinButton
*, gdouble
* new_value
, gpointer widget
)
16606 GtkInstanceSpinButton
* pThis
= static_cast<GtkInstanceSpinButton
*>(widget
);
16607 SolarMutexGuard aGuard
;
16609 TriState eHandled
= pThis
->signal_input(&result
);
16610 if (eHandled
== TRISTATE_INDET
)
16612 if (eHandled
== TRISTATE_TRUE
)
16614 *new_value
= pThis
->toGtk(result
);
16617 return GTK_INPUT_ERROR
;
16620 virtual void signal_activate() override
16622 gtk_spin_button_update(m_pButton
);
16623 GtkInstanceEditable::signal_activate();
16626 double toGtk(sal_Int64 nValue
) const
16628 return static_cast<double>(nValue
) / Power10(get_digits());
16631 sal_Int64
fromGtk(double fValue
) const
16633 return FRound(fValue
* Power10(get_digits()));
16637 GtkInstanceSpinButton(GtkSpinButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
16638 : GtkInstanceEditable(GTK_WIDGET(pButton
), pBuilder
, bTakeOwnership
)
16639 , m_pButton(pButton
)
16640 , m_nValueChangedSignalId(g_signal_connect(pButton
, "value-changed", G_CALLBACK(signalValueChanged
), this))
16641 , m_nOutputSignalId(g_signal_connect(pButton
, "output", G_CALLBACK(signalOutput
), this))
16642 , m_nInputSignalId(g_signal_connect(pButton
, "input", G_CALLBACK(signalInput
), this))
16643 , m_bFormatting(false)
16644 , m_bBlockOutput(false)
16647 #if GTK_CHECK_VERSION(4, 0, 0)
16648 gtk_text_set_activates_default(GTK_TEXT(m_pDelegate
), true);
16652 virtual sal_Int64
get_value() const override
16654 return fromGtk(gtk_spin_button_get_value(m_pButton
));
16657 virtual void set_value(sal_Int64 value
) override
16659 disable_notify_events();
16661 gtk_spin_button_set_value(m_pButton
, toGtk(value
));
16662 enable_notify_events();
16665 virtual void set_text(const OUString
& rText
) override
16667 disable_notify_events();
16668 // tdf#122786 if we're just formatting a value, then we're done,
16669 // however if set_text has been called directly we want to update our
16670 // value from this new text, but don't want to reformat with that value
16671 if (!m_bFormatting
)
16673 #if GTK_CHECK_VERSION(4, 0, 0)
16674 gtk_editable_set_text(m_pEditable
, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
16676 gtk_entry_set_text(GTK_ENTRY(m_pButton
), OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
16679 m_bBlockOutput
= true;
16680 gtk_spin_button_update(m_pButton
);
16681 m_bBlank
= rText
.isEmpty();
16682 m_bBlockOutput
= false;
16686 bool bKeepBlank
= m_bBlank
&& get_value() == 0;
16689 #if GTK_CHECK_VERSION(4, 0, 0)
16690 gtk_editable_set_text(m_pEditable
, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
16692 gtk_entry_set_text(GTK_ENTRY(m_pButton
), OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
16697 enable_notify_events();
16700 virtual void set_range(sal_Int64 min
, sal_Int64 max
) override
16702 disable_notify_events();
16703 gtk_spin_button_set_range(m_pButton
, toGtk(min
), toGtk(max
));
16704 enable_notify_events();
16707 virtual void get_range(sal_Int64
& min
, sal_Int64
& max
) const override
16709 double gtkmin
, gtkmax
;
16710 gtk_spin_button_get_range(m_pButton
, >kmin
, >kmax
);
16711 min
= fromGtk(gtkmin
);
16712 max
= fromGtk(gtkmax
);
16715 virtual void set_increments(int step
, int page
) override
16717 disable_notify_events();
16718 gtk_spin_button_set_increments(m_pButton
, toGtk(step
), toGtk(page
));
16719 enable_notify_events();
16722 virtual void get_increments(int& step
, int& page
) const override
16724 double gtkstep
, gtkpage
;
16725 gtk_spin_button_get_increments(m_pButton
, >kstep
, >kpage
);
16726 step
= fromGtk(gtkstep
);
16727 page
= fromGtk(gtkpage
);
16730 virtual void set_digits(unsigned int digits
) override
16732 disable_notify_events();
16733 gtk_spin_button_set_digits(m_pButton
, digits
);
16734 enable_notify_events();
16737 virtual unsigned int get_digits() const override
16739 return gtk_spin_button_get_digits(m_pButton
);
16742 virtual void set_font(const vcl::Font
& rFont
) override
16744 m_aCustomFont
.use_custom_font(&rFont
, u
"spinbutton");
16747 virtual void disable_notify_events() override
16749 g_signal_handler_block(m_pButton
, m_nValueChangedSignalId
);
16750 GtkInstanceEditable::disable_notify_events();
16753 virtual void enable_notify_events() override
16755 GtkInstanceEditable::enable_notify_events();
16756 g_signal_handler_unblock(m_pButton
, m_nValueChangedSignalId
);
16759 virtual ~GtkInstanceSpinButton() override
16761 g_signal_handler_disconnect(m_pButton
, m_nInputSignalId
);
16762 g_signal_handler_disconnect(m_pButton
, m_nOutputSignalId
);
16763 g_signal_handler_disconnect(m_pButton
, m_nValueChangedSignalId
);
16771 class GtkInstanceFormattedSpinButton
: public GtkInstanceEditable
, public virtual weld::FormattedSpinButton
16774 GtkSpinButton
* m_pButton
;
16775 std::unique_ptr
<weld::EntryFormatter
> m_xOwnFormatter
;
16776 weld::EntryFormatter
* m_pFormatter
;
16777 gulong m_nValueChangedSignalId
;
16778 gulong m_nOutputSignalId
;
16779 gulong m_nInputSignalId
;
16780 bool m_bEmptyField
;
16781 bool m_bSyncingValue
;
16782 double m_dValueWhenEmpty
;
16784 bool signal_output()
16786 double fValue
= gtk_spin_button_get_value(m_pButton
);
16787 m_bEmptyField
&= fValue
== m_dValueWhenEmpty
;
16788 if (!m_bEmptyField
)
16789 GetFormatter().SetValue(fValue
);
16793 static gboolean
signalOutput(GtkSpinButton
*, gpointer widget
)
16795 GtkInstanceFormattedSpinButton
* pThis
= static_cast<GtkInstanceFormattedSpinButton
*>(widget
);
16796 SolarMutexGuard aGuard
;
16797 return pThis
->signal_output();
16800 gint
signal_input(double* value
)
16802 Formatter
& rFormatter
= GetFormatter();
16803 rFormatter
.Modify();
16804 // if the blank-mode is enabled then if the input is empty don't parse
16805 // the input but keep the value as it is. store what the value the
16806 // blank is associated with and until the value is changed, or the text
16807 // is updated from the outside, don't output that value
16808 m_bEmptyField
= rFormatter
.IsEmptyFieldEnabled() && get_text().isEmpty();
16811 m_dValueWhenEmpty
= gtk_spin_button_get_value(m_pButton
);
16812 *value
= m_dValueWhenEmpty
;
16815 *value
= rFormatter
.GetValue();
16819 static gint
signalInput(GtkSpinButton
*, gdouble
* new_value
, gpointer widget
)
16821 GtkInstanceFormattedSpinButton
* pThis
= static_cast<GtkInstanceFormattedSpinButton
*>(widget
);
16822 SolarMutexGuard aGuard
;
16823 return pThis
->signal_input(new_value
);
16826 static void signalValueChanged(GtkSpinButton
*, gpointer widget
)
16828 GtkInstanceFormattedSpinButton
* pThis
= static_cast<GtkInstanceFormattedSpinButton
*>(widget
);
16829 SolarMutexGuard aGuard
;
16830 pThis
->signal_value_changed();
16834 GtkInstanceFormattedSpinButton(GtkSpinButton
* pButton
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
16835 : GtkInstanceEditable(GTK_WIDGET(pButton
), pBuilder
, bTakeOwnership
)
16836 , m_pButton(pButton
)
16837 , m_pFormatter(nullptr)
16838 , m_nValueChangedSignalId(g_signal_connect(pButton
, "value-changed", G_CALLBACK(signalValueChanged
), this))
16839 , m_nOutputSignalId(g_signal_connect(pButton
, "output", G_CALLBACK(signalOutput
), this))
16840 , m_nInputSignalId(g_signal_connect(pButton
, "input", G_CALLBACK(signalInput
), this))
16841 , m_bEmptyField(false)
16842 , m_bSyncingValue(false)
16843 , m_dValueWhenEmpty(0.0)
16847 virtual void set_text(const OUString
& rText
) override
16849 GtkInstanceEditable::set_text(rText
);
16850 Formatter
& rFormatter
= GetFormatter();
16851 m_bEmptyField
= rFormatter
.IsEmptyFieldEnabled() && rText
.isEmpty();
16853 m_dValueWhenEmpty
= gtk_spin_button_get_value(m_pButton
);
16856 virtual void connect_changed(const Link
<weld::Entry
&, void>& rLink
) override
16858 if (!m_pFormatter
) // once a formatter is set, it takes over "changed"
16860 GtkInstanceEditable::connect_changed(rLink
);
16863 m_pFormatter
->connect_changed(rLink
);
16866 virtual void connect_focus_out(const Link
<weld::Widget
&, void>& rLink
) override
16868 if (!m_pFormatter
) // once a formatter is set, it takes over "focus-out"
16870 GtkInstanceEditable::connect_focus_out(rLink
);
16873 m_pFormatter
->connect_focus_out(rLink
);
16876 virtual void SetFormatter(weld::EntryFormatter
* pFormatter
) override
16878 m_xOwnFormatter
.reset();
16879 m_pFormatter
= pFormatter
;
16880 sync_range_from_formatter();
16881 sync_value_from_formatter();
16882 sync_increments_from_formatter();
16885 virtual weld::EntryFormatter
& GetFormatter() override
16889 auto aFocusOutHdl
= m_aFocusOutHdl
;
16890 m_aFocusOutHdl
= Link
<weld::Widget
&, void>();
16891 auto aChangeHdl
= m_aChangeHdl
;
16892 m_aChangeHdl
= Link
<weld::Entry
&, void>();
16894 double fValue
= gtk_spin_button_get_value(m_pButton
);
16896 gtk_spin_button_get_range(m_pButton
, &fMin
, &fMax
);
16898 gtk_spin_button_get_increments(m_pButton
, &fStep
, nullptr);
16899 m_xOwnFormatter
.reset(new weld::EntryFormatter(*this));
16900 m_xOwnFormatter
->SetMinValue(fMin
);
16901 m_xOwnFormatter
->SetMaxValue(fMax
);
16902 m_xOwnFormatter
->SetSpinSize(fStep
);
16903 m_xOwnFormatter
->SetValue(fValue
);
16905 m_xOwnFormatter
->connect_focus_out(aFocusOutHdl
);
16906 m_xOwnFormatter
->connect_changed(aChangeHdl
);
16908 m_pFormatter
= m_xOwnFormatter
.get();
16910 return *m_pFormatter
;
16913 virtual void sync_value_from_formatter() override
16917 // tdf#135317 avoid reenterence
16918 if (m_bSyncingValue
)
16920 m_bSyncingValue
= true;
16921 disable_notify_events();
16922 // tdf#138519 use gtk_adjustment_set_value instead of gtk_spin_button_set_value because the
16923 // latter doesn't change the value if the new value is less than an EPSILON diff of 1e-10
16924 // from the old value
16925 gtk_adjustment_set_value(gtk_spin_button_get_adjustment(m_pButton
), m_pFormatter
->GetValue());
16926 enable_notify_events();
16927 m_bSyncingValue
= false;
16930 virtual void sync_range_from_formatter() override
16934 disable_notify_events();
16935 double fMin
= m_pFormatter
->HasMinValue() ? m_pFormatter
->GetMinValue() : std::numeric_limits
<double>::lowest();
16936 double fMax
= m_pFormatter
->HasMaxValue() ? m_pFormatter
->GetMaxValue() : std::numeric_limits
<double>::max();
16937 gtk_spin_button_set_range(m_pButton
, fMin
, fMax
);
16938 enable_notify_events();
16941 virtual void sync_increments_from_formatter() override
16945 disable_notify_events();
16946 double fSpinSize
= m_pFormatter
->GetSpinSize();
16947 gtk_spin_button_set_increments(m_pButton
, fSpinSize
, fSpinSize
* 10);
16948 enable_notify_events();
16951 virtual void set_font(const vcl::Font
& rFont
) override
16953 m_aCustomFont
.use_custom_font(&rFont
, u
"spinbutton");
16956 virtual void disable_notify_events() override
16958 g_signal_handler_block(m_pButton
, m_nValueChangedSignalId
);
16959 GtkInstanceEditable::disable_notify_events();
16962 virtual void enable_notify_events() override
16964 GtkInstanceEditable::enable_notify_events();
16965 g_signal_handler_unblock(m_pButton
, m_nValueChangedSignalId
);
16968 virtual ~GtkInstanceFormattedSpinButton() override
16970 g_signal_handler_disconnect(m_pButton
, m_nInputSignalId
);
16971 g_signal_handler_disconnect(m_pButton
, m_nOutputSignalId
);
16972 g_signal_handler_disconnect(m_pButton
, m_nValueChangedSignalId
);
16974 m_pFormatter
= nullptr;
16975 m_xOwnFormatter
.reset();
16983 class GtkInstanceLabel
: public GtkInstanceWidget
, public virtual weld::Label
16986 GtkLabel
* m_pLabel
;
16988 void set_text_background_color(const Color
& rColor
)
16990 guint16 nRed
= rColor
.GetRed() << 8;
16991 guint16 nGreen
= rColor
.GetGreen() << 8;
16992 guint16 nBlue
= rColor
.GetBlue() << 8;
16994 PangoAttrType aFilterAttrs
[] = {PANGO_ATTR_BACKGROUND
, PANGO_ATTR_INVALID
};
16996 PangoAttrList
* pOrigList
= gtk_label_get_attributes(m_pLabel
);
16997 PangoAttrList
* pAttrs
= pOrigList
? pango_attr_list_copy(pOrigList
) : pango_attr_list_new();
16998 PangoAttrList
* pRemovedAttrs
= pOrigList
? pango_attr_list_filter(pAttrs
, filter_pango_attrs
, &aFilterAttrs
) : nullptr;
16999 pango_attr_list_insert(pAttrs
, pango_attr_background_new(nRed
, nGreen
, nBlue
));
17000 gtk_label_set_attributes(m_pLabel
, pAttrs
);
17001 pango_attr_list_unref(pAttrs
);
17002 pango_attr_list_unref(pRemovedAttrs
);
17005 void set_text_foreground_color(const Color
& rColor
, bool bSetBold
)
17007 guint16 nRed
= rColor
.GetRed() << 8;
17008 guint16 nGreen
= rColor
.GetGreen() << 8;
17009 guint16 nBlue
= rColor
.GetBlue() << 8;
17011 PangoAttrType aFilterAttrs
[] = {PANGO_ATTR_FOREGROUND
, PANGO_ATTR_WEIGHT
, PANGO_ATTR_INVALID
};
17014 aFilterAttrs
[1] = PANGO_ATTR_INVALID
;
17016 PangoAttrList
* pOrigList
= gtk_label_get_attributes(m_pLabel
);
17017 PangoAttrList
* pAttrs
= pOrigList
? pango_attr_list_copy(pOrigList
) : pango_attr_list_new();
17018 PangoAttrList
* pRemovedAttrs
= pOrigList
? pango_attr_list_filter(pAttrs
, filter_pango_attrs
, &aFilterAttrs
) : nullptr;
17019 if (rColor
!= COL_AUTO
)
17020 pango_attr_list_insert(pAttrs
, pango_attr_foreground_new(nRed
, nGreen
, nBlue
));
17022 pango_attr_list_insert(pAttrs
, pango_attr_weight_new(PANGO_WEIGHT_BOLD
));
17023 gtk_label_set_attributes(m_pLabel
, pAttrs
);
17024 pango_attr_list_unref(pAttrs
);
17025 pango_attr_list_unref(pRemovedAttrs
);
17029 GtkInstanceLabel(GtkLabel
* pLabel
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
17030 : GtkInstanceWidget(GTK_WIDGET(pLabel
), pBuilder
, bTakeOwnership
)
17035 virtual void set_label(const OUString
& rText
) override
17037 ::set_label(m_pLabel
, rText
);
17040 virtual OUString
get_label() const override
17042 return ::get_label(m_pLabel
);
17045 virtual void set_mnemonic_widget(Widget
* pTarget
) override
17047 assert(!gtk_label_get_selectable(m_pLabel
) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
17048 GtkInstanceWidget
* pTargetWidget
= dynamic_cast<GtkInstanceWidget
*>(pTarget
);
17049 gtk_label_set_mnemonic_widget(m_pLabel
, pTargetWidget
? pTargetWidget
->getWidget() : nullptr);
17052 virtual void set_label_type(weld::LabelType eType
) override
17056 case weld::LabelType::Normal
:
17057 gtk_label_set_attributes(m_pLabel
, nullptr);
17059 case weld::LabelType::Warning
:
17060 set_text_background_color(Application::GetSettings().GetStyleSettings().GetWarningColor());
17062 case weld::LabelType::Error
:
17063 set_text_background_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
17065 case weld::LabelType::Title
:
17066 set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetLightColor(), true);
17071 virtual void set_font(const vcl::Font
& rFont
) override
17073 ::set_font(m_pLabel
, rFont
);
17076 virtual void set_font_color(const Color
& rColor
) override
17078 set_text_foreground_color(rColor
, false);
17084 std::unique_ptr
<weld::Label
> GtkInstanceFrame::weld_label_widget() const
17086 GtkWidget
* pLabel
= gtk_frame_get_label_widget(m_pFrame
);
17087 if (!pLabel
|| !GTK_IS_LABEL(pLabel
))
17089 return std::make_unique
<GtkInstanceLabel
>(GTK_LABEL(pLabel
), m_pBuilder
, false);
17094 GdkClipboard
* widget_get_clipboard(GtkWidget
* pWidget
)
17096 #if GTK_CHECK_VERSION(4, 0, 0)
17097 return gtk_widget_get_clipboard(pWidget
);
17099 return gtk_widget_get_clipboard(pWidget
, GDK_SELECTION_CLIPBOARD
);
17103 class GtkInstanceTextView
: public GtkInstanceWidget
, public virtual weld::TextView
17106 GtkTextView
* m_pTextView
;
17107 GtkTextBuffer
* m_pTextBuffer
;
17108 GtkAdjustment
* m_pVAdjustment
;
17109 GtkCssProvider
* m_pFgCssProvider
;
17110 WidgetFont m_aCustomFont
;
17111 int m_nMaxTextLength
;
17112 gulong m_nChangedSignalId
; // we don't disable/enable this one, it's to implement max-length
17113 gulong m_nInsertTextSignalId
;
17114 gulong m_nCursorPosSignalId
;
17115 gulong m_nHasSelectionSignalId
; // we don't disable/enable this one, it's to implement
17116 // auto-scroll to cursor on losing selection
17117 gulong m_nVAdjustChangedSignalId
;
17118 #if !GTK_CHECK_VERSION(4, 0, 0)
17119 gulong m_nButtonPressEvent
; // we don't disable/enable this one, it's to block mouse
17120 // click down from getting to (potential) toplevel
17121 // GtkSalFrame parent, which grabs focus away
17123 static gboolean
signalButtonPressEvent(GtkWidget
*, GdkEventButton
*, gpointer
)
17125 // e.g. on clicking on the help TextView in OTableDesignHelpBar the currently displayed text shouldn't disappear
17130 static void signalChanged(GtkTextBuffer
*, gpointer widget
)
17132 GtkInstanceTextView
* pThis
= static_cast<GtkInstanceTextView
*>(widget
);
17133 SolarMutexGuard aGuard
;
17134 pThis
->signal_changed();
17137 static void signalInserText(GtkTextBuffer
*pBuffer
, GtkTextIter
*pLocation
, gchar
* /*pText*/, gint
/*nLen*/, gpointer widget
)
17139 GtkInstanceTextView
* pThis
= static_cast<GtkInstanceTextView
*>(widget
);
17140 pThis
->insert_text(pBuffer
, pLocation
);
17143 void insert_text(GtkTextBuffer
*pBuffer
, GtkTextIter
*pLocation
)
17145 if (m_nMaxTextLength
)
17147 gint nCount
= gtk_text_buffer_get_char_count(pBuffer
);
17148 if (nCount
> m_nMaxTextLength
)
17150 GtkTextIter nStart
, nEnd
;
17151 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer
, &nStart
, m_nMaxTextLength
);
17152 gtk_text_buffer_get_end_iter(m_pTextBuffer
, &nEnd
);
17153 gtk_text_buffer_delete(m_pTextBuffer
, &nStart
, &nEnd
);
17154 gtk_text_iter_assign(pLocation
, &nStart
);
17159 static void signalCursorPosition(GtkTextBuffer
*, GParamSpec
*, gpointer widget
)
17161 GtkInstanceTextView
* pThis
= static_cast<GtkInstanceTextView
*>(widget
);
17162 pThis
->signal_cursor_position();
17165 static void signalHasSelection(GtkTextBuffer
*, GParamSpec
*, gpointer widget
)
17167 GtkInstanceTextView
* pThis
= static_cast<GtkInstanceTextView
*>(widget
);
17168 pThis
->signal_has_selection();
17171 void signal_has_selection()
17174 in the data browser (Data Sources, shift+ctrl+f4), entering a
17175 multiline cell selects all, on cursoring to the right, the selection
17176 is lost and the cursor is at the end but gtk doesn't auto-scroll to
17177 the cursor so if the text needs scrolling to see the cursor it is off
17178 screen, another cursor makes gtk auto-scroll as wanted. So on losing
17179 selection help gtk out and do the initial scroll ourselves here
17181 if (!gtk_text_buffer_get_has_selection(m_pTextBuffer
))
17183 GtkTextMark
* pMark
= gtk_text_buffer_get_insert(m_pTextBuffer
);
17184 gtk_text_view_scroll_mark_onscreen(m_pTextView
, pMark
);
17188 static void signalVAdjustValueChanged(GtkAdjustment
*, gpointer widget
)
17190 GtkInstanceTextView
* pThis
= static_cast<GtkInstanceTextView
*>(widget
);
17191 SolarMutexGuard aGuard
;
17192 pThis
->signal_vadjustment_changed();
17196 GtkInstanceTextView(GtkTextView
* pTextView
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
17197 : GtkInstanceWidget(GTK_WIDGET(pTextView
), pBuilder
, bTakeOwnership
)
17198 , m_pTextView(pTextView
)
17199 , m_pTextBuffer(gtk_text_view_get_buffer(pTextView
))
17200 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView
)))
17201 , m_pFgCssProvider(nullptr)
17202 , m_aCustomFont(m_pWidget
)
17203 , m_nMaxTextLength(0)
17204 , m_nChangedSignalId(g_signal_connect(m_pTextBuffer
, "changed", G_CALLBACK(signalChanged
), this))
17205 , m_nInsertTextSignalId(g_signal_connect_after(m_pTextBuffer
, "insert-text", G_CALLBACK(signalInserText
), this))
17206 , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer
, "notify::cursor-position", G_CALLBACK(signalCursorPosition
), this))
17207 , m_nHasSelectionSignalId(g_signal_connect(m_pTextBuffer
, "notify::has-selection", G_CALLBACK(signalHasSelection
), this))
17208 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment
, "value-changed", G_CALLBACK(signalVAdjustValueChanged
), this))
17209 #if !GTK_CHECK_VERSION(4, 0, 0)
17210 , m_nButtonPressEvent(g_signal_connect_after(m_pTextView
, "button-press-event", G_CALLBACK(signalButtonPressEvent
), this))
17215 virtual void set_size_request(int nWidth
, int nHeight
) override
17217 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
17218 if (GTK_IS_SCROLLED_WINDOW(pParent
))
17220 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent
), nWidth
);
17221 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent
), nHeight
);
17224 gtk_widget_set_size_request(m_pWidget
, nWidth
, nHeight
);
17227 virtual void set_text(const OUString
& rText
) override
17229 disable_notify_events();
17230 OString
sText(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
17231 gtk_text_buffer_set_text(m_pTextBuffer
, sText
.getStr(), sText
.getLength());
17232 enable_notify_events();
17235 virtual OUString
get_text() const override
17237 GtkTextIter start
, end
;
17238 gtk_text_buffer_get_bounds(m_pTextBuffer
, &start
, &end
);
17239 char* pStr
= gtk_text_buffer_get_text(m_pTextBuffer
, &start
, &end
, true);
17240 OUString
sRet(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
17245 virtual void replace_selection(const OUString
& rText
) override
17247 disable_notify_events();
17248 gtk_text_buffer_delete_selection(m_pTextBuffer
, false, gtk_text_view_get_editable(m_pTextView
));
17249 OString
sText(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
17250 gtk_text_buffer_insert_at_cursor(m_pTextBuffer
, sText
.getStr(), sText
.getLength());
17251 enable_notify_events();
17254 virtual bool get_selection_bounds(int& rStartPos
, int& rEndPos
) override
17256 GtkTextIter start
, end
;
17257 gtk_text_buffer_get_selection_bounds(m_pTextBuffer
, &start
, &end
);
17258 rStartPos
= gtk_text_iter_get_offset(&start
);
17259 rEndPos
= gtk_text_iter_get_offset(&end
);
17260 return rStartPos
!= rEndPos
;
17263 virtual void select_region(int nStartPos
, int nEndPos
) override
17265 disable_notify_events();
17266 GtkTextIter start
, end
;
17267 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer
, &start
, nStartPos
);
17268 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer
, &end
, nEndPos
);
17269 gtk_text_buffer_select_range(m_pTextBuffer
, &start
, &end
);
17270 GtkTextMark
* mark
= gtk_text_buffer_create_mark(m_pTextBuffer
, "scroll", &end
, true);
17271 gtk_text_view_scroll_mark_onscreen(m_pTextView
, mark
);
17272 enable_notify_events();
17275 virtual void set_editable(bool bEditable
) override
17277 gtk_text_view_set_editable(m_pTextView
, bEditable
);
17280 virtual bool get_editable() const override
17282 return gtk_text_view_get_editable(m_pTextView
);
17285 virtual void set_max_length(int nChars
) override
17287 m_nMaxTextLength
= nChars
;
17290 virtual void set_monospace(bool bMonospace
) override
17292 gtk_text_view_set_monospace(m_pTextView
, bMonospace
);
17295 virtual void set_font_color(const Color
& rColor
) override
17297 const bool bRemoveColor
= rColor
== COL_AUTO
;
17298 if (bRemoveColor
&& !m_pFgCssProvider
)
17300 GtkStyleContext
*pWidgetContext
= gtk_widget_get_style_context(GTK_WIDGET(m_pTextView
));
17301 if (m_pFgCssProvider
)
17303 gtk_style_context_remove_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pFgCssProvider
));
17304 m_pFgCssProvider
= nullptr;
17308 OUString sColor
= rColor
.AsRGBHexString();
17309 m_pFgCssProvider
= gtk_css_provider_new();
17310 OUString aBuffer
= "textview text { color: #" + sColor
+ "; }";
17311 OString aResult
= OUStringToOString(aBuffer
, RTL_TEXTENCODING_UTF8
);
17312 css_provider_load_from_data(m_pFgCssProvider
, aResult
.getStr(), aResult
.getLength());
17313 gtk_style_context_add_provider(pWidgetContext
, GTK_STYLE_PROVIDER(m_pFgCssProvider
),
17314 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
17317 virtual void set_font(const vcl::Font
& rFont
) override
17319 m_aCustomFont
.use_custom_font(&rFont
, u
"textview");
17322 virtual vcl::Font
get_font() override
17324 if (const vcl::Font
* pFont
= m_aCustomFont
.get_custom_font())
17326 return GtkInstanceWidget::get_font();
17329 virtual void disable_notify_events() override
17331 g_signal_handler_block(m_pVAdjustment
, m_nVAdjustChangedSignalId
);
17332 g_signal_handler_block(m_pTextBuffer
, m_nCursorPosSignalId
);
17333 g_signal_handler_block(m_pTextBuffer
, m_nChangedSignalId
);
17334 GtkInstanceWidget::disable_notify_events();
17337 virtual void enable_notify_events() override
17339 GtkInstanceWidget::enable_notify_events();
17340 g_signal_handler_unblock(m_pTextBuffer
, m_nChangedSignalId
);
17341 g_signal_handler_unblock(m_pTextBuffer
, m_nCursorPosSignalId
);
17342 g_signal_handler_unblock(m_pVAdjustment
, m_nVAdjustChangedSignalId
);
17345 // in gtk, 'up' when on the first line, will jump to the start of the line
17346 // if not there already
17347 virtual bool can_move_cursor_with_up() const override
17349 GtkTextIter start
, end
;
17350 gtk_text_buffer_get_selection_bounds(m_pTextBuffer
, &start
, &end
);
17351 return !gtk_text_iter_equal(&start
, &end
) || !gtk_text_iter_is_start(&start
);
17354 // in gtk, 'down' when on the first line, will jump to the end of the line
17355 // if not there already
17356 virtual bool can_move_cursor_with_down() const override
17358 GtkTextIter start
, end
;
17359 gtk_text_buffer_get_selection_bounds(m_pTextBuffer
, &start
, &end
);
17360 return !gtk_text_iter_equal(&start
, &end
) || !gtk_text_iter_is_end(&end
);
17363 virtual void cut_clipboard() override
17365 GdkClipboard
*pClipboard
= widget_get_clipboard(GTK_WIDGET(m_pTextView
));
17366 gtk_text_buffer_cut_clipboard(m_pTextBuffer
, pClipboard
, get_editable());
17369 virtual void copy_clipboard() override
17371 GdkClipboard
*pClipboard
= widget_get_clipboard(GTK_WIDGET(m_pTextView
));
17372 gtk_text_buffer_copy_clipboard(m_pTextBuffer
, pClipboard
);
17375 virtual void paste_clipboard() override
17377 GdkClipboard
*pClipboard
= widget_get_clipboard(GTK_WIDGET(m_pTextView
));
17378 gtk_text_buffer_paste_clipboard(m_pTextBuffer
, pClipboard
, nullptr, get_editable());
17381 virtual void set_alignment(TxtAlign eXAlign
) override
17383 GtkJustification eJust
= GTK_JUSTIFY_LEFT
;
17386 case TxtAlign::Left
:
17387 eJust
= GTK_JUSTIFY_LEFT
;
17389 case TxtAlign::Center
:
17390 eJust
= GTK_JUSTIFY_CENTER
;
17392 case TxtAlign::Right
:
17393 eJust
= GTK_JUSTIFY_RIGHT
;
17396 gtk_text_view_set_justification(m_pTextView
, eJust
);
17399 virtual int vadjustment_get_value() const override
17401 return gtk_adjustment_get_value(m_pVAdjustment
);
17404 virtual void vadjustment_set_value(int value
) override
17406 disable_notify_events();
17407 gtk_adjustment_set_value(m_pVAdjustment
, value
);
17408 enable_notify_events();
17411 virtual int vadjustment_get_upper() const override
17413 return gtk_adjustment_get_upper(m_pVAdjustment
);
17416 virtual int vadjustment_get_lower() const override
17418 return gtk_adjustment_get_lower(m_pVAdjustment
);
17421 virtual int vadjustment_get_page_size() const override
17423 return gtk_adjustment_get_page_size(m_pVAdjustment
);
17426 virtual void show() override
17428 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
17429 if (GTK_IS_SCROLLED_WINDOW(pParent
))
17430 gtk_widget_show(pParent
);
17431 gtk_widget_show(m_pWidget
);
17434 virtual void hide() override
17436 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
17437 if (GTK_IS_SCROLLED_WINDOW(pParent
))
17438 gtk_widget_hide(pParent
);
17439 gtk_widget_hide(m_pWidget
);
17442 virtual ~GtkInstanceTextView() override
17444 #if !GTK_CHECK_VERSION(4, 0, 0)
17445 g_signal_handler_disconnect(m_pTextView
, m_nButtonPressEvent
);
17447 g_signal_handler_disconnect(m_pVAdjustment
, m_nVAdjustChangedSignalId
);
17448 g_signal_handler_disconnect(m_pTextBuffer
, m_nInsertTextSignalId
);
17449 g_signal_handler_disconnect(m_pTextBuffer
, m_nChangedSignalId
);
17450 g_signal_handler_disconnect(m_pTextBuffer
, m_nCursorPosSignalId
);
17451 g_signal_handler_disconnect(m_pTextBuffer
, m_nHasSelectionSignalId
);
17462 #if !GTK_CHECK_VERSION(4, 0, 0)
17463 AtkObject
* (*default_drawing_area_get_accessible
)(GtkWidget
*widget
);
17466 class GtkInstanceDrawingArea
: public GtkInstanceWidget
, public virtual weld::DrawingArea
17469 GtkDrawingArea
* m_pDrawingArea
;
17470 a11yref m_xAccessible
;
17471 #if !GTK_CHECK_VERSION(4, 0, 0)
17472 AtkObject
*m_pAccessible
;
17474 ScopedVclPtrInstance
<VirtualDevice
> m_xDevice
;
17475 std::unique_ptr
<IMHandler
> m_xIMHandler
;
17476 cairo_surface_t
* m_pSurface
;
17477 #if !GTK_CHECK_VERSION(4, 0, 0)
17478 gulong m_nDrawSignalId
;
17480 gulong m_nQueryTooltip
;
17481 #if !GTK_CHECK_VERSION(4, 0, 0)
17482 gulong m_nPopupMenu
;
17483 gulong m_nScrollEvent
;
17486 #if GTK_CHECK_VERSION(4, 0, 0)
17487 static void signalDraw(GtkDrawingArea
*, cairo_t
*cr
, int /*width*/, int /*height*/, gpointer widget
)
17489 static gboolean
signalDraw(GtkWidget
*, cairo_t
* cr
, gpointer widget
)
17492 GtkInstanceDrawingArea
* pThis
= static_cast<GtkInstanceDrawingArea
*>(widget
);
17493 SolarMutexGuard aGuard
;
17494 pThis
->signal_draw(cr
);
17495 #if !GTK_CHECK_VERSION(4, 0, 0)
17499 void signal_draw(cairo_t
* cr
)
17505 #if GTK_CHECK_VERSION(4, 0, 0)
17506 double clip_x1
, clip_x2
, clip_y1
, clip_y2
;
17507 cairo_clip_extents(cr
, &clip_x1
, &clip_y1
, &clip_x2
, &clip_y2
);
17510 rect
.width
= clip_x2
- clip_x1
;
17511 rect
.height
= clip_y2
- clip_y1
;
17512 if (rect
.width
<= 0 || rect
.height
<= 0)
17515 if (!gdk_cairo_get_clip_rectangle(cr
, &rect
))
17519 tools::Rectangle
aRect(Point(rect
.x
, rect
.y
), Size(rect
.width
, rect
.height
));
17520 aRect
= m_xDevice
->PixelToLogic(aRect
);
17521 m_xDevice
->Erase(aRect
);
17522 m_aDrawHdl
.Call(std::pair
<vcl::RenderContext
&, const tools::Rectangle
&>(*m_xDevice
, aRect
));
17523 cairo_surface_mark_dirty(m_pSurface
);
17525 cairo_set_source_surface(cr
, m_pSurface
, 0, 0);
17528 tools::Rectangle
aFocusRect(m_aGetFocusRectHdl
.Call(*this));
17529 if (!aFocusRect
.IsEmpty())
17531 gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea
)), cr
,
17532 aFocusRect
.Left(), aFocusRect
.Top(), aFocusRect
.GetWidth(), aFocusRect
.GetHeight());
17535 virtual void signal_size_allocate(guint nWidth
, guint nHeight
) override
17537 Size
aNewSize(nWidth
, nHeight
);
17538 if (m_pSurface
&& aNewSize
== m_xDevice
->GetOutputSizePixel())
17543 m_xDevice
->SetOutputSizePixel(Size(nWidth
, nHeight
));
17544 m_pSurface
= get_underlying_cairo_surface(*m_xDevice
);
17545 GtkInstanceWidget::signal_size_allocate(nWidth
, nHeight
);
17547 void signal_style_updated()
17549 m_aStyleUpdatedHdl
.Call(*this);
17551 static gboolean
signalQueryTooltip(GtkWidget
* pGtkWidget
, gint x
, gint y
,
17552 gboolean
/*keyboard_mode*/, GtkTooltip
*tooltip
,
17555 GtkInstanceDrawingArea
* pThis
= static_cast<GtkInstanceDrawingArea
*>(widget
);
17556 tools::Rectangle
aHelpArea(x
, y
);
17557 OUString aTooltip
= pThis
->signal_query_tooltip(aHelpArea
);
17558 if (aTooltip
.isEmpty())
17560 gtk_tooltip_set_text(tooltip
, OUStringToOString(aTooltip
, RTL_TEXTENCODING_UTF8
).getStr());
17561 GdkRectangle aGdkHelpArea
;
17562 aGdkHelpArea
.x
= aHelpArea
.Left();
17563 aGdkHelpArea
.y
= aHelpArea
.Top();
17564 aGdkHelpArea
.width
= aHelpArea
.GetWidth();
17565 aGdkHelpArea
.height
= aHelpArea
.GetHeight();
17566 if (pThis
->SwapForRTL())
17567 aGdkHelpArea
.x
= gtk_widget_get_allocated_width(pGtkWidget
) - aGdkHelpArea
.width
- 1 - aGdkHelpArea
.x
;
17568 gtk_tooltip_set_tip_area(tooltip
, &aGdkHelpArea
);
17571 virtual bool signal_popup_menu(const CommandEvent
& rCEvt
) override
17573 return signal_command(rCEvt
);
17575 #if !GTK_CHECK_VERSION(4, 0, 0)
17576 bool signal_scroll(const GdkEventScroll
* pEvent
)
17578 SalWheelMouseEvent
aEvt(GtkSalFrame::GetWheelEvent(*pEvent
));
17581 aEvt
.mnX
= gtk_widget_get_allocated_width(m_pWidget
) - 1 - aEvt
.mnX
;
17583 CommandWheelMode nMode
;
17584 sal_uInt16 nCode
= aEvt
.mnCode
;
17585 bool bHorz
= aEvt
.mbHorz
;
17586 if (nCode
& KEY_MOD1
)
17587 nMode
= CommandWheelMode::ZOOM
;
17588 else if (nCode
& KEY_MOD2
)
17589 nMode
= CommandWheelMode::DATAZOOM
;
17592 nMode
= CommandWheelMode::SCROLL
;
17593 // #i85450# interpret shift-wheel as horizontal wheel action
17594 if( (nCode
& (KEY_SHIFT
| KEY_MOD1
| KEY_MOD2
| KEY_MOD3
)) == KEY_SHIFT
)
17598 CommandWheelData
aWheelData(aEvt
.mnDelta
, aEvt
.mnNotchDelta
, aEvt
.mnScrollLines
,
17599 nMode
, nCode
, bHorz
, aEvt
.mbDeltaIsPixel
);
17600 CommandEvent
aCEvt(Point(aEvt
.mnX
, aEvt
.mnY
), CommandEventId::Wheel
, true, &aWheelData
);
17601 return m_aCommandHdl
.Call(aCEvt
);
17603 static gboolean
signalScroll(GtkWidget
*, GdkEventScroll
* pEvent
, gpointer widget
)
17605 GtkInstanceDrawingArea
* pThis
= static_cast<GtkInstanceDrawingArea
*>(widget
);
17606 return pThis
->signal_scroll(pEvent
);
17610 #if GTK_CHECK_VERSION(4, 0, 0)
17611 static void signalResize(GtkDrawingArea
*, int nWidth
, int nHeight
, gpointer widget
)
17613 GtkInstanceWidget
* pThis
= static_cast<GtkInstanceWidget
*>(widget
);
17614 SolarMutexGuard aGuard
;
17615 pThis
->signal_size_allocate(nWidth
, nHeight
);
17619 DECL_LINK(SettingsChangedHdl
, VclWindowEvent
&, void);
17621 GtkInstanceDrawingArea(GtkDrawingArea
* pDrawingArea
, GtkInstanceBuilder
* pBuilder
, const a11yref
& rA11y
, bool bTakeOwnership
)
17622 : GtkInstanceWidget(GTK_WIDGET(pDrawingArea
), pBuilder
, bTakeOwnership
)
17623 , m_pDrawingArea(pDrawingArea
)
17624 , m_xAccessible(rA11y
)
17625 #if !GTK_CHECK_VERSION(4, 0, 0)
17626 , m_pAccessible(nullptr)
17628 , m_xDevice(DeviceFormat::DEFAULT
)
17629 , m_pSurface(nullptr)
17630 , m_nQueryTooltip(g_signal_connect(m_pDrawingArea
, "query-tooltip", G_CALLBACK(signalQueryTooltip
), this))
17631 #if !GTK_CHECK_VERSION(4, 0, 0)
17632 , m_nPopupMenu(g_signal_connect(m_pDrawingArea
, "popup-menu", G_CALLBACK(signalPopupMenu
), this))
17633 , m_nScrollEvent(g_signal_connect(m_pDrawingArea
, "scroll-event", G_CALLBACK(signalScroll
), this))
17636 #if GTK_CHECK_VERSION(4, 0, 0)
17637 gtk_drawing_area_set_draw_func(m_pDrawingArea
, signalDraw
, this, nullptr);
17639 m_nDrawSignalId
= g_signal_connect(m_pDrawingArea
, "draw", G_CALLBACK(signalDraw
), this);
17641 gtk_widget_set_has_tooltip(m_pWidget
, true);
17642 g_object_set_data(G_OBJECT(m_pDrawingArea
), "g-lo-GtkInstanceDrawingArea", this);
17643 m_xDevice
->EnableRTL(get_direction());
17645 ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceDrawingArea
, SettingsChangedHdl
));
17648 #if !GTK_CHECK_VERSION(4, 0, 0)
17649 AtkObject
* GetAtkObject(AtkObject
* pDefaultAccessible
)
17651 if (!m_pAccessible
&& m_xAccessible
.is())
17653 GtkWidget
* pParent
= gtk_widget_get_parent(m_pWidget
);
17654 m_pAccessible
= atk_object_wrapper_new(m_xAccessible
, gtk_widget_get_accessible(pParent
), pDefaultAccessible
);
17656 g_object_ref(m_pAccessible
);
17658 return m_pAccessible
;
17662 #if GTK_CHECK_VERSION(4, 0, 0)
17663 virtual void connect_size_allocate(const Link
<const Size
&, void>& rLink
) override
17665 m_nSizeAllocateSignalId
= g_signal_connect(m_pWidget
, "resize", G_CALLBACK(signalResize
), this);
17666 weld::Widget::connect_size_allocate(rLink
);
17670 virtual void connect_mouse_press(const Link
<const MouseEvent
&, bool>& rLink
) override
17672 #if !GTK_CHECK_VERSION(4, 0, 0)
17673 if (!(gtk_widget_get_events(m_pWidget
) & GDK_BUTTON_PRESS_MASK
))
17674 gtk_widget_add_events(m_pWidget
, GDK_BUTTON_PRESS_MASK
);
17676 GtkInstanceWidget::connect_mouse_press(rLink
);
17679 virtual void connect_mouse_release(const Link
<const MouseEvent
&, bool>& rLink
) override
17681 #if !GTK_CHECK_VERSION(4, 0, 0)
17682 if (!(gtk_widget_get_events(m_pWidget
) & GDK_BUTTON_RELEASE_MASK
))
17683 gtk_widget_add_events(m_pWidget
, GDK_BUTTON_RELEASE_MASK
);
17685 GtkInstanceWidget::connect_mouse_release(rLink
);
17688 virtual void set_direction(bool bRTL
) override
17690 GtkInstanceWidget::set_direction(bRTL
);
17691 m_xDevice
->EnableRTL(bRTL
);
17694 virtual void set_cursor(PointerStyle ePointerStyle
) override
17696 GdkCursor
*pCursor
= GtkSalFrame::getDisplay()->getCursor(ePointerStyle
);
17697 if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea
)))
17698 gtk_widget_realize(GTK_WIDGET(m_pDrawingArea
));
17699 widget_set_cursor(GTK_WIDGET(m_pDrawingArea
), pCursor
);
17702 virtual Point
get_pointer_position() const override
17704 GdkDisplay
*pDisplay
= gtk_widget_get_display(m_pWidget
);
17705 GdkSeat
* pSeat
= gdk_display_get_default_seat(pDisplay
);
17706 GdkDevice
* pPointer
= gdk_seat_get_pointer(pSeat
);
17707 double x(-1), y(-1);
17708 GdkSurface
* pWin
= widget_get_surface(m_pWidget
);
17709 surface_get_device_position(pWin
, pPointer
, x
, y
, nullptr);
17710 return Point(x
, y
);
17713 virtual void set_input_context(const InputContext
& rInputContext
) override
;
17715 virtual void im_context_set_cursor_location(const tools::Rectangle
& rCursorRect
, int nExtTextInputWidth
) override
;
17717 int im_context_get_surrounding(OUString
& rSurroundingText
)
17719 return signal_im_context_get_surrounding(rSurroundingText
);
17722 bool im_context_delete_surrounding(const Selection
& rRange
)
17724 return signal_im_context_delete_surrounding(rRange
);
17727 #if !GTK_CHECK_VERSION(4, 0, 0)
17728 virtual bool do_signal_key_press(const GdkEventKey
* pEvent
) override
;
17729 virtual bool do_signal_key_release(const GdkEventKey
* pEvent
) override
;
17732 virtual void queue_draw() override
17734 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea
));
17737 virtual void queue_draw_area(int x
, int y
, int width
, int height
) override
17739 #if !GTK_CHECK_VERSION(4, 0, 0)
17740 tools::Rectangle
aRect(Point(x
, y
), Size(width
, height
));
17741 aRect
= m_xDevice
->LogicToPixel(aRect
);
17742 gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea
), aRect
.Left(), aRect
.Top(), aRect
.GetWidth(), aRect
.GetHeight());
17744 (void)x
; (void)y
; (void)width
; (void)height
;
17749 virtual a11yref
get_accessible_parent() override
17751 //get_accessible_parent should only be needed for the vcl implementation,
17752 //in the gtk impl the native AtkObject parent set via
17753 //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
17754 //should negate the need.
17755 assert(false && "get_accessible_parent should only be called on a vcl impl");
17756 return uno::Reference
<css::accessibility::XAccessible
>();
17759 virtual a11yrelationset
get_accessible_relation_set() override
17761 //get_accessible_relation_set should only be needed for the vcl implementation,
17762 //in the gtk impl the native equivalent should negate the need.
17763 assert(false && "get_accessible_relation_set should only be called on a vcl impl");
17764 return uno::Reference
<css::accessibility::XAccessibleRelationSet
>();
17767 virtual Point
get_accessible_location_on_screen() override
17769 #if !GTK_CHECK_VERSION(4, 0, 0)
17770 AtkObject
* pAtkObject
= default_drawing_area_get_accessible(m_pWidget
);
17773 #if !GTK_CHECK_VERSION(4, 0, 0)
17774 if (pAtkObject
&& ATK_IS_COMPONENT(pAtkObject
))
17775 atk_component_get_extents(ATK_COMPONENT(pAtkObject
), &x
, &y
, nullptr, nullptr, ATK_XY_SCREEN
);
17777 return Point(x
, y
);
17780 virtual void set_accessible_name(const OUString
& rName
) override
17782 #if !GTK_CHECK_VERSION(4, 0, 0)
17783 AtkObject
* pAtkObject
= default_drawing_area_get_accessible(m_pWidget
);
17786 atk_object_set_name(pAtkObject
, OUStringToOString(rName
, RTL_TEXTENCODING_UTF8
).getStr());
17792 virtual OUString
get_accessible_name() const override
17794 #if !GTK_CHECK_VERSION(4, 0, 0)
17795 AtkObject
* pAtkObject
= default_drawing_area_get_accessible(m_pWidget
);
17796 const char* pStr
= pAtkObject
? atk_object_get_name(pAtkObject
) : nullptr;
17797 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
17803 virtual OUString
get_accessible_description() const override
17805 #if !GTK_CHECK_VERSION(4, 0, 0)
17806 AtkObject
* pAtkObject
= default_drawing_area_get_accessible(m_pWidget
);
17807 const char* pStr
= pAtkObject
? atk_object_get_description(pAtkObject
) : nullptr;
17808 return OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
17814 virtual void enable_drag_source(rtl::Reference
<TransferDataContainer
>& rHelper
, sal_uInt8 eDNDConstants
) override
17816 do_enable_drag_source(rHelper
, eDNDConstants
);
17819 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon
) override
17821 rUnsetDragIcon
= false;
17822 if (m_aDragBeginHdl
.Call(*this))
17827 virtual ~GtkInstanceDrawingArea() override
17829 ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceDrawingArea
, SettingsChangedHdl
));
17831 g_object_steal_data(G_OBJECT(m_pDrawingArea
), "g-lo-GtkInstanceDrawingArea");
17832 #if !GTK_CHECK_VERSION(4, 0, 0)
17834 g_object_unref(m_pAccessible
);
17836 css::uno::Reference
<css::lang::XComponent
> xComp(m_xAccessible
, css::uno::UNO_QUERY
);
17839 #if !GTK_CHECK_VERSION(4, 0, 0)
17840 g_signal_handler_disconnect(m_pDrawingArea
, m_nScrollEvent
);
17842 #if !GTK_CHECK_VERSION(4, 0, 0)
17843 g_signal_handler_disconnect(m_pDrawingArea
, m_nPopupMenu
);
17845 g_signal_handler_disconnect(m_pDrawingArea
, m_nQueryTooltip
);
17846 #if GTK_CHECK_VERSION(4, 0, 0)
17847 gtk_drawing_area_set_draw_func(m_pDrawingArea
, nullptr, nullptr, nullptr);
17849 g_signal_handler_disconnect(m_pDrawingArea
, m_nDrawSignalId
);
17853 virtual OutputDevice
& get_ref_device() override
17858 bool signal_command(const CommandEvent
& rCEvt
)
17860 return m_aCommandHdl
.Call(rCEvt
);
17863 virtual void click(const Point
& rPos
) override
17865 MouseEvent
aEvent(rPos
);
17866 m_aMousePressHdl
.Call(aEvent
);
17867 m_aMouseReleaseHdl
.Call(aEvent
);
17871 IMPL_LINK(GtkInstanceDrawingArea
, SettingsChangedHdl
, VclWindowEvent
&, rEvent
, void)
17873 if (rEvent
.GetId() != VclEventId::WindowDataChanged
)
17876 DataChangedEvent
* pData
= static_cast<DataChangedEvent
*>(rEvent
.GetData());
17877 if (pData
->GetType() == DataChangedEventType::SETTINGS
)
17878 signal_style_updated();
17884 GtkInstanceDrawingArea
* m_pArea
;
17885 #if GTK_CHECK_VERSION(4, 0, 0)
17886 GtkEventController
* m_pFocusController
;
17888 GtkIMContext
* m_pIMContext
;
17889 OUString m_sPreeditText
;
17890 gulong m_nFocusInSignalId
;
17891 gulong m_nFocusOutSignalId
;
17892 bool m_bExtTextInput
;
17895 IMHandler(GtkInstanceDrawingArea
* pArea
)
17897 , m_pIMContext(gtk_im_multicontext_new())
17898 , m_bExtTextInput(false)
17900 GtkWidget
* pWidget
= m_pArea
->getWidget();
17902 #if GTK_CHECK_VERSION(4, 0, 0)
17903 m_pFocusController
= gtk_event_controller_focus_new();
17904 gtk_widget_add_controller(pWidget
, m_pFocusController
);
17906 m_nFocusInSignalId
= g_signal_connect(m_pFocusController
, "enter", G_CALLBACK(signalFocusIn
), this);
17907 m_nFocusOutSignalId
= g_signal_connect(m_pFocusController
, "leave", G_CALLBACK(signalFocusOut
), this);
17909 m_nFocusInSignalId
= g_signal_connect(pWidget
, "focus-in-event", G_CALLBACK(signalFocusIn
), this);
17910 m_nFocusOutSignalId
= g_signal_connect(pWidget
, "focus-out-event", G_CALLBACK(signalFocusOut
), this);
17913 g_signal_connect(m_pIMContext
, "preedit-start", G_CALLBACK(signalIMPreeditStart
), this);
17914 g_signal_connect(m_pIMContext
, "preedit-end", G_CALLBACK(signalIMPreeditEnd
), this);
17915 g_signal_connect(m_pIMContext
, "commit", G_CALLBACK(signalIMCommit
), this);
17916 g_signal_connect(m_pIMContext
, "preedit-changed", G_CALLBACK(signalIMPreeditChanged
), this);
17917 g_signal_connect(m_pIMContext
, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding
), this);
17918 g_signal_connect(m_pIMContext
, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding
), this);
17920 if (!gtk_widget_get_realized(pWidget
))
17921 gtk_widget_realize(pWidget
);
17922 im_context_set_client_widget(m_pIMContext
, pWidget
);
17923 if (gtk_widget_has_focus(m_pArea
->getWidget()))
17924 gtk_im_context_focus_in(m_pIMContext
);
17927 void signalFocus(bool bIn
)
17930 gtk_im_context_focus_in(m_pIMContext
);
17932 gtk_im_context_focus_out(m_pIMContext
);
17935 #if GTK_CHECK_VERSION(4, 0, 0)
17936 static void signalFocusIn(GtkEventControllerFocus
*, gpointer im_handler
)
17938 static gboolean
signalFocusIn(GtkWidget
*, GdkEvent
*, gpointer im_handler
)
17941 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
17942 pThis
->signalFocus(true);
17943 #if !GTK_CHECK_VERSION(4, 0, 0)
17948 #if GTK_CHECK_VERSION(4, 0, 0)
17949 static void signalFocusOut(GtkEventControllerFocus
*, gpointer im_handler
)
17951 static gboolean
signalFocusOut(GtkWidget
*, GdkEvent
*, gpointer im_handler
)
17954 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
17955 pThis
->signalFocus(false);
17956 #if !GTK_CHECK_VERSION(4, 0, 0)
17965 #if GTK_CHECK_VERSION(4, 0, 0)
17966 g_signal_handler_disconnect(m_pFocusController
, m_nFocusOutSignalId
);
17967 g_signal_handler_disconnect(m_pFocusController
, m_nFocusInSignalId
);
17969 g_signal_handler_disconnect(m_pArea
->getWidget(), m_nFocusOutSignalId
);
17970 g_signal_handler_disconnect(m_pArea
->getWidget(), m_nFocusInSignalId
);
17973 if (gtk_widget_has_focus(m_pArea
->getWidget()))
17974 gtk_im_context_focus_out(m_pIMContext
);
17976 // first give IC a chance to deinitialize
17977 im_context_set_client_widget(m_pIMContext
, nullptr);
17979 g_object_unref(m_pIMContext
);
17982 void updateIMSpotLocation()
17984 CommandEvent
aCEvt(Point(), CommandEventId::CursorPos
);
17985 // we expect set_cursor_location to get triggered by this
17986 m_pArea
->signal_command(aCEvt
);
17989 void set_cursor_location(const tools::Rectangle
& rRect
)
17991 GdkRectangle aArea
{static_cast<int>(rRect
.Left()), static_cast<int>(rRect
.Top()),
17992 static_cast<int>(rRect
.GetWidth()), static_cast<int>(rRect
.GetHeight())};
17993 gtk_im_context_set_cursor_location(m_pIMContext
, &aArea
);
17996 static void signalIMCommit(GtkIMContext
* /*pContext*/, gchar
* pText
, gpointer im_handler
)
17998 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
18000 SolarMutexGuard aGuard
;
18002 // at least editeng expects to have seen a start before accepting a commit
18003 pThis
->StartExtTextInput();
18005 OUString
sText(pText
, strlen(pText
), RTL_TEXTENCODING_UTF8
);
18006 CommandExtTextInputData
aData(sText
, nullptr, sText
.getLength(), 0, false);
18007 CommandEvent
aCEvt(Point(), CommandEventId::ExtTextInput
, false, &aData
);
18008 pThis
->m_pArea
->signal_command(aCEvt
);
18010 pThis
->updateIMSpotLocation();
18012 pThis
->EndExtTextInput();
18014 pThis
->m_sPreeditText
.clear();
18017 static void signalIMPreeditChanged(GtkIMContext
* pIMContext
, gpointer im_handler
)
18019 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
18021 SolarMutexGuard aGuard
;
18023 sal_Int32
nCursorPos(0);
18024 sal_uInt8
nCursorFlags(0);
18025 std::vector
<ExtTextInputAttr
> aInputFlags
;
18026 OUString sText
= GtkSalFrame::GetPreeditDetails(pIMContext
, aInputFlags
, nCursorPos
, nCursorFlags
);
18028 // change from nothing to nothing -> do not start preedit e.g. this
18029 // will activate input into a calc cell without user input
18030 if (sText
.isEmpty() && pThis
->m_sPreeditText
.isEmpty())
18033 pThis
->m_sPreeditText
= sText
;
18035 CommandExtTextInputData
aData(sText
, aInputFlags
.data(), nCursorPos
, nCursorFlags
, false);
18036 CommandEvent
aCEvt(Point(), CommandEventId::ExtTextInput
, false, &aData
);
18037 pThis
->m_pArea
->signal_command(aCEvt
);
18039 pThis
->updateIMSpotLocation();
18042 static gboolean
signalIMRetrieveSurrounding(GtkIMContext
* pContext
, gpointer im_handler
)
18044 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
18046 SolarMutexGuard aGuard
;
18048 OUString sSurroundingText
;
18049 int nCursorIndex
= pThis
->m_pArea
->im_context_get_surrounding(sSurroundingText
);
18051 if (nCursorIndex
!= -1)
18053 OString sUTF
= OUStringToOString(sSurroundingText
, RTL_TEXTENCODING_UTF8
);
18054 std::u16string_view
sCursorText(sSurroundingText
.subView(0, nCursorIndex
));
18055 gtk_im_context_set_surrounding(pContext
, sUTF
.getStr(), sUTF
.getLength(),
18056 OUStringToOString(sCursorText
, RTL_TEXTENCODING_UTF8
).getLength());
18062 static gboolean
signalIMDeleteSurrounding(GtkIMContext
*, gint nOffset
, gint nChars
,
18063 gpointer im_handler
)
18067 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
18069 SolarMutexGuard aGuard
;
18071 OUString sSurroundingText
;
18072 sal_Int32 nCursorIndex
= pThis
->m_pArea
->im_context_get_surrounding(sSurroundingText
);
18074 Selection aSelection
= SalFrame::CalcDeleteSurroundingSelection(sSurroundingText
, nCursorIndex
, nOffset
, nChars
);
18075 if (aSelection
!= Selection(SAL_MAX_UINT32
, SAL_MAX_UINT32
))
18076 bRet
= pThis
->m_pArea
->im_context_delete_surrounding(aSelection
);
18080 void StartExtTextInput()
18082 if (m_bExtTextInput
)
18084 CommandEvent
aCEvt(Point(), CommandEventId::StartExtTextInput
);
18085 m_pArea
->signal_command(aCEvt
);
18086 m_bExtTextInput
= true;
18089 static void signalIMPreeditStart(GtkIMContext
*, gpointer im_handler
)
18091 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
18092 SolarMutexGuard aGuard
;
18093 pThis
->StartExtTextInput();
18094 pThis
->updateIMSpotLocation();
18097 void EndExtTextInput()
18099 if (!m_bExtTextInput
)
18101 CommandEvent
aCEvt(Point(), CommandEventId::EndExtTextInput
);
18102 m_pArea
->signal_command(aCEvt
);
18103 m_bExtTextInput
= false;
18106 static void signalIMPreeditEnd(GtkIMContext
*, gpointer im_handler
)
18108 IMHandler
* pThis
= static_cast<IMHandler
*>(im_handler
);
18109 SolarMutexGuard aGuard
;
18110 pThis
->updateIMSpotLocation();
18111 pThis
->EndExtTextInput();
18114 #if !GTK_CHECK_VERSION(4, 0, 0)
18115 bool im_context_filter_keypress(const GdkEventKey
* pEvent
)
18117 return gtk_im_context_filter_keypress(m_pIMContext
, const_cast<GdkEventKey
*>(pEvent
));
18122 #if !GTK_CHECK_VERSION(4, 0, 0)
18123 bool GtkInstanceDrawingArea::do_signal_key_press(const GdkEventKey
* pEvent
)
18125 if (m_xIMHandler
&& m_xIMHandler
->im_context_filter_keypress(pEvent
))
18127 return GtkInstanceWidget::do_signal_key_press(pEvent
);
18130 bool GtkInstanceDrawingArea::do_signal_key_release(const GdkEventKey
* pEvent
)
18132 if (m_xIMHandler
&& m_xIMHandler
->im_context_filter_keypress(pEvent
))
18134 return GtkInstanceWidget::do_signal_key_release(pEvent
);
18138 void GtkInstanceDrawingArea::set_input_context(const InputContext
& rInputContext
)
18140 bool bUseIm(rInputContext
.GetOptions() & InputContextFlags::Text
);
18143 m_xIMHandler
.reset();
18146 // create a new im context
18148 m_xIMHandler
.reset(new IMHandler(this));
18151 void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle
& rCursorRect
, int /*nExtTextInputWidth*/)
18155 m_xIMHandler
->set_cursor_location(rCursorRect
);
18160 #if !GTK_CHECK_VERSION(4, 0, 0)
18161 static gboolean
signalEntryInsertSpecialCharKeyPress(GtkEntry
* pEntry
, GdkEventKey
* pEvent
, gpointer
)
18163 if ((pEvent
->keyval
== GDK_KEY_S
|| pEvent
->keyval
== GDK_KEY_s
) &&
18164 (pEvent
->state
& GDK_MODIFIER_MASK
) == static_cast<GdkModifierType
>(GDK_SHIFT_MASK
|GDK_CONTROL_MASK
))
18166 if (auto pImplFncGetSpecialChars
= vcl::GetGetSpecialCharsFunction())
18168 weld::Window
* pDialogParent
= nullptr;
18170 GtkWidget
* pTopLevel
= widget_get_toplevel(GTK_WIDGET(pEntry
));
18171 if (GtkSalFrame
* pFrame
= pTopLevel
? GtkSalFrame::getFromWindow(pTopLevel
) : nullptr)
18172 pDialogParent
= pFrame
->GetFrameWeld();
18174 std::unique_ptr
<GtkInstanceWindow
> xFrameWeld
;
18175 if (!pDialogParent
&& pTopLevel
)
18177 xFrameWeld
.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel
), nullptr, false));
18178 pDialogParent
= xFrameWeld
.get();
18181 OUString aChars
= pImplFncGetSpecialChars(pDialogParent
, ::get_font(GTK_WIDGET(pEntry
)));
18182 if (!aChars
.isEmpty())
18184 gtk_editable_delete_selection(GTK_EDITABLE(pEntry
));
18185 gint position
= gtk_editable_get_position(GTK_EDITABLE(pEntry
));
18186 OString
sText(OUStringToOString(aChars
, RTL_TEXTENCODING_UTF8
));
18187 gtk_editable_insert_text(GTK_EDITABLE(pEntry
), sText
.getStr(), sText
.getLength(),
18189 gtk_editable_set_position(GTK_EDITABLE(pEntry
), position
);
18200 GtkBuilder
* makeMenuToggleButtonBuilder()
18202 #if !GTK_CHECK_VERSION(4, 0, 0)
18203 OUString
aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton3.ui");
18205 OUString
aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton4.ui");
18208 osl::FileBase::getSystemPathFromFileURL(aUri
, aPath
);
18209 return gtk_builder_new_from_file(OUStringToOString(aPath
, RTL_TEXTENCODING_UTF8
).getStr());
18212 #if !GTK_CHECK_VERSION(4, 0, 0)
18214 GtkBuilder
* makeComboBoxBuilder()
18216 OUString
aUri(AllSettings::GetUIRootDir() + "vcl/ui/combobox.ui");
18218 osl::FileBase::getSystemPathFromFileURL(aUri
, aPath
);
18219 return gtk_builder_new_from_file(OUStringToOString(aPath
, RTL_TEXTENCODING_UTF8
).getStr());
18222 // pop down the toplevel combobox menu when something is activated from a custom
18223 // submenu, i.e. wysiwyg style menu
18224 class CustomRenderMenuButtonHelper
: public MenuHelper
18227 GtkToggleButton
* m_pComboBox
;
18229 CustomRenderMenuButtonHelper(GtkMenu
* pMenu
, GtkToggleButton
* pComboBox
)
18230 : MenuHelper(pMenu
, false)
18231 , m_pComboBox(pComboBox
)
18234 virtual void signal_item_activate(const OString
& /*rIdent*/) override
18236 gtk_toggle_button_set_active(m_pComboBox
, false);
18242 #if GTK_CHECK_VERSION(4, 0, 0)
18244 class GtkInstanceComboBox
: public GtkInstanceWidget
, public vcl::ISearchableStringList
, public virtual weld::ComboBox
18247 GtkComboBox
* m_pComboBox
;
18248 // GtkOverlay* m_pOverlay;
18249 // GtkTreeView* m_pTreeView;
18250 // GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
18251 GtkWidget
* m_pMenuWindow
;
18252 GtkTreeModel
* m_pTreeModel
;
18253 GtkCellRenderer
* m_pButtonTextRenderer
;
18254 GtkWidget
* m_pEntry
;
18255 GtkEditable
* m_pEditable
;
18256 // GtkCellView* m_pCellView;
18257 GtkEventController
* m_pKeyController
;
18258 GtkEventController
* m_pEntryKeyController
;
18259 GtkEventController
* m_pMenuKeyController
;
18260 GtkEventController
* m_pEntryFocusController
;
18261 // std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
18262 WidgetFont m_aCustomFont
;
18263 std::optional
<vcl::Font
> m_xEntryFont
;
18264 std::unique_ptr
<comphelper::string::NaturalStringSorter
> m_xSorter
;
18265 vcl::QuickSelectionEngine m_aQuickSelectionEngine
;
18266 std::vector
<std::unique_ptr
<GtkTreeRowReference
, GtkTreeRowReferenceDeleter
>> m_aSeparatorRows
;
18268 OUString m_sMenuButtonRow
;
18270 // bool m_bHoverSelection;
18271 // bool m_bMouseInOverlayButton;
18272 bool m_bPopupActive
;
18273 bool m_bAutoComplete
;
18274 bool m_bAutoCompleteCaseSensitive
;
18275 bool m_bChangedByMenu
;
18276 bool m_bCustomRenderer
;
18277 bool m_bUserSelectEntry
;
18280 // gulong m_nToggleFocusInSignalId;
18281 // gulong m_nToggleFocusOutSignalId;
18282 // gulong m_nRowActivatedSignalId;
18283 gulong m_nChangedSignalId
;
18284 gulong m_nPopupShownSignalId
;
18285 gulong m_nKeyPressEventSignalId
;
18286 gulong m_nEntryInsertTextSignalId
;
18287 gulong m_nEntryActivateSignalId
;
18288 gulong m_nEntryFocusInSignalId
;
18289 gulong m_nEntryFocusOutSignalId
;
18290 gulong m_nEntryKeyPressEventSignalId
;
18291 guint m_nAutoCompleteIdleId
;
18292 // gint m_nNonCustomLineHeight;
18293 gint m_nPrePopupCursorPos
;
18295 int m_nMaxMRUCount
;
18297 static gboolean
idleAutoComplete(gpointer widget
)
18299 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18300 pThis
->auto_complete();
18304 void auto_complete()
18306 m_nAutoCompleteIdleId
= 0;
18307 OUString aStartText
= get_active_text();
18308 int nStartPos
, nEndPos
;
18309 get_entry_selection_bounds(nStartPos
, nEndPos
);
18310 int nMaxSelection
= std::max(nStartPos
, nEndPos
);
18311 if (nMaxSelection
!= aStartText
.getLength())
18314 disable_notify_events();
18315 int nActive
= get_active();
18316 int nStart
= nActive
;
18325 nZeroRow
+= (m_nMRUCount
+ 1);
18327 if (!m_bAutoCompleteCaseSensitive
)
18329 // Try match case insensitive from current position
18330 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nStart
, false);
18331 if (nPos
== -1 && nStart
!= 0)
18333 // Try match case insensitive, but from start
18334 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nZeroRow
, false);
18340 // Try match case sensitive from current position
18341 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nStart
, true);
18342 if (nPos
== -1 && nStart
!= 0)
18344 // Try match case sensitive, but from start
18345 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nZeroRow
, true);
18351 OUString aText
= get_text_including_mru(nPos
);
18352 if (aText
!= aStartText
)
18354 SolarMutexGuard aGuard
;
18355 set_active_including_mru(nPos
, true);
18357 select_entry_region(aText
.getLength(), aStartText
.getLength());
18359 enable_notify_events();
18362 static void signalEntryInsertText(GtkEntry
* pEntry
, const gchar
* pNewText
, gint nNewTextLength
,
18363 gint
* position
, gpointer widget
)
18365 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18366 SolarMutexGuard aGuard
;
18367 pThis
->signal_entry_insert_text(pEntry
, pNewText
, nNewTextLength
, position
);
18370 void signal_entry_insert_text(GtkEntry
* pEntry
, const gchar
* pNewText
, gint nNewTextLength
, gint
* position
)
18372 if (m_bPopupActive
) // not entered by the user
18375 // first filter inserted text
18376 if (m_aEntryInsertTextHdl
.IsSet())
18378 OUString
sText(pNewText
, nNewTextLength
, RTL_TEXTENCODING_UTF8
);
18379 const bool bContinue
= m_aEntryInsertTextHdl
.Call(sText
);
18380 if (bContinue
&& !sText
.isEmpty())
18382 OString
sFinalText(OUStringToOString(sText
, RTL_TEXTENCODING_UTF8
));
18383 g_signal_handlers_block_by_func(pEntry
, reinterpret_cast<gpointer
>(signalEntryInsertText
), this);
18384 gtk_editable_insert_text(GTK_EDITABLE(pEntry
), sFinalText
.getStr(), sFinalText
.getLength(), position
);
18385 g_signal_handlers_unblock_by_func(pEntry
, reinterpret_cast<gpointer
>(signalEntryInsertText
), this);
18387 g_signal_stop_emission_by_name(pEntry
, "insert-text");
18390 if (m_bAutoComplete
)
18392 // now check for autocompletes
18393 if (m_nAutoCompleteIdleId
)
18394 g_source_remove(m_nAutoCompleteIdleId
);
18395 m_nAutoCompleteIdleId
= g_idle_add(idleAutoComplete
, this);
18399 static void signalChanged(GtkComboBox
*, gpointer widget
)
18401 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18402 SolarMutexGuard aGuard
;
18403 pThis
->fire_signal_changed();
18406 void fire_signal_changed()
18408 m_bUserSelectEntry
= true;
18409 m_bChangedByMenu
= m_bPopupActive
;
18411 m_bChangedByMenu
= false;
18414 static void signalPopupToggled(GObject
*, GParamSpec
*, gpointer widget
)
18416 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18417 SolarMutexGuard aGuard
;
18418 pThis
->signal_popup_toggled();
18422 int get_popup_height(gint
& rPopupWidth
)
18424 const StyleSettings
& rSettings
= Application::GetSettings().GetStyleSettings();
18426 int nMaxRows
= rSettings
.GetListBoxMaximumLineCount();
18427 bool bAddScrollWidth
= false;
18428 int nRows
= get_count_including_mru();
18429 if (nMaxRows
< nRows
)
18432 bAddScrollWidth
= true;
18435 GList
* pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
18436 gint nRowHeight
= get_height_row(m_pTreeView
, pColumns
);
18437 g_list_free(pColumns
);
18439 gint nSeparatorHeight
= get_height_row_separator(m_pTreeView
);
18440 gint nHeight
= get_height_rows(nRowHeight
, nSeparatorHeight
, nRows
);
18442 // if we're using a custom renderer, limit the height to the height nMaxRows would be
18443 // for a normal renderer, and then round down to how many custom rows fit in that
18445 if (m_nNonCustomLineHeight
!= -1 && nRowHeight
)
18447 gint nNormalHeight
= get_height_rows(m_nNonCustomLineHeight
, nSeparatorHeight
, nMaxRows
);
18448 if (nHeight
> nNormalHeight
)
18450 gint nRowsOnly
= nNormalHeight
- get_height_rows(0, nSeparatorHeight
, nMaxRows
);
18451 gint nCustomRows
= (nRowsOnly
+ (nRowHeight
- 1)) / nRowHeight
;
18452 nHeight
= get_height_rows(nRowHeight
, nSeparatorHeight
, nCustomRows
);
18456 if (bAddScrollWidth
)
18457 rPopupWidth
+= rSettings
.GetScrollBarSize();
18463 bool toggle_button_get_active()
18465 GValue value
= G_VALUE_INIT
;
18466 g_value_init(&value
, G_TYPE_BOOLEAN
);
18467 g_object_get_property(G_OBJECT(m_pComboBox
), "popup-shown", &value
);
18468 return g_value_get_boolean(&value
);
18471 void menu_toggled()
18473 if (!m_bPopupActive
)
18476 if (m_bHoverSelection
)
18478 // turn hover selection back off until mouse is moved again
18479 // *after* menu is shown again
18480 gtk_tree_view_set_hover_selection(m_pTreeView
, false);
18481 m_bHoverSelection
= false;
18485 if (!m_bUserSelectEntry
)
18486 set_active_including_mru(m_nPrePopupCursorPos
, true);
18489 // undo show_menu tooltip blocking
18490 GtkWidget
* pParent
= widget_get_toplevel(m_pToggleButton
);
18491 GtkSalFrame
* pFrame
= pParent
? GtkSalFrame::getFromWindow(pParent
) : nullptr;
18493 pFrame
->UnblockTooltip();
18498 m_nPrePopupCursorPos
= get_active();
18500 m_bUserSelectEntry
= false;
18502 // if we are in mru mode always start with the cursor at the top of the menu
18503 if (m_nMaxMRUCount
)
18504 set_active_including_mru(0, true);
18508 virtual void signal_popup_toggled() override
18510 m_aQuickSelectionEngine
.Reset();
18512 bool bOldPopupActive
= m_bPopupActive
;
18513 m_bPopupActive
= toggle_button_get_active();
18517 if (bOldPopupActive
!= m_bPopupActive
)
18519 ComboBox::signal_popup_toggled();
18520 // restore focus to the GtkEntry when the popup is gone, which
18521 // is what the vcl case does, to ease the transition a little,
18522 // but don't do it if the focus was moved out of togglebutton
18523 // by something else already (e.g. font combobox in toolbar
18524 // on a "direct pick" from the menu which moves focus into
18525 // the main document
18526 if (!m_bPopupActive
&& m_pEntry
&& has_child_focus())
18528 disable_notify_events();
18529 gtk_widget_grab_focus(m_pEntry
);
18530 enable_notify_events();
18535 #if GTK_CHECK_VERSION(4, 0, 0)
18536 static void signalEntryFocusIn(GtkEventControllerFocus
*, gpointer widget
)
18538 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18539 SolarMutexGuard aGuard
;
18540 pThis
->signal_entry_focus_in();
18543 static gboolean
signalEntryFocusIn(GtkWidget
*, GdkEvent
*, gpointer widget
)
18545 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18546 pThis
->signal_entry_focus_in();
18551 void signal_entry_focus_in()
18556 #if GTK_CHECK_VERSION(4, 0, 0)
18557 static void signalEntryFocusOut(GtkEventControllerFocus
*, gpointer widget
)
18559 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18560 SolarMutexGuard aGuard
;
18561 pThis
->signal_entry_focus_out();
18564 static gboolean
signalEntryFocusOut(GtkWidget
*, GdkEvent
*, gpointer widget
)
18566 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18567 pThis
->signal_entry_focus_out();
18572 void signal_entry_focus_out()
18574 // if we have an untidy selection on losing focus remove the selection
18575 int nStartPos
, nEndPos
;
18576 if (get_entry_selection_bounds(nStartPos
, nEndPos
))
18578 int nMin
= std::min(nStartPos
, nEndPos
);
18579 int nMax
= std::max(nStartPos
, nEndPos
);
18580 if (nMin
!= 0 || nMax
!= get_active_text().getLength())
18581 select_entry_region(0, 0);
18583 signal_focus_out();
18586 static void signalEntryActivate(GtkEntry
*, gpointer widget
)
18588 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18589 pThis
->signal_entry_activate();
18592 void signal_entry_activate()
18594 if (m_aEntryActivateHdl
.IsSet())
18596 SolarMutexGuard aGuard
;
18597 if (m_aEntryActivateHdl
.Call(*this))
18598 g_signal_stop_emission_by_name(m_pEntry
, "activate");
18603 OUString
get(int pos
, int col
) const
18607 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
18610 gtk_tree_model_get(m_pTreeModel
, &iter
, col
, &pStr
, -1);
18611 sRet
= OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
18617 void set(int pos
, int col
, std::u16string_view rText
)
18620 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
18622 OString
aStr(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
18623 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel
), &iter
, col
, aStr
.getStr(), -1);
18627 int find(std::u16string_view rStr
, int col
, bool bSearchMRUArea
) const
18630 if (!gtk_tree_model_get_iter_first(m_pTreeModel
, &iter
))
18635 if (!bSearchMRUArea
&& m_nMRUCount
)
18637 if (!gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, m_nMRUCount
+ 1))
18639 nRet
+= (m_nMRUCount
+ 1);
18642 OString
aStr(OUStringToOString(rStr
, RTL_TEXTENCODING_UTF8
).getStr());
18646 gtk_tree_model_get(m_pTreeModel
, &iter
, col
, &pStr
, -1);
18647 const bool bEqual
= g_strcmp0(pStr
, aStr
.getStr()) == 0;
18652 } while (gtk_tree_model_iter_next(m_pTreeModel
, &iter
));
18657 bool separator_function(const GtkTreePath
* path
)
18659 return ::separator_function(path
, m_aSeparatorRows
);
18662 bool separator_function(int pos
)
18664 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
18665 bool bRet
= separator_function(path
);
18666 gtk_tree_path_free(path
);
18670 static gboolean
separatorFunction(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter
, gpointer widget
)
18672 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18673 GtkTreePath
* path
= gtk_tree_model_get_path(pTreeModel
, pIter
);
18674 bool bRet
= pThis
->separator_function(path
);
18675 gtk_tree_path_free(path
);
18679 // https://gitlab.gnome.org/GNOME/gtk/issues/310
18681 // in the absence of a built-in solution
18682 // a) support typeahead for the case where there is no entry widget, typing ahead
18683 // into the button itself will select via the vcl selection engine, a matching
18685 static gboolean
signalKeyPress(GtkEventControllerKey
*, guint keyval
, guint keycode
, GdkModifierType state
, gpointer widget
)
18687 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18688 SolarMutexGuard aGuard
;
18689 return pThis
->signal_key_press(CreateKeyEvent(keyval
, keycode
, state
, 0));
18692 // tdf#131076 we want return in a ComboBox to act like return in a
18693 // GtkEntry and activate the default dialog/assistant button
18694 bool combobox_activate()
18696 GtkWidget
*pComboBox
= GTK_WIDGET(m_pComboBox
);
18697 GtkWidget
*pToplevel
= widget_get_toplevel(pComboBox
);
18698 GtkWindow
*pWindow
= GTK_WINDOW(pToplevel
);
18701 if (!GTK_IS_DIALOG(pWindow
) && !GTK_IS_ASSISTANT(pWindow
))
18703 bool bDone
= false;
18704 GtkWidget
*pDefaultWidget
= gtk_window_get_default_widget(pWindow
);
18705 if (pDefaultWidget
&& pDefaultWidget
!= pComboBox
&& gtk_widget_get_sensitive(pDefaultWidget
))
18706 bDone
= gtk_widget_activate(pDefaultWidget
);
18710 static gboolean
signalEntryKeyPress(GtkEventControllerKey
*, guint keyval
, guint keycode
, GdkModifierType state
, gpointer widget
)
18712 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18713 LocalizeDecimalSeparator(keyval
);
18714 return pThis
->signal_entry_key_press(CreateKeyEvent(keyval
, keycode
, state
, 0));
18717 bool signal_entry_key_press(const KeyEvent
& rKEvt
)
18719 vcl::KeyCode aKeyCode
= rKEvt
.GetKeyCode();
18721 bool bDone
= false;
18723 auto nCode
= aKeyCode
.GetCode();
18728 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
18731 int nCount
= get_count_including_mru();
18732 int nActive
= get_active_including_mru() + 1;
18733 while (nActive
< nCount
&& separator_function(nActive
))
18735 if (nActive
< nCount
)
18736 set_active_including_mru(nActive
, true);
18739 else if (nKeyMod
== KEY_MOD2
&& !m_bPopupActive
)
18741 gtk_combo_box_popup(m_pComboBox
);
18748 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
18751 int nStartBound
= m_bPopupActive
? 0 : (m_nMRUCount
+ 1);
18752 int nActive
= get_active_including_mru() - 1;
18753 while (nActive
>= nStartBound
&& separator_function(nActive
))
18755 if (nActive
>= nStartBound
)
18756 set_active_including_mru(nActive
, true);
18763 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
18766 int nCount
= get_count_including_mru();
18767 int nStartBound
= m_bPopupActive
? 0 : (m_nMRUCount
+ 1);
18768 int nActive
= nStartBound
;
18769 while (nActive
< nCount
&& separator_function(nActive
))
18771 if (nActive
< nCount
)
18772 set_active_including_mru(nActive
, true);
18779 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
18782 int nActive
= get_count_including_mru() - 1;
18783 int nStartBound
= m_bPopupActive
? 0 : (m_nMRUCount
+ 1);
18784 while (nActive
>= nStartBound
&& separator_function(nActive
))
18786 if (nActive
>= nStartBound
)
18787 set_active_including_mru(nActive
, true);
18799 bool signal_key_press(const KeyEvent
& rKEvt
)
18802 if (m_bHoverSelection
)
18804 // once a key is pressed, turn off hover selection until mouse is
18805 // moved again otherwise when the treeview scrolls it jumps to the
18806 // position under the mouse.
18807 gtk_tree_view_set_hover_selection(m_pTreeView
, false);
18808 m_bHoverSelection
= false;
18812 vcl::KeyCode aKeyCode
= rKEvt
.GetKeyCode();
18814 bool bDone
= false;
18816 auto nCode
= aKeyCode
.GetCode();
18829 m_aQuickSelectionEngine
.Reset();
18830 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
18831 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
18832 if (nCode
== KEY_RETURN
&& !nKeyMod
)
18834 if (!m_bPopupActive
)
18835 bDone
= combobox_activate();
18838 // treat 'return' as if the active entry was clicked on
18839 signalChanged(m_pComboBox
, this);
18840 gtk_combo_box_popdown(m_pComboBox
);
18844 else if (nCode
== KEY_UP
&& nKeyMod
== KEY_MOD2
&& m_bPopupActive
)
18846 gtk_combo_box_popdown(m_pComboBox
);
18849 else if (nCode
== KEY_DOWN
&& nKeyMod
== KEY_MOD2
&& !m_bPopupActive
)
18851 gtk_combo_box_popup(m_pComboBox
);
18858 m_aQuickSelectionEngine
.Reset();
18859 if (m_bPopupActive
)
18861 gtk_combo_box_popdown(m_pComboBox
);
18867 // tdf#131076 let base space toggle menu popup when it's not already visible
18868 if (nCode
== KEY_SPACE
&& !aKeyCode
.GetModifier() && !m_bPopupActive
)
18871 bDone
= m_aQuickSelectionEngine
.HandleKeyEvent(rKEvt
);
18878 bDone
= signal_entry_key_press(rKEvt
);
18881 // with gtk4-4.2.1 the unconsumed keystrokes don't appear to get to
18882 // the GtkEntry for up/down to move to the next entry without this extra help
18883 // (which means this extra indirection is probably effectively
18884 // the same as if calling signal_entry_key_press directly here)
18885 bDone
= gtk_event_controller_key_forward(GTK_EVENT_CONTROLLER_KEY(m_pMenuKeyController
), m_pEntry
);
18892 vcl::StringEntryIdentifier
typeahead_getEntry(int nPos
, OUString
& out_entryText
) const
18894 int nEntryCount(get_count_including_mru());
18895 if (nPos
>= nEntryCount
)
18897 out_entryText
= get_text_including_mru(nPos
);
18899 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
18901 return reinterpret_cast<vcl::StringEntryIdentifier
>(nPos
+ 1);
18904 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry
)
18906 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
18907 return reinterpret_cast<sal_Int64
>(entry
) - 1;
18910 int tree_view_get_cursor() const
18915 gtk_tree_view_get_cursor(m_pTreeView
, &path
, nullptr);
18919 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
18920 nRet
= indices
[depth
-1];
18921 gtk_tree_path_free(path
);
18928 int get_selected_entry() const
18930 if (m_bPopupActive
)
18931 return tree_view_get_cursor();
18933 return get_active_including_mru();
18936 void set_typeahead_selected_entry(int nSelect
)
18938 set_active_including_mru(nSelect
, true);
18941 virtual vcl::StringEntryIdentifier
CurrentEntry(OUString
& out_entryText
) const override
18943 int nCurrentPos
= get_selected_entry();
18944 return typeahead_getEntry((nCurrentPos
== -1) ? 0 : nCurrentPos
, out_entryText
);
18947 virtual vcl::StringEntryIdentifier
NextEntry(vcl::StringEntryIdentifier currentEntry
, OUString
& out_entryText
) const override
18949 int nNextPos
= typeahead_getEntryPos(currentEntry
) + 1;
18950 return typeahead_getEntry(nNextPos
, out_entryText
);
18953 virtual void SelectEntry(vcl::StringEntryIdentifier entry
) override
18955 int nSelect
= typeahead_getEntryPos(entry
);
18956 if (nSelect
== get_selected_entry())
18958 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
18959 // to select the given entry by typing its starting letters. No need to act.
18964 int nCount
= get_count_including_mru();
18965 if (nSelect
>= nCount
)
18966 nSelect
= nCount
? nCount
-1 : -1;
18968 set_typeahead_selected_entry(nSelect
);
18972 static void signalGrabBroken(GtkWidget
*, GdkEventGrabBroken
*pEvent
, gpointer widget
)
18974 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18975 pThis
->grab_broken(pEvent
);
18978 void grab_broken(const GdkEventGrabBroken
*event
)
18980 if (event
->grab_window
== nullptr)
18982 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
18984 else if (!g_object_get_data(G_OBJECT(event
->grab_window
), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
18986 //try and regrab, so when we lose the grab to the menu of the color palette
18987 //combobox we regain it so the color palette doesn't itself disappear on next
18988 //click on the color palette combobox
18989 do_grab(GTK_WIDGET(m_pMenuWindow
));
18993 static gboolean
signalButtonPress(GtkWidget
* pWidget
, GdkEventButton
* pEvent
, gpointer widget
)
18995 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
18996 return pThis
->button_press(pWidget
, pEvent
);
18999 bool button_press(GtkWidget
* pWidget
, GdkEventButton
* pEvent
)
19001 //we want to pop down if the button was pressed outside our popup
19002 gdouble x
= pEvent
->x_root
;
19003 gdouble y
= pEvent
->y_root
;
19004 gint xoffset
, yoffset
;
19005 gdk_window_get_root_origin(widget_get_surface(pWidget
), &xoffset
, &yoffset
);
19007 GtkAllocation alloc
;
19008 gtk_widget_get_allocation(pWidget
, &alloc
);
19009 xoffset
+= alloc
.x
;
19010 yoffset
+= alloc
.y
;
19012 gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow
), &alloc
);
19013 gint x1
= alloc
.x
+ xoffset
;
19014 gint y1
= alloc
.y
+ yoffset
;
19015 gint x2
= x1
+ alloc
.width
;
19016 gint y2
= y1
+ alloc
.height
;
19018 if (x
> x1
&& x
< x2
&& y
> y1
&& y
< y2
)
19021 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
19026 static gboolean
signalMotion(GtkWidget
*, GdkEventMotion
*, gpointer widget
)
19028 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
19029 pThis
->signal_motion();
19033 void signal_motion()
19035 // if hover-selection was disabled after pressing a key, then turn it back on again
19036 if (!m_bHoverSelection
&& !m_bMouseInOverlayButton
)
19038 gtk_tree_view_set_hover_selection(m_pTreeView
, true);
19039 m_bHoverSelection
= true;
19044 static void signalRowActivated(GtkTreeView
*, GtkTreePath
*, GtkTreeViewColumn
*, gpointer widget
)
19046 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
19047 pThis
->handle_row_activated();
19050 void handle_row_activated()
19052 m_bUserSelectEntry
= true;
19053 m_bChangedByMenu
= true;
19054 disable_notify_events();
19055 int nActive
= get_active();
19057 gtk_editable_set_text(m_pEditable
, OUStringToOString(get_text(nActive
), RTL_TEXTENCODING_UTF8
).getStr());
19060 tree_view_set_cursor(nActive
);
19062 enable_notify_events();
19063 // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
19064 fire_signal_changed();
19070 disable_notify_events();
19071 gtk_combo_box_set_row_separator_func(m_pComboBox
, nullptr, nullptr, nullptr);
19072 m_aSeparatorRows
.clear();
19073 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel
));
19075 enable_notify_events();
19078 virtual int get_max_mru_count() const override
19080 return m_nMaxMRUCount
;
19083 virtual void set_max_mru_count(int nMaxMRUCount
) override
19085 m_nMaxMRUCount
= nMaxMRUCount
;
19091 int nMRUCount
= m_nMRUCount
;
19093 if (m_nMaxMRUCount
)
19095 OUString sActiveText
= get_active_text();
19096 OUString sActiveId
= get_active_id();
19097 insert_including_mru(0, sActiveText
, &sActiveId
, nullptr, nullptr);
19100 for (int i
= 1; i
< m_nMRUCount
- 1; ++i
)
19102 if (get_text_including_mru(i
) == sActiveText
)
19104 remove_including_mru(i
);
19111 while (m_nMRUCount
> m_nMaxMRUCount
)
19113 remove_including_mru(m_nMRUCount
- 1);
19117 if (m_nMRUCount
&& !nMRUCount
)
19118 insert_separator_including_mru(m_nMRUCount
, "separator");
19119 else if (!m_nMRUCount
&& nMRUCount
)
19120 remove_including_mru(m_nMRUCount
); // remove separator
19123 int get_count_including_mru() const
19125 return gtk_tree_model_iter_n_children(m_pTreeModel
, nullptr);
19128 int get_active_including_mru() const
19130 return gtk_combo_box_get_active(m_pComboBox
);
19133 void set_active_including_mru(int pos
, bool bInteractive
)
19135 disable_notify_events();
19137 gtk_combo_box_set_active(m_pComboBox
, pos
);
19139 m_bChangedByMenu
= false;
19140 enable_notify_events();
19142 if (bInteractive
&& !m_bPopupActive
)
19146 int find_text_including_mru(std::u16string_view rStr
, bool bSearchMRU
) const
19148 return find(rStr
, m_nTextCol
, bSearchMRU
);
19151 int find_id_including_mru(std::u16string_view rId
, bool bSearchMRU
) const
19153 return find(rId
, m_nIdCol
, bSearchMRU
);
19156 OUString
get_text_including_mru(int pos
) const
19158 return get(pos
, m_nTextCol
);
19161 OUString
get_id_including_mru(int pos
) const
19163 return get(pos
, m_nIdCol
);
19166 void set_id_including_mru(int pos
, std::u16string_view rId
)
19168 set(pos
, m_nIdCol
, rId
);
19171 void remove_including_mru(int pos
)
19173 disable_notify_events();
19175 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
);
19176 if (!m_aSeparatorRows
.empty())
19178 bool bFound
= false;
19180 GtkTreePath
* pPath
= gtk_tree_path_new_from_indices(pos
, -1);
19182 for (auto aIter
= m_aSeparatorRows
.begin(); aIter
!= m_aSeparatorRows
.end(); ++aIter
)
19184 GtkTreePath
* seppath
= gtk_tree_row_reference_get_path(aIter
->get());
19187 if (gtk_tree_path_compare(pPath
, seppath
) == 0)
19189 gtk_tree_path_free(seppath
);
19193 m_aSeparatorRows
.erase(aIter
);
19198 gtk_tree_path_free(pPath
);
19200 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel
), &iter
);
19201 enable_notify_events();
19204 void insert_separator_including_mru(int pos
, const OUString
& rId
)
19206 disable_notify_events();
19208 if (!gtk_combo_box_get_row_separator_func(m_pComboBox
))
19209 gtk_combo_box_set_row_separator_func(m_pComboBox
, separatorFunction
, this, nullptr);
19210 insert_row(GTK_LIST_STORE(m_pTreeModel
), iter
, pos
, &rId
, u
"", nullptr, nullptr);
19211 GtkTreePath
* pPath
= gtk_tree_path_new_from_indices(pos
, -1);
19212 m_aSeparatorRows
.emplace_back(gtk_tree_row_reference_new(m_pTreeModel
, pPath
));
19213 gtk_tree_path_free(pPath
);
19214 enable_notify_events();
19217 void insert_including_mru(int pos
, std::u16string_view rText
, const OUString
* pId
, const OUString
* pIconName
, const VirtualDevice
* pImageSurface
)
19219 disable_notify_events();
19221 insert_row(GTK_LIST_STORE(m_pTreeModel
), iter
, pos
, pId
, rText
, pIconName
, pImageSurface
);
19222 enable_notify_events();
19226 static gboolean
signalGetChildPosition(GtkOverlay
*, GtkWidget
*, GdkRectangle
* pAllocation
, gpointer widget
)
19228 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
19229 return pThis
->signal_get_child_position(pAllocation
);
19232 bool signal_get_child_position(GdkRectangle
* pAllocation
)
19234 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton
)))
19236 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView
)))
19238 int nRow
= find_id_including_mru(m_sMenuButtonRow
, true);
19242 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton
), &pAllocation
->width
, nullptr);
19244 GtkTreePath
* pPath
= gtk_tree_path_new_from_indices(nRow
, -1);
19245 GList
* pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
19246 tools::Rectangle aRect
= get_row_area(m_pTreeView
, pColumns
, pPath
);
19247 gtk_tree_path_free(pPath
);
19248 g_list_free(pColumns
);
19250 pAllocation
->x
= aRect
.Right() - pAllocation
->width
;
19251 pAllocation
->y
= aRect
.Top();
19252 pAllocation
->height
= aRect
.GetHeight();
19257 static gboolean
signalOverlayButtonCrossing(GtkWidget
*, GdkEventCrossing
* pEvent
, gpointer widget
)
19259 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
19260 pThis
->signal_overlay_button_crossing(pEvent
->type
== GDK_ENTER_NOTIFY
);
19264 void signal_overlay_button_crossing(bool bEnter
)
19266 m_bMouseInOverlayButton
= bEnter
;
19270 if (m_bHoverSelection
)
19272 // once toggled button is pressed, turn off hover selection until
19273 // mouse leaves the overlay button
19274 gtk_tree_view_set_hover_selection(m_pTreeView
, false);
19275 m_bHoverSelection
= false;
19277 int nRow
= find_id_including_mru(m_sMenuButtonRow
, true);
19278 assert(nRow
!= -1);
19279 tree_view_set_cursor(nRow
); // select the buttons row
19283 int include_mru(int pos
)
19285 if (m_nMRUCount
&& pos
!= -1)
19286 pos
+= (m_nMRUCount
+ 1);
19291 GtkInstanceComboBox(GtkComboBox
* pComboBox
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
19292 : GtkInstanceWidget(GTK_WIDGET(pComboBox
), pBuilder
, bTakeOwnership
)
19293 , m_pComboBox(pComboBox
)
19294 // , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
19295 // , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
19296 // , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
19297 , m_pMenuWindow(nullptr)
19298 , m_pTreeModel(gtk_combo_box_get_model(pComboBox
))
19299 , m_pButtonTextRenderer(nullptr)
19300 // , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
19301 , m_pEntry(GTK_IS_ENTRY(gtk_combo_box_get_child(pComboBox
)) ? gtk_combo_box_get_child(pComboBox
) : nullptr)
19302 , m_pEditable(GTK_EDITABLE(m_pEntry
))
19303 , m_aCustomFont(m_pWidget
)
19304 // , m_pCellView(nullptr)
19305 , m_aQuickSelectionEngine(*this)
19306 // , m_bHoverSelection(false)
19307 // , m_bMouseInOverlayButton(false)
19308 , m_bPopupActive(false)
19309 , m_bAutoComplete(false)
19310 , m_bAutoCompleteCaseSensitive(false)
19311 , m_bChangedByMenu(false)
19312 , m_bCustomRenderer(false)
19313 , m_bUserSelectEntry(false)
19314 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox
))
19315 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox
))
19316 // , m_nToggleFocusInSignalId(0)
19317 // , m_nToggleFocusOutSignalId(0)
19318 // , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
19319 , m_nChangedSignalId(g_signal_connect(m_pComboBox
, "changed", G_CALLBACK(signalChanged
), this))
19320 , m_nPopupShownSignalId(g_signal_connect(m_pComboBox
, "notify::popup-shown", G_CALLBACK(signalPopupToggled
), this))
19321 , m_nAutoCompleteIdleId(0)
19322 // , m_nNonCustomLineHeight(-1)
19323 , m_nPrePopupCursorPos(-1)
19325 , m_nMaxMRUCount(0)
19327 for (GtkWidget
* pChild
= gtk_widget_get_first_child(GTK_WIDGET(m_pComboBox
));
19328 pChild
; pChild
= gtk_widget_get_next_sibling(pChild
))
19330 if (GTK_IS_POPOVER(pChild
))
19332 m_pMenuWindow
= pChild
;
19336 SAL_WARN_IF(!m_pMenuWindow
, "vcl.gtk", "GtkInstanceComboBox: couldn't find popup menu");
19338 bool bHasEntry
= gtk_combo_box_get_has_entry(m_pComboBox
);
19339 bool bPixbufUsedSurface
= gtk_tree_model_get_n_columns(m_pTreeModel
) == 4;
19341 bool bFindButtonTextRenderer
= !bHasEntry
;
19342 GtkCellLayout
* pCellLayout
= GTK_CELL_LAYOUT(m_pComboBox
);
19343 GList
* cells
= gtk_cell_layout_get_cells(pCellLayout
);
19344 guint i
= g_list_length(cells
) - 1;;
19345 // reorder the cell renderers
19346 for (GList
* pRenderer
= g_list_first(cells
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
19348 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
19349 gtk_cell_layout_reorder(pCellLayout
, pCellRenderer
, i
--);
19350 if (bFindButtonTextRenderer
)
19352 m_pButtonTextRenderer
= pCellRenderer
;
19353 bFindButtonTextRenderer
= false;
19357 // Seeing as GtkCellRendererPixbuf no longer takes a surface, then insert our own replacement
19358 // to render that instead here
19359 if (bPixbufUsedSurface
)
19361 GtkCellRenderer
* pSurfaceRenderer
= surface_cell_renderer_new();
19362 gtk_cell_layout_pack_start(pCellLayout
, pSurfaceRenderer
, false);
19363 gtk_cell_layout_reorder(pCellLayout
, pSurfaceRenderer
, 0);
19364 gtk_cell_layout_set_attributes(pCellLayout
, pSurfaceRenderer
, "surface", 3, nullptr);
19369 m_bAutoComplete
= true;
19370 m_nEntryInsertTextSignalId
= g_signal_connect(m_pEditable
, "insert-text", G_CALLBACK(signalEntryInsertText
), this);
19371 m_nEntryActivateSignalId
= g_signal_connect(m_pEntry
, "activate", G_CALLBACK(signalEntryActivate
), this);
19372 m_pEntryFocusController
= GTK_EVENT_CONTROLLER(gtk_event_controller_focus_new());
19373 m_nEntryFocusInSignalId
= g_signal_connect(m_pEntryFocusController
, "enter", G_CALLBACK(signalEntryFocusIn
), this);
19374 m_nEntryFocusOutSignalId
= g_signal_connect(m_pEntryFocusController
, "leave", G_CALLBACK(signalEntryFocusOut
), this);
19375 gtk_widget_add_controller(m_pEntry
, m_pEntryFocusController
);
19376 m_pEntryKeyController
= GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
19377 m_nEntryKeyPressEventSignalId
= g_signal_connect(m_pEntryKeyController
, "key-pressed", G_CALLBACK(signalEntryKeyPress
), this);
19378 gtk_widget_add_controller(m_pEntry
, m_pEntryKeyController
);
19379 m_nKeyPressEventSignalId
= 0;
19380 m_pKeyController
= nullptr;
19384 m_nEntryInsertTextSignalId
= 0;
19385 m_nEntryActivateSignalId
= 0;
19386 m_pEntryFocusController
= nullptr;
19387 m_nEntryFocusInSignalId
= 0;
19388 m_nEntryFocusOutSignalId
= 0;
19389 m_pEntryKeyController
= nullptr;
19390 m_nEntryKeyPressEventSignalId
= 0;
19391 m_pKeyController
= GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
19392 m_nKeyPressEventSignalId
= g_signal_connect(m_pKeyController
, "key-pressed", G_CALLBACK(signalKeyPress
), this);
19393 gtk_widget_add_controller(GTK_WIDGET(m_pComboBox
), m_pKeyController
);
19396 // g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
19397 // g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
19398 // g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
19400 // support typeahead for the menu itself, typing into the menu will
19401 // select via the vcl selection engine, a matching entry.
19404 m_pMenuKeyController
= GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
19405 g_signal_connect(m_pMenuKeyController
, "key-pressed", G_CALLBACK(signalKeyPress
), this);
19406 gtk_widget_add_controller(m_pMenuWindow
, m_pMenuKeyController
);
19409 m_pMenuKeyController
= nullptr;
19411 g_signal_connect(m_pOverlay
, "get-child-position", G_CALLBACK(signalGetChildPosition
), this);
19412 gtk_overlay_add_overlay(m_pOverlay
, GTK_WIDGET(m_pOverlayButton
));
19413 g_signal_connect(m_pOverlayButton
, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing
), this);
19414 g_signal_connect(m_pOverlayButton
, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing
), this);
19418 virtual int get_active() const override
19420 int nActive
= get_active_including_mru();
19426 if (nActive
< m_nMRUCount
)
19427 nActive
= find_text(get_text_including_mru(nActive
));
19429 nActive
-= (m_nMRUCount
+ 1);
19435 virtual OUString
get_active_id() const override
19437 int nActive
= get_active();
19438 return nActive
!= -1 ? get_id(nActive
) : OUString();
19441 virtual void set_active_id(const OUString
& rStr
) override
19443 set_active(find_id(rStr
));
19444 m_bChangedByMenu
= false;
19447 virtual void set_size_request(int nWidth
, int nHeight
) override
19449 if (m_pButtonTextRenderer
)
19451 // tweak the cell render to get a narrower size to stick
19454 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
19455 // the popup menu render them in full, in the interim ellipse both of them
19456 g_object_set(G_OBJECT(m_pButtonTextRenderer
), "ellipsize", PANGO_ELLIPSIZE_MIDDLE
, nullptr);
19458 // to find out how much of the width of the combobox belongs to the cell, set
19459 // the cell and widget to the min cell width and see what the difference is
19461 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer
, m_pWidget
, &min
, nullptr);
19462 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer
, min
, -1);
19463 gtk_widget_set_size_request(m_pWidget
, min
, -1);
19464 int nNonCellWidth
= get_preferred_size().Width() - min
;
19466 int nCellWidth
= nWidth
- nNonCellWidth
;
19467 if (nCellWidth
>= 0)
19469 // now set the cell to the max width which it can be within the
19470 // requested widget width
19471 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer
, nWidth
- nNonCellWidth
, -1);
19476 g_object_set(G_OBJECT(m_pButtonTextRenderer
), "ellipsize", PANGO_ELLIPSIZE_NONE
, nullptr);
19477 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer
, -1, -1);
19481 gtk_widget_set_size_request(m_pWidget
, nWidth
, nHeight
);
19484 virtual void set_active(int pos
) override
19486 set_active_including_mru(include_mru(pos
), false);
19489 virtual OUString
get_active_text() const override
19493 const gchar
* pText
= gtk_editable_get_text(m_pEditable
);
19494 return OUString(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
19497 int nActive
= get_active();
19501 return get_text(nActive
);
19504 virtual OUString
get_text(int pos
) const override
19507 pos
+= (m_nMRUCount
+ 1);
19508 return get_text_including_mru(pos
);
19511 virtual OUString
get_id(int pos
) const override
19514 pos
+= (m_nMRUCount
+ 1);
19515 return get_id_including_mru(pos
);
19518 virtual void set_id(int pos
, const OUString
& rId
) override
19521 pos
+= (m_nMRUCount
+ 1);
19522 set_id_including_mru(pos
, rId
);
19525 virtual void insert_vector(const std::vector
<weld::ComboBoxEntry
>& rItems
, bool bKeepExisting
) override
19529 int nInsertionPoint
;
19530 if (!bKeepExisting
)
19533 nInsertionPoint
= 0;
19536 nInsertionPoint
= get_count();
19539 // tdf#125241 inserting backwards is faster
19540 for (auto aI
= rItems
.rbegin(); aI
!= rItems
.rend(); ++aI
)
19542 const auto& rItem
= *aI
;
19543 insert_row(GTK_LIST_STORE(m_pTreeModel
), iter
, nInsertionPoint
, rItem
.sId
.isEmpty() ? nullptr : &rItem
.sId
,
19544 rItem
.sString
, rItem
.sImage
.isEmpty() ? nullptr : &rItem
.sImage
, nullptr);
19550 virtual void remove(int pos
) override
19553 pos
+= (m_nMRUCount
+ 1);
19554 remove_including_mru(pos
);
19557 virtual void insert(int pos
, const OUString
& rText
, const OUString
* pId
, const OUString
* pIconName
, VirtualDevice
* pImageSurface
) override
19559 insert_including_mru(include_mru(pos
), rText
, pId
, pIconName
, pImageSurface
);
19562 virtual void insert_separator(int pos
, const OUString
& rId
) override
19564 pos
= pos
== -1 ? get_count() : pos
;
19566 pos
+= (m_nMRUCount
+ 1);
19567 insert_separator_including_mru(pos
, rId
);
19570 virtual int get_count() const override
19572 int nCount
= get_count_including_mru();
19574 nCount
-= (m_nMRUCount
+ 1);
19578 virtual int find_text(const OUString
& rStr
) const override
19580 int nPos
= find_text_including_mru(rStr
, false);
19581 if (nPos
!= -1 && m_nMRUCount
)
19582 nPos
-= (m_nMRUCount
+ 1);
19586 virtual int find_id(const OUString
& rId
) const override
19588 int nPos
= find_id_including_mru(rId
, false);
19589 if (nPos
!= -1 && m_nMRUCount
)
19590 nPos
-= (m_nMRUCount
+ 1);
19594 virtual void clear() override
19599 virtual void make_sorted() override
19601 m_xSorter
.reset(new comphelper::string::NaturalStringSorter(
19602 ::comphelper::getProcessComponentContext(),
19603 Application::GetSettings().GetUILanguageTag().getLocale()));
19604 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
19605 gtk_tree_sortable_set_sort_column_id(pSortable
, m_nTextCol
, GTK_SORT_ASCENDING
);
19606 gtk_tree_sortable_set_sort_func(pSortable
, m_nTextCol
, default_sort_func
, m_xSorter
.get(), nullptr);
19609 virtual bool has_entry() const override
19611 return gtk_combo_box_get_has_entry(m_pComboBox
);
19614 virtual void set_entry_message_type(weld::EntryMessageType eType
) override
19617 ::set_entry_message_type(GTK_ENTRY(m_pEntry
), eType
);
19620 virtual void set_entry_text(const OUString
& rText
) override
19622 assert(m_pEditable
);
19623 disable_notify_events();
19624 gtk_editable_set_text(m_pEditable
, OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
19625 enable_notify_events();
19628 virtual void set_entry_width_chars(int nChars
) override
19630 assert(m_pEditable
);
19631 disable_notify_events();
19632 gtk_editable_set_width_chars(m_pEditable
, nChars
);
19633 gtk_editable_set_max_width_chars(m_pEditable
, nChars
);
19634 enable_notify_events();
19637 virtual void set_entry_max_length(int nChars
) override
19640 disable_notify_events();
19641 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry
), nChars
);
19642 enable_notify_events();
19645 virtual void select_entry_region(int nStartPos
, int nEndPos
) override
19647 assert(m_pEditable
);
19648 disable_notify_events();
19649 gtk_editable_select_region(m_pEditable
, nStartPos
, nEndPos
);
19650 enable_notify_events();
19653 virtual bool get_entry_selection_bounds(int& rStartPos
, int &rEndPos
) override
19655 assert(m_pEditable
);
19656 return gtk_editable_get_selection_bounds(m_pEditable
, &rStartPos
, &rEndPos
);
19659 virtual void set_entry_completion(bool bEnable
, bool bCaseSensitive
) override
19661 m_bAutoComplete
= bEnable
;
19662 m_bAutoCompleteCaseSensitive
= bCaseSensitive
;
19665 virtual void set_entry_placeholder_text(const OUString
& rText
) override
19668 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry
), rText
.toUtf8().getStr());
19671 virtual void set_entry_editable(bool bEditable
) override
19673 assert(m_pEditable
);
19674 gtk_editable_set_editable(m_pEditable
, bEditable
);
19677 virtual void cut_entry_clipboard() override
19680 gtk_widget_activate_action(m_pEntry
, "cut.clipboard", nullptr);
19683 virtual void copy_entry_clipboard() override
19686 gtk_widget_activate_action(m_pEntry
, "copy.clipboard", nullptr);
19689 virtual void paste_entry_clipboard() override
19692 gtk_widget_activate_action(m_pEntry
, "paste.clipboard", nullptr);
19695 virtual void set_font(const vcl::Font
& rFont
) override
19697 m_aCustomFont
.use_custom_font(&rFont
, u
"combobox");
19700 virtual vcl::Font
get_font() override
19702 if (const vcl::Font
* pFont
= m_aCustomFont
.get_custom_font())
19704 return GtkInstanceWidget::get_font();
19707 virtual void set_entry_font(const vcl::Font
& rFont
) override
19709 m_xEntryFont
= rFont
;
19711 PangoAttrList
* pOrigList
= gtk_entry_get_attributes(GTK_ENTRY(m_pEntry
));
19712 PangoAttrList
* pAttrList
= pOrigList
? pango_attr_list_copy(pOrigList
) : pango_attr_list_new();
19713 update_attr_list(pAttrList
, rFont
);
19714 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry
), pAttrList
);
19715 pango_attr_list_unref(pAttrList
);
19718 virtual vcl::Font
get_entry_font() override
19721 return *m_xEntryFont
;
19723 PangoContext
* pContext
= gtk_widget_get_pango_context(m_pEntry
);
19724 return pango_to_vcl(pango_context_get_font_description(pContext
),
19725 Application::GetSettings().GetUILanguageTag().getLocale());
19728 virtual void disable_notify_events() override
19732 g_signal_handler_block(m_pEditable
, m_nEntryInsertTextSignalId
);
19733 g_signal_handler_block(m_pEntry
, m_nEntryActivateSignalId
);
19734 g_signal_handler_block(m_pEntryFocusController
, m_nEntryFocusInSignalId
);
19735 g_signal_handler_block(m_pEntryFocusController
, m_nEntryFocusOutSignalId
);
19736 g_signal_handler_block(m_pEntryKeyController
, m_nEntryKeyPressEventSignalId
);
19739 g_signal_handler_block(m_pKeyController
, m_nKeyPressEventSignalId
);
19741 // if (m_nToggleFocusInSignalId)
19742 // g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
19743 // if (m_nToggleFocusOutSignalId)
19744 // g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
19745 // g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
19746 g_signal_handler_block(m_pComboBox
, m_nPopupShownSignalId
);
19747 g_signal_handler_block(m_pComboBox
, m_nChangedSignalId
);
19748 GtkInstanceWidget::disable_notify_events();
19751 virtual void enable_notify_events() override
19753 GtkInstanceWidget::enable_notify_events();
19754 g_signal_handler_unblock(m_pComboBox
, m_nChangedSignalId
);
19755 g_signal_handler_unblock(m_pComboBox
, m_nPopupShownSignalId
);
19756 // g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
19757 // if (m_nToggleFocusInSignalId)
19758 // g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
19759 // if (m_nToggleFocusOutSignalId)
19760 // g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
19763 g_signal_handler_unblock(m_pEntry
, m_nEntryActivateSignalId
);
19764 g_signal_handler_unblock(m_pEntryFocusController
, m_nEntryFocusInSignalId
);
19765 g_signal_handler_unblock(m_pEntryFocusController
, m_nEntryFocusOutSignalId
);
19766 g_signal_handler_unblock(m_pEntryKeyController
, m_nEntryKeyPressEventSignalId
);
19767 g_signal_handler_unblock(m_pEditable
, m_nEntryInsertTextSignalId
);
19770 g_signal_handler_unblock(m_pKeyController
, m_nKeyPressEventSignalId
);
19773 virtual void freeze() override
19775 disable_notify_events();
19776 bool bIsFirstFreeze
= IsFirstFreeze();
19777 GtkInstanceWidget::freeze();
19778 if (bIsFirstFreeze
)
19780 g_object_ref(m_pTreeModel
);
19781 // gtk_tree_view_set_model(m_pTreeView, nullptr);
19782 g_object_freeze_notify(G_OBJECT(m_pTreeModel
));
19785 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
19786 gtk_tree_sortable_set_sort_column_id(pSortable
, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
, GTK_SORT_ASCENDING
);
19789 enable_notify_events();
19792 virtual void thaw() override
19794 disable_notify_events();
19799 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
19800 gtk_tree_sortable_set_sort_column_id(pSortable
, m_nTextCol
, GTK_SORT_ASCENDING
);
19802 g_object_thaw_notify(G_OBJECT(m_pTreeModel
));
19803 // gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
19804 g_object_unref(m_pTreeModel
);
19806 GtkInstanceWidget::thaw();
19807 enable_notify_events();
19810 virtual bool get_popup_shown() const override
19812 return m_bPopupActive
;
19815 virtual void connect_focus_in(const Link
<Widget
&, void>& rLink
) override
19817 // if (!m_nToggleFocusInSignalId)
19818 // m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
19819 GtkInstanceWidget::connect_focus_in(rLink
);
19822 virtual void connect_focus_out(const Link
<Widget
&, void>& rLink
) override
19824 // if (!m_nToggleFocusOutSignalId)
19825 // m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
19826 GtkInstanceWidget::connect_focus_out(rLink
);
19829 virtual void grab_focus() override
19834 gtk_widget_grab_focus(m_pEntry
);
19837 // gtk_widget_grab_focus(m_pToggleButton);
19838 gtk_widget_grab_focus(GTK_WIDGET(m_pComboBox
));
19842 virtual bool has_focus() const override
19844 if (m_pEntry
&& gtk_widget_has_focus(m_pEntry
))
19847 // if (gtk_widget_has_focus(m_pToggleButton))
19851 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow
)))
19853 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton
)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView
)))
19858 return GtkInstanceWidget::has_focus();
19861 virtual bool changed_by_direct_pick() const override
19863 return m_bChangedByMenu
;
19866 virtual void set_custom_renderer(bool bOn
) override
19868 if (bOn
== m_bCustomRenderer
)
19871 GList
* pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
19872 // keep the original height around for optimal popup height calculation
19873 m_nNonCustomLineHeight
= bOn
? ::get_height_row(m_pTreeView
, pColumns
) : -1;
19874 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pColumns
->data
);
19875 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn
));
19878 GtkCellRenderer
*pRenderer
= custom_cell_renderer_new();
19879 GValue value
= G_VALUE_INIT
;
19880 g_value_init(&value
, G_TYPE_POINTER
);
19881 g_value_set_pointer(&value
, static_cast<gpointer
>(this));
19882 g_object_set_property(G_OBJECT(pRenderer
), "instance", &value
);
19883 gtk_tree_view_column_pack_start(pColumn
, pRenderer
, true);
19884 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "text", m_nTextCol
);
19885 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "id", m_nIdCol
);
19889 GtkCellRenderer
*pRenderer
= gtk_cell_renderer_text_new();
19890 gtk_tree_view_column_pack_start(pColumn
, pRenderer
, true);
19891 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "text", m_nTextCol
);
19893 g_list_free(pColumns
);
19894 m_bCustomRenderer
= bOn
;
19898 void call_signal_custom_render(VirtualDevice
& rOutput
, const tools::Rectangle
& rRect
, bool bSelected
, const OUString
& rId
)
19900 signal_custom_render(rOutput
, rRect
, bSelected
, rId
);
19903 Size
call_signal_custom_get_size(VirtualDevice
& rOutput
)
19905 return signal_custom_get_size(rOutput
);
19908 VclPtr
<VirtualDevice
> create_render_virtual_device() const override
19910 return create_virtual_device();
19913 virtual void set_item_menu(const OString
& rIdent
, weld::Menu
* pMenu
) override
19916 m_xCustomMenuButtonHelper
.reset();
19917 GtkInstanceMenu
* pPopoverWidget
= dynamic_cast<GtkInstanceMenu
*>(pMenu
);
19918 GtkWidget
* pMenuWidget
= GTK_WIDGET(pPopoverWidget
? pPopoverWidget
->getMenu() : nullptr);
19919 gtk_menu_button_set_popup(m_pOverlayButton
, pMenuWidget
);
19920 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton
), pMenuWidget
!= nullptr);
19921 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton
)); // force location recalc
19923 m_xCustomMenuButtonHelper
.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget
), GTK_TOGGLE_BUTTON(m_pToggleButton
)));
19924 m_sMenuButtonRow
= OUString::fromUtf8(rIdent
);
19926 (void)rIdent
; (void)pMenu
;
19930 OUString
get_mru_entries() const override
19932 const sal_Unicode cSep
= ';';
19934 OUStringBuffer aEntries
;
19935 for (sal_Int32 n
= 0; n
< m_nMRUCount
; n
++)
19937 aEntries
.append(get_text_including_mru(n
));
19938 if (n
< m_nMRUCount
- 1)
19939 aEntries
.append(cSep
);
19941 return aEntries
.makeStringAndClear();
19944 virtual void set_mru_entries(const OUString
& rEntries
) override
19946 const sal_Unicode cSep
= ';';
19948 // Remove old MRU entries
19949 for (sal_Int32 n
= m_nMRUCount
; n
;)
19950 remove_including_mru(--n
);
19952 sal_Int32 nMRUCount
= 0;
19953 sal_Int32 nIndex
= 0;
19956 OUString aEntry
= rEntries
.getToken(0, cSep
, nIndex
);
19957 // Accept only existing entries
19958 int nPos
= find_text(aEntry
);
19961 OUString sId
= get_id(nPos
);
19962 insert_including_mru(0, aEntry
, &sId
, nullptr, nullptr);
19966 while (nIndex
>= 0);
19968 if (nMRUCount
&& !m_nMRUCount
)
19969 insert_separator_including_mru(nMRUCount
, "separator");
19970 else if (!nMRUCount
&& m_nMRUCount
)
19971 remove_including_mru(m_nMRUCount
); // remove separator
19973 m_nMRUCount
= nMRUCount
;
19976 int get_menu_button_width() const override
19979 bool bVisible
= gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton
));
19981 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton
), true);
19983 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton
), &nWidth
, nullptr);
19985 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton
), false);
19992 virtual ~GtkInstanceComboBox() override
19994 // m_xCustomMenuButtonHelper.reset();
19996 if (m_nAutoCompleteIdleId
)
19997 g_source_remove(m_nAutoCompleteIdleId
);
20000 g_signal_handler_disconnect(m_pEditable
, m_nEntryInsertTextSignalId
);
20001 g_signal_handler_disconnect(m_pEntry
, m_nEntryActivateSignalId
);
20002 g_signal_handler_disconnect(m_pEntryFocusController
, m_nEntryFocusInSignalId
);
20003 g_signal_handler_disconnect(m_pEntryFocusController
, m_nEntryFocusOutSignalId
);
20004 g_signal_handler_disconnect(m_pEntryKeyController
, m_nEntryKeyPressEventSignalId
);
20007 g_signal_handler_disconnect(m_pKeyController
, m_nKeyPressEventSignalId
);
20008 // if (m_nToggleFocusInSignalId)
20009 // g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
20010 // if (m_nToggleFocusOutSignalId)
20011 // g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
20012 // g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
20013 g_signal_handler_disconnect(m_pComboBox
, m_nPopupShownSignalId
);
20014 g_signal_handler_disconnect(m_pComboBox
, m_nChangedSignalId
);
20016 // gtk_tree_view_set_model(m_pTreeView, nullptr);
20023 class GtkInstanceComboBox
: public GtkInstanceContainer
, public vcl::ISearchableStringList
, public virtual weld::ComboBox
20026 GtkBuilder
* m_pComboBuilder
;
20027 GtkComboBox
* m_pComboBox
;
20028 GtkOverlay
* m_pOverlay
;
20029 GtkTreeView
* m_pTreeView
;
20030 GtkMenuButton
* m_pOverlayButton
; // button that the StyleDropdown uses on an active row
20031 GtkWindow
* m_pMenuWindow
;
20032 GtkTreeModel
* m_pTreeModel
;
20033 GtkCellRenderer
* m_pButtonTextRenderer
;
20034 GtkCellRenderer
* m_pMenuTextRenderer
;
20035 GtkWidget
* m_pToggleButton
;
20036 GtkWidget
* m_pEntry
;
20037 GtkCellView
* m_pCellView
;
20038 WidgetFont m_aCustomFont
;
20039 std::unique_ptr
<CustomRenderMenuButtonHelper
> m_xCustomMenuButtonHelper
;
20040 std::optional
<vcl::Font
> m_xEntryFont
;
20041 std::unique_ptr
<comphelper::string::NaturalStringSorter
> m_xSorter
;
20042 vcl::QuickSelectionEngine m_aQuickSelectionEngine
;
20043 std::vector
<std::unique_ptr
<GtkTreeRowReference
, GtkTreeRowReferenceDeleter
>> m_aSeparatorRows
;
20044 OUString m_sMenuButtonRow
;
20045 bool m_bHoverSelection
;
20046 bool m_bMouseInOverlayButton
;
20047 bool m_bPopupActive
;
20048 bool m_bAutoComplete
;
20049 bool m_bAutoCompleteCaseSensitive
;
20050 bool m_bChangedByMenu
;
20051 bool m_bCustomRenderer
;
20052 bool m_bActivateCalled
;
20055 gulong m_nToggleFocusInSignalId
;
20056 gulong m_nToggleFocusOutSignalId
;
20057 gulong m_nRowActivatedSignalId
;
20058 gulong m_nChangedSignalId
;
20059 gulong m_nPopupShownSignalId
;
20060 gulong m_nKeyPressEventSignalId
;
20061 gulong m_nEntryInsertTextSignalId
;
20062 gulong m_nEntryActivateSignalId
;
20063 gulong m_nEntryFocusInSignalId
;
20064 gulong m_nEntryFocusOutSignalId
;
20065 gulong m_nEntryKeyPressEventSignalId
;
20066 guint m_nAutoCompleteIdleId
;
20067 gint m_nNonCustomLineHeight
;
20068 gint m_nPrePopupCursorPos
;
20070 int m_nMaxMRUCount
;
20072 static gboolean
idleAutoComplete(gpointer widget
)
20074 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20075 pThis
->auto_complete();
20079 void auto_complete()
20081 m_nAutoCompleteIdleId
= 0;
20082 OUString aStartText
= get_active_text();
20083 int nStartPos
, nEndPos
;
20084 get_entry_selection_bounds(nStartPos
, nEndPos
);
20085 int nMaxSelection
= std::max(nStartPos
, nEndPos
);
20086 if (nMaxSelection
!= aStartText
.getLength())
20089 disable_notify_events();
20090 int nActive
= get_active();
20091 int nStart
= nActive
;
20100 nZeroRow
+= (m_nMRUCount
+ 1);
20102 if (!m_bAutoCompleteCaseSensitive
)
20104 // Try match case insensitive from current position
20105 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nStart
, false);
20106 if (nPos
== -1 && nStart
!= 0)
20108 // Try match case insensitive, but from start
20109 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nZeroRow
, false);
20115 // Try match case sensitive from current position
20116 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nStart
, true);
20117 if (nPos
== -1 && nStart
!= 0)
20119 // Try match case sensitive, but from start
20120 nPos
= starts_with(m_pTreeModel
, aStartText
, 0, nZeroRow
, true);
20126 OUString aText
= get_text_including_mru(nPos
);
20127 if (aText
!= aStartText
)
20129 SolarMutexGuard aGuard
;
20130 set_active_including_mru(nPos
, true);
20132 select_entry_region(aText
.getLength(), aStartText
.getLength());
20134 enable_notify_events();
20137 static void signalEntryInsertText(GtkEntry
* pEntry
, const gchar
* pNewText
, gint nNewTextLength
,
20138 gint
* position
, gpointer widget
)
20140 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20141 SolarMutexGuard aGuard
;
20142 pThis
->signal_entry_insert_text(pEntry
, pNewText
, nNewTextLength
, position
);
20145 void signal_entry_insert_text(GtkEntry
* pEntry
, const gchar
* pNewText
, gint nNewTextLength
, gint
* position
)
20147 // first filter inserted text
20148 if (m_aEntryInsertTextHdl
.IsSet())
20150 OUString
sText(pNewText
, nNewTextLength
, RTL_TEXTENCODING_UTF8
);
20151 const bool bContinue
= m_aEntryInsertTextHdl
.Call(sText
);
20152 if (bContinue
&& !sText
.isEmpty())
20154 OString
sFinalText(OUStringToOString(sText
, RTL_TEXTENCODING_UTF8
));
20155 g_signal_handlers_block_by_func(pEntry
, reinterpret_cast<gpointer
>(signalEntryInsertText
), this);
20156 gtk_editable_insert_text(GTK_EDITABLE(pEntry
), sFinalText
.getStr(), sFinalText
.getLength(), position
);
20157 g_signal_handlers_unblock_by_func(pEntry
, reinterpret_cast<gpointer
>(signalEntryInsertText
), this);
20159 g_signal_stop_emission_by_name(pEntry
, "insert-text");
20161 if (m_bAutoComplete
)
20163 // now check for autocompletes
20164 if (m_nAutoCompleteIdleId
)
20165 g_source_remove(m_nAutoCompleteIdleId
);
20166 m_nAutoCompleteIdleId
= g_idle_add(idleAutoComplete
, this);
20170 static void signalChanged(GtkEntry
*, gpointer widget
)
20172 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20173 SolarMutexGuard aGuard
;
20174 pThis
->fire_signal_changed();
20177 void fire_signal_changed()
20180 m_bChangedByMenu
= false;
20183 static void signalPopupToggled(GtkToggleButton
* /*pToggleButton*/, gpointer widget
)
20185 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20186 pThis
->signal_popup_toggled();
20189 int get_popup_height(gint
& rPopupWidth
)
20191 const StyleSettings
& rSettings
= Application::GetSettings().GetStyleSettings();
20193 int nMaxRows
= rSettings
.GetListBoxMaximumLineCount();
20194 bool bAddScrollWidth
= false;
20195 int nRows
= get_count_including_mru();
20196 if (nMaxRows
< nRows
)
20199 bAddScrollWidth
= true;
20202 GList
* pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
20203 gint nRowHeight
= get_height_row(m_pTreeView
, pColumns
);
20204 g_list_free(pColumns
);
20206 gint nSeparatorHeight
= get_height_row_separator(m_pTreeView
);
20207 gint nHeight
= get_height_rows(nRowHeight
, nSeparatorHeight
, nRows
);
20209 // if we're using a custom renderer, limit the height to the height nMaxRows would be
20210 // for a normal renderer, and then round down to how many custom rows fit in that
20212 if (m_nNonCustomLineHeight
!= -1 && nRowHeight
)
20214 gint nNormalHeight
= get_height_rows(m_nNonCustomLineHeight
, nSeparatorHeight
, nMaxRows
);
20215 if (nHeight
> nNormalHeight
)
20217 gint nRowsOnly
= nNormalHeight
- get_height_rows(0, nSeparatorHeight
, nMaxRows
);
20218 gint nCustomRows
= (nRowsOnly
+ (nRowHeight
- 1)) / nRowHeight
;
20219 nHeight
= get_height_rows(nRowHeight
, nSeparatorHeight
, nCustomRows
);
20223 if (bAddScrollWidth
)
20224 rPopupWidth
+= rSettings
.GetScrollBarSize();
20229 void menu_toggled()
20231 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton
)))
20233 if (m_bHoverSelection
)
20235 // turn hover selection back off until mouse is moved again
20236 // *after* menu is shown again
20237 gtk_tree_view_set_hover_selection(m_pTreeView
, false);
20238 m_bHoverSelection
= false;
20241 bool bHadFocus
= gtk_window_has_toplevel_focus(m_pMenuWindow
);
20243 do_ungrab(GTK_WIDGET(m_pMenuWindow
));
20245 gtk_widget_hide(GTK_WIDGET(m_pMenuWindow
));
20247 GdkSurface
* pSurface
= widget_get_surface(GTK_WIDGET(m_pMenuWindow
));
20248 g_object_set_data(G_OBJECT(pSurface
), "g-lo-InstancePopup", GINT_TO_POINTER(false));
20250 // so gdk_window_move_to_rect will work again the next time
20251 gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow
));
20253 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow
), -1, -1);
20255 if (!m_bActivateCalled
)
20256 tree_view_set_cursor(m_nPrePopupCursorPos
);
20258 // undo show_menu tooltip blocking
20259 GtkWidget
* pParent
= widget_get_toplevel(m_pToggleButton
);
20260 GtkSalFrame
* pFrame
= pParent
? GtkSalFrame::getFromWindow(pParent
) : nullptr;
20262 pFrame
->UnblockTooltip();
20266 GdkSurface
* pParentSurface
= pParent
? widget_get_surface(pParent
) : nullptr;
20267 void* pParentIsPopover
= pParentSurface
? g_object_get_data(G_OBJECT(pParentSurface
), "g-lo-InstancePopup") : nullptr;
20268 if (pParentIsPopover
)
20269 do_grab(m_pToggleButton
);
20270 gtk_widget_grab_focus(m_pToggleButton
);
20275 GtkWidget
* pComboBox
= GTK_WIDGET(getContainer());
20277 gint nComboWidth
= gtk_widget_get_allocated_width(pComboBox
);
20278 GtkRequisition size
;
20279 gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow
), nullptr, &size
);
20281 gint nPopupWidth
= size
.width
;
20282 gint nPopupHeight
= get_popup_height(nPopupWidth
);
20283 nPopupWidth
= std::max(nPopupWidth
, nComboWidth
);
20285 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow
), nPopupWidth
, nPopupHeight
);
20287 m_nPrePopupCursorPos
= get_active();
20289 m_bActivateCalled
= false;
20291 // if we are in mru mode always start with the cursor at the top of the menu
20292 if (m_nMaxMRUCount
)
20293 tree_view_set_cursor(0);
20295 GdkRectangle aAnchor
{0, 0, gtk_widget_get_allocated_width(pComboBox
), gtk_widget_get_allocated_height(pComboBox
) };
20296 show_menu(pComboBox
, m_pMenuWindow
, aAnchor
, weld::Placement::Under
, true);
20297 GdkSurface
* pSurface
= widget_get_surface(GTK_WIDGET(m_pMenuWindow
));
20298 g_object_set_data(G_OBJECT(pSurface
), "g-lo-InstancePopup", GINT_TO_POINTER(true));
20302 virtual void signal_popup_toggled() override
20304 m_aQuickSelectionEngine
.Reset();
20308 bool bIsShown
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton
));
20309 if (m_bPopupActive
== bIsShown
)
20312 m_bPopupActive
= bIsShown
;
20313 ComboBox::signal_popup_toggled();
20314 if (!m_bPopupActive
&& m_pEntry
)
20316 disable_notify_events();
20317 //restore focus to the GtkEntry when the popup is gone, which
20318 //is what the vcl case does, to ease the transition a little
20319 gtk_widget_grab_focus(m_pEntry
);
20320 enable_notify_events();
20324 static gboolean
signalEntryFocusIn(GtkWidget
*, GdkEvent
*, gpointer widget
)
20326 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20327 pThis
->signal_entry_focus_in();
20331 void signal_entry_focus_in()
20336 static gboolean
signalEntryFocusOut(GtkWidget
*, GdkEvent
*, gpointer widget
)
20338 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20339 pThis
->signal_entry_focus_out();
20343 void signal_entry_focus_out()
20345 // if we have an untidy selection on losing focus remove the selection
20346 int nStartPos
, nEndPos
;
20347 if (get_entry_selection_bounds(nStartPos
, nEndPos
))
20349 int nMin
= std::min(nStartPos
, nEndPos
);
20350 int nMax
= std::max(nStartPos
, nEndPos
);
20351 if (nMin
!= 0 || nMax
!= get_active_text().getLength())
20352 select_entry_region(0, 0);
20354 signal_focus_out();
20357 static void signalEntryActivate(GtkEntry
*, gpointer widget
)
20359 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20360 pThis
->signal_entry_activate();
20363 void signal_entry_activate()
20365 if (m_aEntryActivateHdl
.IsSet())
20367 SolarMutexGuard aGuard
;
20368 if (m_aEntryActivateHdl
.Call(*this))
20369 g_signal_stop_emission_by_name(m_pEntry
, "activate");
20374 OUString
get(int pos
, int col
) const
20378 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
20381 gtk_tree_model_get(m_pTreeModel
, &iter
, col
, &pStr
, -1);
20382 sRet
= OUString(pStr
, pStr
? strlen(pStr
) : 0, RTL_TEXTENCODING_UTF8
);
20388 void set(int pos
, int col
, std::u16string_view rText
)
20391 if (gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
))
20393 OString
aStr(OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
));
20394 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel
), &iter
, col
, aStr
.getStr(), -1);
20398 int find(std::u16string_view rStr
, int col
, bool bSearchMRUArea
) const
20401 if (!gtk_tree_model_get_iter_first(m_pTreeModel
, &iter
))
20406 if (!bSearchMRUArea
&& m_nMRUCount
)
20408 if (!gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, m_nMRUCount
+ 1))
20410 nRet
+= (m_nMRUCount
+ 1);
20413 OString
aStr(OUStringToOString(rStr
, RTL_TEXTENCODING_UTF8
).getStr());
20417 gtk_tree_model_get(m_pTreeModel
, &iter
, col
, &pStr
, -1);
20418 const bool bEqual
= g_strcmp0(pStr
, aStr
.getStr()) == 0;
20423 } while (gtk_tree_model_iter_next(m_pTreeModel
, &iter
));
20428 bool separator_function(const GtkTreePath
* path
)
20430 return ::separator_function(path
, m_aSeparatorRows
);
20433 bool separator_function(int pos
)
20435 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
20436 bool bRet
= separator_function(path
);
20437 gtk_tree_path_free(path
);
20441 static gboolean
separatorFunction(GtkTreeModel
* pTreeModel
, GtkTreeIter
* pIter
, gpointer widget
)
20443 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20444 GtkTreePath
* path
= gtk_tree_model_get_path(pTreeModel
, pIter
);
20445 bool bRet
= pThis
->separator_function(path
);
20446 gtk_tree_path_free(path
);
20450 // https://gitlab.gnome.org/GNOME/gtk/issues/310
20452 // in the absence of a built-in solution
20453 // a) support typeahead for the case where there is no entry widget, typing ahead
20454 // into the button itself will select via the vcl selection engine, a matching
20456 static gboolean
signalKeyPress(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
20458 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20459 return pThis
->signal_key_press(pEvent
);
20462 // tdf#131076 we want return in a ComboBox to act like return in a
20463 // GtkEntry and activate the default dialog/assistant button
20464 bool combobox_activate()
20466 GtkWidget
*pComboBox
= GTK_WIDGET(m_pToggleButton
);
20467 GtkWidget
*pToplevel
= widget_get_toplevel(pComboBox
);
20468 GtkWindow
*pWindow
= GTK_WINDOW(pToplevel
);
20471 if (!GTK_IS_DIALOG(pWindow
) && !GTK_IS_ASSISTANT(pWindow
))
20473 bool bDone
= false;
20474 GtkWidget
*pDefaultWidget
= gtk_window_get_default_widget(pWindow
);
20475 if (pDefaultWidget
&& pDefaultWidget
!= m_pToggleButton
&& gtk_widget_get_sensitive(pDefaultWidget
))
20476 bDone
= gtk_widget_activate(pDefaultWidget
);
20480 static gboolean
signalEntryKeyPress(GtkEntry
* pEntry
, GdkEventKey
* pEvent
, gpointer widget
)
20482 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20483 LocalizeDecimalSeparator(pEvent
->keyval
);
20484 if (signalEntryInsertSpecialCharKeyPress(pEntry
, pEvent
, nullptr))
20486 return pThis
->signal_entry_key_press(pEvent
);
20489 bool signal_entry_key_press(const GdkEventKey
* pEvent
)
20491 KeyEvent
aKEvt(GtkToVcl(*pEvent
));
20493 vcl::KeyCode aKeyCode
= aKEvt
.GetKeyCode();
20495 bool bDone
= false;
20497 auto nCode
= aKeyCode
.GetCode();
20502 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
20505 int nCount
= get_count_including_mru();
20506 int nActive
= get_active_including_mru() + 1;
20507 while (nActive
< nCount
&& separator_function(nActive
))
20509 if (nActive
< nCount
)
20510 set_active_including_mru(nActive
, true);
20513 else if (nKeyMod
== KEY_MOD2
&& !m_bPopupActive
)
20515 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), true);
20522 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
20525 int nStartBound
= m_bPopupActive
|| !m_nMRUCount
? 0 : (m_nMRUCount
+ 1);
20526 int nActive
= get_active_including_mru() - 1;
20527 while (nActive
>= nStartBound
&& separator_function(nActive
))
20529 if (nActive
>= nStartBound
)
20530 set_active_including_mru(nActive
, true);
20537 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
20540 int nCount
= get_count_including_mru();
20541 int nStartBound
= m_bPopupActive
|| !m_nMaxMRUCount
? 0 : (m_nMRUCount
+ 1);
20542 int nActive
= nStartBound
;
20543 while (nActive
< nCount
&& separator_function(nActive
))
20545 if (nActive
< nCount
)
20546 set_active_including_mru(nActive
, true);
20553 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
20556 int nActive
= get_count_including_mru() - 1;
20557 int nStartBound
= m_bPopupActive
? 0 : (m_nMRUCount
+ 1);
20558 while (nActive
>= nStartBound
&& separator_function(nActive
))
20560 if (nActive
>= nStartBound
)
20561 set_active_including_mru(nActive
, true);
20573 bool signal_key_press(const GdkEventKey
* pEvent
)
20575 if (m_bHoverSelection
)
20577 // once a key is pressed, turn off hover selection until mouse is
20578 // moved again otherwise when the treeview scrolls it jumps to the
20579 // position under the mouse.
20580 gtk_tree_view_set_hover_selection(m_pTreeView
, false);
20581 m_bHoverSelection
= false;
20584 KeyEvent
aKEvt(GtkToVcl(*pEvent
));
20586 vcl::KeyCode aKeyCode
= aKEvt
.GetKeyCode();
20588 bool bDone
= false;
20590 auto nCode
= aKeyCode
.GetCode();
20603 m_aQuickSelectionEngine
.Reset();
20604 sal_uInt16 nKeyMod
= aKeyCode
.GetModifier();
20605 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
20606 if (nCode
== KEY_RETURN
&& !nKeyMod
&& !m_bPopupActive
)
20607 bDone
= combobox_activate();
20608 else if (nCode
== KEY_UP
&& nKeyMod
== KEY_MOD2
&& m_bPopupActive
)
20610 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
20613 else if (nCode
== KEY_DOWN
&& nKeyMod
== KEY_MOD2
&& !m_bPopupActive
)
20615 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), true);
20622 m_aQuickSelectionEngine
.Reset();
20623 if (m_bPopupActive
)
20625 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
20631 // tdf#131076 let base space toggle menu popup when it's not already visible
20632 if (nCode
== KEY_SPACE
&& !aKeyCode
.GetModifier() && !m_bPopupActive
)
20635 bDone
= m_aQuickSelectionEngine
.HandleKeyEvent(aKEvt
);
20639 if (!bDone
&& !m_pEntry
)
20640 bDone
= signal_entry_key_press(pEvent
);
20645 vcl::StringEntryIdentifier
typeahead_getEntry(int nPos
, OUString
& out_entryText
) const
20647 int nEntryCount(get_count_including_mru());
20648 if (nPos
>= nEntryCount
)
20650 out_entryText
= get_text_including_mru(nPos
);
20652 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
20654 return reinterpret_cast<vcl::StringEntryIdentifier
>(nPos
+ 1);
20657 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry
)
20659 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
20660 return reinterpret_cast<sal_Int64
>(entry
) - 1;
20663 void tree_view_set_cursor(int pos
)
20667 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView
));
20669 gtk_cell_view_set_displayed_row(m_pCellView
, nullptr);
20673 GtkTreePath
* path
= gtk_tree_path_new_from_indices(pos
, -1);
20674 if (gtk_tree_view_get_model(m_pTreeView
))
20675 gtk_tree_view_scroll_to_cell(m_pTreeView
, path
, nullptr, false, 0, 0);
20676 gtk_tree_view_set_cursor(m_pTreeView
, path
, nullptr, false);
20678 gtk_cell_view_set_displayed_row(m_pCellView
, path
);
20679 gtk_tree_path_free(path
);
20683 int tree_view_get_cursor() const
20688 gtk_tree_view_get_cursor(m_pTreeView
, &path
, nullptr);
20692 gint
* indices
= gtk_tree_path_get_indices_with_depth(path
, &depth
);
20693 nRet
= indices
[depth
-1];
20694 gtk_tree_path_free(path
);
20700 int get_selected_entry() const
20702 if (m_bPopupActive
)
20703 return tree_view_get_cursor();
20705 return get_active_including_mru();
20708 void set_typeahead_selected_entry(int nSelect
)
20710 if (m_bPopupActive
)
20711 tree_view_set_cursor(nSelect
);
20713 set_active_including_mru(nSelect
, true);
20716 virtual vcl::StringEntryIdentifier
CurrentEntry(OUString
& out_entryText
) const override
20718 int nCurrentPos
= get_selected_entry();
20719 return typeahead_getEntry((nCurrentPos
== -1) ? 0 : nCurrentPos
, out_entryText
);
20722 virtual vcl::StringEntryIdentifier
NextEntry(vcl::StringEntryIdentifier currentEntry
, OUString
& out_entryText
) const override
20724 int nNextPos
= typeahead_getEntryPos(currentEntry
) + 1;
20725 return typeahead_getEntry(nNextPos
, out_entryText
);
20728 virtual void SelectEntry(vcl::StringEntryIdentifier entry
) override
20730 int nSelect
= typeahead_getEntryPos(entry
);
20731 if (nSelect
== get_selected_entry())
20733 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
20734 // to select the given entry by typing its starting letters. No need to act.
20739 int nCount
= get_count_including_mru();
20740 if (nSelect
>= nCount
)
20741 nSelect
= nCount
? nCount
-1 : -1;
20743 set_typeahead_selected_entry(nSelect
);
20746 static void signalGrabBroken(GtkWidget
*, GdkEventGrabBroken
*pEvent
, gpointer widget
)
20748 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20749 pThis
->grab_broken(pEvent
);
20752 void grab_broken(const GdkEventGrabBroken
*event
)
20754 if (event
->grab_window
== nullptr)
20756 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
20758 else if (!g_object_get_data(G_OBJECT(event
->grab_window
), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
20760 //try and regrab, so when we lose the grab to the menu of the color palette
20761 //combobox we regain it so the color palette doesn't itself disappear on next
20762 //click on the color palette combobox
20763 do_grab(GTK_WIDGET(m_pMenuWindow
));
20767 static gboolean
signalButtonPress(GtkWidget
*, GdkEventButton
* pEvent
, gpointer widget
)
20769 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20770 return pThis
->button_press(pEvent
);
20773 bool button_press(GdkEventButton
* pEvent
)
20775 //we want to pop down if the button was pressed outside our popup
20776 if (button_event_is_outside(GTK_WIDGET(m_pMenuWindow
), pEvent
))
20777 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
20781 static gboolean
signalMotion(GtkWidget
*, GdkEventMotion
*, gpointer widget
)
20783 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20784 pThis
->signal_motion();
20788 void signal_motion()
20790 // if hover-selection was disabled after pressing a key, then turn it back on again
20791 if (!m_bHoverSelection
&& !m_bMouseInOverlayButton
)
20793 gtk_tree_view_set_hover_selection(m_pTreeView
, true);
20794 m_bHoverSelection
= true;
20798 static void signalRowActivated(GtkTreeView
*, GtkTreePath
*, GtkTreeViewColumn
*, gpointer widget
)
20800 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20801 pThis
->handle_row_activated();
20804 void handle_row_activated()
20806 m_bActivateCalled
= true;
20807 m_bChangedByMenu
= true;
20808 disable_notify_events();
20809 int nActive
= get_active();
20811 gtk_entry_set_text(GTK_ENTRY(m_pEntry
), OUStringToOString(get_text(nActive
), RTL_TEXTENCODING_UTF8
).getStr());
20813 tree_view_set_cursor(nActive
);
20814 enable_notify_events();
20815 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton
), false);
20816 fire_signal_changed();
20822 disable_notify_events();
20823 gtk_tree_view_set_row_separator_func(m_pTreeView
, nullptr, nullptr, nullptr);
20824 m_aSeparatorRows
.clear();
20825 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel
));
20827 enable_notify_events();
20830 virtual int get_max_mru_count() const override
20832 return m_nMaxMRUCount
;
20835 virtual void set_max_mru_count(int nMaxMRUCount
) override
20837 m_nMaxMRUCount
= nMaxMRUCount
;
20843 int nMRUCount
= m_nMRUCount
;
20845 if (m_nMaxMRUCount
)
20847 OUString sActiveText
= get_active_text();
20848 OUString sActiveId
= get_active_id();
20849 insert_including_mru(0, sActiveText
, &sActiveId
, nullptr, nullptr);
20852 for (int i
= 1; i
< m_nMRUCount
- 1; ++i
)
20854 if (get_text_including_mru(i
) == sActiveText
)
20856 remove_including_mru(i
);
20863 while (m_nMRUCount
> m_nMaxMRUCount
)
20865 remove_including_mru(m_nMRUCount
- 1);
20869 if (m_nMRUCount
&& !nMRUCount
)
20870 insert_separator_including_mru(m_nMRUCount
, "separator");
20871 else if (!m_nMRUCount
&& nMRUCount
)
20872 remove_including_mru(m_nMRUCount
); // remove separator
20875 int get_count_including_mru() const
20877 return gtk_tree_model_iter_n_children(m_pTreeModel
, nullptr);
20880 int get_active_including_mru() const
20882 return tree_view_get_cursor();
20885 void set_active_including_mru(int pos
, bool bInteractive
)
20887 disable_notify_events();
20889 tree_view_set_cursor(pos
);
20894 gtk_entry_set_text(GTK_ENTRY(m_pEntry
), OUStringToOString(get_text_including_mru(pos
), RTL_TEXTENCODING_UTF8
).getStr());
20896 gtk_entry_set_text(GTK_ENTRY(m_pEntry
), "");
20899 m_bChangedByMenu
= false;
20900 enable_notify_events();
20902 if (bInteractive
&& !m_bPopupActive
)
20906 int find_text_including_mru(std::u16string_view rStr
, bool bSearchMRU
) const
20908 return find(rStr
, m_nTextCol
, bSearchMRU
);
20911 int find_id_including_mru(std::u16string_view rId
, bool bSearchMRU
) const
20913 return find(rId
, m_nIdCol
, bSearchMRU
);
20916 OUString
get_text_including_mru(int pos
) const
20918 return get(pos
, m_nTextCol
);
20921 OUString
get_id_including_mru(int pos
) const
20923 return get(pos
, m_nIdCol
);
20926 void set_id_including_mru(int pos
, std::u16string_view rId
)
20928 set(pos
, m_nIdCol
, rId
);
20931 void remove_including_mru(int pos
)
20933 disable_notify_events();
20935 gtk_tree_model_iter_nth_child(m_pTreeModel
, &iter
, nullptr, pos
);
20936 if (!m_aSeparatorRows
.empty())
20938 bool bFound
= false;
20940 GtkTreePath
* pPath
= gtk_tree_path_new_from_indices(pos
, -1);
20942 for (auto aIter
= m_aSeparatorRows
.begin(); aIter
!= m_aSeparatorRows
.end(); ++aIter
)
20944 GtkTreePath
* seppath
= gtk_tree_row_reference_get_path(aIter
->get());
20947 if (gtk_tree_path_compare(pPath
, seppath
) == 0)
20949 gtk_tree_path_free(seppath
);
20953 m_aSeparatorRows
.erase(aIter
);
20958 gtk_tree_path_free(pPath
);
20960 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel
), &iter
);
20961 enable_notify_events();
20964 void insert_separator_including_mru(int pos
, const OUString
& rId
)
20966 disable_notify_events();
20968 if (!gtk_tree_view_get_row_separator_func(m_pTreeView
))
20969 gtk_tree_view_set_row_separator_func(m_pTreeView
, separatorFunction
, this, nullptr);
20970 insert_row(GTK_LIST_STORE(m_pTreeModel
), iter
, pos
, &rId
, u
"", nullptr, nullptr);
20971 GtkTreePath
* pPath
= gtk_tree_path_new_from_indices(pos
, -1);
20972 m_aSeparatorRows
.emplace_back(gtk_tree_row_reference_new(m_pTreeModel
, pPath
));
20973 gtk_tree_path_free(pPath
);
20974 enable_notify_events();
20977 void insert_including_mru(int pos
, std::u16string_view rText
, const OUString
* pId
, const OUString
* pIconName
, const VirtualDevice
* pImageSurface
)
20979 disable_notify_events();
20981 insert_row(GTK_LIST_STORE(m_pTreeModel
), iter
, pos
, pId
, rText
, pIconName
, pImageSurface
);
20982 enable_notify_events();
20985 static gboolean
signalGetChildPosition(GtkOverlay
*, GtkWidget
*, GdkRectangle
* pAllocation
, gpointer widget
)
20987 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
20988 return pThis
->signal_get_child_position(pAllocation
);
20991 bool signal_get_child_position(GdkRectangle
* pAllocation
)
20993 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton
)))
20995 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView
)))
20997 int nRow
= find_id_including_mru(m_sMenuButtonRow
, true);
21001 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton
), &pAllocation
->width
, nullptr);
21003 GtkTreePath
* pPath
= gtk_tree_path_new_from_indices(nRow
, -1);
21004 GList
* pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
21005 tools::Rectangle aRect
= get_row_area(m_pTreeView
, pColumns
, pPath
);
21006 gtk_tree_path_free(pPath
);
21007 g_list_free(pColumns
);
21009 pAllocation
->x
= aRect
.Right() - pAllocation
->width
;
21010 pAllocation
->y
= aRect
.Top();
21011 pAllocation
->height
= aRect
.GetHeight();
21016 static gboolean
signalOverlayButtonCrossing(GtkWidget
*, GdkEventCrossing
* pEvent
, gpointer widget
)
21018 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
21019 pThis
->signal_overlay_button_crossing(pEvent
->type
== GDK_ENTER_NOTIFY
);
21023 void signal_overlay_button_crossing(bool bEnter
)
21025 m_bMouseInOverlayButton
= bEnter
;
21029 if (m_bHoverSelection
)
21031 // once toggled button is pressed, turn off hover selection until
21032 // mouse leaves the overlay button
21033 gtk_tree_view_set_hover_selection(m_pTreeView
, false);
21034 m_bHoverSelection
= false;
21036 int nRow
= find_id_including_mru(m_sMenuButtonRow
, true);
21037 assert(nRow
!= -1);
21038 tree_view_set_cursor(nRow
); // select the buttons row
21041 void signal_combo_mnemonic_activate()
21044 gtk_widget_grab_focus(m_pEntry
);
21046 gtk_widget_grab_focus(m_pToggleButton
);
21049 static gboolean
signalComboMnemonicActivate(GtkWidget
*, gboolean
, gpointer widget
)
21051 GtkInstanceComboBox
* pThis
= static_cast<GtkInstanceComboBox
*>(widget
);
21052 pThis
->signal_combo_mnemonic_activate();
21056 int include_mru(int pos
)
21058 if (m_nMRUCount
&& pos
!= -1)
21059 pos
+= (m_nMRUCount
+ 1);
21064 GtkInstanceComboBox(GtkBuilder
* pComboBuilder
, GtkComboBox
* pComboBox
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
21065 : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder
, "box")), pBuilder
, bTakeOwnership
)
21066 , m_pComboBuilder(pComboBuilder
)
21067 , m_pComboBox(pComboBox
)
21068 , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder
, "overlay")))
21069 , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder
, "treeview")))
21070 , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder
, "overlaybutton")))
21071 , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder
, "popup")))
21072 , m_pTreeModel(gtk_combo_box_get_model(pComboBox
))
21073 , m_pButtonTextRenderer(nullptr)
21074 , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder
, "button")))
21075 , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder
, "entry")))
21076 , m_pCellView(nullptr)
21077 , m_aCustomFont(m_pWidget
)
21078 , m_aQuickSelectionEngine(*this)
21079 , m_bHoverSelection(false)
21080 , m_bMouseInOverlayButton(false)
21081 , m_bPopupActive(false)
21082 , m_bAutoComplete(false)
21083 , m_bAutoCompleteCaseSensitive(false)
21084 , m_bChangedByMenu(false)
21085 , m_bCustomRenderer(false)
21086 , m_bActivateCalled(false)
21087 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox
))
21088 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox
))
21089 , m_nToggleFocusInSignalId(0)
21090 , m_nToggleFocusOutSignalId(0)
21091 , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView
, "row-activated", G_CALLBACK(signalRowActivated
), this))
21092 , m_nChangedSignalId(g_signal_connect(m_pEntry
, "changed", G_CALLBACK(signalChanged
), this))
21093 , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton
, "toggled", G_CALLBACK(signalPopupToggled
), this))
21094 , m_nAutoCompleteIdleId(0)
21095 , m_nNonCustomLineHeight(-1)
21096 , m_nPrePopupCursorPos(-1)
21098 , m_nMaxMRUCount(0)
21100 int nActive
= gtk_combo_box_get_active(m_pComboBox
);
21102 if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox
)), "small-button"))
21103 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button");
21105 insertAsParent(GTK_WIDGET(m_pComboBox
), GTK_WIDGET(getContainer()));
21106 gtk_widget_set_visible(GTK_WIDGET(m_pComboBox
), false);
21107 gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox
), true);
21109 gtk_tree_view_set_model(m_pTreeView
, m_pTreeModel
);
21110 /* tdf#136455 gtk_combo_box_set_model with a null Model should be good
21111 enough. But in practice, while the ComboBox model is unset, GTK
21112 doesn't unset the ComboBox menus model, so that remains listening to
21113 additions to the ListStore and slowing things down massively.
21114 Using a new model does reset the menu to listen to that unused one instead */
21115 gtk_combo_box_set_model(m_pComboBox
, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING
, G_TYPE_STRING
)));
21117 GtkTreeViewColumn
* pCol
= gtk_tree_view_column_new();
21118 gtk_tree_view_append_column(m_pTreeView
, pCol
);
21120 bool bPixbufUsedSurface
= gtk_tree_model_get_n_columns(m_pTreeModel
) == 4;
21122 GList
* cells
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox
));
21123 // move the cell renderers from the combobox to the replacement treeview
21124 m_pMenuTextRenderer
= static_cast<GtkCellRenderer
*>(cells
->data
);
21125 for (GList
* pRenderer
= g_list_first(cells
); pRenderer
; pRenderer
= g_list_next(pRenderer
))
21127 GtkCellRenderer
* pCellRenderer
= GTK_CELL_RENDERER(pRenderer
->data
);
21128 bool bTextRenderer
= pCellRenderer
== m_pMenuTextRenderer
;
21129 gtk_tree_view_column_pack_end(pCol
, pCellRenderer
, bTextRenderer
);
21130 if (!bTextRenderer
)
21132 if (bPixbufUsedSurface
)
21133 gtk_tree_view_column_set_attributes(pCol
, pCellRenderer
, "surface", 3, nullptr);
21135 gtk_tree_view_column_set_attributes(pCol
, pCellRenderer
, "pixbuf", 2, nullptr);
21139 gtk_tree_view_column_set_attributes(pCol
, m_pMenuTextRenderer
, "text", m_nTextCol
, nullptr);
21141 if (gtk_combo_box_get_has_entry(m_pComboBox
))
21143 m_bAutoComplete
= true;
21144 m_nEntryInsertTextSignalId
= g_signal_connect(m_pEntry
, "insert-text", G_CALLBACK(signalEntryInsertText
), this);
21145 m_nEntryActivateSignalId
= g_signal_connect(m_pEntry
, "activate", G_CALLBACK(signalEntryActivate
), this);
21146 m_nEntryFocusInSignalId
= g_signal_connect(m_pEntry
, "focus-in-event", G_CALLBACK(signalEntryFocusIn
), this);
21147 m_nEntryFocusOutSignalId
= g_signal_connect(m_pEntry
, "focus-out-event", G_CALLBACK(signalEntryFocusOut
), this);
21148 m_nEntryKeyPressEventSignalId
= g_signal_connect(m_pEntry
, "key-press-event", G_CALLBACK(signalEntryKeyPress
), this);
21149 m_nKeyPressEventSignalId
= 0;
21153 gtk_widget_set_visible(m_pEntry
, false);
21154 m_pEntry
= nullptr;
21156 GtkWidget
* pArrow
= GTK_WIDGET(gtk_builder_get_object(pComboBuilder
, "arrow"));
21157 gtk_container_child_set(getContainer(), m_pToggleButton
, "expand", true, nullptr);
21159 auto m_pCellArea
= gtk_cell_area_box_new();
21160 m_pCellView
= GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea
, nullptr));
21161 gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView
), true);
21162 GtkBox
* pBox
= GTK_BOX(gtk_widget_get_parent(pArrow
));
21164 gint
nImageSpacing(2);
21165 GtkStyleContext
*pContext
= gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton
));
21166 gtk_style_context_get_style(pContext
, "image-spacing", &nImageSpacing
, nullptr);
21167 gtk_box_set_spacing(pBox
, nImageSpacing
);
21169 gtk_box_pack_start(pBox
, GTK_WIDGET(m_pCellView
), false, true, 0);
21171 gtk_cell_view_set_fit_model(m_pCellView
, true);
21172 gtk_cell_view_set_model(m_pCellView
, m_pTreeModel
);
21174 m_pButtonTextRenderer
= gtk_cell_renderer_text_new();
21175 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView
), m_pButtonTextRenderer
, true);
21176 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView
), m_pButtonTextRenderer
, "text", m_nTextCol
, nullptr);
21177 if (g_list_length(cells
) > 1)
21179 GtkCellRenderer
* pCellRenderer
= gtk_cell_renderer_pixbuf_new();
21180 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView
), pCellRenderer
, false);
21181 if (bPixbufUsedSurface
)
21182 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView
), pCellRenderer
, "surface", 3, nullptr);
21184 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView
), pCellRenderer
, "pixbuf", 2, nullptr);
21187 gtk_widget_show_all(GTK_WIDGET(m_pCellView
));
21189 m_nEntryInsertTextSignalId
= 0;
21190 m_nEntryActivateSignalId
= 0;
21191 m_nEntryFocusInSignalId
= 0;
21192 m_nEntryFocusOutSignalId
= 0;
21193 m_nEntryKeyPressEventSignalId
= 0;
21194 m_nKeyPressEventSignalId
= g_signal_connect(m_pToggleButton
, "key-press-event", G_CALLBACK(signalKeyPress
), this);
21197 g_list_free(cells
);
21200 tree_view_set_cursor(nActive
);
21202 g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate
), this);
21204 g_signal_connect(m_pMenuWindow
, "grab-broken-event", G_CALLBACK(signalGrabBroken
), this);
21205 g_signal_connect(m_pMenuWindow
, "button-press-event", G_CALLBACK(signalButtonPress
), this);
21206 g_signal_connect(m_pMenuWindow
, "motion-notify-event", G_CALLBACK(signalMotion
), this);
21207 // support typeahead for the menu itself, typing into the menu will
21208 // select via the vcl selection engine, a matching entry.
21209 g_signal_connect(m_pMenuWindow
, "key-press-event", G_CALLBACK(signalKeyPress
), this);
21211 g_signal_connect(m_pOverlay
, "get-child-position", G_CALLBACK(signalGetChildPosition
), this);
21212 gtk_overlay_add_overlay(m_pOverlay
, GTK_WIDGET(m_pOverlayButton
));
21213 g_signal_connect(m_pOverlayButton
, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing
), this);
21214 g_signal_connect(m_pOverlayButton
, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing
), this);
21217 virtual int get_active() const override
21219 int nActive
= get_active_including_mru();
21225 if (nActive
< m_nMRUCount
)
21226 nActive
= find_text(get_text_including_mru(nActive
));
21228 nActive
-= (m_nMRUCount
+ 1);
21234 virtual OUString
get_active_id() const override
21236 int nActive
= get_active();
21237 return nActive
!= -1 ? get_id(nActive
) : OUString();
21240 virtual void set_active_id(const OUString
& rStr
) override
21242 set_active(find_id(rStr
));
21243 m_bChangedByMenu
= false;
21246 virtual void set_size_request(int nWidth
, int nHeight
) override
21248 if (m_pButtonTextRenderer
)
21250 // tweak the cell render to get a narrower size to stick
21253 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
21254 // the popup menu render them in full, in the interim ellipse both of them
21255 g_object_set(G_OBJECT(m_pButtonTextRenderer
), "ellipsize", PANGO_ELLIPSIZE_MIDDLE
, nullptr);
21257 // to find out how much of the width of the combobox belongs to the cell, set
21258 // the cell and widget to the min cell width and see what the difference is
21260 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer
, m_pWidget
, &min
, nullptr);
21261 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer
, min
, -1);
21262 gtk_widget_set_size_request(m_pWidget
, min
, -1);
21263 int nNonCellWidth
= get_preferred_size().Width() - min
;
21265 int nCellWidth
= nWidth
- nNonCellWidth
;
21266 if (nCellWidth
>= 0)
21268 // now set the cell to the max width which it can be within the
21269 // requested widget width
21270 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer
, nWidth
- nNonCellWidth
, -1);
21275 g_object_set(G_OBJECT(m_pButtonTextRenderer
), "ellipsize", PANGO_ELLIPSIZE_NONE
, nullptr);
21276 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer
, -1, -1);
21280 gtk_widget_set_size_request(m_pWidget
, nWidth
, nHeight
);
21283 virtual void set_active(int pos
) override
21285 set_active_including_mru(include_mru(pos
), false);
21288 virtual OUString
get_active_text() const override
21292 const gchar
* pText
= gtk_entry_get_text(GTK_ENTRY(m_pEntry
));
21293 return OUString(pText
, pText
? strlen(pText
) : 0, RTL_TEXTENCODING_UTF8
);
21296 int nActive
= get_active();
21300 return get_text(nActive
);
21303 virtual OUString
get_text(int pos
) const override
21306 pos
+= (m_nMRUCount
+ 1);
21307 return get_text_including_mru(pos
);
21310 virtual OUString
get_id(int pos
) const override
21313 pos
+= (m_nMRUCount
+ 1);
21314 return get_id_including_mru(pos
);
21317 virtual void set_id(int pos
, const OUString
& rId
) override
21320 pos
+= (m_nMRUCount
+ 1);
21321 set_id_including_mru(pos
, rId
);
21324 virtual void insert_vector(const std::vector
<weld::ComboBoxEntry
>& rItems
, bool bKeepExisting
) override
21328 int nInsertionPoint
;
21329 if (!bKeepExisting
)
21332 nInsertionPoint
= 0;
21335 nInsertionPoint
= get_count();
21338 // tdf#125241 inserting backwards is faster
21339 for (auto aI
= rItems
.rbegin(); aI
!= rItems
.rend(); ++aI
)
21341 const auto& rItem
= *aI
;
21342 insert_row(GTK_LIST_STORE(m_pTreeModel
), iter
, nInsertionPoint
, rItem
.sId
.isEmpty() ? nullptr : &rItem
.sId
,
21343 rItem
.sString
, rItem
.sImage
.isEmpty() ? nullptr : &rItem
.sImage
, nullptr);
21349 virtual void remove(int pos
) override
21352 pos
+= (m_nMRUCount
+ 1);
21353 remove_including_mru(pos
);
21356 virtual void insert(int pos
, const OUString
& rText
, const OUString
* pId
, const OUString
* pIconName
, VirtualDevice
* pImageSurface
) override
21358 insert_including_mru(include_mru(pos
), rText
, pId
, pIconName
, pImageSurface
);
21361 virtual void insert_separator(int pos
, const OUString
& rId
) override
21363 pos
= pos
== -1 ? get_count() : pos
;
21365 pos
+= (m_nMRUCount
+ 1);
21366 insert_separator_including_mru(pos
, rId
);
21369 virtual int get_count() const override
21371 int nCount
= get_count_including_mru();
21373 nCount
-= (m_nMRUCount
+ 1);
21377 virtual int find_text(const OUString
& rStr
) const override
21379 int nPos
= find_text_including_mru(rStr
, false);
21380 if (nPos
!= -1 && m_nMRUCount
)
21381 nPos
-= (m_nMRUCount
+ 1);
21385 virtual int find_id(const OUString
& rId
) const override
21387 int nPos
= find_id_including_mru(rId
, false);
21388 if (nPos
!= -1 && m_nMRUCount
)
21389 nPos
-= (m_nMRUCount
+ 1);
21393 virtual void clear() override
21398 virtual void make_sorted() override
21400 m_xSorter
.reset(new comphelper::string::NaturalStringSorter(
21401 ::comphelper::getProcessComponentContext(),
21402 Application::GetSettings().GetUILanguageTag().getLocale()));
21403 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
21404 gtk_tree_sortable_set_sort_column_id(pSortable
, m_nTextCol
, GTK_SORT_ASCENDING
);
21405 gtk_tree_sortable_set_sort_func(pSortable
, m_nTextCol
, default_sort_func
, m_xSorter
.get(), nullptr);
21408 virtual bool has_entry() const override
21410 return gtk_combo_box_get_has_entry(m_pComboBox
);
21413 virtual void set_entry_message_type(weld::EntryMessageType eType
) override
21416 ::set_entry_message_type(GTK_ENTRY(m_pEntry
), eType
);
21419 virtual void set_entry_text(const OUString
& rText
) override
21422 disable_notify_events();
21423 gtk_entry_set_text(GTK_ENTRY(m_pEntry
), OUStringToOString(rText
, RTL_TEXTENCODING_UTF8
).getStr());
21424 enable_notify_events();
21427 virtual void set_entry_width_chars(int nChars
) override
21430 disable_notify_events();
21431 gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry
), nChars
);
21432 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry
), nChars
);
21433 enable_notify_events();
21436 virtual void set_entry_max_length(int nChars
) override
21439 disable_notify_events();
21440 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry
), nChars
);
21441 enable_notify_events();
21444 virtual void select_entry_region(int nStartPos
, int nEndPos
) override
21447 disable_notify_events();
21448 gtk_editable_select_region(GTK_EDITABLE(m_pEntry
), nStartPos
, nEndPos
);
21449 enable_notify_events();
21452 virtual bool get_entry_selection_bounds(int& rStartPos
, int &rEndPos
) override
21455 return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry
), &rStartPos
, &rEndPos
);
21458 virtual void set_entry_completion(bool bEnable
, bool bCaseSensitive
) override
21460 m_bAutoComplete
= bEnable
;
21461 m_bAutoCompleteCaseSensitive
= bCaseSensitive
;
21464 virtual void set_entry_placeholder_text(const OUString
& rText
) override
21467 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry
), rText
.toUtf8().getStr());
21470 virtual void set_entry_editable(bool bEditable
) override
21473 gtk_editable_set_editable(GTK_EDITABLE(m_pEntry
), bEditable
);
21476 virtual void cut_entry_clipboard() override
21479 gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry
));
21482 virtual void copy_entry_clipboard() override
21485 gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry
));
21488 virtual void paste_entry_clipboard() override
21491 gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry
));
21494 virtual void set_font(const vcl::Font
& rFont
) override
21496 m_aCustomFont
.use_custom_font(&rFont
, u
"box#combobox");
21499 virtual vcl::Font
get_font() override
21501 if (const vcl::Font
* pFont
= m_aCustomFont
.get_custom_font())
21503 return GtkInstanceWidget::get_font();
21506 virtual void set_entry_font(const vcl::Font
& rFont
) override
21508 m_xEntryFont
= rFont
;
21510 PangoAttrList
* pOrigList
= gtk_entry_get_attributes(GTK_ENTRY(m_pEntry
));
21511 PangoAttrList
* pAttrList
= pOrigList
? pango_attr_list_copy(pOrigList
) : pango_attr_list_new();
21512 update_attr_list(pAttrList
, rFont
);
21513 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry
), pAttrList
);
21514 pango_attr_list_unref(pAttrList
);
21517 virtual vcl::Font
get_entry_font() override
21520 return *m_xEntryFont
;
21522 PangoContext
* pContext
= gtk_widget_get_pango_context(m_pEntry
);
21523 return pango_to_vcl(pango_context_get_font_description(pContext
),
21524 Application::GetSettings().GetUILanguageTag().getLocale());
21527 virtual void disable_notify_events() override
21531 g_signal_handler_block(m_pEntry
, m_nEntryInsertTextSignalId
);
21532 g_signal_handler_block(m_pEntry
, m_nEntryActivateSignalId
);
21533 g_signal_handler_block(m_pEntry
, m_nEntryFocusInSignalId
);
21534 g_signal_handler_block(m_pEntry
, m_nEntryFocusOutSignalId
);
21535 g_signal_handler_block(m_pEntry
, m_nEntryKeyPressEventSignalId
);
21536 g_signal_handler_block(m_pEntry
, m_nChangedSignalId
);
21539 g_signal_handler_block(m_pToggleButton
, m_nKeyPressEventSignalId
);
21540 if (m_nToggleFocusInSignalId
)
21541 g_signal_handler_block(m_pToggleButton
, m_nToggleFocusInSignalId
);
21542 if (m_nToggleFocusOutSignalId
)
21543 g_signal_handler_block(m_pToggleButton
, m_nToggleFocusOutSignalId
);
21544 g_signal_handler_block(m_pTreeView
, m_nRowActivatedSignalId
);
21545 g_signal_handler_block(m_pToggleButton
, m_nPopupShownSignalId
);
21546 GtkInstanceContainer::disable_notify_events();
21549 virtual void enable_notify_events() override
21551 GtkInstanceContainer::enable_notify_events();
21552 g_signal_handler_unblock(m_pToggleButton
, m_nPopupShownSignalId
);
21553 g_signal_handler_unblock(m_pTreeView
, m_nRowActivatedSignalId
);
21554 if (m_nToggleFocusInSignalId
)
21555 g_signal_handler_unblock(m_pToggleButton
, m_nToggleFocusInSignalId
);
21556 if (m_nToggleFocusOutSignalId
)
21557 g_signal_handler_unblock(m_pToggleButton
, m_nToggleFocusOutSignalId
);
21560 g_signal_handler_unblock(m_pEntry
, m_nChangedSignalId
);
21561 g_signal_handler_unblock(m_pEntry
, m_nEntryActivateSignalId
);
21562 g_signal_handler_unblock(m_pEntry
, m_nEntryFocusInSignalId
);
21563 g_signal_handler_unblock(m_pEntry
, m_nEntryFocusOutSignalId
);
21564 g_signal_handler_unblock(m_pEntry
, m_nEntryKeyPressEventSignalId
);
21565 g_signal_handler_unblock(m_pEntry
, m_nEntryInsertTextSignalId
);
21568 g_signal_handler_unblock(m_pToggleButton
, m_nKeyPressEventSignalId
);
21571 virtual void freeze() override
21573 disable_notify_events();
21574 bool bIsFirstFreeze
= IsFirstFreeze();
21575 GtkInstanceContainer::freeze();
21576 if (bIsFirstFreeze
)
21578 g_object_ref(m_pTreeModel
);
21579 gtk_tree_view_set_model(m_pTreeView
, nullptr);
21580 g_object_freeze_notify(G_OBJECT(m_pTreeModel
));
21583 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
21584 gtk_tree_sortable_set_sort_column_id(pSortable
, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
, GTK_SORT_ASCENDING
);
21587 enable_notify_events();
21590 virtual void thaw() override
21592 disable_notify_events();
21597 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(m_pTreeModel
);
21598 gtk_tree_sortable_set_sort_column_id(pSortable
, m_nTextCol
, GTK_SORT_ASCENDING
);
21600 g_object_thaw_notify(G_OBJECT(m_pTreeModel
));
21601 gtk_tree_view_set_model(m_pTreeView
, m_pTreeModel
);
21602 g_object_unref(m_pTreeModel
);
21604 GtkInstanceContainer::thaw();
21605 enable_notify_events();
21608 virtual bool get_popup_shown() const override
21610 return m_bPopupActive
;
21613 virtual void connect_focus_in(const Link
<Widget
&, void>& rLink
) override
21615 if (!m_nToggleFocusInSignalId
)
21616 m_nToggleFocusInSignalId
= g_signal_connect_after(m_pToggleButton
, "focus-in-event", G_CALLBACK(signalFocusIn
), this);
21617 GtkInstanceContainer::connect_focus_in(rLink
);
21620 virtual void connect_focus_out(const Link
<Widget
&, void>& rLink
) override
21622 if (!m_nToggleFocusOutSignalId
)
21623 m_nToggleFocusOutSignalId
= g_signal_connect_after(m_pToggleButton
, "focus-out-event", G_CALLBACK(signalFocusOut
), this);
21624 GtkInstanceContainer::connect_focus_out(rLink
);
21627 virtual void grab_focus() override
21632 gtk_widget_grab_focus(m_pEntry
);
21634 gtk_widget_grab_focus(m_pToggleButton
);
21637 virtual bool has_focus() const override
21639 if (m_pEntry
&& gtk_widget_has_focus(m_pEntry
))
21642 if (gtk_widget_has_focus(m_pToggleButton
))
21645 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow
)))
21647 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton
)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView
)))
21651 return GtkInstanceWidget::has_focus();
21654 virtual bool changed_by_direct_pick() const override
21656 return m_bChangedByMenu
;
21659 virtual void set_custom_renderer(bool bOn
) override
21661 if (bOn
== m_bCustomRenderer
)
21663 GList
* pColumns
= gtk_tree_view_get_columns(m_pTreeView
);
21664 // keep the original height around for optimal popup height calculation
21665 m_nNonCustomLineHeight
= bOn
? ::get_height_row(m_pTreeView
, pColumns
) : -1;
21666 GtkTreeViewColumn
* pColumn
= GTK_TREE_VIEW_COLUMN(pColumns
->data
);
21667 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn
));
21670 GtkCellRenderer
*pRenderer
= custom_cell_renderer_new();
21671 GValue value
= G_VALUE_INIT
;
21672 g_value_init(&value
, G_TYPE_POINTER
);
21673 g_value_set_pointer(&value
, static_cast<gpointer
>(this));
21674 g_object_set_property(G_OBJECT(pRenderer
), "instance", &value
);
21675 gtk_tree_view_column_pack_start(pColumn
, pRenderer
, true);
21676 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "text", m_nTextCol
);
21677 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "id", m_nIdCol
);
21681 GtkCellRenderer
*pRenderer
= gtk_cell_renderer_text_new();
21682 gtk_tree_view_column_pack_start(pColumn
, pRenderer
, true);
21683 gtk_tree_view_column_add_attribute(pColumn
, pRenderer
, "text", m_nTextCol
);
21685 g_list_free(pColumns
);
21686 m_bCustomRenderer
= bOn
;
21689 void call_signal_custom_render(VirtualDevice
& rOutput
, const tools::Rectangle
& rRect
, bool bSelected
, const OUString
& rId
)
21691 signal_custom_render(rOutput
, rRect
, bSelected
, rId
);
21694 Size
call_signal_custom_get_size(VirtualDevice
& rOutput
)
21696 return signal_custom_get_size(rOutput
);
21699 VclPtr
<VirtualDevice
> create_render_virtual_device() const override
21701 return create_virtual_device();
21704 virtual void set_item_menu(const OString
& rIdent
, weld::Menu
* pMenu
) override
21706 m_xCustomMenuButtonHelper
.reset();
21707 GtkInstanceMenu
* pPopoverWidget
= dynamic_cast<GtkInstanceMenu
*>(pMenu
);
21708 GtkWidget
* pMenuWidget
= GTK_WIDGET(pPopoverWidget
? pPopoverWidget
->getMenu() : nullptr);
21709 gtk_menu_button_set_popup(m_pOverlayButton
, pMenuWidget
);
21710 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton
), pMenuWidget
!= nullptr);
21711 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton
)); // force location recalc
21713 m_xCustomMenuButtonHelper
.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget
), GTK_TOGGLE_BUTTON(m_pToggleButton
)));
21714 m_sMenuButtonRow
= OUString::fromUtf8(rIdent
);
21717 OUString
get_mru_entries() const override
21719 const sal_Unicode cSep
= ';';
21721 OUStringBuffer aEntries
;
21722 for (sal_Int32 n
= 0; n
< m_nMRUCount
; n
++)
21724 aEntries
.append(get_text_including_mru(n
));
21725 if (n
< m_nMRUCount
- 1)
21726 aEntries
.append(cSep
);
21728 return aEntries
.makeStringAndClear();
21731 virtual void set_mru_entries(const OUString
& rEntries
) override
21733 const sal_Unicode cSep
= ';';
21735 // Remove old MRU entries
21736 for (sal_Int32 n
= m_nMRUCount
; n
;)
21737 remove_including_mru(--n
);
21739 sal_Int32 nMRUCount
= 0;
21740 sal_Int32 nIndex
= 0;
21743 OUString aEntry
= rEntries
.getToken(0, cSep
, nIndex
);
21744 // Accept only existing entries
21745 int nPos
= find_text(aEntry
);
21748 OUString sId
= get_id(nPos
);
21749 insert_including_mru(0, aEntry
, &sId
, nullptr, nullptr);
21753 while (nIndex
>= 0);
21755 if (nMRUCount
&& !m_nMRUCount
)
21756 insert_separator_including_mru(nMRUCount
, "separator");
21757 else if (!nMRUCount
&& m_nMRUCount
)
21758 remove_including_mru(m_nMRUCount
); // remove separator
21760 m_nMRUCount
= nMRUCount
;
21763 int get_menu_button_width() const override
21765 bool bVisible
= gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton
));
21767 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton
), true);
21769 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton
), &nWidth
, nullptr);
21771 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton
), false);
21775 virtual ~GtkInstanceComboBox() override
21777 m_xCustomMenuButtonHelper
.reset();
21779 if (m_nAutoCompleteIdleId
)
21780 g_source_remove(m_nAutoCompleteIdleId
);
21783 g_signal_handler_disconnect(m_pEntry
, m_nChangedSignalId
);
21784 g_signal_handler_disconnect(m_pEntry
, m_nEntryInsertTextSignalId
);
21785 g_signal_handler_disconnect(m_pEntry
, m_nEntryActivateSignalId
);
21786 g_signal_handler_disconnect(m_pEntry
, m_nEntryFocusInSignalId
);
21787 g_signal_handler_disconnect(m_pEntry
, m_nEntryFocusOutSignalId
);
21788 g_signal_handler_disconnect(m_pEntry
, m_nEntryKeyPressEventSignalId
);
21791 g_signal_handler_disconnect(m_pToggleButton
, m_nKeyPressEventSignalId
);
21792 if (m_nToggleFocusInSignalId
)
21793 g_signal_handler_disconnect(m_pToggleButton
, m_nToggleFocusInSignalId
);
21794 if (m_nToggleFocusOutSignalId
)
21795 g_signal_handler_disconnect(m_pToggleButton
, m_nToggleFocusOutSignalId
);
21796 g_signal_handler_disconnect(m_pTreeView
, m_nRowActivatedSignalId
);
21797 g_signal_handler_disconnect(m_pToggleButton
, m_nPopupShownSignalId
);
21799 gtk_combo_box_set_model(m_pComboBox
, m_pTreeModel
);
21800 gtk_tree_view_set_model(m_pTreeView
, nullptr);
21802 // restore original hierarchy in dtor so a new GtkInstanceComboBox will
21803 // result in the same layout each time
21805 DisconnectMouseEvents();
21807 g_object_ref(m_pComboBox
);
21809 GtkContainer
* pContainer
= getContainer();
21811 gtk_container_remove(pContainer
, GTK_WIDGET(m_pComboBox
));
21813 replaceWidget(GTK_WIDGET(pContainer
), GTK_WIDGET(m_pComboBox
));
21815 g_object_unref(m_pComboBox
);
21818 g_object_unref(m_pComboBuilder
);
21826 void custom_cell_renderer_ensure_device(CustomCellRenderer
*cellsurface
, gpointer user_data
)
21828 if (!cellsurface
->device
)
21830 cellsurface
->device
= VclPtr
<VirtualDevice
>::Create();
21831 cellsurface
->device
->SetBackground(COL_TRANSPARENT
);
21832 GtkInstanceWidget
* pWidget
= static_cast<GtkInstanceWidget
*>(user_data
);
21833 // expand the point size of the desired font to the equivalent pixel size
21834 weld::SetPointFont(*cellsurface
->device
, pWidget
->get_font());
21838 Size
custom_cell_renderer_get_size(VirtualDevice
& rDevice
, const OUString
& rCellId
, gpointer user_data
)
21840 GtkInstanceWidget
* pWidget
= static_cast<GtkInstanceWidget
*>(user_data
);
21841 if (GtkInstanceTreeView
* pTreeView
= dynamic_cast<GtkInstanceTreeView
*>(pWidget
))
21842 return pTreeView
->call_signal_custom_get_size(rDevice
, rCellId
);
21843 else if (GtkInstanceComboBox
* pComboBox
= dynamic_cast<GtkInstanceComboBox
*>(pWidget
))
21844 return pComboBox
->call_signal_custom_get_size(rDevice
);
21848 void custom_cell_renderer_render(VirtualDevice
& rDevice
, const tools::Rectangle
& rRect
, bool bSelected
, const OUString
& rCellId
, gpointer user_data
)
21850 GtkInstanceWidget
* pWidget
= static_cast<GtkInstanceWidget
*>(user_data
);
21851 if (GtkInstanceTreeView
* pTreeView
= dynamic_cast<GtkInstanceTreeView
*>(pWidget
))
21852 pTreeView
->call_signal_custom_render(rDevice
, rRect
, bSelected
, rCellId
);
21853 else if (GtkInstanceComboBox
* pComboBox
= dynamic_cast<GtkInstanceComboBox
*>(pWidget
))
21854 pComboBox
->call_signal_custom_render(rDevice
, rRect
, bSelected
, rCellId
);
21859 class GtkInstanceEntryTreeView
: public GtkInstanceContainer
, public virtual weld::EntryTreeView
21862 GtkInstanceEntry
* m_pEntry
;
21863 GtkInstanceTreeView
* m_pTreeView
;
21864 #if !GTK_CHECK_VERSION(4, 0, 0)
21865 gulong m_nKeyPressSignalId
;
21867 gulong m_nEntryInsertTextSignalId
;
21868 guint m_nAutoCompleteIdleId
;
21869 bool m_bAutoCompleteCaseSensitive
;
21870 bool m_bTreeChange
;
21872 #if !GTK_CHECK_VERSION(4, 0, 0)
21873 bool signal_key_press(GdkEventKey
* pEvent
)
21875 if (GtkSalFrame::GetMouseModCode(pEvent
->state
)) // only with no modifiers held
21878 if (pEvent
->keyval
== GDK_KEY_KP_Up
|| pEvent
->keyval
== GDK_KEY_Up
|| pEvent
->keyval
== GDK_KEY_KP_Page_Up
|| pEvent
->keyval
== GDK_KEY_Page_Up
||
21879 pEvent
->keyval
== GDK_KEY_KP_Down
|| pEvent
->keyval
== GDK_KEY_Down
|| pEvent
->keyval
== GDK_KEY_KP_Page_Down
|| pEvent
->keyval
== GDK_KEY_Page_Down
)
21882 disable_notify_events();
21883 GtkWidget
* pWidget
= m_pTreeView
->getWidget();
21884 if (m_pTreeView
->get_selected_index() == -1)
21886 m_pTreeView
->set_cursor(0);
21887 m_pTreeView
->select(0);
21888 m_xEntry
->set_text(m_xTreeView
->get_selected_text());
21892 gtk_widget_grab_focus(pWidget
);
21893 g_signal_emit_by_name(pWidget
, "key-press-event", pEvent
, &ret
);
21894 m_xEntry
->set_text(m_xTreeView
->get_selected_text());
21895 gtk_widget_grab_focus(m_pEntry
->getWidget());
21897 m_xEntry
->select_region(0, -1);
21898 enable_notify_events();
21899 m_bTreeChange
= true;
21900 m_pEntry
->fire_signal_changed();
21901 m_bTreeChange
= false;
21907 static gboolean
signalKeyPress(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
21909 GtkInstanceEntryTreeView
* pThis
= static_cast<GtkInstanceEntryTreeView
*>(widget
);
21910 return pThis
->signal_key_press(pEvent
);
21914 static gboolean
idleAutoComplete(gpointer widget
)
21916 GtkInstanceEntryTreeView
* pThis
= static_cast<GtkInstanceEntryTreeView
*>(widget
);
21917 pThis
->auto_complete();
21921 void auto_complete()
21923 m_nAutoCompleteIdleId
= 0;
21924 OUString aStartText
= get_active_text();
21925 int nStartPos
, nEndPos
;
21926 get_entry_selection_bounds(nStartPos
, nEndPos
);
21927 int nMaxSelection
= std::max(nStartPos
, nEndPos
);
21928 if (nMaxSelection
!= aStartText
.getLength())
21931 disable_notify_events();
21932 int nActive
= get_active();
21933 int nStart
= nActive
;
21938 // Try match case sensitive from current position
21939 int nPos
= m_pTreeView
->starts_with(aStartText
, nStart
, true);
21940 if (nPos
== -1 && nStart
!= 0)
21942 // Try match case insensitive, but from start
21943 nPos
= m_pTreeView
->starts_with(aStartText
, 0, true);
21946 if (!m_bAutoCompleteCaseSensitive
)
21948 // Try match case insensitive from current position
21949 nPos
= m_pTreeView
->starts_with(aStartText
, nStart
, false);
21950 if (nPos
== -1 && nStart
!= 0)
21952 // Try match case insensitive, but from start
21953 nPos
= m_pTreeView
->starts_with(aStartText
, 0, false);
21959 // Try match case sensitive from current position
21960 nPos
= m_pTreeView
->starts_with(aStartText
, nStart
, true);
21961 if (nPos
== -1 && nStart
!= 0)
21963 // Try match case sensitive, but from start
21964 nPos
= m_pTreeView
->starts_with(aStartText
, 0, true);
21970 OUString aText
= get_text(nPos
);
21971 if (aText
!= aStartText
)
21972 set_active_text(aText
);
21973 select_entry_region(aText
.getLength(), aStartText
.getLength());
21975 enable_notify_events();
21978 void signal_entry_insert_text(GtkEntry
*, const gchar
*, gint
, gint
*)
21980 // now check for autocompletes
21981 if (m_nAutoCompleteIdleId
)
21982 g_source_remove(m_nAutoCompleteIdleId
);
21983 m_nAutoCompleteIdleId
= g_idle_add(idleAutoComplete
, this);
21986 static void signalEntryInsertText(GtkEntry
* pEntry
, const gchar
* pNewText
, gint nNewTextLength
,
21987 gint
* position
, gpointer widget
)
21989 GtkInstanceEntryTreeView
* pThis
= static_cast<GtkInstanceEntryTreeView
*>(widget
);
21990 pThis
->signal_entry_insert_text(pEntry
, pNewText
, nNewTextLength
, position
);
21995 #if GTK_CHECK_VERSION(4, 0, 0)
21996 GtkInstanceEntryTreeView(GtkWidget
* pContainer
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
,
21997 std::unique_ptr
<weld::Entry
> xEntry
, std::unique_ptr
<weld::TreeView
> xTreeView
)
21999 GtkInstanceEntryTreeView(GtkContainer
* pContainer
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
,
22000 std::unique_ptr
<weld::Entry
> xEntry
, std::unique_ptr
<weld::TreeView
> xTreeView
)
22002 : EntryTreeView(std::move(xEntry
), std::move(xTreeView
))
22003 , GtkInstanceContainer(pContainer
, pBuilder
, bTakeOwnership
)
22004 , m_pEntry(dynamic_cast<GtkInstanceEntry
*>(m_xEntry
.get()))
22005 , m_pTreeView(dynamic_cast<GtkInstanceTreeView
*>(m_xTreeView
.get()))
22006 , m_nAutoCompleteIdleId(0)
22007 , m_bAutoCompleteCaseSensitive(false)
22008 , m_bTreeChange(false)
22011 GtkWidget
* pWidget
= m_pEntry
->getWidget();
22012 #if !GTK_CHECK_VERSION(4, 0, 0)
22013 m_nKeyPressSignalId
= g_signal_connect(pWidget
, "key-press-event", G_CALLBACK(signalKeyPress
), this);
22015 m_nEntryInsertTextSignalId
= g_signal_connect(pWidget
, "insert-text", G_CALLBACK(signalEntryInsertText
), this);
22018 virtual void insert_separator(int /*pos*/, const OUString
& /*rId*/) override
22023 virtual void make_sorted() override
22025 GtkWidget
* pTreeView
= m_pTreeView
->getWidget();
22026 GtkTreeModel
* pModel
= gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView
));
22027 GtkTreeSortable
* pSortable
= GTK_TREE_SORTABLE(pModel
);
22028 gtk_tree_sortable_set_sort_column_id(pSortable
, 1, GTK_SORT_ASCENDING
);
22031 virtual void set_entry_completion(bool bEnable
, bool bCaseSensitive
) override
22033 assert(!bEnable
&& "not implemented yet"); (void)bEnable
;
22034 m_bAutoCompleteCaseSensitive
= bCaseSensitive
;
22037 virtual void set_entry_placeholder_text(const OUString
& rText
) override
22039 m_xEntry
->set_placeholder_text(rText
);
22042 virtual void set_entry_editable(bool bEditable
) override
22044 m_xEntry
->set_editable(bEditable
);
22047 virtual void cut_entry_clipboard() override
22049 m_xEntry
->cut_clipboard();
22052 virtual void copy_entry_clipboard() override
22054 m_xEntry
->copy_clipboard();
22057 virtual void paste_entry_clipboard() override
22059 m_xEntry
->paste_clipboard();
22062 virtual void set_font(const vcl::Font
&) override
22064 assert(false && "not implemented");
22067 virtual void set_entry_font(const vcl::Font
& rFont
) override
22069 m_xEntry
->set_font(rFont
);
22072 virtual vcl::Font
get_entry_font() override
22074 return m_xEntry
->get_font();
22077 virtual void grab_focus() override
{ m_xEntry
->grab_focus(); }
22079 virtual void connect_focus_in(const Link
<Widget
&, void>& rLink
) override
22081 m_xEntry
->connect_focus_in(rLink
);
22084 virtual void connect_focus_out(const Link
<Widget
&, void>& rLink
) override
22086 m_xEntry
->connect_focus_out(rLink
);
22089 virtual void disable_notify_events() override
22091 GtkWidget
* pWidget
= m_pEntry
->getWidget();
22092 g_signal_handler_block(pWidget
, m_nEntryInsertTextSignalId
);
22093 #if !GTK_CHECK_VERSION(4, 0, 0)
22094 g_signal_handler_block(pWidget
, m_nKeyPressSignalId
);
22096 m_pTreeView
->disable_notify_events();
22097 GtkInstanceContainer::disable_notify_events();
22100 virtual void enable_notify_events() override
22102 GtkWidget
* pWidget
= m_pEntry
->getWidget();
22103 #if !GTK_CHECK_VERSION(4, 0, 0)
22104 g_signal_handler_unblock(pWidget
, m_nKeyPressSignalId
);
22106 g_signal_handler_unblock(pWidget
, m_nEntryInsertTextSignalId
);
22107 m_pTreeView
->enable_notify_events();
22108 GtkInstanceContainer::disable_notify_events();
22111 virtual bool changed_by_direct_pick() const override
22113 return m_bTreeChange
;
22116 virtual void set_custom_renderer(bool /*bOn*/) override
22118 assert(false && "not implemented");
22121 virtual int get_max_mru_count() const override
22123 assert(false && "not implemented");
22127 virtual void set_max_mru_count(int) override
22129 assert(false && "not implemented");
22132 virtual OUString
get_mru_entries() const override
22134 assert(false && "not implemented");
22138 virtual void set_mru_entries(const OUString
&) override
22140 assert(false && "not implemented");
22143 virtual void set_item_menu(const OString
&, weld::Menu
*) override
22145 assert(false && "not implemented");
22148 VclPtr
<VirtualDevice
> create_render_virtual_device() const override
22150 return create_virtual_device();
22153 int get_menu_button_width() const override
22155 assert(false && "not implemented");
22159 virtual ~GtkInstanceEntryTreeView() override
22161 if (m_nAutoCompleteIdleId
)
22162 g_source_remove(m_nAutoCompleteIdleId
);
22163 GtkWidget
* pWidget
= m_pEntry
->getWidget();
22164 #if !GTK_CHECK_VERSION(4, 0, 0)
22165 g_signal_handler_disconnect(pWidget
, m_nKeyPressSignalId
);
22167 g_signal_handler_disconnect(pWidget
, m_nEntryInsertTextSignalId
);
22175 class GtkInstanceExpander
: public GtkInstanceWidget
, public virtual weld::Expander
22178 GtkExpander
* m_pExpander
;
22179 gulong m_nSignalId
;
22180 #if !GTK_CHECK_VERSION(4, 0, 0)
22181 gulong m_nButtonPressEventSignalId
;
22182 gulong m_nMappedSignalId
;
22185 static void signalExpanded(GtkExpander
* pExpander
, GParamSpec
*, gpointer widget
)
22187 GtkInstanceExpander
* pThis
= static_cast<GtkInstanceExpander
*>(widget
);
22188 SolarMutexGuard aGuard
;
22190 #if !GTK_CHECK_VERSION(4, 0, 0)
22191 if (gtk_expander_get_resize_toplevel(pExpander
))
22193 GtkWidget
*pToplevel
= widget_get_toplevel(GTK_WIDGET(pExpander
));
22195 // https://gitlab.gnome.org/GNOME/gtk/issues/70
22196 // I imagine at some point a release with a fix will be available in which
22197 // case this can be avoided depending on version number
22198 if (pToplevel
&& GTK_IS_WINDOW(pToplevel
) && gtk_widget_get_realized(pToplevel
))
22200 int nToplevelWidth
, nToplevelHeight
;
22203 GtkWidget
* child
= gtk_bin_get_child(GTK_BIN(pExpander
));
22204 gtk_widget_get_preferred_height(child
, &nChildHeight
, nullptr);
22205 gtk_window_get_size(GTK_WINDOW(pToplevel
), &nToplevelWidth
, &nToplevelHeight
);
22207 if (pThis
->get_expanded())
22208 nToplevelHeight
+= nChildHeight
;
22210 nToplevelHeight
-= nChildHeight
;
22212 gtk_window_resize(GTK_WINDOW(pToplevel
), nToplevelWidth
, nToplevelHeight
);
22219 pThis
->signal_expanded();
22222 #if !GTK_CHECK_VERSION(4, 0, 0)
22223 static gboolean
signalButton(GtkWidget
*, GdkEventButton
*, gpointer
)
22225 // don't let button press get to parent window, for the case of the
22226 // an expander in a sidebar where otherwise single click to expand
22231 /* tdf#141186 if the expander is initially collapsed then when mapped all its
22232 children are mapped too. If they are mapped then the mnemonics of the
22233 children are taken into account on shortcuts and non-visible children in a
22234 collapsed expander can be triggered which is confusing.
22236 If the expander is expanded and collapsed the child is unmapped and the
22237 problem doesn't occur.
22239 So to avoid the problem of an initially collapsed expander, listen to
22240 the map event and if the expander is mapped but collapsed then unmap the
22241 child of the expander.
22243 This problem was seen in gtk3-3.24.33 and not with gtk4-4.6.4 so a gtk3
22246 static void signalMap(GtkWidget
*, gpointer widget
)
22248 GtkInstanceExpander
* pThis
= static_cast<GtkInstanceExpander
*>(widget
);
22249 if (!gtk_expander_get_expanded(pThis
->m_pExpander
))
22251 if (GtkWidget
* pChild
= gtk_bin_get_child(GTK_BIN(pThis
->m_pExpander
)))
22252 gtk_widget_unmap(pChild
);
22258 GtkInstanceExpander(GtkExpander
* pExpander
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
22259 : GtkInstanceWidget(GTK_WIDGET(pExpander
), pBuilder
, bTakeOwnership
)
22260 , m_pExpander(pExpander
)
22261 , m_nSignalId(g_signal_connect(m_pExpander
, "notify::expanded", G_CALLBACK(signalExpanded
), this))
22262 #if !GTK_CHECK_VERSION(4, 0, 0)
22263 , m_nButtonPressEventSignalId(g_signal_connect_after(m_pExpander
, "button-press-event", G_CALLBACK(signalButton
), this))
22264 , m_nMappedSignalId(g_signal_connect_after(m_pExpander
, "map", G_CALLBACK(signalMap
), this))
22269 virtual void set_label(const OUString
& rText
) override
22271 ::set_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander
)), rText
);
22274 virtual OUString
get_label() const override
22276 return ::get_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander
)));
22279 virtual bool get_expanded() const override
22281 return gtk_expander_get_expanded(m_pExpander
);
22284 virtual void set_expanded(bool bExpand
) override
22286 gtk_expander_set_expanded(m_pExpander
, bExpand
);
22289 virtual ~GtkInstanceExpander() override
22291 #if !GTK_CHECK_VERSION(4, 0, 0)
22292 g_signal_handler_disconnect(m_pExpander
, m_nMappedSignalId
);
22293 g_signal_handler_disconnect(m_pExpander
, m_nButtonPressEventSignalId
);
22295 g_signal_handler_disconnect(m_pExpander
, m_nSignalId
);
22303 gboolean
signalTooltipQuery(GtkWidget
* pWidget
, gint
/*x*/, gint
/*y*/,
22304 gboolean
/*keyboard_mode*/, GtkTooltip
*tooltip
)
22306 const ImplSVHelpData
& aHelpData
= ImplGetSVHelpData();
22307 if (aHelpData
.mbBalloonHelp
) // extended tips
22309 #if !GTK_CHECK_VERSION(4, 0, 0)
22310 // by default use accessible description
22311 AtkObject
* pAtkObject
= gtk_widget_get_accessible(pWidget
);
22312 const char* pDesc
= pAtkObject
? atk_object_get_description(pAtkObject
) : nullptr;
22313 if (pDesc
&& pDesc
[0])
22315 gtk_tooltip_set_text(tooltip
, pDesc
);
22320 // fallback to the mechanism which needs help installed
22321 OString sHelpId
= ::get_help_id(pWidget
);
22322 Help
* pHelp
= !sHelpId
.isEmpty() ? Application::GetHelp() : nullptr;
22325 OUString sHelpText
= pHelp
->GetHelpText(OStringToOUString(sHelpId
, RTL_TEXTENCODING_UTF8
), static_cast<weld::Widget
*>(nullptr));
22326 if (!sHelpText
.isEmpty())
22328 gtk_tooltip_set_text(tooltip
, OUStringToOString(sHelpText
, RTL_TEXTENCODING_UTF8
).getStr());
22334 const char* pDesc
= gtk_widget_get_tooltip_text(pWidget
);
22335 if (pDesc
&& pDesc
[0])
22337 gtk_tooltip_set_text(tooltip
, pDesc
);
22348 class GtkInstancePopover
: public GtkInstanceContainer
, public virtual weld::Popover
22351 #if !GTK_CHECK_VERSION(4, 0, 0)
22352 //popover cannot escape dialog under X so we might need to stick up own window instead
22353 GtkWindow
* m_pMenuHack
;
22354 bool m_bMenuPoppedUp
;
22355 bool m_nButtonPressSeen
;
22357 GtkPopover
* m_pPopover
;
22358 gulong m_nSignalId
;
22359 ImplSVEvent
* m_pClosedEvent
;
22361 static void signalClosed(GtkPopover
*, gpointer widget
)
22363 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22364 // call signal-closed async so the closed callback isn't called
22365 // while the GtkPopover handler is still in-execution
22366 pThis
->launch_signal_closed();
22369 DECL_LINK(async_signal_closed
, void*, void);
22371 void launch_signal_closed()
22373 if (m_pClosedEvent
)
22374 Application::RemoveUserEvent(m_pClosedEvent
);
22375 m_pClosedEvent
= Application::PostUserEvent(LINK(this, GtkInstancePopover
, async_signal_closed
));
22378 #if !GTK_CHECK_VERSION(4, 0, 0)
22379 static gboolean
keyPress(GtkWidget
*, GdkEventKey
* pEvent
, gpointer widget
)
22381 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22382 return pThis
->key_press(pEvent
);
22385 bool key_press(const GdkEventKey
* pEvent
)
22387 if (pEvent
->keyval
== GDK_KEY_Escape
)
22395 static gboolean
signalButtonPress(GtkWidget
* /*pWidget*/, GdkEventButton
* /*pEvent*/, gpointer widget
)
22397 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22398 pThis
->m_nButtonPressSeen
= true;
22402 static gboolean
signalButtonRelease(GtkWidget
* /*pWidget*/, GdkEventButton
* pEvent
, gpointer widget
)
22404 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22405 if (pThis
->m_nButtonPressSeen
&& button_event_is_outside(GTK_WIDGET(pThis
->m_pMenuHack
), pEvent
))
22410 bool forward_event_if_popup_under_mouse(GdkEvent
* pEvent
)
22412 GtkWidget
* pEventWidget
= gtk_get_event_widget(pEvent
);
22413 GtkWidget
* pTopLevel
= widget_get_toplevel(pEventWidget
);
22415 if (pTopLevel
== GTK_WIDGET(m_pMenuHack
))
22418 GdkSurface
* pSurface
= widget_get_surface(pTopLevel
);
22419 void* pMouseEnteredAnotherPopup
= g_object_get_data(G_OBJECT(pSurface
), "g-lo-InstancePopup");
22420 if (!pMouseEnteredAnotherPopup
)
22423 return gtk_widget_event(pEventWidget
, pEvent
);
22426 static gboolean
signalButtonCrossing(GtkWidget
*, GdkEvent
* pEvent
, gpointer widget
)
22428 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22429 return pThis
->forward_event_if_popup_under_mouse(pEvent
);
22432 static gboolean
signalMotion(GtkWidget
*, GdkEvent
* pEvent
, gpointer widget
)
22434 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22435 return pThis
->forward_event_if_popup_under_mouse(pEvent
);
22438 static void signalGrabBroken(GtkWidget
*, GdkEventGrabBroken
*pEvent
, gpointer widget
)
22440 GtkInstancePopover
* pThis
= static_cast<GtkInstancePopover
*>(widget
);
22441 pThis
->grab_broken(pEvent
);
22444 void grab_broken(const GdkEventGrabBroken
*event
)
22446 if (event
->grab_window
== nullptr)
22450 else if (!g_object_get_data(G_OBJECT(event
->grab_window
), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
22452 //try and regrab, so when we lose the grab to the menu of the color palette
22453 //combobox we regain it so the color palette doesn't itself disappear on next
22454 //click on the color palette combobox
22455 do_grab(GTK_WIDGET(m_pMenuHack
));
22462 GtkInstancePopover(GtkPopover
* pPopover
, GtkInstanceBuilder
* pBuilder
, bool bTakeOwnership
)
22463 #if !GTK_CHECK_VERSION(4, 0, 0)
22464 : GtkInstanceContainer(GTK_CONTAINER(pPopover
), pBuilder
, bTakeOwnership
)
22465 , m_pMenuHack(nullptr)
22466 , m_bMenuPoppedUp(false)
22467 , m_nButtonPressSeen(false)
22469 : GtkInstanceContainer(GTK_WIDGET(pPopover
), pBuilder
, bTakeOwnership
)
22471 , m_pPopover(pPopover
)
22472 , m_nSignalId(g_signal_connect(m_pPopover
, "closed", G_CALLBACK(signalClosed
), this))
22473 , m_pClosedEvent(nullptr)
22475 #if !GTK_CHECK_VERSION(4, 0, 0)
22476 //under wayland a Popover will work to "escape" the parent dialog, not
22477 //so under X, so come up with this hack to use a raw GtkWindow
22478 GdkDisplay
*pDisplay
= gtk_widget_get_display(GTK_WIDGET(m_pPopover
));
22479 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay
))
22481 m_pMenuHack
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP
));
22482 gtk_window_set_type_hint(m_pMenuHack
, GDK_WINDOW_TYPE_HINT_COMBO
);
22483 gtk_window_set_resizable(m_pMenuHack
, false);
22484 g_signal_connect(m_pMenuHack
, "key-press-event", G_CALLBACK(keyPress
), this);
22485 g_signal_connect(m_pMenuHack
, "grab-broken-event", G_CALLBACK(signalGrabBroken
), this);
22486 g_signal_connect(m_pMenuHack
, "button-press-event", G_CALLBACK(signalButtonPress
), this);
22487 g_signal_connect(m_pMenuHack
, "button-release-event", G_CALLBACK(signalButtonRelease
), this);
22488 // to emulate a modeless popover we forward the leave/enter/motion events to the widgets
22489 // they would have gone to a if we were really modeless
22490 if (!gtk_popover_get_modal(m_pPopover
))
22492 g_signal_connect(m_pMenuHack
, "leave-notify-event", G_CALLBACK(signalButtonCrossing
), this);
22493 g_signal_connect(m_pMenuHack
, "enter-notify-event", G_CALLBACK(signalButtonCrossing
), this);
22494 g_signal_connect(m_pMenuHack
, "motion-notify-event", G_CALLBACK(signalMotion
), this);
22500 virtual void popup_at_rect(weld::Widget
* pParent
, const tools::Rectangle
& rRect
, weld::Placement ePlace
) override
22502 GtkInstanceWidget
* pGtkWidget
= dynamic_cast<GtkInstanceWidget
*>(pParent
);
22503 assert(pGtkWidget
);
22505 GtkWidget
* pWidget
= pGtkWidget
->getWidget();
22507 GdkRectangle aRect
;
22508 pWidget
= getPopupRect(pWidget
, rRect
, aRect
);
22510 #if GTK_CHECK_VERSION(4, 0, 0)
22511 gtk_widget_set_parent(GTK_WIDGET(m_pPopover
), pWidget
);
22513 gtk_popover_set_relative_to(m_pPopover
, pWidget
);
22515 gtk_popover_set_pointing_to(m_pPopover
, &aRect
);
22517 if (ePlace
== weld::Placement::Under
)
22518 gtk_popover_set_position(m_pPopover
, GTK_POS_BOTTOM
);
22521 if (::SwapForRTL(pWidget
))
22522 gtk_popover_set_position(m_pPopover
, GTK_POS_LEFT
);
22524 gtk_popover_set_position(m_pPopover
, GTK_POS_RIGHT
);
22527 #if !GTK_CHECK_VERSION(4, 0, 0)
22528 //under wayland a Popover will work to "escape" the parent dialog, not
22529 //so under X, so come up with this hack to use a raw GtkWindow
22530 GdkDisplay
*pDisplay
= gtk_widget_get_display(GTK_WIDGET(m_pPopover
));
22531 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay
))
22533 if (!m_bMenuPoppedUp
)
22535 MovePopoverContentsToWindow(GTK_WIDGET(m_pPopover
), m_pMenuHack
, pWidget
, aRect
, ePlace
);
22536 m_bMenuPoppedUp
= true;
22542 gtk_popover_popup(m_pPopover
);
22545 #if !GTK_CHECK_VERSION(4, 0, 0)
22546 virtual bool get_visible() const override
22549 return gtk_widget_get_visible(GTK_WIDGET(m_pMenuHack
));
22550 return gtk_widget_get_visible(m_pWidget
);
22553 virtual void ensureMouseEventWidget() override
22555 if (!m_pMouseEventBox
&& m_pMenuHack
)
22557 m_pMouseEventBox
= GTK_WIDGET(m_pMenuHack
);
22560 GtkInstanceContainer::ensureMouseEventWidget();
22564 virtual void popdown() override
22566 #if !GTK_CHECK_VERSION(4, 0, 0)
22567 //under wayland a Popover will work to "escape" the parent dialog, not
22568 //so under X, so come up with this hack to use a raw GtkWindow
22569 GdkDisplay
*pDisplay
= gtk_widget_get_display(GTK_WIDGET(m_pPopover
));
22570 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay
))
22572 if (m_bMenuPoppedUp
)
22574 m_nButtonPressSeen
= false;
22575 MoveWindowContentsToPopover(m_pMenuHack
, GTK_WIDGET(m_pPopover
), gtk_popover_get_relative_to(m_pPopover
));
22576 m_bMenuPoppedUp
= false;
22583 gtk_popover_popdown(m_pPopover
);
22586 void PopdownAndFlushClosedSignal()
22590 if (m_pClosedEvent
)
22592 Application::RemoveUserEvent(m_pClosedEvent
);
22593 async_signal_closed(nullptr);
22597 virtual void resize_to_request() override
22599 // resizing to request is what gtk does automatically
22602 virtual ~GtkInstancePopover() override
22604 PopdownAndFlushClosedSignal();
22605 DisconnectMouseEvents();
22606 #if !GTK_CHECK_VERSION(4, 0, 0)
22608 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack
));
22610 g_signal_handler_disconnect(m_pPopover
, m_nSignalId
);
22614 IMPL_LINK_NOARG(GtkInstancePopover
, async_signal_closed
, void*, void)
22616 m_pClosedEvent
= nullptr;
22622 #if !GTK_CHECK_VERSION(4, 0, 0)
22627 AtkObject
* drawing_area_get_accessible(GtkWidget
*pWidget
)
22629 AtkObject
* pDefaultAccessible
= default_drawing_area_get_accessible(pWidget
);
22630 void* pData
= g_object_get_data(G_OBJECT(pWidget
), "g-lo-GtkInstanceDrawingArea");
22631 GtkInstanceDrawingArea
* pDrawingArea
= static_cast<GtkInstanceDrawingArea
*>(pData
);
22632 AtkObject
*pAtkObj
= pDrawingArea
? pDrawingArea
->GetAtkObject(pDefaultAccessible
) : nullptr;
22635 return pDefaultAccessible
;
22638 void ensure_intercept_drawing_area_accessibility()
22643 gpointer pClass
= g_type_class_ref(GTK_TYPE_DRAWING_AREA
);
22644 GtkWidgetClass
* pWidgetClass
= GTK_WIDGET_CLASS(pClass
);
22645 default_drawing_area_get_accessible
= pWidgetClass
->get_accessible
;
22646 pWidgetClass
->get_accessible
= drawing_area_get_accessible
;
22647 g_type_class_unref(pClass
);
22652 void ensure_disable_ctrl_page_up_down(GType eType
)
22654 gpointer pClass
= g_type_class_ref(eType
);
22655 GtkWidgetClass
* pWidgetClass
= GTK_WIDGET_CLASS(pClass
);
22656 GtkBindingSet
* pBindingSet
= gtk_binding_set_by_class(pWidgetClass
);
22657 gtk_binding_entry_remove(pBindingSet
, GDK_KEY_Page_Up
, GDK_CONTROL_MASK
);
22658 gtk_binding_entry_remove(pBindingSet
, GDK_KEY_Page_Up
, static_cast<GdkModifierType
>(GDK_SHIFT_MASK
|GDK_CONTROL_MASK
));
22659 gtk_binding_entry_remove(pBindingSet
, GDK_KEY_Page_Down
, GDK_CONTROL_MASK
);
22660 gtk_binding_entry_remove(pBindingSet
, GDK_KEY_Page_Down
, static_cast<GdkModifierType
>(GDK_SHIFT_MASK
|GDK_CONTROL_MASK
));
22661 g_type_class_unref(pClass
);
22664 // tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
22665 // keystrokes are consumed by the surrounding notebook bindings instead
22666 void ensure_disable_ctrl_page_up_down_bindings()
22671 ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW
);
22672 ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON
);
22682 bool IsAllowedBuiltInIcon(std::u16string_view iconName
)
22684 // limit the named icons to those known by VclBuilder
22685 return VclBuilder::mapStockToSymbol(iconName
) != SymbolType::DONTKNOW
;
22692 void load_ui_file(GtkBuilder
* pBuilder
, const OUString
& rUri
)
22694 #if GTK_CHECK_VERSION(4, 0, 0)
22695 builder_add_from_gtk3_file(pBuilder
, rUri
);
22698 osl::FileBase::getSystemPathFromFileURL(rUri
, aPath
);
22699 GError
*err
= nullptr;
22700 auto rc
= gtk_builder_add_from_file(pBuilder
, OUStringToOString(aPath
, RTL_TEXTENCODING_UTF8
).getStr(), &err
);
22704 SAL_WARN( "vcl.gtk", "GtkInstanceBuilder: error when calling gtk_builder_add_from_file: " << err
->message
);
22707 assert(rc
&& "could not load UI file");
22711 class GtkInstanceBuilder
: public weld::Builder
22714 ResHookProc m_pStringReplace
;
22715 OString m_aUtf8HelpRoot
;
22716 OUString m_aIconTheme
;
22717 OUString m_aUILang
;
22718 GtkBuilder
* m_pBuilder
;
22719 GSList
* m_pObjectList
;
22720 GtkWidget
* m_pParentWidget
;
22721 gulong m_nNotifySignalId
;
22722 std::vector
<GtkButton
*> m_aMnemonicButtons
;
22723 #if GTK_CHECK_VERSION(4, 0, 0)
22724 std::vector
<GtkCheckButton
*> m_aMnemonicCheckButtons
;
22726 std::vector
<GtkLabel
*> m_aMnemonicLabels
;
22728 VclPtr
<SystemChildWindow
> m_xInterimGlue
;
22729 bool m_bAllowCycleFocusOut
;
22731 void postprocess_widget(GtkWidget
* pWidget
)
22733 const bool bHideHelp
= comphelper::LibreOfficeKit::isActive() &&
22734 officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
22737 //wanted: better way to do this, e.g. make gtk use gio for
22738 //loading from a filename and provide gio protocol handler
22739 //for our image in a zip urls
22741 //unpack the images and keep them as dirs and just
22742 //add the paths to the gtk icon theme dir
22743 if (GTK_IS_IMAGE(pWidget
))
22745 GtkImage
* pImage
= GTK_IMAGE(pWidget
);
22746 if (const gchar
* icon_name
= image_get_icon_name(pImage
))
22748 OUString
aIconName(icon_name
, strlen(icon_name
), RTL_TEXTENCODING_UTF8
);
22749 if (!IsAllowedBuiltInIcon(aIconName
))
22751 if (GdkPixbuf
* pixbuf
= load_icon_by_name_theme_lang(aIconName
, m_aIconTheme
, m_aUILang
))
22753 gtk_image_set_from_pixbuf(pImage
, pixbuf
);
22754 g_object_unref(pixbuf
);
22759 #if GTK_CHECK_VERSION(4, 0, 0)
22760 else if (GTK_IS_PICTURE(pWidget
))
22762 GtkPicture
* pPicture
= GTK_PICTURE(pWidget
);
22763 if (GFile
* icon_file
= gtk_picture_get_file(pPicture
))
22765 char* icon_name
= g_file_get_uri(icon_file
);
22766 OUString
aIconName(icon_name
, strlen(icon_name
), RTL_TEXTENCODING_UTF8
);
22768 assert(aIconName
.startsWith("private:///graphicrepository/"));
22769 aIconName
.startsWith("private:///graphicrepository/", &aIconName
);
22770 if (GdkPixbuf
* pixbuf
= load_icon_by_name_theme_lang(aIconName
, m_aIconTheme
, m_aUILang
))
22772 gtk_picture_set_pixbuf(GTK_PICTURE(pWidget
), pixbuf
);
22773 g_object_unref(pixbuf
);
22778 #if !GTK_CHECK_VERSION(4, 0, 0)
22779 else if (GTK_IS_TOOL_BUTTON(pWidget
))
22781 GtkToolButton
* pToolButton
= GTK_TOOL_BUTTON(pWidget
);
22782 if (const gchar
* icon_name
= gtk_tool_button_get_icon_name(pToolButton
))
22784 OUString
aIconName(icon_name
, strlen(icon_name
), RTL_TEXTENCODING_UTF8
);
22785 if (!IsAllowedBuiltInIcon(aIconName
))
22787 if (GdkPixbuf
* pixbuf
= load_icon_by_name_theme_lang(aIconName
, m_aIconTheme
, m_aUILang
))
22789 GtkWidget
* pImage
= gtk_image_new_from_pixbuf(pixbuf
);
22790 g_object_unref(pixbuf
);
22791 gtk_tool_button_set_icon_widget(pToolButton
, pImage
);
22792 gtk_widget_show(pImage
);
22797 // if no tooltip reuse the label as default tooltip
22798 if (!gtk_widget_get_tooltip_text(pWidget
))
22800 if (const gchar
* label
= gtk_tool_button_get_label(pToolButton
))
22801 gtk_widget_set_tooltip_text(pWidget
, label
);
22805 else if (GTK_IS_BUTTON(pWidget
))
22807 GtkButton
* pButton
= GTK_BUTTON(pWidget
);
22808 if (const gchar
* icon_name
= gtk_button_get_icon_name(pButton
))
22810 OUString
aIconName(icon_name
, strlen(icon_name
), RTL_TEXTENCODING_UTF8
);
22811 if (!IsAllowedBuiltInIcon(aIconName
))
22813 if (GdkPixbuf
* pixbuf
= load_icon_by_name_theme_lang(aIconName
, m_aIconTheme
, m_aUILang
))
22815 GtkWidget
* pImage
= gtk_image_new_from_pixbuf(pixbuf
);
22816 gtk_widget_set_halign(pImage
, GTK_ALIGN_CENTER
);
22817 gtk_widget_set_valign(pImage
, GTK_ALIGN_CENTER
);
22818 g_object_unref(pixbuf
);
22819 gtk_button_set_child(pButton
, pImage
);
22820 gtk_widget_show(pImage
);
22825 else if (GTK_IS_MENU_BUTTON(pWidget
))
22827 GtkMenuButton
* pButton
= GTK_MENU_BUTTON(pWidget
);
22828 if (const gchar
* icon_name
= gtk_menu_button_get_icon_name(pButton
))
22830 OUString
aIconName(icon_name
, strlen(icon_name
), RTL_TEXTENCODING_UTF8
);
22831 if (!IsAllowedBuiltInIcon(aIconName
))
22833 if (GdkPixbuf
* pixbuf
= load_icon_by_name_theme_lang(aIconName
, m_aIconTheme
, m_aUILang
))
22835 GtkWidget
* pImage
= gtk_image_new_from_pixbuf(pixbuf
);
22836 gtk_widget_set_halign(pImage
, GTK_ALIGN_CENTER
);
22837 gtk_widget_set_valign(pImage
, GTK_ALIGN_CENTER
);
22838 g_object_unref(pixbuf
);
22839 // TODO after gtk 4.6 is released require that version and drop this
22840 static auto menu_button_set_child
= reinterpret_cast<void (*) (GtkMenuButton
*, GtkWidget
*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
22841 if (menu_button_set_child
)
22842 menu_button_set_child(pButton
, pImage
);
22843 gtk_widget_show(pImage
);
22851 OString sBuildableName
= ::get_buildable_id(GTK_BUILDABLE(pWidget
));
22852 if (!sBuildableName
.isEmpty())
22854 OString sHelpId
= m_aUtf8HelpRoot
+ sBuildableName
;
22855 set_help_id(pWidget
, sHelpId
);
22856 //hook up for extended help
22857 const ImplSVHelpData
& aHelpData
= ImplGetSVHelpData();
22858 if (aHelpData
.mbBalloonHelp
&& !GTK_IS_DIALOG(pWidget
) && !GTK_IS_ASSISTANT(pWidget
))
22860 gtk_widget_set_has_tooltip(pWidget
, true);
22861 g_signal_connect(pWidget
, "query-tooltip", G_CALLBACK(signalTooltipQuery
), nullptr);
22864 if (bHideHelp
&& sBuildableName
== "help")
22865 gtk_widget_hide(pWidget
);
22868 if (m_pStringReplace
)
22870 // tdf#136498 %PRODUCTNAME shown in tool tips
22871 const char* pTooltip
= gtk_widget_get_tooltip_text(pWidget
);
22872 if (pTooltip
&& pTooltip
[0])
22874 OUString
aTooltip(pTooltip
, strlen(pTooltip
), RTL_TEXTENCODING_UTF8
);
22875 aTooltip
= (*m_pStringReplace
)(aTooltip
);
22876 gtk_widget_set_tooltip_text(pWidget
, OUStringToOString(aTooltip
, RTL_TEXTENCODING_UTF8
).getStr());
22880 // expand placeholder and collect potentially missing mnemonics
22881 if (GTK_IS_BUTTON(pWidget
))
22883 GtkButton
* pButton
= GTK_BUTTON(pWidget
);
22884 if (m_pStringReplace
)
22886 OUString
aLabel(button_get_label(pButton
));
22887 if (!aLabel
.isEmpty())
22888 button_set_label(pButton
, (*m_pStringReplace
)(aLabel
));
22890 if (gtk_button_get_use_underline(pButton
))
22891 m_aMnemonicButtons
.push_back(pButton
);
22893 #if GTK_CHECK_VERSION(4, 0, 0)
22894 else if (GTK_IS_CHECK_BUTTON(pWidget
))
22896 GtkCheckButton
* pButton
= GTK_CHECK_BUTTON(pWidget
);
22897 if (m_pStringReplace
)
22899 OUString
aLabel(get_label(pButton
));
22900 if (!aLabel
.isEmpty())
22901 set_label(pButton
, (*m_pStringReplace
)(aLabel
));
22903 if (gtk_check_button_get_use_underline(pButton
))
22904 m_aMnemonicCheckButtons
.push_back(pButton
);
22907 else if (GTK_IS_LABEL(pWidget
))
22909 GtkLabel
* pLabel
= GTK_LABEL(pWidget
);
22910 if (m_pStringReplace
)
22912 OUString
aLabel(get_label(pLabel
));
22913 if (!aLabel
.isEmpty())
22914 set_label(pLabel
, (*m_pStringReplace
)(aLabel
));
22916 if (gtk_label_get_use_underline(pLabel
))
22917 m_aMnemonicLabels
.push_back(pLabel
);
22919 else if (GTK_IS_TEXT_VIEW(pWidget
))
22921 GtkTextView
* pTextView
= GTK_TEXT_VIEW(pWidget
);
22922 if (m_pStringReplace
)
22924 GtkTextBuffer
* pBuffer
= gtk_text_view_get_buffer(pTextView
);
22925 GtkTextIter start
, end
;
22926 gtk_text_buffer_get_bounds(pBuffer
, &start
, &end
);
22927 char* pTextStr
= gtk_text_buffer_get_text(pBuffer
, &start
, &end
, true);
22928 int nTextLen
= pTextStr
? strlen(pTextStr
) : 0;
22931 OUString
sOldText(pTextStr
, nTextLen
, RTL_TEXTENCODING_UTF8
);
22932 OString
sText(OUStringToOString((*m_pStringReplace
)(sOldText
), RTL_TEXTENCODING_UTF8
));
22933 gtk_text_buffer_set_text(pBuffer
, sText
.getStr(), sText
.getLength());
22938 #if !GTK_CHECK_VERSION(4, 0, 0)
22939 else if (GTK_IS_ENTRY(pWidget
))
22941 g_signal_connect(pWidget
, "key-press-event", G_CALLBACK(signalEntryInsertSpecialCharKeyPress
), nullptr);
22944 else if (GTK_IS_WINDOW(pWidget
))
22946 if (m_pStringReplace
)
22948 GtkWindow
* pWindow
= GTK_WINDOW(pWidget
);
22949 set_title(pWindow
, (*m_pStringReplace
)(get_title(pWindow
)));
22950 if (GTK_IS_MESSAGE_DIALOG(pWindow
))
22952 GtkMessageDialog
* pMessageDialog
= GTK_MESSAGE_DIALOG(pWindow
);
22953 set_primary_text(pMessageDialog
, (*m_pStringReplace
)(get_primary_text(pMessageDialog
)));
22954 set_secondary_text(pMessageDialog
, (*m_pStringReplace
)(get_secondary_text(pMessageDialog
)));
22960 //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
22961 //In order for GtkBuilder to find the translations bindtextdomain has to be called
22962 //for the domain. So here on the first setting of "domain" we call Translate::Create
22963 //to make sure that happens. Without this, if some other part of LibreOffice has
22964 //used the translation machinery for this domain it will still work, but if it
22965 //hasn't, e.g. tdf#119929, then the translation fails
22966 void translation_domain_set()
22968 Translate::Create(gtk_builder_get_translation_domain(m_pBuilder
), LanguageTag(m_aUILang
));
22969 g_signal_handler_disconnect(m_pBuilder
, m_nNotifySignalId
);
22972 static void signalNotify(GObject
*, GParamSpec
*pSpec
, gpointer pData
)
22974 g_return_if_fail(pSpec
!= nullptr);
22975 if (strcmp(pSpec
->name
, "translation-domain") == 0)
22977 GtkInstanceBuilder
* pBuilder
= static_cast<GtkInstanceBuilder
*>(pData
);
22978 pBuilder
->translation_domain_set();
22982 static void postprocess(gpointer data
, gpointer user_data
)
22984 GObject
* pObject
= static_cast<GObject
*>(data
);
22985 if (!GTK_IS_WIDGET(pObject
))
22987 GtkInstanceBuilder
* pThis
= static_cast<GtkInstanceBuilder
*>(user_data
);
22988 pThis
->postprocess_widget(GTK_WIDGET(pObject
));
22991 void DisallowCycleFocusOut()
22993 assert(!m_bAllowCycleFocusOut
); // we only expect this to be called when this holds
22995 GtkWidget
* pTopLevel
= widget_get_toplevel(m_pParentWidget
);
22997 GtkSalFrame
* pFrame
= GtkSalFrame::getFromWindow(pTopLevel
);
22999 // unhook handler and let gtk cycle its own way through this widget's
23000 // children because it has no non-gtk siblings
23001 pFrame
->DisallowCycleFocusOut();
23004 static void signalMap(GtkWidget
*, gpointer user_data
)
23006 GtkInstanceBuilder
* pThis
= static_cast<GtkInstanceBuilder
*>(user_data
);
23007 // tdf#138047 wait until map to do this because the final SalFrame may
23008 // not be the same as at ctor time
23009 pThis
->DisallowCycleFocusOut();
23012 void AllowCycleFocusOut()
23014 assert(!m_bAllowCycleFocusOut
); // we only expect this to be called when this holds
23016 GtkWidget
* pTopLevel
= widget_get_toplevel(m_pParentWidget
);
23018 GtkSalFrame
* pFrame
= GtkSalFrame::getFromWindow(pTopLevel
);
23020 // rehook handler and let vcl cycle its own way through this widget's
23022 pFrame
->AllowCycleFocusOut();
23024 // tdf#145567 if the focus is in this hierarchy then, now that we are tearing down,
23025 // move focus to the usual focus candidate for the frame
23026 GtkWindow
* pFocusWin
= get_active_window();
23027 GtkWidget
* pFocus
= pFocusWin
? gtk_window_get_focus(pFocusWin
) : nullptr;
23028 bool bHasFocus
= pFocus
&& gtk_widget_is_ancestor(pFocus
, pTopLevel
);
23030 pFrame
->GrabFocus();
23033 static void signalUnmap(GtkWidget
*, gpointer user_data
)
23035 GtkInstanceBuilder
* pThis
= static_cast<GtkInstanceBuilder
*>(user_data
);
23036 pThis
->AllowCycleFocusOut();
23040 GtkInstanceBuilder(GtkWidget
* pParent
, std::u16string_view rUIRoot
, const OUString
& rUIFile
,
23041 SystemChildWindow
* pInterimGlue
, bool bAllowCycleFocusOut
)
23043 , m_pStringReplace(Translate::GetReadStringHook())
23044 , m_pParentWidget(pParent
)
23045 , m_nNotifySignalId(0)
23046 , m_xInterimGlue(pInterimGlue
)
23047 , m_bAllowCycleFocusOut(bAllowCycleFocusOut
)
23049 OUString
sHelpRoot(rUIFile
);
23050 #if !GTK_CHECK_VERSION(4, 0, 0)
23051 ensure_intercept_drawing_area_accessibility();
23052 ensure_disable_ctrl_page_up_down_bindings();
23055 sal_Int32 nIdx
= sHelpRoot
.lastIndexOf('.');
23057 sHelpRoot
= sHelpRoot
.copy(0, nIdx
);
23059 m_aUtf8HelpRoot
= OUStringToOString(sHelpRoot
, RTL_TEXTENCODING_UTF8
);
23060 m_aIconTheme
= Application::GetSettings().GetStyleSettings().DetermineIconTheme();
23061 m_aUILang
= Application::GetSettings().GetUILanguageTag().getBcp47();
23063 OUString
aUri(rUIRoot
+ rUIFile
);
23065 m_pBuilder
= gtk_builder_new();
23066 m_nNotifySignalId
= g_signal_connect_data(G_OBJECT(m_pBuilder
), "notify", G_CALLBACK(signalNotify
), this, nullptr, G_CONNECT_AFTER
);
23068 load_ui_file(m_pBuilder
, aUri
);
23070 m_pObjectList
= gtk_builder_get_objects(m_pBuilder
);
23071 g_slist_foreach(m_pObjectList
, postprocess
, this);
23073 GenerateMissingMnemonics();
23075 if (m_xInterimGlue
)
23077 assert(m_pParentWidget
);
23078 g_object_set_data(G_OBJECT(m_pParentWidget
), "InterimWindowGlue", m_xInterimGlue
.get());
23080 if (!m_bAllowCycleFocusOut
)
23082 g_signal_connect(G_OBJECT(m_pParentWidget
), "map", G_CALLBACK(signalMap
), this);
23083 g_signal_connect(G_OBJECT(m_pParentWidget
), "unmap", G_CALLBACK(signalUnmap
), this);
23088 void GenerateMissingMnemonics()
23090 MnemonicGenerator
aMnemonicGenerator('_');
23091 for (const auto a
: m_aMnemonicButtons
)
23092 aMnemonicGenerator
.RegisterMnemonic(button_get_label(a
));
23093 #if GTK_CHECK_VERSION(4, 0, 0)
23094 for (const auto a
: m_aMnemonicCheckButtons
)
23095 aMnemonicGenerator
.RegisterMnemonic(get_label(a
));
23097 for (const auto a
: m_aMnemonicLabels
)
23098 aMnemonicGenerator
.RegisterMnemonic(get_label(a
));
23100 for (const auto a
: m_aMnemonicButtons
)
23102 OUString
aLabel(button_get_label(a
));
23103 OUString aNewLabel
= aMnemonicGenerator
.CreateMnemonic(aLabel
);
23104 if (aLabel
== aNewLabel
)
23106 button_set_label(a
, aNewLabel
);
23108 #if GTK_CHECK_VERSION(4, 0, 0)
23109 for (const auto a
: m_aMnemonicCheckButtons
)
23111 OUString
aLabel(get_label(a
));
23112 OUString aNewLabel
= aMnemonicGenerator
.CreateMnemonic(aLabel
);
23113 if (aLabel
== aNewLabel
)
23115 set_label(a
, aNewLabel
);
23118 for (const auto a
: m_aMnemonicLabels
)
23120 OUString
aLabel(get_label(a
));
23121 OUString aNewLabel
= aMnemonicGenerator
.CreateMnemonic(aLabel
);
23122 if (aLabel
== aNewLabel
)
23124 set_label(a
, aNewLabel
);
23127 m_aMnemonicLabels
.clear();
23128 #if GTK_CHECK_VERSION(4, 0, 0)
23129 m_aMnemonicCheckButtons
.clear();
23131 m_aMnemonicButtons
.clear();
23134 OString
get_current_page_help_id()
23136 OString sPageHelpId
;
23137 // check to see if there is a notebook called tabcontrol and get the
23138 // helpid for the current page of that
23139 std::unique_ptr
<weld::Notebook
> xNotebook(weld_notebook("tabcontrol"));
23142 if (GtkInstanceContainer
* pPage
= dynamic_cast<GtkInstanceContainer
*>(xNotebook
->get_page(xNotebook
->get_current_page_ident())))
23144 GtkWidget
* pContainer
= pPage
->getWidget();
23145 if (GtkWidget
* pPageWidget
= widget_get_first_child(pContainer
))
23146 sPageHelpId
= ::get_help_id(pPageWidget
);
23149 return sPageHelpId
;
23152 virtual ~GtkInstanceBuilder() override
23154 g_slist_free(m_pObjectList
);
23155 g_object_unref(m_pBuilder
);
23157 if (m_xInterimGlue
&& !m_bAllowCycleFocusOut
)
23158 AllowCycleFocusOut();
23160 m_xInterimGlue
.disposeAndClear();
23163 //ideally we would have/use weld::Container add and explicitly
23164 //call add when we want to do this, but in the vcl impl the
23165 //parent has to be set when the child is created, so for the
23166 //gtk impl emulate this by doing this implicitly at weld time
23167 void auto_add_parentless_widgets_to_container(GtkWidget
* pWidget
)
23169 if (GTK_IS_POPOVER(pWidget
))
23171 if (GTK_IS_WINDOW(pWidget
))
23173 #if GTK_CHECK_VERSION(4, 0, 0)
23174 if (!gtk_widget_get_parent(pWidget
))
23175 gtk_widget_set_parent(pWidget
, m_pParentWidget
);
23177 if (widget_get_toplevel(pWidget
) == pWidget
)
23178 gtk_container_add(GTK_CONTAINER(m_pParentWidget
), pWidget
);
23182 virtual std::unique_ptr
<weld::MessageDialog
> weld_message_dialog(const OString
&id
) override
23184 GtkMessageDialog
* pMessageDialog
= GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23185 if (!pMessageDialog
)
23187 gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog
), GTK_WINDOW(widget_get_toplevel(m_pParentWidget
)));
23188 return std::make_unique
<GtkInstanceMessageDialog
>(pMessageDialog
, this, true);
23191 virtual std::unique_ptr
<weld::Assistant
> weld_assistant(const OString
&id
) override
23193 GtkAssistant
* pAssistant
= GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23196 if (m_pParentWidget
)
23197 gtk_window_set_transient_for(GTK_WINDOW(pAssistant
), GTK_WINDOW(widget_get_toplevel(m_pParentWidget
)));
23198 return std::make_unique
<GtkInstanceAssistant
>(pAssistant
, this, true);
23201 virtual std::unique_ptr
<weld::Dialog
> weld_dialog(const OString
&id
) override
23203 GtkWindow
* pDialog
= GTK_WINDOW(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23206 if (m_pParentWidget
)
23207 gtk_window_set_transient_for(pDialog
, GTK_WINDOW(widget_get_toplevel(m_pParentWidget
)));
23208 return std::make_unique
<GtkInstanceDialog
>(pDialog
, this, true);
23211 virtual std::unique_ptr
<weld::Window
> create_screenshot_window() override
23213 GtkWidget
* pTopLevel
= nullptr;
23215 for (GSList
* l
= m_pObjectList
; l
; l
= g_slist_next(l
))
23217 GObject
* pObj
= static_cast<GObject
*>(l
->data
);
23219 if (!GTK_IS_WIDGET(pObj
) || gtk_widget_get_parent(GTK_WIDGET(pObj
)))
23223 pTopLevel
= GTK_WIDGET(pObj
);
23224 else if (GTK_IS_WINDOW(pObj
))
23225 pTopLevel
= GTK_WIDGET(pObj
);
23231 GtkWindow
* pDialog
;
23232 if (GTK_IS_WINDOW(pTopLevel
))
23233 pDialog
= GTK_WINDOW(pTopLevel
);
23236 pDialog
= GTK_WINDOW(gtk_dialog_new());
23237 ::set_help_id(GTK_WIDGET(pDialog
), ::get_help_id(pTopLevel
));
23239 GtkWidget
* pContentArea
= gtk_dialog_get_content_area(GTK_DIALOG(pDialog
));
23240 #if !GTK_CHECK_VERSION(4, 0, 0)
23241 gtk_container_add(GTK_CONTAINER(pContentArea
), pTopLevel
);
23242 gtk_widget_show_all(pTopLevel
);
23244 gtk_box_append(GTK_BOX(pContentArea
), pTopLevel
);
23245 gtk_widget_show(pTopLevel
);
23249 if (m_pParentWidget
)
23250 gtk_window_set_transient_for(pDialog
, GTK_WINDOW(widget_get_toplevel(m_pParentWidget
)));
23251 return std::make_unique
<GtkInstanceDialog
>(pDialog
, this, true);
23254 virtual std::unique_ptr
<weld::Widget
> weld_widget(const OString
&id
) override
23256 GtkWidget
* pWidget
= GTK_WIDGET(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23259 auto_add_parentless_widgets_to_container(pWidget
);
23260 return std::make_unique
<GtkInstanceWidget
>(pWidget
, this, false);
23263 virtual std::unique_ptr
<weld::Container
> weld_container(const OString
&id
) override
23265 #if !GTK_CHECK_VERSION(4, 0, 0)
23266 GtkContainer
* pContainer
= GTK_CONTAINER(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23268 GtkWidget
* pContainer
= GTK_WIDGET(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23272 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer
));
23273 return std::make_unique
<GtkInstanceContainer
>(pContainer
, this, false);
23276 virtual std::unique_ptr
<weld::Box
> weld_box(const OString
&id
) override
23278 GtkBox
* pBox
= GTK_BOX(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23281 auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox
));
23282 return std::make_unique
<GtkInstanceBox
>(pBox
, this, false);
23285 virtual std::unique_ptr
<weld::Paned
> weld_paned(const OString
&id
) override
23287 GtkPaned
* pPaned
= GTK_PANED(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23290 auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned
));
23291 return std::make_unique
<GtkInstancePaned
>(pPaned
, this, false);
23294 virtual std::unique_ptr
<weld::Frame
> weld_frame(const OString
&id
) override
23296 GtkFrame
* pFrame
= GTK_FRAME(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23299 auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame
));
23300 return std::make_unique
<GtkInstanceFrame
>(pFrame
, this, false);
23303 virtual std::unique_ptr
<weld::ScrolledWindow
> weld_scrolled_window(const OString
&id
, bool bUserManagedScrolling
= false) override
23305 GtkScrolledWindow
* pScrolledWindow
= GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23306 if (!pScrolledWindow
)
23308 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow
));
23309 return std::make_unique
<GtkInstanceScrolledWindow
>(pScrolledWindow
, this, false, bUserManagedScrolling
);
23312 virtual std::unique_ptr
<weld::Notebook
> weld_notebook(const OString
&id
) override
23314 GtkNotebook
* pNotebook
= GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23317 auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook
));
23318 return std::make_unique
<GtkInstanceNotebook
>(pNotebook
, this, false);
23321 virtual std::unique_ptr
<weld::Button
> weld_button(const OString
&id
) override
23323 GtkButton
* pButton
= GTK_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23326 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton
));
23327 return std::make_unique
<GtkInstanceButton
>(pButton
, this, false);
23330 virtual std::unique_ptr
<weld::MenuButton
> weld_menu_button(const OString
&id
) override
23332 GtkMenuButton
* pButton
= GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23335 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton
));
23336 return std::make_unique
<GtkInstanceMenuButton
>(pButton
, nullptr, this, false);
23339 virtual std::unique_ptr
<weld::MenuToggleButton
> weld_menu_toggle_button(const OString
&id
) override
23341 GtkMenuButton
* pButton
= GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23344 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton
));
23345 // gtk doesn't come with exactly the same concept
23346 GtkBuilder
* pMenuToggleButton
= makeMenuToggleButtonBuilder();
23347 return std::make_unique
<GtkInstanceMenuToggleButton
>(pMenuToggleButton
, pButton
, this, false);
23350 virtual std::unique_ptr
<weld::LinkButton
> weld_link_button(const OString
&id
) override
23352 GtkLinkButton
* pButton
= GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23355 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton
));
23356 return std::make_unique
<GtkInstanceLinkButton
>(pButton
, this, false);
23359 virtual std::unique_ptr
<weld::ToggleButton
> weld_toggle_button(const OString
&id
) override
23361 GtkToggleButton
* pToggleButton
= GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23362 if (!pToggleButton
)
23364 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton
));
23365 return std::make_unique
<GtkInstanceToggleButton
>(pToggleButton
, this, false);
23368 virtual std::unique_ptr
<weld::RadioButton
> weld_radio_button(const OString
&id
) override
23370 #if GTK_CHECK_VERSION(4, 0, 0)
23371 GtkCheckButton
* pRadioButton
= GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23373 GtkRadioButton
* pRadioButton
= GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23377 auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton
));
23378 return std::make_unique
<GtkInstanceRadioButton
>(pRadioButton
, this, false);
23381 virtual std::unique_ptr
<weld::CheckButton
> weld_check_button(const OString
&id
) override
23383 GtkCheckButton
* pCheckButton
= GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23386 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton
));
23387 return std::make_unique
<GtkInstanceCheckButton
>(pCheckButton
, this, false);
23390 virtual std::unique_ptr
<weld::Scale
> weld_scale(const OString
&id
) override
23392 GtkScale
* pScale
= GTK_SCALE(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23395 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale
));
23396 return std::make_unique
<GtkInstanceScale
>(pScale
, this, false);
23399 virtual std::unique_ptr
<weld::ProgressBar
> weld_progress_bar(const OString
&id
) override
23401 GtkProgressBar
* pProgressBar
= GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23404 auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar
));
23405 return std::make_unique
<GtkInstanceProgressBar
>(pProgressBar
, this, false);
23408 virtual std::unique_ptr
<weld::Spinner
> weld_spinner(const OString
&id
) override
23410 GtkSpinner
* pSpinner
= GTK_SPINNER(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23413 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner
));
23414 return std::make_unique
<GtkInstanceSpinner
>(pSpinner
, this, false);
23417 virtual std::unique_ptr
<weld::Image
> weld_image(const OString
&id
) override
23419 GtkWidget
* pWidget
= GTK_WIDGET(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23422 if (GTK_IS_IMAGE(pWidget
))
23424 auto_add_parentless_widgets_to_container(pWidget
);
23425 return std::make_unique
<GtkInstanceImage
>(GTK_IMAGE(pWidget
), this, false);
23427 #if GTK_CHECK_VERSION(4, 0, 0)
23428 if (GTK_IS_PICTURE(pWidget
))
23430 auto_add_parentless_widgets_to_container(pWidget
);
23431 return std::make_unique
<GtkInstancePicture
>(GTK_PICTURE(pWidget
), this, false);
23437 virtual std::unique_ptr
<weld::Calendar
> weld_calendar(const OString
&id
) override
23439 GtkCalendar
* pCalendar
= GTK_CALENDAR(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23442 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar
));
23443 return std::make_unique
<GtkInstanceCalendar
>(pCalendar
, this, false);
23446 virtual std::unique_ptr
<weld::Entry
> weld_entry(const OString
&id
) override
23448 GtkEntry
* pEntry
= GTK_ENTRY(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23451 auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry
));
23452 return std::make_unique
<GtkInstanceEntry
>(pEntry
, this, false);
23455 virtual std::unique_ptr
<weld::SpinButton
> weld_spin_button(const OString
&id
) override
23457 GtkSpinButton
* pSpinButton
= GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23460 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton
));
23461 return std::make_unique
<GtkInstanceSpinButton
>(pSpinButton
, this, false);
23464 virtual std::unique_ptr
<weld::MetricSpinButton
> weld_metric_spin_button(const OString
& id
, FieldUnit eUnit
) override
23466 return std::make_unique
<weld::MetricSpinButton
>(weld_spin_button(id
), eUnit
);
23469 virtual std::unique_ptr
<weld::FormattedSpinButton
> weld_formatted_spin_button(const OString
&id
) override
23471 GtkSpinButton
* pSpinButton
= GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23474 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton
));
23475 return std::make_unique
<GtkInstanceFormattedSpinButton
>(pSpinButton
, this, false);
23478 virtual std::unique_ptr
<weld::ComboBox
> weld_combo_box(const OString
&id
) override
23480 GtkComboBox
* pComboBox
= GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23483 auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox
));
23485 #if GTK_CHECK_VERSION(4, 0, 0)
23486 return std::make_unique
<GtkInstanceComboBox
>(pComboBox
, this, false);
23488 /* we replace GtkComboBox because of difficulties with too tall menus
23490 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
23491 has_entry long menus take forever to appear (tdf#125388)
23493 on measuring each row, the GtkComboBox GtkTreeMenu will call
23494 its area_apply_attributes_cb function on the row, but that calls
23495 gtk_tree_menu_get_path_item which then loops through each child of the
23496 menu looking for the widget of the row, so performance drops to useless.
23498 All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
23499 with fragile hackery which assumes that the unwanted callback is the only one with a
23501 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
23502 when a super tall combobox menu is activated, and the selected
23503 entry is sufficiently far down the list, then the menu doesn't
23504 appear under wayland
23506 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
23507 no typeahead support
23509 4) we want to be able to control the width of the button, but have a drop down menu which
23510 is not limited to the width of the button
23512 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
23513 super tall menu doesn't appear under X sometimes
23515 GtkBuilder
* pComboBuilder
= makeComboBoxBuilder();
23516 return std::make_unique
<GtkInstanceComboBox
>(pComboBuilder
, pComboBox
, this, false);
23520 virtual std::unique_ptr
<weld::TreeView
> weld_tree_view(const OString
&id
) override
23522 GtkTreeView
* pTreeView
= GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23525 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView
));
23526 return std::make_unique
<GtkInstanceTreeView
>(pTreeView
, this, false);
23529 virtual std::unique_ptr
<weld::IconView
> weld_icon_view(const OString
&id
) override
23531 GtkIconView
* pIconView
= GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23534 auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView
));
23535 return std::make_unique
<GtkInstanceIconView
>(pIconView
, this, false);
23538 virtual std::unique_ptr
<weld::EntryTreeView
> weld_entry_tree_view(const OString
& containerid
, const OString
& entryid
, const OString
& treeviewid
) override
23540 #if GTK_CHECK_VERSION(4, 0, 0)
23541 GtkWidget
* pContainer
= GTK_WIDGET(gtk_builder_get_object(m_pBuilder
, containerid
.getStr()));
23543 GtkContainer
* pContainer
= GTK_CONTAINER(gtk_builder_get_object(m_pBuilder
, containerid
.getStr()));
23547 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer
));
23548 return std::make_unique
<GtkInstanceEntryTreeView
>(pContainer
, this, false,
23549 weld_entry(entryid
),
23550 weld_tree_view(treeviewid
));
23553 virtual std::unique_ptr
<weld::Label
> weld_label(const OString
&id
) override
23555 GtkLabel
* pLabel
= GTK_LABEL(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23558 auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel
));
23559 return std::make_unique
<GtkInstanceLabel
>(pLabel
, this, false);
23562 virtual std::unique_ptr
<weld::TextView
> weld_text_view(const OString
&id
) override
23564 GtkTextView
* pTextView
= GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23567 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView
));
23568 return std::make_unique
<GtkInstanceTextView
>(pTextView
, this, false);
23571 virtual std::unique_ptr
<weld::Expander
> weld_expander(const OString
&id
) override
23573 GtkExpander
* pExpander
= GTK_EXPANDER(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23576 auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander
));
23577 return std::make_unique
<GtkInstanceExpander
>(pExpander
, this, false);
23580 virtual std::unique_ptr
<weld::DrawingArea
> weld_drawing_area(const OString
&id
, const a11yref
& rA11y
,
23581 FactoryFunction
/*pUITestFactoryFunction*/, void* /*pUserData*/) override
23583 GtkDrawingArea
* pDrawingArea
= GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23586 auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea
));
23587 return std::make_unique
<GtkInstanceDrawingArea
>(pDrawingArea
, this, rA11y
, false);
23590 virtual std::unique_ptr
<weld::Menu
> weld_menu(const OString
&id
) override
23592 #if GTK_CHECK_VERSION(4, 0, 0)
23593 GtkPopoverMenu
* pMenu
= GTK_POPOVER_MENU(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23595 GtkMenu
* pMenu
= GTK_MENU(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23599 return std::make_unique
<GtkInstanceMenu
>(pMenu
, true);
23602 virtual std::unique_ptr
<weld::Popover
> weld_popover(const OString
&id
) override
23604 GtkPopover
* pPopover
= GTK_POPOVER(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23607 #if GTK_CHECK_VERSION(4, 0, 0)
23608 return std::make_unique
<GtkInstancePopover
>(pPopover
, this, false);
23610 return std::make_unique
<GtkInstancePopover
>(pPopover
, this, true);
23614 virtual std::unique_ptr
<weld::Toolbar
> weld_toolbar(const OString
&id
) override
23616 #if GTK_CHECK_VERSION(4, 0, 0)
23617 GtkBox
* pToolbar
= GTK_BOX(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23619 GtkToolbar
* pToolbar
= GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder
, id
.getStr()));
23623 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar
));
23624 return std::make_unique
<GtkInstanceToolbar
>(pToolbar
, this, false);
23627 virtual std::unique_ptr
<weld::SizeGroup
> create_size_group() override
23629 return std::make_unique
<GtkInstanceSizeGroup
>();
23635 void GtkInstanceWindow::help()
23637 //show help for widget with keyboard focus
23638 GtkWidget
* pWidget
= gtk_window_get_focus(m_pWindow
);
23640 pWidget
= GTK_WIDGET(m_pWindow
);
23641 OString sHelpId
= ::get_help_id(pWidget
);
23642 while (sHelpId
.isEmpty())
23644 pWidget
= gtk_widget_get_parent(pWidget
);
23647 sHelpId
= ::get_help_id(pWidget
);
23649 std::unique_ptr
<weld::Widget
> xTemp(pWidget
!= m_pWidget
? new GtkInstanceWidget(pWidget
, m_pBuilder
, false) : nullptr);
23650 weld::Widget
* pSource
= xTemp
? xTemp
.get() : this;
23651 bool bRunNormalHelpRequest
= !m_aHelpRequestHdl
.IsSet() || m_aHelpRequestHdl
.Call(*pSource
);
23652 Help
* pHelp
= bRunNormalHelpRequest
? Application::GetHelp() : nullptr;
23656 #if !GTK_CHECK_VERSION(4, 0, 0)
23657 // tdf#126007, there's a nice fallback route for offline help where
23658 // the current page of a notebook will get checked when the help
23659 // button is pressed and there was no help for the dialog found.
23661 // But for online help that route doesn't get taken, so bodge this here
23662 // by using the page help id if available and if the help button itself
23663 // was the original id
23664 if (m_pBuilder
&& sHelpId
.endsWith("/help"))
23666 OString sPageId
= m_pBuilder
->get_current_page_help_id();
23667 if (!sPageId
.isEmpty())
23671 // tdf#129068 likewise the help for the wrapping dialog is less
23672 // helpful than the help for the content area could be
23673 GtkContainer
* pContainer
= nullptr;
23674 if (GTK_IS_DIALOG(m_pWindow
))
23675 pContainer
= GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow
)));
23676 else if (GTK_IS_ASSISTANT(m_pWindow
))
23678 GtkAssistant
* pAssistant
= GTK_ASSISTANT(m_pWindow
);
23679 pContainer
= GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant
, gtk_assistant_get_current_page(pAssistant
)));
23683 GtkWidget
* pContentWidget
= widget_get_first_child(GTK_WIDGET(pContainer
));
23684 if (pContentWidget
)
23685 sHelpId
= ::get_help_id(pContentWidget
);
23690 pHelp
->Start(OStringToOUString(sHelpId
, RTL_TEXTENCODING_UTF8
), pSource
);
23693 //iterate upwards through the hierarchy from this widgets through its parents
23694 //calling func with their helpid until func returns true or we run out of parents
23695 void GtkInstanceWidget::help_hierarchy_foreach(const std::function
<bool(const OString
&)>& func
)
23697 GtkWidget
* pParent
= m_pWidget
;
23698 while ((pParent
= gtk_widget_get_parent(pParent
)))
23700 if (func(::get_help_id(pParent
)))
23705 std::unique_ptr
<weld::Builder
> GtkInstance::CreateBuilder(weld::Widget
* pParent
, const OUString
& rUIRoot
, const OUString
& rUIFile
)
23707 GtkInstanceWidget
* pParentWidget
= dynamic_cast<GtkInstanceWidget
*>(pParent
);
23708 GtkWidget
* pBuilderParent
= pParentWidget
? pParentWidget
->getWidget() : nullptr;
23709 return std::make_unique
<GtkInstanceBuilder
>(pBuilderParent
, rUIRoot
, rUIFile
, nullptr, true);
23712 #if !GTK_CHECK_VERSION(4, 0, 0)
23713 // tdf#135965 for the case of native widgets inside a GtkSalFrame and F1 pressed, run help
23714 // on gtk widget help ids until we hit a vcl parent and then use vcl window help ids
23715 gboolean
GtkSalFrame::NativeWidgetHelpPressed(GtkAccelGroup
*, GObject
*, guint
, GdkModifierType
, gpointer pFrame
)
23717 Help
* pHelp
= Application::GetHelp();
23721 GtkWindow
* pWindow
= static_cast<GtkWindow
*>(pFrame
);
23723 vcl::Window
* pChildWindow
= nullptr;
23725 //show help for widget with keyboard focus
23726 GtkWidget
* pWidget
= gtk_window_get_focus(pWindow
);
23728 pWidget
= GTK_WIDGET(pWindow
);
23729 OString sHelpId
= ::get_help_id(pWidget
);
23730 while (sHelpId
.isEmpty())
23732 pWidget
= gtk_widget_get_parent(pWidget
);
23735 pChildWindow
= static_cast<vcl::Window
*>(g_object_get_data(G_OBJECT(pWidget
), "InterimWindowGlue"));
23738 sHelpId
= pChildWindow
->GetHelpId();
23741 sHelpId
= ::get_help_id(pWidget
);
23746 while (sHelpId
.isEmpty())
23748 pChildWindow
= pChildWindow
->GetParent();
23751 sHelpId
= pChildWindow
->GetHelpId();
23755 pHelp
->Start(OStringToOUString(sHelpId
, RTL_TEXTENCODING_UTF8
), pChildWindow
);
23761 std::unique_ptr
<weld::Widget
> xTemp(new GtkInstanceWidget(pWidget
, nullptr, false));
23762 pHelp
->Start(OStringToOUString(sHelpId
, RTL_TEXTENCODING_UTF8
), xTemp
.get());
23767 std::unique_ptr
<weld::Builder
> GtkInstance::CreateInterimBuilder(vcl::Window
* pParent
, const OUString
& rUIRoot
, const OUString
& rUIFile
,
23768 bool bAllowCycleFocusOut
, sal_uInt64
)
23770 // Create a foreign window which we know is a GtkGrid and make the native widgets a child of that, so we can
23771 // support GtkWidgets within a vcl::Window
23772 SystemWindowData winData
= {};
23773 winData
.bClipUsingNativeWidget
= true;
23774 auto xEmbedWindow
= VclPtr
<SystemChildWindow
>::Create(pParent
, 0, &winData
, false);
23775 xEmbedWindow
->Show(true, ShowFlags::NoActivate
);
23776 xEmbedWindow
->set_expand(true);
23778 const SystemEnvData
* pEnvData
= xEmbedWindow
->GetSystemData();
23782 GtkWidget
*pWindow
= static_cast<GtkWidget
*>(pEnvData
->pWidget
);
23783 #if !GTK_CHECK_VERSION(4, 0, 0)
23784 gtk_widget_show_all(pWindow
);
23786 gtk_widget_show(pWindow
);
23789 // build the widget tree as a child of the GtkEventBox GtkGrid parent
23790 return std::make_unique
<GtkInstanceBuilder
>(pWindow
, rUIRoot
, rUIFile
, xEmbedWindow
.get(), bAllowCycleFocusOut
);
23793 weld::MessageDialog
* GtkInstance::CreateMessageDialog(weld::Widget
* pParent
, VclMessageType eMessageType
, VclButtonsType eButtonsType
, const OUString
&rPrimaryMessage
)
23795 GtkInstanceWidget
* pParentInstance
= dynamic_cast<GtkInstanceWidget
*>(pParent
);
23796 GtkWindow
* pParentWindow
= pParentInstance
? pParentInstance
->getWindow() : nullptr;
23797 GtkMessageDialog
* pMessageDialog
= GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow
, GTK_DIALOG_MODAL
,
23798 VclToGtk(eMessageType
), VclToGtk(eButtonsType
), "%s",
23799 OUStringToOString(rPrimaryMessage
, RTL_TEXTENCODING_UTF8
).getStr()));
23800 return new GtkInstanceMessageDialog(pMessageDialog
, nullptr, true);
23803 weld::Window
* GtkInstance::GetFrameWeld(const css::uno::Reference
<css::awt::XWindow
>& rWindow
)
23805 if (SalGtkXWindow
* pGtkXWindow
= dynamic_cast<SalGtkXWindow
*>(rWindow
.get()))
23806 return pGtkXWindow
->getFrameWeld();
23807 return SalInstance::GetFrameWeld(rWindow
);
23810 weld::Window
* GtkSalFrame::GetFrameWeld() const
23813 m_xFrameWeld
.reset(new GtkInstanceWindow(GTK_WINDOW(widget_get_toplevel(getWindow())), nullptr, false));
23814 return m_xFrameWeld
.get();
23817 void* GtkInstance::CreateGStreamerSink(const SystemChildWindow
*pWindow
)
23819 #if ENABLE_GSTREAMER_1_0
23820 auto aSymbol
= gstElementFactoryNameSymbol();
23824 const SystemEnvData
* pEnvData
= pWindow
->GetSystemData();
23828 GstElement
* pVideosink
= aSymbol("gtksink", "gtksink");
23832 GtkWidget
*pGstWidget
;
23833 g_object_get(pVideosink
, "widget", &pGstWidget
, nullptr);
23834 gtk_widget_set_vexpand(pGstWidget
, true);
23835 gtk_widget_set_hexpand(pGstWidget
, true);
23837 GtkWidget
*pParent
= static_cast<GtkWidget
*>(pEnvData
->pWidget
);
23838 #if !GTK_CHECK_VERSION(4, 0, 0)
23839 gtk_container_add(GTK_CONTAINER(pParent
), pGstWidget
);
23841 g_object_unref(pGstWidget
);
23842 #if !GTK_CHECK_VERSION(4, 0, 0)
23843 gtk_widget_show_all(pParent
);
23845 gtk_widget_show(pParent
);
23855 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */