cid#1555070 COPY_INSTEAD_OF_MOVE
[LibreOffice.git] / vcl / unx / gtk3 / gtkinst.cxx
blobdf7b939f39ae18d64aea4584ab1311e70d0ae2e5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/config.h>
12 #include <deque>
13 #include <optional>
14 #include <stack>
15 #include <string.h>
16 #include <string_view>
18 #include <dndhelper.hxx>
19 #include <o3tl/test_info.hxx>
20 #include <osl/process.h>
21 #include <osl/file.hxx>
22 #include <unx/gtk/gtkdata.hxx>
23 #include <unx/gtk/gtkinst.hxx>
24 #include <unx/genprn.h>
25 #include <unx/salobj.h>
26 #include <unx/gtk/gtkgdi.hxx>
27 #include <unx/gtk/gtkframe.hxx>
28 #include <unx/gtk/gtkobject.hxx>
29 #include <unx/gtk/gtksalmenu.hxx>
30 #include <headless/svpvd.hxx>
31 #include <headless/svpbmp.hxx>
32 #include <utility>
33 #include <vcl/builder.hxx>
34 #include <vcl/inputtypes.hxx>
35 #include <vcl/specialchars.hxx>
36 #include <vcl/sysdata.hxx>
37 #include <vcl/transfer.hxx>
38 #include <vcl/toolkit/floatwin.hxx>
39 #include <unx/genpspgraphics.h>
40 #include <rtl/strbuf.hxx>
41 #include <sal/log.hxx>
42 #include <rtl/uri.hxx>
44 #include <basegfx/numeric/ftools.hxx>
45 #include <vcl/settings.hxx>
47 #include <dlfcn.h>
48 #include <fcntl.h>
49 #include <unistd.h>
51 #if !GTK_CHECK_VERSION(4, 0, 0)
52 #include "a11y/atkwrapper.hxx"
53 #endif
54 #include <com/sun/star/awt/XVclWindowPeer.hpp>
55 #include <com/sun/star/datatransfer/XTransferable.hpp>
56 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
57 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
58 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
59 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
60 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
61 #include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
62 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
63 #include <com/sun/star/lang/IllegalArgumentException.hpp>
64 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
65 #include <com/sun/star/lang/XServiceInfo.hpp>
66 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
67 #include <com/sun/star/lang/XInitialization.hpp>
68 #include <comphelper/lok.hxx>
69 #include <comphelper/processfactory.hxx>
70 #include <comphelper/propertyvalue.hxx>
71 #include <comphelper/sequence.hxx>
72 #include <comphelper/string.hxx>
73 #include <cppuhelper/compbase.hxx>
74 #include <cppuhelper/implbase.hxx>
75 #include <cppuhelper/supportsservice.hxx>
76 #include <officecfg/Office/Common.hxx>
77 #include <rtl/bootstrap.hxx>
78 #include <o3tl/unreachable.hxx>
79 #include <o3tl/string_view.hxx>
80 #include <svl/zforlist.hxx>
81 #include <svl/zformat.hxx>
82 #include <tools/helpers.hxx>
83 #include <tools/fract.hxx>
84 #include <tools/stream.hxx>
85 #include <unotools/resmgr.hxx>
86 #include <unotools/tempfile.hxx>
87 #include <unx/gstsink.hxx>
88 #include <vcl/ImageTree.hxx>
89 #include <vcl/abstdlg.hxx>
90 #include <vcl/event.hxx>
91 #include <vcl/i18nhelp.hxx>
92 #include <vcl/quickselectionengine.hxx>
93 #include <vcl/mnemonic.hxx>
94 #include <vcl/filter/PngImageWriter.hxx>
95 #include <vcl/stdtext.hxx>
96 #include <vcl/syswin.hxx>
97 #include <vcl/virdev.hxx>
98 #include <vcl/weld.hxx>
99 #include <vcl/wrkwin.hxx>
100 #include "customcellrenderer.hxx"
101 #include <strings.hrc>
102 #include <window.h>
103 #include <numeric>
104 #include <boost/property_tree/ptree.hpp>
105 #include <opengl/zone.hxx>
107 using namespace com::sun::star;
108 using namespace com::sun::star::uno;
109 using namespace com::sun::star::lang;
111 extern "C"
113 #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalInstance()->GetYieldMutex())
114 #if !GTK_CHECK_VERSION(4, 0, 0)
115 static void GdkThreadsEnter()
117 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
118 pYieldMutex->ThreadsEnter();
120 static void GdkThreadsLeave()
122 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
123 pYieldMutex->ThreadsLeave();
125 #endif
127 VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
129 SAL_INFO(
130 "vcl.gtk",
131 "create vcl plugin instance with gtk version " << gtk_get_major_version()
132 << " " << gtk_get_minor_version() << " " << gtk_get_micro_version());
134 if (gtk_get_major_version() == 3 && gtk_get_minor_version() < 18)
136 g_warning("require gtk >= 3.18 for theme expectations");
137 return nullptr;
140 // for gtk2 it is always built with X support, so this is always called
141 // for gtk3 it is normally built with X and Wayland support, if
142 // X is supported GDK_WINDOWING_X11 is defined and this is always
143 // called, regardless of if we're running under X or Wayland.
144 // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
145 // X, because we need to do it earlier than we have a display
146 #if defined(GDK_WINDOWING_X11)
147 /* #i92121# workaround deadlocks in the X11 implementation
149 static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
150 /* #i90094#
151 from now on we know that an X connection will be
152 established, so protect X against itself
154 if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
155 XInitThreads();
156 #endif
158 #if !GTK_CHECK_VERSION(4, 0, 0)
159 // init gdk thread protection
160 bool const sup = g_thread_supported();
161 // extracted from the 'if' to avoid Clang -Wunreachable-code
162 if ( !sup )
163 g_thread_init( nullptr );
165 gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
166 SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
167 #endif
169 auto pYieldMutex = std::make_unique<GtkYieldMutex>();
171 #if !GTK_CHECK_VERSION(4, 0, 0)
172 gdk_threads_init();
173 #endif
175 GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
176 SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
178 // Create SalData, this does not leak
179 new GtkSalData();
181 return pInstance;
185 #if !GTK_CHECK_VERSION(4, 0, 0)
186 static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
188 VclInputFlags nType = VclInputFlags::NONE;
189 switch (gdk_event_get_event_type(pEvent))
191 case GDK_MOTION_NOTIFY:
192 case GDK_BUTTON_PRESS:
193 #if !GTK_CHECK_VERSION(4, 0, 0)
194 case GDK_2BUTTON_PRESS:
195 case GDK_3BUTTON_PRESS:
196 #endif
197 case GDK_BUTTON_RELEASE:
198 case GDK_ENTER_NOTIFY:
199 case GDK_LEAVE_NOTIFY:
200 case GDK_SCROLL:
201 nType = VclInputFlags::MOUSE;
202 break;
203 case GDK_KEY_PRESS:
204 // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
205 nType = VclInputFlags::KEYBOARD;
206 break;
207 #if !GTK_CHECK_VERSION(4, 0, 0)
208 case GDK_EXPOSE:
209 nType = VclInputFlags::PAINT;
210 break;
211 #endif
212 default:
213 nType = VclInputFlags::OTHER;
214 break;
216 return nType;
218 #endif
220 GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
221 : SvpSalInstance( std::move(pMutex) )
222 , m_pTimer(nullptr)
223 , bNeedsInit(true)
224 , m_pLastCairoFontOptions(nullptr)
226 m_bSupportsOpenGL = true;
229 //We want to defer initializing gtk until we are after uno has been
230 //bootstrapped so we can ask the config what the UI language is so that we can
231 //force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
232 //UI in a LTR locale
233 void GtkInstance::AfterAppInit()
235 EnsureInit();
238 void GtkInstance::EnsureInit()
240 if (!bNeedsInit)
241 return;
242 // initialize SalData
243 GtkSalData *pSalData = GetGtkSalData();
244 pSalData->Init();
245 GtkSalData::initNWF();
247 ImplSVData* pSVData = ImplGetSVData();
248 #ifdef GTK_TOOLKIT_NAME
249 // [-loplugin:ostr] if we use a literal here, we get use-after-free on shutdown
250 pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
251 #else
252 // [-loplugin:ostr] if we use a literal here, we get use-after-free on shutdown
253 pSVData->maAppData.mxToolkitName = OUString("gtk3");
254 #endif
256 bNeedsInit = false;
259 GtkInstance::~GtkInstance()
261 assert( nullptr == m_pTimer );
262 ResetLastSeenCairoFontOptions(nullptr);
265 SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
267 EnsureInit();
268 return new GtkSalFrame( pParent, nStyle );
271 SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
273 EnsureInit();
274 return new GtkSalFrame( pParentData );
277 SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
279 EnsureInit();
280 //FIXME: Missing CreateObject functionality ...
281 if (pWindowData && pWindowData->bClipUsingNativeWidget)
282 return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame*>(pParent), bShow);
283 return new GtkSalObject(static_cast<GtkSalFrame*>(pParent), bShow);
286 extern "C"
288 typedef void*(* getDefaultFnc)();
289 typedef void(* addItemFnc)(void *, const char *);
292 void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
294 EnsureInit();
295 OString sGtkURL;
296 rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
297 if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
298 sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
299 else
301 //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
302 //Decode %XX components
303 OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
304 //Convert back to system locale encoding
305 OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
306 //Encode to an escaped ASCII-encoded URI
307 gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
308 sGtkURL = OString(g_uri);
309 g_free(g_uri);
311 GtkRecentManager *manager = gtk_recent_manager_get_default ();
312 gtk_recent_manager_add_item (manager, sGtkURL.getStr());
315 SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
316 ImplJobSetup* pSetupData )
318 EnsureInit();
319 mbPrinterInit = true;
320 // create and initialize SalInfoPrinter
321 PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
322 configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
323 return pPrinter;
326 std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
328 EnsureInit();
329 mbPrinterInit = true;
330 return std::unique_ptr<SalPrinter>(new PspSalPrinter(pInfoPrinter));
334 * These methods always occur in pairs
335 * A ThreadsEnter is followed by a ThreadsLeave
336 * We need to queue up the recursive lock count
337 * for each pair, so we can accurately restore
338 * it later.
340 thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
342 void GtkYieldMutex::ThreadsEnter()
344 acquire();
345 if (yieldCounts.empty())
346 return;
347 auto n = yieldCounts.top();
348 yieldCounts.pop();
350 const bool bUndoingLeaveWithoutEnter = n == 0;
351 // if the ThreadsLeave bLeaveWithoutEnter of true condition occurred to
352 // create this entry then return early undoing the initial acquire of the
353 // function
354 if G_UNLIKELY(bUndoingLeaveWithoutEnter)
356 release();
357 return;
360 assert(n > 0);
361 n--;
362 if (n > 0)
363 acquire(n);
366 void GtkYieldMutex::ThreadsLeave()
368 const bool bLeaveWithoutEnter = m_nCount == 0;
369 SAL_WARN_IF(bLeaveWithoutEnter, "vcl.gtk", "gdk_threads_leave without matching gdk_threads_enter");
370 yieldCounts.push(m_nCount);
371 if G_UNLIKELY(bLeaveWithoutEnter) // this ideally shouldn't happen, but can due to the gtk3 file dialog
372 return;
373 release(true);
376 std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
377 tools::Long &nDX, tools::Long &nDY,
378 DeviceFormat /*eFormat*/,
379 const SystemGraphicsData* pGd )
381 EnsureInit();
382 SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
383 assert(pSvpSalGraphics);
384 // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
385 cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
386 std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
387 if (!xNew->SetSize(nDX, nDY))
388 xNew.reset();
389 return xNew;
392 std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
394 EnsureInit();
395 return SvpSalInstance::CreateSalBitmap();
398 std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
400 EnsureInit();
401 GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
402 pSalMenu->SetMenu( pVCLMenu );
403 return std::unique_ptr<SalMenu>(pSalMenu);
406 std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
408 EnsureInit();
409 return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
412 SalTimer* GtkInstance::CreateSalTimer()
414 EnsureInit();
415 assert( nullptr == m_pTimer );
416 if ( nullptr == m_pTimer )
417 m_pTimer = new GtkSalTimer();
418 return m_pTimer;
421 void GtkInstance::RemoveTimer ()
423 EnsureInit();
424 m_pTimer = nullptr;
427 bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
429 EnsureInit();
430 return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
433 bool GtkInstance::IsTimerExpired()
435 EnsureInit();
436 return (m_pTimer && m_pTimer->Expired());
439 namespace
441 bool DisplayHasAnyInput()
443 GdkDisplay* pDisplay = gdk_display_get_default();
444 #if defined(GDK_WINDOWING_WAYLAND)
445 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
447 bool bRet = false;
448 wl_display* pWLDisplay = gdk_wayland_display_get_wl_display(pDisplay);
449 static auto wayland_display_get_fd = reinterpret_cast<int (*) (wl_display*)>(dlsym(nullptr, "wl_display_get_fd"));
450 if (wayland_display_get_fd)
452 GPollFD aPollFD;
453 aPollFD.fd = wayland_display_get_fd(pWLDisplay);
454 aPollFD.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
455 bRet = g_poll(&aPollFD, 1, 0) > 0;
457 return bRet;
459 #endif
460 #if defined(GDK_WINDOWING_X11)
461 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
463 GPollFD aPollFD;
464 aPollFD.fd = ConnectionNumber(gdk_x11_display_get_xdisplay(pDisplay));
465 aPollFD.events = G_IO_IN;
466 return g_poll(&aPollFD, 1, 0) > 0;
468 #endif
469 return false;
473 bool GtkInstance::AnyInput( VclInputFlags nType )
475 EnsureInit();
476 if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
477 return true;
479 // strip timer bits now
480 nType = nType & ~VclInputFlags::TIMER;
482 static constexpr VclInputFlags ANY_INPUT_EXCLUDING_TIMER = VCL_INPUT_ANY & ~VclInputFlags::TIMER;
484 const bool bCheckForAnyInput = nType == ANY_INPUT_EXCLUDING_TIMER;
486 bool bRet = false;
488 if (bCheckForAnyInput)
489 bRet = DisplayHasAnyInput();
491 #if !GTK_CHECK_VERSION(4, 0, 0)
492 GdkDisplay* pDisplay = gdk_display_get_default();
493 if (!gdk_display_has_pending(pDisplay))
494 return bRet;
496 if (bCheckForAnyInput)
497 return true;
499 std::deque<GdkEvent*> aEvents;
500 GdkEvent *pEvent = nullptr;
501 while ((pEvent = gdk_display_get_event(pDisplay)))
503 aEvents.push_back(pEvent);
504 VclInputFlags nEventType = categorizeEvent(pEvent);
505 if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
507 bRet = true;
511 while (!aEvents.empty())
513 pEvent = aEvents.front();
514 gdk_display_put_event(pDisplay, pEvent);
515 gdk_event_free(pEvent);
516 aEvents.pop_front();
518 #endif
520 return bRet;
523 std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
525 EnsureInit();
526 return std::make_unique<GenPspGraphics>();
529 const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
531 #if !GTK_CHECK_VERSION(4, 0, 0)
532 const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
533 #else
534 auto pDefaultWin = ImplGetDefaultWindow();
535 assert(pDefaultWin);
536 SalFrame* pDefaultFrame = pDefaultWin->ImplGetFrame();
537 GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pDefaultFrame);
538 assert(pGtkFrame);
539 const cairo_font_options_t* pCairoFontOptions = pGtkFrame->get_font_options();
540 #endif
541 if (!m_pLastCairoFontOptions && pCairoFontOptions)
542 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
543 return pCairoFontOptions;
546 const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
548 return m_pLastCairoFontOptions;
551 void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
553 if (m_pLastCairoFontOptions)
554 cairo_font_options_destroy(m_pLastCairoFontOptions);
555 if (pCairoFontOptions)
556 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
557 else
558 m_pLastCairoFontOptions = nullptr;
562 namespace
564 struct TypeEntry
566 const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
567 const char* pType; // Mime encoding on our side
570 const TypeEntry aConversionTab[] =
572 { "ISO10646-1", "text/plain;charset=utf-16" },
573 { "UTF8_STRING", "text/plain;charset=utf-8" },
574 { "UTF-8", "text/plain;charset=utf-8" },
575 { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
576 // ISO encodings
577 { "ISO8859-2", "text/plain;charset=iso8859-2" },
578 { "ISO8859-3", "text/plain;charset=iso8859-3" },
579 { "ISO8859-4", "text/plain;charset=iso8859-4" },
580 { "ISO8859-5", "text/plain;charset=iso8859-5" },
581 { "ISO8859-6", "text/plain;charset=iso8859-6" },
582 { "ISO8859-7", "text/plain;charset=iso8859-7" },
583 { "ISO8859-8", "text/plain;charset=iso8859-8" },
584 { "ISO8859-9", "text/plain;charset=iso8859-9" },
585 { "ISO8859-10", "text/plain;charset=iso8859-10" },
586 { "ISO8859-13", "text/plain;charset=iso8859-13" },
587 { "ISO8859-14", "text/plain;charset=iso8859-14" },
588 { "ISO8859-15", "text/plain;charset=iso8859-15" },
589 // asian encodings
590 { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
591 { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
592 { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
593 { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
594 { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
595 { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
596 // eastern european encodings
597 { "KOI8-R", "text/plain;charset=koi8-r" },
598 { "KOI8-U", "text/plain;charset=koi8-u" },
599 // String (== iso8859-1)
600 { "STRING", "text/plain;charset=iso8859-1" },
601 // special for compound text
602 { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
604 // PIXMAP
605 { "PIXMAP", "image/bmp" }
608 class DataFlavorEq
610 private:
611 const css::datatransfer::DataFlavor& m_rData;
612 public:
613 explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
614 bool operator() (const css::datatransfer::DataFlavor& rData) const
616 return rData.MimeType == m_rData.MimeType &&
617 rData.DataType == m_rData.DataType;
622 #if GTK_CHECK_VERSION(4, 0, 0)
623 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(const char * const *targets, gint n_targets)
624 #else
625 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
626 #endif
628 std::vector<css::datatransfer::DataFlavor> aVector;
630 bool bHaveText = false, bHaveUTF16 = false;
632 for (gint i = 0; i < n_targets; ++i)
634 #if GTK_CHECK_VERSION(4, 0, 0)
635 const gchar* pName = targets[i];
636 #else
637 gchar* pName = gdk_atom_name(targets[i]);
638 #endif
639 const char* pFinalName = pName;
640 css::datatransfer::DataFlavor aFlavor;
642 // omit text/plain;charset=unicode since it is not well defined
643 if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
645 #if !GTK_CHECK_VERSION(4, 0, 0)
646 g_free(pName);
647 #endif
648 continue;
651 for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
653 if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
655 pFinalName = aConversionTab[j].pType;
656 break;
660 // There are more non-MIME-types reported that are not translated by
661 // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
662 // them out for now before they confuse this code's clients:
663 if (rtl_str_indexOfChar(pFinalName, '/') == -1)
665 #if !GTK_CHECK_VERSION(4, 0, 0)
666 g_free(pName);
667 #endif
668 continue;
671 aFlavor.MimeType = OUString(pFinalName,
672 strlen(pFinalName),
673 RTL_TEXTENCODING_UTF8);
675 m_aMimeTypeToGtkType[aFlavor.MimeType] = targets[i];
677 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
679 sal_Int32 nIndex(0);
680 if (o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
682 bHaveText = true;
683 std::u16string_view aToken(o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex));
684 if (aToken == u"charset=utf-16")
686 bHaveUTF16 = true;
687 aFlavor.DataType = cppu::UnoType<OUString>::get();
690 aVector.push_back(aFlavor);
691 #if !GTK_CHECK_VERSION(4, 0, 0)
692 g_free(pName);
693 #endif
696 //If we have text, but no UTF-16 format which is basically the only
697 //text-format LibreOffice supports for cnp then claim we do and we
698 //will convert on demand
699 if (bHaveText && !bHaveUTF16)
701 css::datatransfer::DataFlavor aFlavor;
702 aFlavor.MimeType = "text/plain;charset=utf-16";
703 aFlavor.DataType = cppu::UnoType<OUString>::get();
704 aVector.push_back(aFlavor);
707 return aVector;
710 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
712 return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
715 sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
717 const std::vector<css::datatransfer::DataFlavor> aAll =
718 getTransferDataFlavorsAsVector();
720 return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
723 #if GTK_CHECK_VERSION(4, 0, 0)
724 void read_transfer_result::read_block_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
726 GInputStream* stream = G_INPUT_STREAM(source);
727 read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
729 gsize bytes_read = g_input_stream_read_finish(stream, res, nullptr);
731 bool bFinished = bytes_read == 0;
733 if (bFinished)
735 g_object_unref(stream);
736 pRes->aVector.resize(pRes->nRead);
737 pRes->bDone = true;
738 g_main_context_wakeup(nullptr);
739 return;
742 pRes->nRead += bytes_read;
744 pRes->aVector.resize(pRes->nRead + read_transfer_result::BlockSize);
746 g_input_stream_read_async(stream,
747 pRes->aVector.data() + pRes->nRead,
748 read_transfer_result::BlockSize,
749 G_PRIORITY_DEFAULT,
750 nullptr,
751 read_block_async_completed,
752 user_data);
755 OUString read_transfer_result::get_as_string() const
757 const char* pStr = reinterpret_cast<const char*>(aVector.data());
758 return OUString(pStr, aVector.size(), RTL_TEXTENCODING_UTF8).replaceAll("\r\n", "\n");
761 css::uno::Sequence<sal_Int8> read_transfer_result::get_as_sequence() const
763 return css::uno::Sequence<sal_Int8>(aVector.data(), aVector.size());
765 #endif
767 namespace {
769 GdkClipboard* clipboard_get(SelectionType eSelection)
771 #if GTK_CHECK_VERSION(4, 0, 0)
772 if (eSelection == SELECTION_CLIPBOARD)
773 return gdk_display_get_clipboard(gdk_display_get_default());
774 return gdk_display_get_primary_clipboard(gdk_display_get_default());
775 #else
776 return gtk_clipboard_get(eSelection == SELECTION_CLIPBOARD ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY);
777 #endif
780 #if GTK_CHECK_VERSION(4, 0, 0)
782 void read_clipboard_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
784 GdkClipboard* clipboard = GDK_CLIPBOARD(source);
785 read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
787 GInputStream* pResult = gdk_clipboard_read_finish(clipboard, res, nullptr, nullptr);
789 if (!pResult)
791 pRes->bDone = true;
792 g_main_context_wakeup(nullptr);
793 return;
796 pRes->aVector.resize(read_transfer_result::BlockSize);
798 g_input_stream_read_async(pResult,
799 pRes->aVector.data(),
800 pRes->aVector.size(),
801 G_PRIORITY_DEFAULT,
802 nullptr,
803 read_transfer_result::read_block_async_completed,
804 user_data);
807 #endif
809 class GtkClipboardTransferable : public GtkTransferable
811 private:
812 SelectionType m_eSelection;
814 public:
816 explicit GtkClipboardTransferable(SelectionType eSelection)
817 : m_eSelection(eSelection)
822 * XTransferable
825 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
827 css::uno::Any aRet;
829 css::datatransfer::DataFlavor aFlavor(rFlavor);
830 if (aFlavor.MimeType == "text/plain;charset=utf-16")
831 aFlavor.MimeType = "text/plain;charset=utf-8";
833 GdkClipboard* clipboard = clipboard_get(m_eSelection);
835 #if !GTK_CHECK_VERSION(4, 0, 0)
836 if (aFlavor.MimeType == "text/plain;charset=utf-8")
838 gchar *pText = gtk_clipboard_wait_for_text(clipboard);
839 OUString aStr(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
840 g_free(pText);
841 aRet <<= aStr.replaceAll("\r\n", "\n");
842 return aRet;
844 #endif
846 auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
847 if (it == m_aMimeTypeToGtkType.end())
848 return css::uno::Any();
850 #if !GTK_CHECK_VERSION(4, 0, 0)
851 GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
852 it->second);
853 if (!data)
855 return css::uno::Any();
857 gint length;
858 const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
859 &length);
860 Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
861 gtk_selection_data_free(data);
862 aRet <<= aSeq;
863 #else
864 SalInstance* pInstance = GetSalInstance();
865 read_transfer_result aRes;
866 const char *mime_types[] = { it->second.getStr(), nullptr };
868 gdk_clipboard_read_async(clipboard,
869 mime_types,
870 G_PRIORITY_DEFAULT,
871 nullptr,
872 read_clipboard_async_completed,
873 &aRes);
875 while (!aRes.bDone)
876 pInstance->DoYield(true, false);
878 if (aFlavor.MimeType == "text/plain;charset=utf-8")
879 aRet <<= aRes.get_as_string();
880 else
881 aRet <<= aRes.get_as_sequence();
882 #endif
883 return aRet;
886 std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
887 override
889 std::vector<css::datatransfer::DataFlavor> aVector;
891 GdkClipboard* clipboard = clipboard_get(m_eSelection);
893 #if GTK_CHECK_VERSION(4, 0, 0)
894 GdkContentFormats* pFormats = gdk_clipboard_get_formats(clipboard);
895 gsize n_targets;
896 const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
897 aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
898 #else
899 GdkAtom *targets;
900 gint n_targets;
901 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
903 aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
904 g_free(targets);
906 #endif
908 return aVector;
912 class VclGtkClipboard :
913 public cppu::WeakComponentImplHelper<
914 datatransfer::clipboard::XSystemClipboard,
915 datatransfer::clipboard::XFlushableClipboard,
916 XServiceInfo>
918 SelectionType m_eSelection;
919 osl::Mutex m_aMutex;
920 gulong m_nOwnerChangedSignalId;
921 ImplSVEvent* m_pSetClipboardEvent;
922 Reference<css::datatransfer::XTransferable> m_aContents;
923 Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
924 std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
925 #if GTK_CHECK_VERSION(4, 0, 0)
926 std::vector<OString> m_aGtkTargets;
927 TransferableContent* m_pClipboardContent;
928 #else
929 std::vector<GtkTargetEntry> m_aGtkTargets;
930 #endif
931 VclToGtkHelper m_aConversionHelper;
933 DECL_LINK(AsyncSetGtkClipboard, void*, void);
935 #if GTK_CHECK_VERSION(4, 0, 0)
936 DECL_LINK(DetachClipboard, void*, void);
937 #endif
939 public:
941 explicit VclGtkClipboard(SelectionType eSelection);
942 virtual ~VclGtkClipboard() override;
945 * XServiceInfo
948 virtual OUString SAL_CALL getImplementationName() override;
949 virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
950 virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
953 * XClipboard
956 virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
958 virtual void SAL_CALL setContents(
959 const Reference< css::datatransfer::XTransferable >& xTrans,
960 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
962 virtual OUString SAL_CALL getName() override;
965 * XClipboardEx
968 virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
971 * XFlushableClipboard
973 virtual void SAL_CALL flushClipboard() override;
976 * XClipboardNotifier
978 virtual void SAL_CALL addClipboardListener(
979 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
981 virtual void SAL_CALL removeClipboardListener(
982 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
984 #if !GTK_CHECK_VERSION(4, 0, 0)
985 void ClipboardGet(GtkSelectionData *selection_data, guint info);
986 #endif
987 void OwnerPossiblyChanged(GdkClipboard *clipboard);
988 void ClipboardClear();
989 void SetGtkClipboard();
990 void SyncGtkClipboard();
995 OUString VclGtkClipboard::getImplementationName()
997 return u"com.sun.star.datatransfer.VclGtkClipboard"_ustr;
1000 Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
1002 Sequence<OUString> aRet { u"com.sun.star.datatransfer.clipboard.SystemClipboard"_ustr };
1003 return aRet;
1006 sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
1008 return cppu::supportsService(this, ServiceName);
1011 Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
1013 if (!m_aContents.is())
1015 //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
1016 //the owner of the clipboard and have not already fetched it.
1017 m_aContents = new GtkClipboardTransferable(m_eSelection);
1018 #if GTK_CHECK_VERSION(4, 0, 0)
1019 if (m_pClipboardContent)
1020 transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
1021 #endif
1023 return m_aContents;
1026 #if !GTK_CHECK_VERSION(4, 0, 0)
1027 void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
1029 if (!m_aContents.is())
1030 return;
1031 // tdf#129809 take a reference in case m_aContents is replaced during this
1032 // call
1033 Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
1034 m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
1037 namespace
1039 const OString& getPID()
1041 static OString sPID;
1042 if (!sPID.getLength())
1044 oslProcessIdentifier aProcessId = 0;
1045 oslProcessInfo info;
1046 info.Size = sizeof (oslProcessInfo);
1047 if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
1048 aProcessId = info.Ident;
1049 sPID = OString::number(aProcessId);
1051 return sPID;
1054 void ClipboardGetFunc(GdkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
1055 guint info,
1056 gpointer user_data_or_owner)
1058 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
1059 pThis->ClipboardGet(selection_data, info);
1062 void ClipboardClearFunc(GdkClipboard* /*clipboard*/, gpointer user_data_or_owner)
1064 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
1065 pThis->ClipboardClear();
1068 #endif
1070 namespace
1072 #if GTK_CHECK_VERSION(4, 0, 0)
1073 void handle_owner_change(GdkClipboard *clipboard, gpointer user_data)
1075 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
1076 pThis->OwnerPossiblyChanged(clipboard);
1078 #else
1079 void handle_owner_change(GdkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
1081 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
1082 pThis->OwnerPossiblyChanged(clipboard);
1084 #endif
1087 void VclGtkClipboard::OwnerPossiblyChanged(GdkClipboard* clipboard)
1089 SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
1090 if (!m_aContents.is())
1091 return;
1093 #if GTK_CHECK_VERSION(4, 0, 0)
1094 bool bSelf = gdk_clipboard_is_local(clipboard);
1095 #else
1096 //if gdk_display_supports_selection_notification is not supported, e.g. like
1097 //right now under wayland, then you only get owner-changed notifications at
1098 //opportune times when the selection might have changed. So here
1099 //we see if the selection supports a dummy selection type identifying
1100 //our pid, in which case it's us.
1101 bool bSelf = false;
1103 //disconnect and reconnect after gtk_clipboard_wait_for_targets to
1104 //avoid possible recursion
1105 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
1107 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1108 GdkAtom *targets;
1109 gint n_targets;
1110 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
1112 for (gint i = 0; i < n_targets && !bSelf; ++i)
1114 gchar* pName = gdk_atom_name(targets[i]);
1115 if (strcmp(pName, sTunnel.getStr()) == 0)
1117 bSelf = true;
1119 g_free(pName);
1122 g_free(targets);
1125 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
1126 G_CALLBACK(handle_owner_change), this);
1127 #endif
1129 if (!bSelf)
1131 //null out m_aContents to return control to the system-one which
1132 //will be retrieved if getContents is called again
1133 setContents(Reference<css::datatransfer::XTransferable>(),
1134 Reference<css::datatransfer::clipboard::XClipboardOwner>());
1138 void VclGtkClipboard::ClipboardClear()
1140 if (m_pSetClipboardEvent)
1142 Application::RemoveUserEvent(m_pSetClipboardEvent);
1143 m_pSetClipboardEvent = nullptr;
1145 #if !GTK_CHECK_VERSION(4, 0, 0)
1146 for (auto &a : m_aGtkTargets)
1147 g_free(a.target);
1148 #endif
1149 m_aGtkTargets.clear();
1152 #if GTK_CHECK_VERSION(4, 0, 0)
1153 IMPL_LINK_NOARG(VclGtkClipboard, DetachClipboard, void*, void)
1155 ClipboardClear();
1158 OString VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
1160 OString aEntry = OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8);
1161 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
1162 DataFlavorEq(rFlavor));
1163 if (it == aInfoToFlavor.end())
1164 aInfoToFlavor.push_back(rFlavor);
1165 return aEntry;
1167 #else
1168 GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
1170 GtkTargetEntry aEntry;
1171 aEntry.target =
1172 g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
1173 aEntry.flags = 0;
1174 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
1175 DataFlavorEq(rFlavor));
1176 if (it != aInfoToFlavor.end())
1177 aEntry.info = std::distance(aInfoToFlavor.begin(), it);
1178 else
1180 aEntry.info = aInfoToFlavor.size();
1181 aInfoToFlavor.push_back(rFlavor);
1183 return aEntry;
1185 #endif
1187 #if GTK_CHECK_VERSION(4, 0, 0)
1189 namespace
1191 void write_mime_type_done(GObject* pStream, GAsyncResult* pResult, gpointer pTaskPtr)
1193 GTask* pTask = static_cast<GTask*>(pTaskPtr);
1195 GError* pError = nullptr;
1196 if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(pStream),
1197 pResult, nullptr, &pError))
1199 g_task_return_error(pTask, pError);
1201 else
1203 g_task_return_boolean(pTask, true);
1206 g_object_unref(pTask);
1209 class MimeTypeEq
1211 private:
1212 const OUString& m_rMimeType;
1213 public:
1214 explicit MimeTypeEq(const OUString& rMimeType) : m_rMimeType(rMimeType) {}
1215 bool operator() (const css::datatransfer::DataFlavor& rData) const
1217 return rData.MimeType == m_rMimeType;
1222 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
1223 GdkContentProvider* provider,
1224 const char* mime_type,
1225 GOutputStream* stream,
1226 int io_priority,
1227 GCancellable* cancellable,
1228 GAsyncReadyCallback callback,
1229 gpointer user_data)
1231 GTask *task = g_task_new(provider, cancellable, callback, user_data);
1232 g_task_set_priority(task, io_priority);
1234 OUString sMimeType(mime_type, strlen(mime_type), RTL_TEXTENCODING_UTF8);
1236 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
1237 MimeTypeEq(sMimeType));
1238 if (it == aInfoToFlavor.end())
1240 SAL_WARN( "vcl.gtk", "unknown mime-type request from clipboard");
1241 g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1242 "unknown mime-type “%s” request from clipboard", mime_type);
1243 g_object_unref(task);
1244 return;
1247 css::datatransfer::DataFlavor aFlavor(*it);
1248 if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
1249 aFlavor.MimeType = "text/plain;charset=utf-8";
1251 Sequence<sal_Int8> aData;
1252 Any aValue;
1256 aValue = rTrans->getTransferData(aFlavor);
1258 catch (...)
1262 if (aValue.getValueTypeClass() == TypeClass_STRING)
1264 OUString aString;
1265 aValue >>= aString;
1266 aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
1268 else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
1270 aValue >>= aData;
1272 else if (aFlavor.MimeType == "text/plain;charset=utf-8")
1274 //didn't have utf-8, try utf-16 and convert
1275 aFlavor.MimeType = "text/plain;charset=utf-16";
1276 aFlavor.DataType = cppu::UnoType<OUString>::get();
1279 aValue = rTrans->getTransferData(aFlavor);
1281 catch (...)
1284 OUString aString;
1285 aValue >>= aString;
1286 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
1288 g_output_stream_write_all_async(stream, aUTF8String.getStr(), aUTF8String.getLength(),
1289 io_priority, cancellable, write_mime_type_done, task);
1290 return;
1293 g_output_stream_write_all_async(stream, aData.getArray(), aData.getLength(),
1294 io_priority, cancellable, write_mime_type_done, task);
1296 #else
1297 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
1298 GtkSelectionData *selection_data, guint info)
1300 GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
1301 RTL_TEXTENCODING_UTF8).getStr(),
1302 false));
1304 css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
1305 if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
1306 aFlavor.MimeType = "text/plain;charset=utf-8";
1308 Sequence<sal_Int8> aData;
1309 Any aValue;
1313 aValue = rTrans->getTransferData(aFlavor);
1315 catch (...)
1319 if (aValue.getValueTypeClass() == TypeClass_STRING)
1321 OUString aString;
1322 aValue >>= aString;
1323 aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
1325 else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
1327 aValue >>= aData;
1329 else if (aFlavor.MimeType == "text/plain;charset=utf-8")
1331 //didn't have utf-8, try utf-16 and convert
1332 aFlavor.MimeType = "text/plain;charset=utf-16";
1333 aFlavor.DataType = cppu::UnoType<OUString>::get();
1336 aValue = rTrans->getTransferData(aFlavor);
1338 catch (...)
1341 OUString aString;
1342 aValue >>= aString;
1343 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
1344 gtk_selection_data_set(selection_data, type, 8,
1345 reinterpret_cast<const guchar *>(aUTF8String.getStr()),
1346 aUTF8String.getLength());
1347 return;
1350 gtk_selection_data_set(selection_data, type, 8,
1351 reinterpret_cast<const guchar *>(aData.getArray()),
1352 aData.getLength());
1354 #endif
1356 VclGtkClipboard::VclGtkClipboard(SelectionType eSelection)
1357 : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
1358 datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
1359 (m_aMutex)
1360 , m_eSelection(eSelection)
1361 , m_pSetClipboardEvent(nullptr)
1362 #if GTK_CHECK_VERSION(4, 0, 0)
1363 , m_pClipboardContent(nullptr)
1364 #endif
1366 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1367 #if GTK_CHECK_VERSION(4, 0, 0)
1368 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "changed",
1369 G_CALLBACK(handle_owner_change), this);
1370 #else
1371 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
1372 G_CALLBACK(handle_owner_change), this);
1373 #endif
1376 void VclGtkClipboard::flushClipboard()
1378 #if !GTK_CHECK_VERSION(4, 0, 0)
1379 SolarMutexGuard aGuard;
1381 if (m_eSelection != SELECTION_CLIPBOARD)
1382 return;
1384 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1385 gtk_clipboard_store(clipboard);
1386 #endif
1389 VclGtkClipboard::~VclGtkClipboard()
1391 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1392 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
1393 if (!m_aGtkTargets.empty())
1395 #if GTK_CHECK_VERSION(4, 0, 0)
1396 gdk_clipboard_set_content(clipboard, nullptr);
1397 m_pClipboardContent = nullptr;
1398 #else
1399 gtk_clipboard_clear(clipboard);
1400 #endif
1401 ClipboardClear();
1403 assert(!m_pSetClipboardEvent);
1404 assert(m_aGtkTargets.empty());
1407 #if GTK_CHECK_VERSION(4, 0, 0)
1408 std::vector<OString> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
1409 #else
1410 std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
1411 #endif
1413 #if GTK_CHECK_VERSION(4, 0, 0)
1414 std::vector<OString> aGtkTargets;
1415 #else
1416 std::vector<GtkTargetEntry> aGtkTargets;
1417 #endif
1419 bool bHaveText(false), bHaveUTF8(false);
1420 for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
1422 sal_Int32 nIndex(0);
1423 if (o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
1425 bHaveText = true;
1426 std::u16string_view aToken(o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex));
1427 if (aToken == u"charset=utf-8")
1429 bHaveUTF8 = true;
1432 aGtkTargets.push_back(makeGtkTargetEntry(rFlavor));
1435 if (bHaveText)
1437 css::datatransfer::DataFlavor aFlavor;
1438 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
1439 if (!bHaveUTF8)
1441 aFlavor.MimeType = "text/plain;charset=utf-8";
1442 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1444 aFlavor.MimeType = "UTF8_STRING";
1445 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1446 aFlavor.MimeType = "STRING";
1447 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1450 return aGtkTargets;
1453 IMPL_LINK_NOARG(VclGtkClipboard, AsyncSetGtkClipboard, void*, void)
1455 osl::Guard aGuard( m_aMutex );
1456 m_pSetClipboardEvent = nullptr;
1457 SetGtkClipboard();
1460 void VclGtkClipboard::SyncGtkClipboard()
1462 osl::Guard aGuard(m_aMutex);
1463 if (m_pSetClipboardEvent)
1465 Application::RemoveUserEvent(m_pSetClipboardEvent);
1466 m_pSetClipboardEvent = nullptr;
1467 SetGtkClipboard();
1471 void VclGtkClipboard::SetGtkClipboard()
1473 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1474 #if GTK_CHECK_VERSION(4, 0, 0)
1475 m_pClipboardContent = TRANSFERABLE_CONTENT(transerable_content_new(&m_aConversionHelper, m_aContents.get()));
1476 transerable_content_set_detach_clipboard_link(m_pClipboardContent, LINK(this, VclGtkClipboard, DetachClipboard));
1477 gdk_clipboard_set_content(clipboard, GDK_CONTENT_PROVIDER(m_pClipboardContent));
1478 #else
1479 gtk_clipboard_set_with_data(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size(),
1480 ClipboardGetFunc, ClipboardClearFunc, this);
1481 gtk_clipboard_set_can_store(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size());
1482 #endif
1485 void VclGtkClipboard::setContents(
1486 const Reference< css::datatransfer::XTransferable >& xTrans,
1487 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
1489 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
1490 if (xTrans.is())
1492 aFormats = xTrans->getTransferDataFlavors();
1495 osl::ClearableMutexGuard aGuard( m_aMutex );
1496 Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
1497 Reference< datatransfer::XTransferable > xOldContents( m_aContents );
1498 m_aContents = xTrans;
1499 #if GTK_CHECK_VERSION(4, 0, 0)
1500 if (m_pClipboardContent)
1501 transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
1502 #endif
1503 m_aOwner = xClipboardOwner;
1505 std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
1506 datatransfer::clipboard::ClipboardEvent aEv;
1508 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1509 if (!m_aGtkTargets.empty())
1511 #if GTK_CHECK_VERSION(4, 0, 0)
1512 gdk_clipboard_set_content(clipboard, nullptr);
1513 m_pClipboardContent = nullptr;
1514 #else
1515 gtk_clipboard_clear(clipboard);
1516 #endif
1517 ClipboardClear();
1519 assert(m_aGtkTargets.empty());
1520 if (m_aContents.is())
1522 #if GTK_CHECK_VERSION(4, 0, 0)
1523 std::vector<OString> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1524 #else
1525 std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1526 #endif
1527 if (!aGtkTargets.empty())
1529 #if !GTK_CHECK_VERSION(4, 0, 0)
1530 GtkTargetEntry aEntry;
1531 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1532 aEntry.target = g_strdup(sTunnel.getStr());
1533 aEntry.flags = 0;
1534 aEntry.info = 0;
1535 aGtkTargets.push_back(aEntry);
1536 #endif
1537 m_aGtkTargets = aGtkTargets;
1539 if (!m_pSetClipboardEvent)
1540 m_pSetClipboardEvent = Application::PostUserEvent(LINK(this, VclGtkClipboard, AsyncSetGtkClipboard));
1544 aEv.Contents = getContents();
1546 aGuard.clear();
1548 if (xOldOwner.is() && xOldOwner != xClipboardOwner)
1549 xOldOwner->lostOwnership( this, xOldContents );
1550 for (auto const& listener : aListeners)
1552 listener->changedContents( aEv );
1556 OUString VclGtkClipboard::getName()
1558 return (m_eSelection == SELECTION_CLIPBOARD) ? u"CLIPBOARD"_ustr : u"PRIMARY"_ustr;
1561 sal_Int8 VclGtkClipboard::getRenderingCapabilities()
1563 return 0;
1566 void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1568 osl::Guard aGuard( m_aMutex );
1570 m_aListeners.push_back( listener );
1573 void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1575 osl::Guard aGuard( m_aMutex );
1577 std::erase(m_aListeners, listener);
1580 Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
1582 if ( o3tl::IsRunningUnitTest() || o3tl::IsRunningUITest() )
1583 return SalInstance::CreateClipboard( arguments );
1585 OUString sel;
1586 if (!arguments.hasElements()) {
1587 sel = "CLIPBOARD";
1588 } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
1589 throw css::lang::IllegalArgumentException(
1590 u"bad GtkInstance::CreateClipboard arguments"_ustr,
1591 css::uno::Reference<css::uno::XInterface>(), -1);
1594 SelectionType eSelection = (sel == "CLIPBOARD") ? SELECTION_CLIPBOARD : SELECTION_PRIMARY;
1596 if (m_aClipboards[eSelection].is())
1597 return m_aClipboards[eSelection];
1599 Reference<XInterface> xClipboard(getXWeak(new VclGtkClipboard(eSelection)));
1600 m_aClipboards[eSelection] = xClipboard;
1601 return xClipboard;
1604 GtkInstDropTarget::GtkInstDropTarget()
1605 : WeakComponentImplHelper(m_aMutex)
1606 , m_pFrame(nullptr)
1607 , m_pFormatConversionRequest(nullptr)
1608 , m_bActive(false)
1609 #if !GTK_CHECK_VERSION(4, 0, 0)
1610 , m_bInDrag(false)
1611 #endif
1612 , m_nDefaultActions(0)
1616 OUString SAL_CALL GtkInstDropTarget::getImplementationName()
1618 return u"com.sun.star.datatransfer.dnd.VclGtkDropTarget"_ustr;
1621 sal_Bool SAL_CALL GtkInstDropTarget::supportsService(OUString const & ServiceName)
1623 return cppu::supportsService(this, ServiceName);
1626 css::uno::Sequence<OUString> SAL_CALL GtkInstDropTarget::getSupportedServiceNames()
1628 Sequence<OUString> aRet { u"com.sun.star.datatransfer.dnd.GtkDropTarget"_ustr };
1629 return aRet;
1632 GtkInstDropTarget::~GtkInstDropTarget()
1634 if (m_pFrame)
1635 m_pFrame->deregisterDropTarget(this);
1638 void GtkInstDropTarget::deinitialize()
1640 m_pFrame = nullptr;
1641 m_bActive = false;
1644 void GtkInstDropTarget::initialize(const Sequence<Any>& rArguments)
1646 if (rArguments.getLength() < 2)
1648 throw RuntimeException(u"DropTarget::initialize: Cannot install window event handler"_ustr,
1649 getXWeak());
1652 sal_IntPtr nFrame = 0;
1653 rArguments.getConstArray()[1] >>= nFrame;
1655 if (!nFrame)
1657 throw RuntimeException(u"DropTarget::initialize: missing SalFrame"_ustr,
1658 getXWeak());
1661 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1662 m_pFrame->registerDropTarget(this);
1663 m_bActive = true;
1666 void GtkInstDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1668 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1670 m_aListeners.push_back( xListener );
1673 void GtkInstDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1675 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1677 std::erase(m_aListeners, xListener);
1680 void GtkInstDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
1682 osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
1683 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1684 aGuard.clear();
1686 for (auto const& listener : aListeners)
1688 listener->drop( dtde );
1692 void GtkInstDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
1694 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1695 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1696 aGuard.clear();
1698 for (auto const& listener : aListeners)
1700 listener->dragEnter( dtde );
1704 void GtkInstDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
1706 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1707 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1708 aGuard.clear();
1710 for (auto const& listener : aListeners)
1712 listener->dragOver( dtde );
1716 void GtkInstDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
1718 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1719 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1720 aGuard.clear();
1722 for (auto const& listener : aListeners)
1724 listener->dragExit( dte );
1728 sal_Bool GtkInstDropTarget::isActive()
1730 return m_bActive;
1733 void GtkInstDropTarget::setActive(sal_Bool bActive)
1735 m_bActive = bActive;
1738 sal_Int8 GtkInstDropTarget::getDefaultActions()
1740 return m_nDefaultActions;
1743 void GtkInstDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
1745 m_nDefaultActions = nDefaultActions;
1748 Reference<XInterface> GtkInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
1750 return vcl::X11DnDHelper(new GtkInstDropTarget(), pSysEnv->aShellWindow);
1753 GtkInstDragSource::~GtkInstDragSource()
1755 if (m_pFrame)
1756 m_pFrame->deregisterDragSource(this);
1758 if (GtkInstDragSource::g_ActiveDragSource == this)
1760 SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkInstDragSource before dtor");
1761 GtkInstDragSource::g_ActiveDragSource = nullptr;
1765 void GtkInstDragSource::deinitialize()
1767 m_pFrame = nullptr;
1770 sal_Bool GtkInstDragSource::isDragImageSupported()
1772 return true;
1775 sal_Int32 GtkInstDragSource::getDefaultCursor( sal_Int8 )
1777 return 0;
1780 void GtkInstDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
1782 if (rArguments.getLength() < 2)
1784 throw RuntimeException(u"DragSource::initialize: Cannot install window event handler"_ustr,
1785 getXWeak());
1788 sal_IntPtr nFrame = 0;
1789 rArguments.getConstArray()[1] >>= nFrame;
1791 if (!nFrame)
1793 throw RuntimeException(u"DragSource::initialize: missing SalFrame"_ustr,
1794 getXWeak());
1797 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1798 m_pFrame->registerDragSource(this);
1801 OUString SAL_CALL GtkInstDragSource::getImplementationName()
1803 return u"com.sun.star.datatransfer.dnd.VclGtkDragSource"_ustr;
1806 sal_Bool SAL_CALL GtkInstDragSource::supportsService(OUString const & ServiceName)
1808 return cppu::supportsService(this, ServiceName);
1811 css::uno::Sequence<OUString> SAL_CALL GtkInstDragSource::getSupportedServiceNames()
1813 Sequence<OUString> aRet { u"com.sun.star.datatransfer.dnd.GtkDragSource"_ustr };
1814 return aRet;
1817 Reference<XInterface> GtkInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
1819 return vcl::X11DnDHelper(new GtkInstDragSource(), pSysEnv->aShellWindow);
1822 namespace {
1824 class GtkOpenGLContext : public OpenGLContext
1826 GLWindow m_aGLWin;
1827 GtkWidget *m_pGLArea;
1828 GdkGLContext *m_pContext;
1829 gulong m_nDestroySignalId;
1830 gulong m_nRenderSignalId;
1831 guint m_nAreaFrameBuffer;
1832 guint m_nFrameBuffer;
1833 guint m_nRenderBuffer;
1834 guint m_nDepthBuffer;
1835 guint m_nFrameScratchBuffer;
1836 guint m_nRenderScratchBuffer;
1837 guint m_nDepthScratchBuffer;
1839 public:
1840 GtkOpenGLContext()
1841 : m_pGLArea(nullptr)
1842 , m_pContext(nullptr)
1843 , m_nDestroySignalId(0)
1844 , m_nRenderSignalId(0)
1845 , m_nAreaFrameBuffer(0)
1846 , m_nFrameBuffer(0)
1847 , m_nRenderBuffer(0)
1848 , m_nDepthBuffer(0)
1849 , m_nFrameScratchBuffer(0)
1850 , m_nRenderScratchBuffer(0)
1851 , m_nDepthScratchBuffer(0)
1855 virtual void initWindow() override
1857 if( !m_pChildWindow )
1859 SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
1860 m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
1863 if (m_pChildWindow)
1865 InitChildWindow(m_pChildWindow.get());
1869 private:
1870 virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
1871 virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
1873 static void signalDestroy(GtkWidget*, gpointer context)
1875 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
1876 pThis->m_pGLArea = nullptr;
1877 pThis->m_nDestroySignalId = 0;
1878 pThis->m_nRenderSignalId = 0;
1881 static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
1883 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
1885 int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
1886 int width = pThis->m_aGLWin.Width * scale;
1887 int height = pThis->m_aGLWin.Height * scale;
1889 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1891 glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
1892 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1894 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1895 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1897 gdk_gl_context_make_current(pThis->m_pContext);
1898 return true;
1901 virtual void adjustToNewSize() override
1903 if (!m_pGLArea)
1904 return;
1906 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1907 int width = m_aGLWin.Width * scale;
1908 int height = m_aGLWin.Height * scale;
1910 // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
1911 int allocwidth = std::max(width, 1);
1912 int allocheight = std::max(height, 1);
1914 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1915 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1917 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1918 return;
1921 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1922 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1923 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1924 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1925 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
1927 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1928 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1929 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1930 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1932 gdk_gl_context_make_current(m_pContext);
1933 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1934 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1935 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
1937 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1938 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1939 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1940 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1941 glViewport(0, 0, width, height);
1943 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1944 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1945 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1946 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1947 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1949 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1950 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1951 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1952 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1954 glViewport(0, 0, width, height);
1957 // Use a throw away toplevel to determine the OpenGL version because once
1958 // an GdkGLContext is created for a window then it seems that
1959 // glGenVertexArrays will always be called when the window gets rendered.
1960 static int GetOpenGLVersion()
1962 int nMajorGLVersion(0);
1964 GtkWidget* pWindow;
1965 #if !GTK_CHECK_VERSION(4,0,0)
1966 pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1967 #else
1968 pWindow = gtk_window_new();
1969 #endif
1971 gtk_widget_realize(pWindow);
1973 if (GdkSurface* pSurface = widget_get_surface(pWindow))
1975 if (GdkGLContext* pContext = surface_create_gl_context(pSurface))
1977 if (gdk_gl_context_realize(pContext, nullptr))
1979 OpenGLZone aZone;
1980 gdk_gl_context_make_current(pContext);
1981 gdk_gl_context_get_version(pContext, &nMajorGLVersion, nullptr);
1982 gdk_gl_context_clear_current();
1984 g_object_unref(pContext);
1988 #if !GTK_CHECK_VERSION(4,0,0)
1989 gtk_widget_destroy(pWindow);
1990 #else
1991 gtk_window_destroy(GTK_WINDOW(pWindow));
1992 #endif
1993 return nMajorGLVersion;
1996 virtual bool ImplInit() override
1998 static int nOpenGLVersion = GetOpenGLVersion();
1999 if (nOpenGLVersion < 3)
2001 SAL_WARN("vcl.gtk", "gtk GL requires glGenVertexArrays which is OpenGL 3, while system provides: " << nOpenGLVersion);
2002 return false;
2005 const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
2006 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
2007 m_pGLArea = gtk_gl_area_new();
2008 m_nDestroySignalId = g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
2009 m_nRenderSignalId = g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
2010 gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
2011 gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
2012 gtk_widget_set_hexpand(m_pGLArea, true);
2013 gtk_widget_set_vexpand(m_pGLArea, true);
2014 #if !GTK_CHECK_VERSION(4, 0, 0)
2015 gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
2016 gtk_widget_show_all(pParent);
2017 #else
2018 gtk_grid_attach(GTK_GRID(pParent), m_pGLArea, 0, 0, 1, 1);
2019 gtk_widget_show(pParent);
2020 gtk_widget_show(m_pGLArea);
2021 #endif
2023 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
2024 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
2026 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
2027 return false;
2030 gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
2031 glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
2033 GdkSurface* pWindow = widget_get_surface(pParent);
2034 m_pContext = surface_create_gl_context(pWindow);
2035 if (!m_pContext)
2036 return false;
2038 if (!gdk_gl_context_realize(m_pContext, nullptr))
2039 return false;
2041 gdk_gl_context_make_current(m_pContext);
2042 glGenFramebuffersEXT(1, &m_nFrameBuffer);
2043 glGenRenderbuffersEXT(1, &m_nRenderBuffer);
2044 glGenRenderbuffersEXT(1, &m_nDepthBuffer);
2045 glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
2046 glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
2047 glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
2049 bool bRet = InitGL();
2050 InitGLDebugging();
2051 return bRet;
2054 virtual void restoreDefaultFramebuffer() override
2056 OpenGLContext::restoreDefaultFramebuffer();
2057 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
2058 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
2059 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
2062 virtual void makeCurrent() override
2064 if (isCurrent())
2065 return;
2067 clearCurrent();
2069 if (m_pGLArea)
2071 int scale = gtk_widget_get_scale_factor(m_pGLArea);
2072 int width = m_aGLWin.Width * scale;
2073 int height = m_aGLWin.Height * scale;
2075 gdk_gl_context_make_current(m_pContext);
2077 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
2078 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
2079 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
2080 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
2081 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
2082 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
2083 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
2084 glViewport(0, 0, width, height);
2087 registerAsCurrent();
2090 virtual void destroyCurrentContext() override
2092 gdk_gl_context_clear_current();
2095 virtual bool isCurrent() override
2097 return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
2100 virtual void sync() override
2104 virtual void resetCurrent() override
2106 clearCurrent();
2107 gdk_gl_context_clear_current();
2110 virtual void swapBuffers() override
2112 int scale = gtk_widget_get_scale_factor(m_pGLArea);
2113 int width = m_aGLWin.Width * scale;
2114 int height = m_aGLWin.Height * scale;
2116 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
2117 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
2119 glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
2120 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
2122 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
2123 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
2125 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
2126 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
2128 gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
2129 BuffersSwapped();
2132 virtual ~GtkOpenGLContext() override
2134 if (m_nDestroySignalId)
2135 g_signal_handler_disconnect(m_pGLArea, m_nDestroySignalId);
2136 if (m_nRenderSignalId)
2137 g_signal_handler_disconnect(m_pGLArea, m_nRenderSignalId);
2138 if (m_pContext)
2139 g_clear_object(&m_pContext);
2145 OpenGLContext* GtkInstance::CreateOpenGLContext()
2147 return new GtkOpenGLContext;
2150 // tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
2151 bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
2153 static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
2154 if (!get_type)
2155 return false;
2156 static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
2157 return bResult;
2160 bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
2162 static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
2163 if (!get_type)
2164 return false;
2165 static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
2166 return bResult;
2169 namespace
2172 class GtkInstanceBuilder;
2174 void set_help_id(const GtkWidget *pWidget, std::u16string_view rHelpId)
2176 gchar *helpid = g_strdup(OUStringToOString(rHelpId, RTL_TEXTENCODING_UTF8).getStr());
2177 g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
2180 OUString get_help_id(const GtkWidget *pWidget)
2182 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
2183 const gchar* pStr = static_cast<const gchar*>(pData);
2184 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2187 KeyEvent CreateKeyEvent(guint keyval, guint16 hardware_keycode, guint state, guint8 group)
2189 sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(keyval);
2190 #if !GTK_CHECK_VERSION(4, 0, 0)
2191 if (nKeyCode == 0)
2193 guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), hardware_keycode, group);
2194 nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
2196 #else
2197 (void)hardware_keycode;
2198 (void)group;
2199 #endif
2200 nKeyCode |= GtkSalFrame::GetKeyModCode(state);
2201 return KeyEvent(gdk_keyval_to_unicode(keyval), nKeyCode, 0);
2204 #if !GTK_CHECK_VERSION(4, 0, 0)
2205 KeyEvent GtkToVcl(const GdkEventKey& rEvent)
2207 return CreateKeyEvent(rEvent.keyval, rEvent.hardware_keycode, rEvent.state, rEvent.group);
2209 #endif
2212 static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
2214 MouseEventModifiers nMode = MouseEventModifiers::NONE;
2215 if ( nButton == MOUSE_LEFT )
2216 nMode |= MouseEventModifiers::SIMPLECLICK;
2217 if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
2218 nMode |= MouseEventModifiers::SELECT;
2219 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
2220 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
2221 nMode |= MouseEventModifiers::MULTISELECT;
2222 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
2223 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
2224 nMode |= MouseEventModifiers::RANGESELECT;
2225 return nMode;
2228 static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
2230 MouseEventModifiers nMode = MouseEventModifiers::NONE;
2231 if ( !nCode )
2232 nMode |= MouseEventModifiers::SIMPLEMOVE;
2233 if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
2234 nMode |= MouseEventModifiers::DRAGMOVE;
2235 if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
2236 nMode |= MouseEventModifiers::DRAGCOPY;
2237 return nMode;
2240 namespace
2242 bool SwapForRTL(GtkWidget* pWidget)
2244 GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
2245 if (eDir == GTK_TEXT_DIR_RTL)
2246 return true;
2247 if (eDir == GTK_TEXT_DIR_LTR)
2248 return false;
2249 return AllSettings::GetLayoutRTL();
2252 GtkWidget* getPopupRect(GtkWidget* pWidget, const tools::Rectangle& rInRect, GdkRectangle& rOutRect)
2254 if (GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pWidget))
2256 // this is the relatively unusual case where pParent is the toplevel GtkSalFrame and not a stock GtkWidget
2257 // so use the same style of logic as GtkSalMenu::ShowNativePopupMenu to get the right position
2258 AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pFrame->GetWindow(), rInRect);
2259 aFloatRect.Move(-pFrame->maGeometry.x(), -pFrame->maGeometry.y());
2261 rOutRect = GdkRectangle{static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
2262 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
2264 pWidget = pFrame->getMouseEventWidget();
2266 else
2268 rOutRect = GdkRectangle{static_cast<int>(rInRect.Left()), static_cast<int>(rInRect.Top()),
2269 static_cast<int>(rInRect.GetWidth()), static_cast<int>(rInRect.GetHeight())};
2270 if (SwapForRTL(pWidget))
2271 rOutRect.x = gtk_widget_get_allocated_width(pWidget) - rOutRect.width - 1 - rOutRect.x;
2273 return pWidget;
2276 void replaceWidget(GtkWidget* pWidget, GtkWidget* pReplacement)
2278 // remove the widget and replace it with pReplacement
2279 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
2281 // if pWidget was un-parented then don't bother
2282 if (!pParent)
2283 return;
2285 g_object_ref(pWidget);
2287 gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
2288 if (GTK_IS_GRID(pParent))
2290 #if !GTK_CHECK_VERSION(4, 0, 0)
2291 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
2292 "left-attach", &nLeftAttach,
2293 "top-attach", &nTopAttach,
2294 "width", &nWidth,
2295 "height", &nHeight,
2296 nullptr);
2297 #else
2298 gtk_grid_query_child(GTK_GRID(pParent), pWidget,
2299 &nLeftAttach, &nTopAttach,
2300 &nWidth, &nHeight);
2301 #endif
2304 #if !GTK_CHECK_VERSION(4, 0, 0)
2305 gboolean bExpand(false), bFill(false);
2306 GtkPackType ePackType(GTK_PACK_START);
2307 guint nPadding(0);
2308 gint nPosition(0);
2309 if (GTK_IS_BOX(pParent))
2311 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
2312 "expand", &bExpand,
2313 "fill", &bFill,
2314 "pack-type", &ePackType,
2315 "padding", &nPadding,
2316 "position", &nPosition,
2317 nullptr);
2319 #endif
2321 #if !GTK_CHECK_VERSION(4, 0, 0)
2322 // for gtk3 remove before replacement inserted, or there are warnings
2323 // from GTK_BIN about having two children
2324 container_remove(pParent, pWidget);
2325 #endif
2327 gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
2328 #if !GTK_CHECK_VERSION(4, 0, 0)
2329 gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
2330 #endif
2332 int nReqWidth, nReqHeight;
2333 gtk_widget_get_size_request(pWidget, &nReqWidth, &nReqHeight);
2334 gtk_widget_set_size_request(pReplacement, nReqWidth, nReqHeight);
2336 static GQuark quark_size_groups = g_quark_from_static_string("gtk-widget-size-groups");
2337 GSList* pSizeGroups = static_cast<GSList*>(g_object_get_qdata(G_OBJECT(pWidget), quark_size_groups));
2338 while (pSizeGroups)
2340 GtkSizeGroup *pSizeGroup = static_cast<GtkSizeGroup*>(pSizeGroups->data);
2341 pSizeGroups = pSizeGroups->next;
2342 gtk_size_group_remove_widget(pSizeGroup, pWidget);
2343 gtk_size_group_add_widget(pSizeGroup, pReplacement);
2346 // tdf#135368 change the mnemonic to point to our replacement
2347 GList* pLabels = gtk_widget_list_mnemonic_labels(pWidget);
2348 for (GList* pLabel = g_list_first(pLabels); pLabel; pLabel = g_list_next(pLabel))
2350 GtkWidget* pLabelWidget = static_cast<GtkWidget*>(pLabel->data);
2351 if (!GTK_IS_LABEL(pLabelWidget))
2352 continue;
2353 gtk_label_set_mnemonic_widget(GTK_LABEL(pLabelWidget), pReplacement);
2355 g_list_free(pLabels);
2358 if (GTK_IS_GRID(pParent))
2360 gtk_grid_attach(GTK_GRID(pParent), pReplacement, nLeftAttach, nTopAttach, nWidth, nHeight);
2362 else if (GTK_IS_BOX(pParent))
2364 #if !GTK_CHECK_VERSION(4, 0, 0)
2365 gtk_box_pack_start(GTK_BOX(pParent), pReplacement, bExpand, bFill, nPadding);
2366 gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
2367 "pack-type", ePackType,
2368 "position", nPosition,
2369 nullptr);
2370 #else
2371 gtk_box_insert_child_after(GTK_BOX(pParent), pReplacement, pWidget);
2372 #endif
2374 #if !GTK_CHECK_VERSION(4, 0, 0)
2375 else
2376 gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
2377 #endif
2379 if (gtk_widget_get_hexpand_set(pWidget))
2380 gtk_widget_set_hexpand(pReplacement, gtk_widget_get_hexpand(pWidget));
2382 if (gtk_widget_get_vexpand_set(pWidget))
2383 gtk_widget_set_vexpand(pReplacement, gtk_widget_get_vexpand(pWidget));
2385 gtk_widget_set_halign(pReplacement, gtk_widget_get_halign(pWidget));
2386 gtk_widget_set_valign(pReplacement, gtk_widget_get_valign(pWidget));
2388 #if GTK_CHECK_VERSION(4, 0, 0)
2389 // for gtk4 remove after replacement inserted so we could use gtk_box_insert_child_after
2390 container_remove(pParent, pWidget);
2391 #endif
2393 // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
2394 g_object_unref(pWidget);
2397 void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
2399 g_object_ref(pWidget);
2401 replaceWidget(pWidget, pReplacement);
2403 // coverity[pass_freed_arg : FALSE] - pWidget is not freed here due to initial g_object_ref
2404 container_add(pReplacement, pWidget);
2406 // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
2407 g_object_unref(pWidget);
2410 GtkWidget* ensureEventWidget(GtkWidget* pWidget)
2412 #if GTK_CHECK_VERSION(4, 0, 0)
2413 return pWidget;
2414 #else
2416 if (!pWidget)
2417 return nullptr;
2419 GtkWidget* pMouseEventBox;
2420 // not every widget has a GdkWindow and can get any event, so if we
2421 // want an event it doesn't have, insert a GtkEventBox so we can get
2422 // those
2423 if (gtk_widget_get_has_window(pWidget))
2424 pMouseEventBox = pWidget;
2425 else
2427 // remove the widget and replace it with an eventbox and put the old
2428 // widget into it
2429 pMouseEventBox = gtk_event_box_new();
2430 gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
2431 gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
2432 insertAsParent(pWidget, pMouseEventBox);
2435 return pMouseEventBox;
2436 #endif
2440 namespace {
2442 #if !GTK_CHECK_VERSION(4, 0, 0)
2443 GdkDragAction VclToGdk(sal_Int8 dragOperation)
2445 GdkDragAction eRet(static_cast<GdkDragAction>(0));
2446 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
2447 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
2448 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
2449 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
2450 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
2451 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
2452 return eRet;
2454 #endif
2456 GtkWindow* get_active_window()
2458 GtkWindow* pFocus = nullptr;
2460 GList* pList = gtk_window_list_toplevels();
2462 for (GList* pEntry = pList; pEntry; pEntry = pEntry->next)
2464 #if GTK_CHECK_VERSION(4, 0, 0)
2465 if (gtk_window_is_active(GTK_WINDOW(pEntry->data)))
2466 #else
2467 if (gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry->data)))
2468 #endif
2470 pFocus = GTK_WINDOW(pEntry->data);
2471 break;
2475 g_list_free(pList);
2477 return pFocus;
2480 void LocalizeDecimalSeparator(guint& keyval)
2482 const bool bDecimalKey = keyval == GDK_KEY_KP_Decimal || keyval == GDK_KEY_KP_Separator;
2483 // #i1820# (and tdf#154623) use locale specific decimal separator
2484 if (bDecimalKey && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2486 GtkWindow* pFocusWin = get_active_window();
2487 GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
2488 // tdf#138932 except if the target is a GtkEntry used for passwords
2489 // GTK4: TODO is it a GtkEntry or a child GtkText that has the focus in this situation?
2490 if (!pFocus || !GTK_IS_ENTRY(pFocus) || gtk_entry_get_visibility(GTK_ENTRY(pFocus)))
2492 OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
2493 keyval = aSep[0];
2498 void set_cursor(GtkWidget* pWidget, const char *pName)
2500 if (!gtk_widget_get_realized(pWidget))
2501 gtk_widget_realize(pWidget);
2502 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
2503 #if GTK_CHECK_VERSION(4, 0, 0)
2504 GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pName, nullptr) : nullptr;
2505 #else
2506 GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
2507 #endif
2508 widget_set_cursor(pWidget, pCursor);
2509 gdk_display_flush(pDisplay);
2510 if (pCursor)
2511 g_object_unref(pCursor);
2514 vcl::Font get_font(GtkWidget* pWidget)
2516 PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
2517 return pango_to_vcl(pango_context_get_font_description(pContext),
2518 Application::GetSettings().GetUILanguageTag().getLocale());
2523 OUString get_buildable_id(GtkBuildable* pWidget)
2525 #if GTK_CHECK_VERSION(4, 0, 0)
2526 const gchar* pStr = gtk_buildable_get_buildable_id(pWidget);
2527 #else
2528 const gchar* pStr = gtk_buildable_get_name(pWidget);
2529 #endif
2530 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2533 void set_buildable_id(GtkBuildable* pWidget, const OUString& rId)
2535 #if GTK_CHECK_VERSION(4, 0, 0)
2536 GtkBuildableIface *iface = GTK_BUILDABLE_GET_IFACE(pWidget);
2537 (*iface->set_id)(pWidget, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
2538 #else
2539 gtk_buildable_set_name(pWidget, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
2540 #endif
2543 namespace {
2545 class GtkInstanceWidget : public virtual weld::Widget
2547 protected:
2548 GtkWidget* m_pWidget;
2549 GtkWidget* m_pMouseEventBox;
2550 GtkInstanceBuilder* m_pBuilder;
2552 #if !GTK_CHECK_VERSION(4, 0, 0)
2553 DECL_LINK(async_drag_cancel, void*, void);
2554 #endif
2556 bool IsFirstFreeze() const { return m_nFreezeCount == 0; }
2557 bool IsLastThaw() const { return m_nFreezeCount == 1; }
2559 #if GTK_CHECK_VERSION(4, 0, 0)
2560 static void signalFocusIn(GtkEventControllerFocus*, gpointer widget)
2562 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2563 SolarMutexGuard aGuard;
2564 pThis->signal_focus_in();
2566 #else
2567 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
2569 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2570 SolarMutexGuard aGuard;
2571 pThis->signal_focus_in();
2572 return false;
2574 #endif
2576 void signal_focus_in()
2578 GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
2579 // see commentary in GtkSalObjectWidgetClip::Show
2580 if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
2581 return;
2583 m_aFocusInHdl.Call(*this);
2586 static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
2588 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2589 SolarMutexGuard aGuard;
2590 return pThis->signal_mnemonic_activate();
2593 bool signal_mnemonic_activate()
2595 return m_aMnemonicActivateHdl.Call(*this);
2598 #if GTK_CHECK_VERSION(4, 0, 0)
2599 static void signalFocusOut(GtkEventControllerFocus*, gpointer widget)
2601 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2602 SolarMutexGuard aGuard;
2603 pThis->signal_focus_in();
2605 #else
2606 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
2608 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2609 SolarMutexGuard aGuard;
2610 pThis->signal_focus_out();
2611 return false;
2613 #endif
2615 #if !GTK_CHECK_VERSION(4, 0, 0)
2616 void launch_drag_cancel(GdkDragContext* context)
2618 // post our drag cancel to happen at the next available event cycle
2619 if (m_pDragCancelEvent)
2620 return;
2621 g_object_ref(context);
2622 m_pDragCancelEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_drag_cancel), context);
2624 #endif
2626 void signal_focus_out()
2628 GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
2629 // see commentary in GtkSalObjectWidgetClip::Show
2630 if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
2631 return;
2633 m_aFocusOutHdl.Call(*this);
2636 virtual void ensureMouseEventWidget()
2638 if (!m_pMouseEventBox)
2639 m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
2642 void ensureButtonPressSignal()
2644 if (!m_nButtonPressSignalId)
2646 #if GTK_CHECK_VERSION(4, 0, 0)
2647 GtkEventController* pClickController = get_click_controller();
2648 m_nButtonPressSignalId = g_signal_connect(pClickController, "pressed", G_CALLBACK(signalButtonPress), this);
2649 #else
2650 ensureMouseEventWidget();
2651 m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButtonPress), this);
2652 #endif
2656 void ensureButtonReleaseSignal()
2658 if (!m_nButtonReleaseSignalId)
2660 #if GTK_CHECK_VERSION(4, 0, 0)
2661 GtkEventController* pClickController = get_click_controller();
2662 m_nButtonReleaseSignalId = g_signal_connect(pClickController, "released", G_CALLBACK(signalButtonRelease), this);
2663 #else
2664 ensureMouseEventWidget();
2665 m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButtonRelease), this);
2666 #endif
2670 #if !GTK_CHECK_VERSION(4, 0, 0)
2671 static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
2673 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2674 SolarMutexGuard aGuard;
2675 //center it when we don't know where else to use
2676 Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
2677 gtk_widget_get_allocated_height(pWidget) / 2);
2678 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
2679 return pThis->signal_popup_menu(aCEvt);
2681 #endif
2683 virtual void connect_style_updated(const Link<Widget&, void>& rLink) override
2685 if (m_aStyleUpdatedHdl.IsSet())
2686 ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
2687 weld::Widget::connect_style_updated(rLink);
2688 if (m_aStyleUpdatedHdl.IsSet())
2689 ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
2692 bool SwapForRTL() const
2694 return ::SwapForRTL(m_pWidget);
2697 void do_enable_drag_source(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
2699 ensure_drag_source();
2701 #if !GTK_CHECK_VERSION(4, 0, 0)
2702 auto aFormats = rHelper->getTransferDataFlavors();
2703 std::vector<GtkTargetEntry> aGtkTargets(m_xDragSource->FormatsToGtk(aFormats));
2705 m_eDragAction = VclToGdk(eDNDConstants);
2706 drag_source_set(aGtkTargets, m_eDragAction);
2708 for (auto &a : aGtkTargets)
2709 g_free(a.target);
2711 m_xDragSource->set_datatransfer(rHelper, rHelper);
2712 #else
2713 (void)rHelper;
2714 (void)eDNDConstants;
2715 #endif
2718 void localizeDecimalSeparator()
2720 // tdf#128867 if localize decimal separator is active we will always
2721 // need to be able to change the output of the decimal key press
2722 if (!m_nKeyPressSignalId && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2724 #if GTK_CHECK_VERSION(4, 0, 0)
2725 m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
2726 #else
2727 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
2728 #endif
2732 void ensure_drag_begin_end()
2734 if (!m_nDragBeginSignalId)
2736 // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
2737 #if GTK_CHECK_VERSION(4, 0, 0)
2738 m_nDragBeginSignalId = g_signal_connect_after(get_drag_controller(), "drag-begin", G_CALLBACK(signalDragBegin), this);
2739 #else
2740 m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
2741 #endif
2743 if (!m_nDragEndSignalId)
2745 #if GTK_CHECK_VERSION(4, 0, 0)
2746 m_nDragEndSignalId = g_signal_connect(get_drag_controller(), "drag-end", G_CALLBACK(signalDragEnd), this);
2747 #else
2748 m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
2749 #endif
2753 void DisconnectMouseEvents()
2755 if (m_nButtonPressSignalId)
2757 #if GTK_CHECK_VERSION(4, 0, 0)
2758 g_signal_handler_disconnect(get_click_controller(), m_nButtonPressSignalId);
2759 #else
2760 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
2761 #endif
2762 m_nButtonPressSignalId = 0;
2764 if (m_nMotionSignalId)
2766 #if GTK_CHECK_VERSION(4, 0, 0)
2767 g_signal_handler_disconnect(get_motion_controller(), m_nMotionSignalId);
2768 #else
2769 g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
2770 #endif
2771 m_nMotionSignalId = 0;
2773 if (m_nLeaveSignalId)
2775 #if GTK_CHECK_VERSION(4, 0, 0)
2776 g_signal_handler_disconnect(get_motion_controller(), m_nLeaveSignalId);
2777 #else
2778 g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
2779 #endif
2780 m_nLeaveSignalId = 0;
2782 if (m_nEnterSignalId)
2784 #if GTK_CHECK_VERSION(4, 0, 0)
2785 g_signal_handler_disconnect(get_motion_controller(), m_nEnterSignalId);
2786 #else
2787 g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
2788 #endif
2789 m_nEnterSignalId = 0;
2791 if (m_nButtonReleaseSignalId)
2793 #if GTK_CHECK_VERSION(4, 0, 0)
2794 g_signal_handler_disconnect(get_click_controller(), m_nButtonReleaseSignalId);
2795 #else
2796 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
2797 #endif
2798 m_nButtonReleaseSignalId = 0;
2801 #if !GTK_CHECK_VERSION(4, 0, 0)
2802 if (!m_pMouseEventBox || m_pMouseEventBox == m_pWidget)
2803 return;
2805 // GtkWindow replacement for GtkPopover case
2806 if (!GTK_IS_EVENT_BOX(m_pMouseEventBox))
2808 m_pMouseEventBox = nullptr;
2809 return;
2812 // put things back they way we found them
2813 GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
2815 g_object_ref(m_pWidget);
2816 gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
2818 gtk_widget_destroy(m_pMouseEventBox);
2820 gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
2821 // coverity[freed_arg : FALSE] - this does not free m_pWidget, it is reffed by pParent
2822 g_object_unref(m_pWidget);
2824 m_pMouseEventBox = m_pWidget;
2825 #endif
2828 private:
2829 bool m_bTakeOwnership;
2830 #if !GTK_CHECK_VERSION(4, 0, 0)
2831 bool m_bDraggedOver;
2832 #endif
2833 int m_nWaitCount;
2834 int m_nFreezeCount;
2835 sal_uInt16 m_nLastMouseButton;
2836 #if !GTK_CHECK_VERSION(4, 0, 0)
2837 sal_uInt16 m_nLastMouseClicks;
2838 #endif
2839 int m_nPressedButton;
2840 #if !GTK_CHECK_VERSION(4, 0, 0)
2841 protected:
2842 int m_nPressStartX;
2843 int m_nPressStartY;
2844 private:
2845 #endif
2846 ImplSVEvent* m_pDragCancelEvent;
2847 GtkCssProvider* m_pBgCssProvider;
2848 #if !GTK_CHECK_VERSION(4, 0, 0)
2849 GdkDragAction m_eDragAction;
2850 #endif
2851 gulong m_nFocusInSignalId;
2852 gulong m_nMnemonicActivateSignalId;
2853 gulong m_nFocusOutSignalId;
2854 gulong m_nKeyPressSignalId;
2855 gulong m_nKeyReleaseSignalId;
2856 protected:
2857 gulong m_nSizeAllocateSignalId;
2858 private:
2859 gulong m_nButtonPressSignalId;
2860 gulong m_nMotionSignalId;
2861 gulong m_nLeaveSignalId;
2862 gulong m_nEnterSignalId;
2863 gulong m_nButtonReleaseSignalId;
2864 gulong m_nDragMotionSignalId;
2865 gulong m_nDragDropSignalId;
2866 gulong m_nDragDropReceivedSignalId;
2867 gulong m_nDragLeaveSignalId;
2868 gulong m_nDragBeginSignalId;
2869 gulong m_nDragEndSignalId;
2870 gulong m_nDragFailedSignalId;
2871 gulong m_nDragDataDeleteignalId;
2872 gulong m_nDragGetSignalId;
2874 #if GTK_CHECK_VERSION(4, 0, 0)
2875 int m_nGrabCount;
2876 GtkEventController* m_pFocusController;
2877 GtkEventController* m_pClickController;
2878 GtkEventController* m_pMotionController;
2879 GtkEventController* m_pDragController;
2880 GtkEventController* m_pKeyController;
2881 #endif
2883 rtl::Reference<GtkInstDropTarget> m_xDropTarget;
2884 rtl::Reference<GtkInstDragSource> m_xDragSource;
2886 static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
2888 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2889 SolarMutexGuard aGuard;
2890 pThis->signal_size_allocate(allocation->width, allocation->height);
2893 #if GTK_CHECK_VERSION(4, 0, 0)
2894 static gboolean signalKeyPressed(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
2896 LocalizeDecimalSeparator(keyval);
2897 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2898 return pThis->signal_key_press(keyval, keycode, state);
2901 static gboolean signalKeyReleased(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
2903 LocalizeDecimalSeparator(keyval);
2904 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2905 return pThis->signal_key_release(keyval, keycode, state);
2907 #else
2908 static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
2910 LocalizeDecimalSeparator(pEvent->keyval);
2911 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2912 if (pEvent->type == GDK_KEY_PRESS)
2913 return pThis->signal_key_press(pEvent);
2914 return pThis->signal_key_release(pEvent);
2916 #endif
2918 virtual bool signal_popup_menu(const CommandEvent&)
2920 return false;
2923 #if GTK_CHECK_VERSION(4, 0, 0)
2924 static void signalButtonPress(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
2926 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2927 SolarMutexGuard aGuard;
2928 pThis->signal_button(pGesture, SalEvent::MouseButtonDown, n_press, x, y);
2931 static void signalButtonRelease(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
2933 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2934 SolarMutexGuard aGuard;
2935 pThis->signal_button(pGesture, SalEvent::MouseButtonUp, n_press, x, y);
2938 void signal_button(GtkGestureClick* pGesture, SalEvent nEventType, int n_press, gdouble x, gdouble y)
2940 m_nPressedButton = -1;
2942 Point aPos(x, y);
2943 if (SwapForRTL())
2944 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2946 if (n_press == 1)
2948 GdkEventSequence* pSequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(pGesture));
2949 GdkEvent* pEvent = gtk_gesture_get_last_event(GTK_GESTURE(pGesture), pSequence);
2950 if (gdk_event_triggers_context_menu(pEvent))
2952 //if handled for context menu, stop processing
2953 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
2954 if (signal_popup_menu(aCEvt))
2956 gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
2957 return;
2962 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
2963 int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
2965 switch (nButton)
2967 case 1:
2968 m_nLastMouseButton = MOUSE_LEFT;
2969 break;
2970 case 2:
2971 m_nLastMouseButton = MOUSE_MIDDLE;
2972 break;
2973 case 3:
2974 m_nLastMouseButton = MOUSE_RIGHT;
2975 break;
2976 default:
2977 return;
2980 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(eType);
2981 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
2982 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2983 MouseEvent aMEvt(aPos, n_press, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
2985 if (nEventType == SalEvent::MouseButtonDown && m_aMousePressHdl.Call(aMEvt))
2986 gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
2988 if (nEventType == SalEvent::MouseButtonUp && m_aMouseReleaseHdl.Call(aMEvt))
2989 gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
2992 #else
2994 static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
2996 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2997 SolarMutexGuard aGuard;
2998 return pThis->signal_button(pEvent);
3001 static gboolean signalButtonRelease(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
3003 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3004 SolarMutexGuard aGuard;
3005 return pThis->signal_button(pEvent);
3008 bool signal_button(GdkEventButton* pEvent)
3010 m_nPressedButton = -1;
3012 Point aPos(pEvent->x, pEvent->y);
3013 if (SwapForRTL())
3014 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
3016 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
3018 //if handled for context menu, stop processing
3019 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
3020 if (signal_popup_menu(aCEvt))
3021 return true;
3024 /* Save press to possibly begin a drag */
3025 if (pEvent->type != GDK_BUTTON_RELEASE)
3027 m_nPressedButton = pEvent->button;
3028 m_nPressStartX = pEvent->x;
3029 m_nPressStartY = pEvent->y;
3032 if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
3033 return false;
3035 SalEvent nEventType = SalEvent::NONE;
3036 switch (pEvent->type)
3038 case GDK_BUTTON_PRESS:
3039 if (GdkEvent* pPeekEvent = gdk_event_peek())
3041 bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
3042 pPeekEvent->type == GDK_3BUTTON_PRESS;
3043 gdk_event_free(pPeekEvent);
3044 if (bSkip)
3046 return false;
3049 nEventType = SalEvent::MouseButtonDown;
3050 m_nLastMouseClicks = 1;
3051 break;
3052 case GDK_2BUTTON_PRESS:
3053 m_nLastMouseClicks = 2;
3054 nEventType = SalEvent::MouseButtonDown;
3055 break;
3056 case GDK_3BUTTON_PRESS:
3057 m_nLastMouseClicks = 3;
3058 nEventType = SalEvent::MouseButtonDown;
3059 break;
3060 case GDK_BUTTON_RELEASE:
3061 nEventType = SalEvent::MouseButtonUp;
3062 break;
3063 default:
3064 return false;
3067 switch (pEvent->button)
3069 case 1:
3070 m_nLastMouseButton = MOUSE_LEFT;
3071 break;
3072 case 2:
3073 m_nLastMouseButton = MOUSE_MIDDLE;
3074 break;
3075 case 3:
3076 m_nLastMouseButton = MOUSE_RIGHT;
3077 break;
3078 default:
3079 return false;
3082 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
3083 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
3084 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
3085 MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
3087 if (nEventType == SalEvent::MouseButtonDown)
3089 if (!m_aMousePressHdl.IsSet())
3090 return false;
3091 return m_aMousePressHdl.Call(aMEvt);
3094 if (!m_aMouseReleaseHdl.IsSet())
3095 return false;
3096 return m_aMouseReleaseHdl.Call(aMEvt);
3098 #endif
3100 bool simple_signal_motion(double x, double y, guint nState)
3102 if (!m_aMouseMotionHdl.IsSet())
3103 return false;
3105 Point aPos(x, y);
3106 if (SwapForRTL())
3107 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
3108 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
3109 MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
3111 return m_aMouseMotionHdl.Call(aMEvt);
3114 #if GTK_CHECK_VERSION(4, 0, 0)
3115 static void signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
3117 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3118 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3120 SolarMutexGuard aGuard;
3121 pThis->simple_signal_motion(x, y, eType);
3124 #else
3125 static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
3127 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3128 SolarMutexGuard aGuard;
3129 return pThis->signal_motion(pEvent);
3132 bool signal_motion(const GdkEventMotion* pEvent)
3134 const bool bDragData = m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is() && gtk_drag_source_get_target_list(m_pWidget);
3135 bool bUnsetDragIcon(false);
3136 if (bDragData && gtk_drag_check_threshold(m_pWidget, m_nPressStartX, m_nPressStartY, pEvent->x, pEvent->y) && !do_signal_drag_begin(bUnsetDragIcon))
3138 GdkDragContext* pContext = gtk_drag_begin_with_coordinates(m_pWidget,
3139 gtk_drag_source_get_target_list(m_pWidget),
3140 m_eDragAction,
3141 m_nPressedButton,
3142 const_cast<GdkEvent*>(reinterpret_cast<const GdkEvent*>(pEvent)),
3143 m_nPressStartX, m_nPressStartY);
3145 if (pContext && bUnsetDragIcon)
3147 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
3148 gtk_drag_set_icon_surface(pContext, surface);
3149 cairo_surface_destroy(surface);
3152 m_nPressedButton = -1;
3153 return false;
3156 return simple_signal_motion(pEvent->x, pEvent->y, pEvent->state);
3158 #endif
3160 bool signal_crossing(double x, double y, guint nState, MouseEventModifiers eMouseEventModifiers)
3162 if (!m_aMouseMotionHdl.IsSet())
3163 return false;
3165 Point aPos(x, y);
3166 if (SwapForRTL())
3167 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
3168 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
3169 MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
3170 eModifiers = eModifiers | eMouseEventModifiers;
3171 MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
3173 m_aMouseMotionHdl.Call(aMEvt);
3174 return false;
3177 #if GTK_CHECK_VERSION(4, 0, 0)
3178 static void signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
3180 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3181 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3182 SolarMutexGuard aGuard;
3183 pThis->signal_crossing(x, y, eType, MouseEventModifiers::ENTERWINDOW);
3186 static void signalLeave(GtkEventControllerMotion *pController, gpointer widget)
3188 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3189 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3190 SolarMutexGuard aGuard;
3191 pThis->signal_crossing(-1, -1, eType, MouseEventModifiers::LEAVEWINDOW);
3193 #else
3194 static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
3196 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3197 MouseEventModifiers eMouseEventModifiers = pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW;
3198 SolarMutexGuard aGuard;
3199 return pThis->signal_crossing(pEvent->x, pEvent->y, pEvent->state, eMouseEventModifiers);
3201 #endif
3203 virtual void drag_started()
3207 #if !GTK_CHECK_VERSION(4, 0, 0)
3208 static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
3210 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3211 if (!pThis->m_bDraggedOver)
3213 pThis->m_bDraggedOver = true;
3214 pThis->drag_started();
3216 return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
3219 static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
3221 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3222 return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
3225 static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
3227 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3228 pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
3230 #endif
3232 virtual void drag_ended()
3236 #if !GTK_CHECK_VERSION(4, 0, 0)
3237 static void signalDragLeave(GtkWidget* pWidget, GdkDragContext*, guint /*time*/, gpointer widget)
3239 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3240 pThis->m_xDropTarget->signalDragLeave(pWidget);
3241 if (pThis->m_bDraggedOver)
3243 pThis->m_bDraggedOver = false;
3244 pThis->drag_ended();
3247 #endif
3249 #if GTK_CHECK_VERSION(4, 0, 0)
3250 static void signalDragBegin(GtkDragSource* context, GdkDrag*, gpointer widget)
3251 #else
3252 static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
3253 #endif
3255 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3256 pThis->signal_drag_begin(context);
3259 void ensure_drag_source()
3261 if (!m_xDragSource)
3263 m_xDragSource.set(new GtkInstDragSource);
3265 #if !GTK_CHECK_VERSION(4, 0, 0)
3266 m_nDragFailedSignalId = g_signal_connect(m_pWidget, "drag-failed", G_CALLBACK(signalDragFailed), this);
3267 m_nDragDataDeleteignalId = g_signal_connect(m_pWidget, "drag-data-delete", G_CALLBACK(signalDragDelete), this);
3268 m_nDragGetSignalId = g_signal_connect(m_pWidget, "drag-data-get", G_CALLBACK(signalDragDataGet), this);
3269 #endif
3271 ensure_drag_begin_end();
3275 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
3277 rUnsetDragIcon = false;
3278 return false;
3281 #if GTK_CHECK_VERSION(4, 0, 0)
3282 virtual void drag_set_icon(GtkDragSource*)
3283 #else
3284 virtual void drag_set_icon(GdkDragContext*)
3285 #endif
3289 #if GTK_CHECK_VERSION(4, 0, 0)
3290 void signal_drag_begin(GtkDragSource* context)
3291 #else
3292 void signal_drag_begin(GdkDragContext* context)
3293 #endif
3295 bool bUnsetDragIcon(false);
3296 if (do_signal_drag_begin(bUnsetDragIcon))
3298 #if !GTK_CHECK_VERSION(4, 0, 0)
3299 launch_drag_cancel(context);
3300 #endif
3301 return;
3303 if (bUnsetDragIcon)
3305 #if !GTK_CHECK_VERSION(4, 0, 0)
3306 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
3307 gtk_drag_set_icon_surface(context, surface);
3308 cairo_surface_destroy(surface);
3309 #endif
3311 else
3313 drag_set_icon(context);
3316 if (!m_xDragSource)
3317 return;
3318 m_xDragSource->setActiveDragSource();
3321 virtual void do_signal_drag_end()
3325 #if GTK_CHECK_VERSION(4, 0, 0)
3326 static void signalDragEnd(GtkGestureDrag* /*gesture*/, double /*offset_x*/, double /*offset_y*/, gpointer widget)
3327 #else
3328 static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
3329 #endif
3331 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3332 pThis->do_signal_drag_end();
3333 #if !GTK_CHECK_VERSION(4, 0, 0)
3334 if (pThis->m_xDragSource.is())
3335 pThis->m_xDragSource->dragEnd(context);
3336 #endif
3339 #if !GTK_CHECK_VERSION(4, 0, 0)
3340 static gboolean signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer widget)
3342 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3343 pThis->m_xDragSource->dragFailed();
3344 return false;
3347 static void signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer widget)
3349 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3350 pThis->m_xDragSource->dragDelete();
3353 static void signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
3354 guint /*time*/, gpointer widget)
3356 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3357 pThis->m_xDragSource->dragDataGet(data, info);
3359 #endif
3361 #if !GTK_CHECK_VERSION(4, 0, 0)
3362 virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
3364 if (rGtkTargets.empty() && !eDragAction)
3365 gtk_drag_source_unset(m_pWidget);
3366 else
3367 gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
3369 #endif
3371 void do_set_background(const Color& rColor)
3373 const bool bRemoveColor = rColor == COL_AUTO;
3374 if (bRemoveColor && !m_pBgCssProvider)
3375 return;
3376 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
3377 if (m_pBgCssProvider)
3379 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
3380 m_pBgCssProvider = nullptr;
3382 if (bRemoveColor)
3383 return;
3384 OUString sColor = rColor.AsRGBHexString();
3385 m_pBgCssProvider = gtk_css_provider_new();
3386 OUString aBuffer = "* { background-color: #" + sColor + "; }";
3387 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
3388 css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength());
3389 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
3390 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3393 DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
3395 #if !GTK_CHECK_VERSION(4, 0, 0)
3396 static void update_style(GtkWidget* pWidget, gpointer pData)
3398 if (GTK_IS_CONTAINER(pWidget))
3399 gtk_container_foreach(GTK_CONTAINER(pWidget), update_style, pData);
3400 GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(pWidget);
3401 pWidgetClass->style_updated(pWidget);
3403 #endif
3405 public:
3406 GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3407 : m_pWidget(pWidget)
3408 , m_pMouseEventBox(nullptr)
3409 , m_pBuilder(pBuilder)
3410 , m_bTakeOwnership(bTakeOwnership)
3411 #if !GTK_CHECK_VERSION(4, 0, 0)
3412 , m_bDraggedOver(false)
3413 #endif
3414 , m_nWaitCount(0)
3415 , m_nFreezeCount(0)
3416 , m_nLastMouseButton(0)
3417 #if !GTK_CHECK_VERSION(4, 0, 0)
3418 , m_nLastMouseClicks(0)
3419 #endif
3420 , m_nPressedButton(-1)
3421 #if !GTK_CHECK_VERSION(4, 0, 0)
3422 , m_nPressStartX(-1)
3423 , m_nPressStartY(-1)
3424 #endif
3425 , m_pDragCancelEvent(nullptr)
3426 , m_pBgCssProvider(nullptr)
3427 #if !GTK_CHECK_VERSION(4, 0, 0)
3428 , m_eDragAction(GdkDragAction(0))
3429 #endif
3430 , m_nFocusInSignalId(0)
3431 , m_nMnemonicActivateSignalId(0)
3432 , m_nFocusOutSignalId(0)
3433 , m_nKeyPressSignalId(0)
3434 , m_nKeyReleaseSignalId(0)
3435 , m_nSizeAllocateSignalId(0)
3436 , m_nButtonPressSignalId(0)
3437 , m_nMotionSignalId(0)
3438 , m_nLeaveSignalId(0)
3439 , m_nEnterSignalId(0)
3440 , m_nButtonReleaseSignalId(0)
3441 , m_nDragMotionSignalId(0)
3442 , m_nDragDropSignalId(0)
3443 , m_nDragDropReceivedSignalId(0)
3444 , m_nDragLeaveSignalId(0)
3445 , m_nDragBeginSignalId(0)
3446 , m_nDragEndSignalId(0)
3447 , m_nDragFailedSignalId(0)
3448 , m_nDragDataDeleteignalId(0)
3449 , m_nDragGetSignalId(0)
3450 #if GTK_CHECK_VERSION(4, 0, 0)
3451 , m_nGrabCount(0)
3452 , m_pFocusController(nullptr)
3453 , m_pClickController(nullptr)
3454 , m_pMotionController(nullptr)
3455 , m_pDragController(nullptr)
3456 , m_pKeyController(nullptr)
3457 #endif
3459 if (!bTakeOwnership)
3460 g_object_ref(m_pWidget);
3462 #if !GTK_CHECK_VERSION(4, 0, 0)
3463 const char* pId = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
3464 if (pId)
3466 static auto func = reinterpret_cast<void(*)(AtkObject*, const char*)>(dlsym(nullptr, "atk_object_set_accessible_id"));
3467 if (func)
3469 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3470 assert(pAtkObject);
3471 (*func)(pAtkObject, pId);
3474 #endif
3476 localizeDecimalSeparator();
3479 virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
3481 if (!m_nKeyPressSignalId)
3483 #if GTK_CHECK_VERSION(4, 0, 0)
3484 m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
3485 #else
3486 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
3487 #endif
3489 weld::Widget::connect_key_press(rLink);
3492 virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
3494 if (!m_nKeyReleaseSignalId)
3496 #if GTK_CHECK_VERSION(4, 0, 0)
3497 m_nKeyReleaseSignalId = g_signal_connect(get_key_controller(), "key-released", G_CALLBACK(signalKeyReleased), this);
3498 #else
3499 m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
3500 #endif
3502 weld::Widget::connect_key_release(rLink);
3505 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
3507 ensureButtonPressSignal();
3508 weld::Widget::connect_mouse_press(rLink);
3511 virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
3513 #if GTK_CHECK_VERSION(4, 0, 0)
3514 GtkEventController* pMotionController = get_motion_controller();
3515 if (!m_nMotionSignalId)
3516 m_nMotionSignalId = g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
3517 if (!m_nLeaveSignalId)
3518 m_nLeaveSignalId = g_signal_connect(pMotionController, "leave", G_CALLBACK(signalEnter), this);
3519 if (!m_nEnterSignalId)
3520 m_nEnterSignalId = g_signal_connect(pMotionController, "enter", G_CALLBACK(signalLeave), this);
3521 #else
3522 ensureMouseEventWidget();
3523 if (!m_nMotionSignalId)
3524 m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
3525 if (!m_nLeaveSignalId)
3526 m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
3527 if (!m_nEnterSignalId)
3528 m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
3529 #endif
3530 weld::Widget::connect_mouse_move(rLink);
3533 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
3535 ensureButtonReleaseSignal();
3536 weld::Widget::connect_mouse_release(rLink);
3539 virtual void set_sensitive(bool sensitive) override
3541 gtk_widget_set_sensitive(m_pWidget, sensitive);
3544 virtual bool get_sensitive() const override
3546 return gtk_widget_get_sensitive(m_pWidget);
3549 virtual bool get_visible() const override
3551 return gtk_widget_get_visible(m_pWidget);
3554 virtual bool is_visible() const override
3556 return gtk_widget_is_visible(m_pWidget);
3559 virtual void set_can_focus(bool bCanFocus) override
3561 gtk_widget_set_can_focus(m_pWidget, bCanFocus);
3564 virtual void grab_focus() override
3566 if (has_focus())
3567 return;
3568 gtk_widget_grab_focus(m_pWidget);
3571 virtual bool has_focus() const override
3573 return gtk_widget_has_focus(m_pWidget);
3576 virtual bool is_active() const override
3578 GtkWindow* pTopLevel = GTK_WINDOW(widget_get_toplevel(m_pWidget));
3579 return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
3582 // is the focus in a child of this widget, where a transient popup attached
3583 // to a widget is considered a child of that widget
3584 virtual bool has_child_focus() const override
3586 GtkWindow* pFocusWin = get_active_window();
3587 if (!pFocusWin)
3588 return false;
3589 GtkWidget* pFocus = gtk_window_get_focus(pFocusWin);
3590 if (pFocus && gtk_widget_is_ancestor(pFocus, m_pWidget))
3591 return true;
3592 #if !GTK_CHECK_VERSION(4, 0, 0)
3593 GtkWidget* pAttachedTo = gtk_window_get_attached_to(pFocusWin);
3594 if (!pAttachedTo)
3595 return false;
3596 if (pAttachedTo == m_pWidget || gtk_widget_is_ancestor(pAttachedTo, m_pWidget))
3597 return true;
3598 #endif
3599 return false;
3602 virtual void show() override
3604 gtk_widget_show(m_pWidget);
3607 virtual void hide() override
3609 gtk_widget_hide(m_pWidget);
3612 virtual void set_size_request(int nWidth, int nHeight) override
3614 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3615 if (GTK_IS_VIEWPORT(pParent))
3616 pParent = gtk_widget_get_parent(pParent);
3617 if (GTK_IS_SCROLLED_WINDOW(pParent))
3619 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
3620 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
3622 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
3625 virtual Size get_size_request() const override
3627 int nWidth, nHeight;
3628 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
3629 return Size(nWidth, nHeight);
3632 virtual Size get_preferred_size() const override
3634 GtkRequisition size;
3635 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
3636 return Size(size.width, size.height);
3639 virtual float get_approximate_digit_width() const override
3641 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
3642 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
3643 pango_context_get_font_description(pContext),
3644 pango_context_get_language(pContext));
3645 float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
3646 pango_font_metrics_unref(pMetrics);
3648 return nDigitWidth / PANGO_SCALE;
3651 virtual int get_text_height() const override
3653 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
3654 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
3655 pango_context_get_font_description(pContext),
3656 pango_context_get_language(pContext));
3657 int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
3658 pango_font_metrics_unref(pMetrics);
3659 return nLineHeight / PANGO_SCALE;
3662 virtual Size get_pixel_size(const OUString& rText) const override
3664 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
3665 PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
3666 gint nWidth, nHeight;
3667 pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
3668 g_object_unref(pLayout);
3669 return Size(nWidth, nHeight);
3672 virtual vcl::Font get_font() override
3674 return ::get_font(m_pWidget);
3677 virtual void set_grid_left_attach(int nAttach) override
3679 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3680 #if GTK_CHECK_VERSION(4, 0, 0)
3681 int row, width, height;
3682 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &row, &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, nAttach, row, width, height);
3686 g_object_unref(m_pWidget);
3687 #else
3688 gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "left-attach", nAttach, nullptr);
3689 #endif
3692 virtual int get_grid_left_attach() const override
3694 gint nAttach(0);
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, &nAttach, nullptr, nullptr, nullptr);
3698 #else
3699 gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "left-attach", &nAttach, nullptr);
3700 #endif
3701 return nAttach;
3704 virtual void set_grid_width(int nCols) override
3706 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3707 #if GTK_CHECK_VERSION(4, 0, 0)
3708 int col, row, height;
3709 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, &row, nullptr, &height);
3710 g_object_ref(m_pWidget);
3711 gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
3712 gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, row, nCols, height);
3713 g_object_unref(m_pWidget);
3714 #else
3715 gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "width", nCols, nullptr);
3716 #endif
3719 virtual void set_grid_top_attach(int nAttach) override
3721 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3722 #if GTK_CHECK_VERSION(4, 0, 0)
3723 int col, width, height;
3724 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, nullptr, &width, &height);
3725 g_object_ref(m_pWidget);
3726 gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
3727 gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, nAttach, width, height);
3728 g_object_unref(m_pWidget);
3729 #else
3730 gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "top-attach", nAttach, nullptr);
3731 #endif
3734 virtual int get_grid_top_attach() const override
3736 gint nAttach(0);
3737 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3738 #if GTK_CHECK_VERSION(4, 0, 0)
3739 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &nAttach, nullptr, nullptr);
3740 #else
3741 gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "top-attach", &nAttach, nullptr);
3742 #endif
3743 return nAttach;
3746 virtual void set_hexpand(bool bExpand) override
3748 gtk_widget_set_hexpand(m_pWidget, bExpand);
3751 virtual bool get_hexpand() const override
3753 return gtk_widget_get_hexpand(m_pWidget);
3756 virtual void set_vexpand(bool bExpand) override
3758 gtk_widget_set_vexpand(m_pWidget, bExpand);
3761 virtual bool get_vexpand() const override
3763 return gtk_widget_get_vexpand(m_pWidget);
3766 virtual void set_margin_top(int nMargin) override
3768 gtk_widget_set_margin_top(m_pWidget, nMargin);
3771 virtual void set_margin_bottom(int nMargin) override
3773 gtk_widget_set_margin_bottom(m_pWidget, nMargin);
3776 virtual void set_margin_start(int nMargin) override
3778 gtk_widget_set_margin_start(m_pWidget, nMargin);
3781 virtual void set_margin_end(int nMargin) override
3783 gtk_widget_set_margin_end(m_pWidget, nMargin);
3786 virtual int get_margin_top() const override
3788 return gtk_widget_get_margin_top(m_pWidget);
3791 virtual int get_margin_bottom() const override
3793 return gtk_widget_get_margin_bottom(m_pWidget);
3796 virtual int get_margin_start() const override
3798 return gtk_widget_get_margin_start(m_pWidget);
3801 virtual int get_margin_end() const override
3803 return gtk_widget_get_margin_end(m_pWidget);
3806 virtual void set_accessible_name(const OUString& rName) override
3808 #if GTK_CHECK_VERSION(4, 0, 0)
3809 gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL,
3810 OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr(), -1);
3811 #else
3812 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3813 if (!pAtkObject)
3814 return;
3815 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
3816 #endif
3819 virtual void set_accessible_description(const OUString& rDescription) override
3821 #if GTK_CHECK_VERSION(4, 0, 0)
3822 gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
3823 OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr(), -1);
3824 #else
3825 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3826 if (!pAtkObject)
3827 return;
3828 atk_object_set_description(pAtkObject, OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr());
3829 #endif
3832 virtual OUString get_accessible_name() const override
3834 #if !GTK_CHECK_VERSION(4, 0, 0)
3835 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3836 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
3837 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3838 #else
3839 char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL, nullptr);
3840 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3841 g_free(pStr);
3842 return sRet;
3843 #endif
3846 virtual OUString get_accessible_description() const override
3848 #if !GTK_CHECK_VERSION(4, 0, 0)
3849 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3850 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
3851 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3852 #else
3853 char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, nullptr);
3854 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3855 g_free(pStr);
3856 return sRet;
3857 #endif
3860 virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
3862 GtkWidget* pGtkLabel = pLabel ? dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget() : nullptr;
3863 #if GTK_CHECK_VERSION(4, 0, 0)
3864 gtk_accessible_update_relation(GTK_ACCESSIBLE(m_pWidget),
3865 GTK_ACCESSIBLE_RELATION_LABELLED_BY,
3866 pGtkLabel, nullptr,
3867 -1);
3868 #else
3869 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3870 if (!pAtkObject)
3871 return;
3872 AtkObject *pAtkLabel = pGtkLabel ? gtk_widget_get_accessible(pGtkLabel) : nullptr;
3873 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
3874 AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
3875 if (pRelation)
3877 // clear ATK_RELATION_LABEL_FOR from old label
3878 GPtrArray* pOldLabelTarget = atk_relation_get_target(pRelation);
3879 guint nElements = pOldLabelTarget ? pOldLabelTarget->len : 0;
3880 for (guint i = 0; i < nElements; ++i)
3882 gpointer pOldLabelObject = g_ptr_array_index(pOldLabelTarget, i);
3883 AtkRelationSet *pOldLabelRelationSet = atk_object_ref_relation_set(ATK_OBJECT(pOldLabelObject));
3884 if (AtkRelation *pOldLabelRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR))
3885 atk_relation_set_remove(pOldLabelRelationSet, pOldLabelRelation);
3886 g_object_unref(pOldLabelRelationSet);
3888 atk_relation_set_remove(pRelationSet, pRelation);
3891 if (pAtkLabel)
3893 AtkObject *obj_array_labelled_by[1];
3894 obj_array_labelled_by[0] = pAtkLabel;
3895 pRelation = atk_relation_new(obj_array_labelled_by, 1, ATK_RELATION_LABELLED_BY);
3896 atk_relation_set_add(pRelationSet, pRelation);
3898 // add ATK_RELATION_LABEL_FOR to new label to match
3899 AtkRelationSet *pNewLabelRelationSet = atk_object_ref_relation_set(pAtkLabel);
3900 AtkRelation *pNewLabelRelation = atk_relation_set_get_relation_by_type(pNewLabelRelationSet, ATK_RELATION_LABEL_FOR);
3901 if (pNewLabelRelation)
3902 atk_relation_set_remove(pNewLabelRelationSet, pRelation);
3903 AtkObject *obj_array_label_for[1];
3904 obj_array_label_for[0] = pAtkObject;
3905 pNewLabelRelation = atk_relation_new(obj_array_label_for, 1, ATK_RELATION_LABEL_FOR);
3906 atk_relation_set_add(pNewLabelRelationSet, pNewLabelRelation);
3907 g_object_unref(pNewLabelRelationSet);
3910 g_object_unref(pRelationSet);
3911 #endif
3914 virtual bool get_extents_relative_to(const weld::Widget& rRelative, int& x, int &y, int& width, int &height) const override
3916 //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
3917 //the document underneath to auto-scroll to place content in a visible location
3918 gtk_coord fX(0.0), fY(0.0);
3919 bool ret = gtk_widget_translate_coordinates(m_pWidget,
3920 dynamic_cast<const GtkInstanceWidget&>(rRelative).getWidget(),
3921 0, 0, &fX, &fY);
3922 x = fX;
3923 y = fY;
3924 width = gtk_widget_get_allocated_width(m_pWidget);
3925 height = gtk_widget_get_allocated_height(m_pWidget);
3926 return ret;
3929 virtual void set_tooltip_text(const OUString& rTip) override
3931 gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
3934 virtual OUString get_tooltip_text() const override
3936 const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
3937 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3940 virtual void set_cursor_data(void * /*pData*/) override {};
3942 virtual std::unique_ptr<weld::Container> weld_parent() const override;
3944 virtual OUString get_buildable_name() const override
3946 return ::get_buildable_id(GTK_BUILDABLE(m_pWidget));
3949 virtual void set_buildable_name(const OUString& rId) override
3951 ::set_buildable_id(GTK_BUILDABLE(m_pWidget), rId);
3954 virtual void set_help_id(const OUString& rHelpId) override
3956 ::set_help_id(m_pWidget, rHelpId);
3959 virtual OUString get_help_id() const override
3961 OUString sRet = ::get_help_id(m_pWidget);
3962 if (sRet.isEmpty())
3963 sRet = "null";
3964 return sRet;
3967 GtkWidget* getWidget() const
3969 return m_pWidget;
3972 GtkWindow* getWindow() const
3974 return GTK_WINDOW(widget_get_toplevel(m_pWidget));
3977 #if GTK_CHECK_VERSION(4, 0, 0)
3978 GtkEventController* get_focus_controller()
3980 if (!m_pFocusController)
3982 gtk_widget_set_focusable(m_pWidget, true);
3983 m_pFocusController = gtk_event_controller_focus_new();
3984 gtk_widget_add_controller(m_pWidget, m_pFocusController);
3986 return m_pFocusController;
3989 #if GTK_CHECK_VERSION(4, 0, 0)
3990 GtkEventController* get_click_controller()
3992 if (!m_pClickController)
3994 GtkGesture *pClick = gtk_gesture_click_new();
3995 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
3996 m_pClickController = GTK_EVENT_CONTROLLER(pClick);
3997 gtk_widget_add_controller(m_pWidget, m_pClickController);
3999 return m_pClickController;
4002 GtkEventController* get_motion_controller()
4004 if (!m_pMotionController)
4006 m_pMotionController = gtk_event_controller_motion_new();
4007 gtk_widget_add_controller(m_pWidget, m_pMotionController);
4009 return m_pMotionController;
4012 GtkEventController* get_drag_controller()
4014 if (!m_pDragController)
4016 GtkDragSource* pDrag = gtk_drag_source_new();
4017 m_pDragController = GTK_EVENT_CONTROLLER(pDrag);
4018 gtk_widget_add_controller(m_pWidget, m_pDragController);
4020 return m_pDragController;
4023 GtkEventController* get_key_controller()
4025 if (!m_pKeyController)
4027 m_pKeyController = gtk_event_controller_key_new();
4028 gtk_widget_add_controller(m_pWidget, m_pKeyController);
4030 return m_pKeyController;
4033 #endif
4036 #endif
4038 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
4040 if (!m_nFocusInSignalId)
4042 #if GTK_CHECK_VERSION(4, 0, 0)
4043 m_nFocusInSignalId = g_signal_connect(get_focus_controller(), "enter", G_CALLBACK(signalFocusIn), this);
4044 #else
4045 m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
4046 #endif
4049 weld::Widget::connect_focus_in(rLink);
4052 virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
4054 if (!m_nMnemonicActivateSignalId)
4055 m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
4056 weld::Widget::connect_mnemonic_activate(rLink);
4059 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
4061 if (!m_nFocusOutSignalId)
4063 #if GTK_CHECK_VERSION(4, 0, 0)
4064 m_nFocusOutSignalId = g_signal_connect(get_focus_controller(), "leave", G_CALLBACK(signalFocusOut), this);
4065 #else
4066 m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
4067 #endif
4069 weld::Widget::connect_focus_out(rLink);
4072 virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
4074 m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
4075 weld::Widget::connect_size_allocate(rLink);
4078 virtual void signal_size_allocate(guint nWidth, guint nHeight)
4080 m_aSizeAllocateHdl.Call(Size(nWidth, nHeight));
4083 #if GTK_CHECK_VERSION(4, 0, 0)
4084 bool signal_key_press(guint keyval, guint keycode, GdkModifierType state)
4086 if (m_aKeyPressHdl.IsSet())
4088 SolarMutexGuard aGuard;
4089 return m_aKeyPressHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
4091 return false;
4094 bool signal_key_release(guint keyval, guint keycode, GdkModifierType state)
4096 if (m_aKeyReleaseHdl.IsSet())
4098 SolarMutexGuard aGuard;
4099 return m_aKeyReleaseHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
4101 return false;
4103 #else
4105 virtual bool do_signal_key_press(const GdkEventKey* pEvent)
4107 if (m_aKeyPressHdl.IsSet())
4109 SolarMutexGuard aGuard;
4110 return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
4112 return false;
4115 virtual bool do_signal_key_release(const GdkEventKey* pEvent)
4117 if (m_aKeyReleaseHdl.IsSet())
4119 SolarMutexGuard aGuard;
4120 return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
4122 return false;
4125 bool signal_key_press(const GdkEventKey* pEvent)
4127 return do_signal_key_press(pEvent);
4130 bool signal_key_release(const GdkEventKey* pEvent)
4132 return do_signal_key_release(pEvent);
4134 #endif
4136 virtual void grab_add() override
4138 #if GTK_CHECK_VERSION(4, 0, 0)
4139 ++m_nGrabCount;
4140 #else
4141 gtk_grab_add(m_pWidget);
4142 #endif
4145 virtual bool has_grab() const override
4147 #if GTK_CHECK_VERSION(4, 0, 0)
4148 return m_nGrabCount != 0;
4149 #else
4150 return gtk_widget_has_grab(m_pWidget);
4151 #endif
4154 virtual void grab_remove() override
4156 #if GTK_CHECK_VERSION(4, 0, 0)
4157 --m_nGrabCount;
4158 #else
4159 gtk_grab_remove(m_pWidget);
4160 #endif
4163 virtual bool get_direction() const override
4165 return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
4168 virtual void set_direction(bool bRTL) override
4170 gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
4173 virtual void freeze() override
4175 ++m_nFreezeCount;
4176 #if !GTK_CHECK_VERSION(4, 0, 0)
4177 gtk_widget_freeze_child_notify(m_pWidget);
4178 #endif
4179 g_object_freeze_notify(G_OBJECT(m_pWidget));
4182 virtual void thaw() override
4184 --m_nFreezeCount;
4185 g_object_thaw_notify(G_OBJECT(m_pWidget));
4186 #if !GTK_CHECK_VERSION(4, 0, 0)
4187 gtk_widget_thaw_child_notify(m_pWidget);
4188 #endif
4191 virtual void set_busy_cursor(bool bBusy) override
4193 if (bBusy)
4194 ++m_nWaitCount;
4195 else
4196 --m_nWaitCount;
4197 if (m_nWaitCount == 1)
4198 set_cursor(m_pWidget, "progress");
4199 else if (m_nWaitCount == 0)
4200 set_cursor(m_pWidget, nullptr);
4201 assert (m_nWaitCount >= 0);
4204 virtual void queue_resize() override
4206 gtk_widget_queue_resize(m_pWidget);
4209 virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
4211 if (!m_xDropTarget)
4213 m_xDropTarget.set(new GtkInstDropTarget);
4214 #if !GTK_CHECK_VERSION(4, 0, 0)
4215 if (!gtk_drag_dest_get_track_motion(m_pWidget))
4217 gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
4218 gtk_drag_dest_set_track_motion(m_pWidget, true);
4220 m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
4221 m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
4222 m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
4223 m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
4224 #endif
4226 return m_xDropTarget;
4229 virtual css::uno::Reference<css::datatransfer::clipboard::XClipboard> get_clipboard() const override
4231 // the gen backend can have per-frame clipboards which is (presumably) useful for LibreOffice Online
4232 // but normal usage is the shared system clipboard
4233 return GetSystemClipboard();
4236 virtual void connect_get_property_tree(const Link<tools::JsonWriter&, void>& /*rLink*/) override
4238 //not implemented for the gtk variant
4241 virtual void get_property_tree(tools::JsonWriter& /*rJsonWriter*/) override
4243 //not implemented for the gtk variant
4246 virtual void call_attention_to() override
4248 // Change the class name to restart the animation under
4249 // its other name: https://css-tricks.com/restart-css-animation/
4250 #if GTK_CHECK_VERSION(4, 0, 0)
4251 if (gtk_widget_has_css_class(m_pWidget, "call_attention_1"))
4253 gtk_widget_remove_css_class(m_pWidget, "call_attention_1");
4254 gtk_widget_add_css_class(m_pWidget, "call_attention_2");
4256 else
4258 gtk_widget_remove_css_class(m_pWidget, "call_attention_2");
4259 gtk_widget_add_css_class(m_pWidget, "call_attention_1");
4261 #else
4262 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
4263 if (gtk_style_context_has_class(pWidgetContext, "call_attention_1"))
4265 gtk_style_context_remove_class(pWidgetContext, "call_attention_1");
4266 gtk_style_context_add_class(pWidgetContext, "call_attention_2");
4268 else
4270 gtk_style_context_remove_class(pWidgetContext, "call_attention_2");
4271 gtk_style_context_add_class(pWidgetContext, "call_attention_1");
4273 #endif
4276 virtual void set_stack_background() override
4278 do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());
4281 virtual void set_title_background() override
4283 do_set_background(Application::GetSettings().GetStyleSettings().GetShadowColor());
4286 virtual void set_highlight_background() override
4288 do_set_background(Application::GetSettings().GetStyleSettings().GetHighlightColor());
4291 virtual void set_background(const Color& rColor) override
4293 do_set_background(rColor);
4296 virtual void set_toolbar_background() override
4298 // no-op
4301 virtual ~GtkInstanceWidget() override
4303 if (m_aStyleUpdatedHdl.IsSet())
4304 ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
4306 if (m_pDragCancelEvent)
4307 Application::RemoveUserEvent(m_pDragCancelEvent);
4308 if (m_nDragMotionSignalId)
4309 g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
4310 if (m_nDragDropSignalId)
4311 g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
4312 if (m_nDragDropReceivedSignalId)
4313 g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
4314 if (m_nDragLeaveSignalId)
4315 g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
4316 if (m_nDragEndSignalId)
4318 #if GTK_CHECK_VERSION(4, 0, 0)
4319 g_signal_handler_disconnect(get_drag_controller(), m_nDragEndSignalId);
4320 #else
4321 g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
4322 #endif
4324 if (m_nDragBeginSignalId)
4326 #if GTK_CHECK_VERSION(4, 0, 0)
4327 g_signal_handler_disconnect(get_drag_controller(), m_nDragBeginSignalId);
4328 #else
4329 g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
4330 #endif
4332 if (m_nDragFailedSignalId)
4333 g_signal_handler_disconnect(m_pWidget, m_nDragFailedSignalId);
4334 if (m_nDragDataDeleteignalId)
4335 g_signal_handler_disconnect(m_pWidget, m_nDragDataDeleteignalId);
4336 if (m_nDragGetSignalId)
4337 g_signal_handler_disconnect(m_pWidget, m_nDragGetSignalId);
4338 if (m_nKeyPressSignalId)
4340 #if GTK_CHECK_VERSION(4, 0, 0)
4341 g_signal_handler_disconnect(get_key_controller(), m_nKeyPressSignalId);
4342 #else
4343 g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
4344 #endif
4346 if (m_nKeyReleaseSignalId)
4348 #if GTK_CHECK_VERSION(4, 0, 0)
4349 g_signal_handler_disconnect(get_key_controller(), m_nKeyReleaseSignalId);
4350 #else
4351 g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
4352 #endif
4355 if (m_nFocusInSignalId)
4357 #if GTK_CHECK_VERSION(4, 0, 0)
4358 g_signal_handler_disconnect(get_focus_controller(), m_nFocusInSignalId);
4359 #else
4360 g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
4361 #endif
4363 if (m_nMnemonicActivateSignalId)
4364 g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
4365 if (m_nFocusOutSignalId)
4367 #if GTK_CHECK_VERSION(4, 0, 0)
4368 g_signal_handler_disconnect(get_focus_controller(), m_nFocusOutSignalId);
4369 #else
4370 g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
4371 #endif
4373 if (m_nSizeAllocateSignalId)
4374 g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
4376 do_set_background(COL_AUTO);
4378 DisconnectMouseEvents();
4380 if (m_bTakeOwnership)
4382 #if !GTK_CHECK_VERSION(4, 0, 0)
4383 gtk_widget_destroy(m_pWidget);
4384 #else
4385 gtk_window_destroy(GTK_WINDOW(m_pWidget));
4386 #endif
4388 else
4389 g_object_unref(m_pWidget);
4392 virtual void disable_notify_events()
4394 if (m_nFocusInSignalId)
4396 #if GTK_CHECK_VERSION(4, 0, 0)
4397 g_signal_handler_block(get_focus_controller(), m_nFocusInSignalId);
4398 #else
4399 g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
4400 #endif
4402 if (m_nMnemonicActivateSignalId)
4403 g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
4404 if (m_nFocusOutSignalId)
4406 #if GTK_CHECK_VERSION(4, 0, 0)
4407 g_signal_handler_block(get_focus_controller(), m_nFocusOutSignalId);
4408 #else
4409 g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
4410 #endif
4412 if (m_nSizeAllocateSignalId)
4413 g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
4416 virtual void enable_notify_events()
4418 if (m_nSizeAllocateSignalId)
4419 g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
4420 if (m_nFocusOutSignalId)
4422 #if GTK_CHECK_VERSION(4, 0, 0)
4423 g_signal_handler_unblock(get_focus_controller(), m_nFocusOutSignalId);
4424 #else
4425 g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
4426 #endif
4428 if (m_nMnemonicActivateSignalId)
4429 g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
4431 if (m_nFocusInSignalId)
4433 #if GTK_CHECK_VERSION(4, 0, 0)
4434 g_signal_handler_unblock(get_focus_controller(), m_nFocusInSignalId);
4435 #else
4436 g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
4437 #endif
4441 virtual void help_hierarchy_foreach(const std::function<bool(const OUString&)>& func) override;
4443 virtual OUString strip_mnemonic(const OUString &rLabel) const override
4445 return rLabel.replaceFirst("_", "");
4448 virtual VclPtr<VirtualDevice> create_virtual_device() const override
4450 // create with no separate alpha layer like everything sane does
4451 auto xRet = VclPtr<VirtualDevice>::Create();
4452 xRet->SetBackground(COL_TRANSPARENT);
4453 return xRet;
4456 virtual void draw(OutputDevice& rOutput, const Point& rPos, const Size& rPixelSize) override
4458 // detect if we have to manually setup its size
4459 bool bAlreadyRealized = gtk_widget_get_realized(m_pWidget);
4460 // has to be visible for draw to work
4461 bool bAlreadyVisible = gtk_widget_get_visible(m_pWidget);
4462 // has to be mapped for draw to work
4463 bool bAlreadyMapped = gtk_widget_get_mapped(m_pWidget);
4465 if (!bAlreadyRealized)
4467 #if !GTK_CHECK_VERSION(4, 0, 0)
4469 tdf#141633 The "sample db" example (Mockup.odb) has multiline
4470 entries used in its "Journal Entry" column. Those are painted by
4471 taking snapshots of a never-really-shown textview widget.
4472 Without this style_updated then the textview is always drawn
4473 using its original default font size and changing the page zoom
4474 has no effect on the size of text in the "Journal Entry" column.
4476 update_style(m_pWidget, nullptr);
4477 #endif
4478 gtk_widget_realize(m_pWidget);
4480 if (!bAlreadyVisible)
4481 gtk_widget_show(m_pWidget);
4482 if (!bAlreadyMapped)
4483 gtk_widget_map(m_pWidget);
4485 assert(gtk_widget_is_drawable(m_pWidget)); // all that should result in this holding
4487 // turn off animations, otherwise we get a frame of an animation sequence
4488 gboolean bAnimations;
4489 GtkSettings* pSettings = gtk_widget_get_settings(m_pWidget);
4490 g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
4491 if (bAnimations)
4492 g_object_set(pSettings, "gtk-enable-animations", false, nullptr);
4494 Size aSize(rPixelSize);
4496 GtkAllocation aOrigAllocation;
4497 gtk_widget_get_allocation(m_pWidget, &aOrigAllocation);
4499 GtkAllocation aNewAllocation {aOrigAllocation.x,
4500 aOrigAllocation.y,
4501 static_cast<int>(aSize.Width()),
4502 static_cast<int>(aSize.Height()) };
4503 #if !GTK_CHECK_VERSION(4, 0, 0)
4504 gtk_widget_size_allocate(m_pWidget, &aNewAllocation);
4505 #else
4506 gtk_widget_size_allocate(m_pWidget, &aNewAllocation, 0);
4507 #endif
4509 #if !GTK_CHECK_VERSION(4, 0, 0)
4510 if (GTK_IS_CONTAINER(m_pWidget))
4511 gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
4512 #endif
4514 VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
4515 xOutput->SetOutputSizePixel(aSize);
4517 switch (rOutput.GetOutDevType())
4519 case OUTDEV_WINDOW:
4520 case OUTDEV_VIRDEV:
4521 xOutput->DrawOutDev(Point(), aSize, rPos, aSize, rOutput);
4522 break;
4523 case OUTDEV_PRINTER:
4524 case OUTDEV_PDF:
4525 xOutput->SetBackground(rOutput.GetBackground());
4526 xOutput->Erase();
4527 break;
4530 cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
4531 cairo_t* cr = cairo_create(pSurface);
4533 #if !GTK_CHECK_VERSION(4, 0, 0)
4534 gtk_widget_draw(m_pWidget, cr);
4535 #else
4536 GtkSnapshot* pSnapshot = gtk_snapshot_new();
4537 GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(m_pWidget);
4538 pWidgetClass->snapshot(m_pWidget, pSnapshot);
4539 GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
4540 gsk_render_node_draw(pNode, cr);
4541 gsk_render_node_unref(pNode);
4542 #endif
4544 cairo_destroy(cr);
4546 #if !GTK_CHECK_VERSION(4, 0, 0)
4547 gtk_widget_set_allocation(m_pWidget, &aOrigAllocation);
4548 gtk_widget_size_allocate(m_pWidget, &aOrigAllocation);
4549 #else
4550 gtk_widget_size_allocate(m_pWidget, &aOrigAllocation, 0);
4551 #endif
4553 switch (rOutput.GetOutDevType())
4555 case OUTDEV_WINDOW:
4556 case OUTDEV_VIRDEV:
4557 rOutput.DrawOutDev(rPos, aSize, Point(), aSize, *xOutput);
4558 break;
4559 case OUTDEV_PRINTER:
4560 case OUTDEV_PDF:
4561 rOutput.DrawBitmapEx(rPos, xOutput->GetBitmapEx(Point(), aSize));
4562 break;
4565 if (bAnimations)
4566 g_object_set(pSettings, "gtk-enable-animations", true, nullptr);
4568 if (!bAlreadyMapped)
4569 gtk_widget_unmap(m_pWidget);
4570 if (!bAlreadyVisible)
4571 gtk_widget_hide(m_pWidget);
4572 if (!bAlreadyRealized)
4573 gtk_widget_unrealize(m_pWidget);
4579 IMPL_LINK(GtkInstanceWidget, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
4581 if (rEvent.GetId() != VclEventId::WindowDataChanged)
4582 return;
4584 DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
4585 if (pData->GetType() == DataChangedEventType::SETTINGS)
4586 m_aStyleUpdatedHdl.Call(*this);
4589 #if !GTK_CHECK_VERSION(4, 0, 0)
4590 IMPL_LINK(GtkInstanceWidget, async_drag_cancel, void*, arg, void)
4592 m_pDragCancelEvent = nullptr;
4593 GdkDragContext* context = static_cast<GdkDragContext*>(arg);
4595 // tdf#132477 simply calling gtk_drag_cancel on the treeview dnd under X
4596 // doesn't seem to work as hoped for (though under wayland all is well).
4597 // Under X the next (allowed) drag effort doesn't work to drop anything,
4598 // but a then repeated attempt does.
4599 // emitting cancel to get gtk to cancel the drag for us does work as hoped for.
4600 g_signal_emit_by_name(context, "cancel", 0, GDK_DRAG_CANCEL_USER_CANCELLED);
4602 g_object_unref(context);
4604 #endif
4606 namespace
4608 OString MapToGtkAccelerator(const OUString &rStr)
4610 return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
4613 OUString get_label(GtkLabel* pLabel)
4615 const gchar* pStr = gtk_label_get_label(pLabel);
4616 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4619 void set_label(GtkLabel* pLabel, const OUString& rText)
4621 gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
4624 #if GTK_CHECK_VERSION(4, 0, 0)
4625 GtkWidget* find_label_widget(GtkWidget* pContainer)
4627 GtkWidget* pLabel = nullptr;
4628 for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
4629 pChild; pChild = gtk_widget_get_next_sibling(pChild))
4631 if (GTK_IS_LABEL(pChild))
4633 pLabel = pChild;
4634 break;
4636 else
4638 pLabel = find_label_widget(pChild);
4639 if (pLabel)
4640 break;
4643 return pLabel;
4646 GtkWidget* find_image_widget(GtkWidget* pContainer)
4648 GtkWidget* pImage = nullptr;
4649 for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
4650 pChild; pChild = gtk_widget_get_next_sibling(pChild))
4652 if (GTK_IS_IMAGE(pChild))
4654 pImage = pChild;
4655 break;
4657 else
4659 pImage = find_image_widget(pChild);
4660 if (pImage)
4661 break;
4664 return pImage;
4666 #else
4667 GtkWidget* find_label_widget(GtkContainer* pContainer)
4669 GList* pChildren = gtk_container_get_children(pContainer);
4671 GtkWidget* pChild = nullptr;
4672 for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
4674 if (GTK_IS_LABEL(pCandidate->data))
4676 pChild = GTK_WIDGET(pCandidate->data);
4677 break;
4679 else if (GTK_IS_CONTAINER(pCandidate->data))
4681 pChild = find_label_widget(GTK_CONTAINER(pCandidate->data));
4682 if (pChild)
4683 break;
4686 g_list_free(pChildren);
4688 return pChild;
4691 GtkWidget* find_image_widget(GtkContainer* pContainer)
4693 GList* pChildren = gtk_container_get_children(pContainer);
4695 GtkWidget* pChild = nullptr;
4696 for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
4698 if (GTK_IS_IMAGE(pCandidate->data))
4700 pChild = GTK_WIDGET(pCandidate->data);
4701 break;
4703 else if (GTK_IS_CONTAINER(pCandidate->data))
4705 pChild = find_image_widget(GTK_CONTAINER(pCandidate->data));
4706 if (pChild)
4707 break;
4710 g_list_free(pChildren);
4712 return pChild;
4714 #endif
4716 GtkLabel* get_label_widget(GtkWidget* pButton)
4718 #if !GTK_CHECK_VERSION(4, 0, 0)
4719 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
4721 if (GTK_IS_CONTAINER(pChild))
4722 pChild = find_label_widget(GTK_CONTAINER(pChild));
4723 else if (!GTK_IS_LABEL(pChild))
4724 pChild = nullptr;
4726 return GTK_LABEL(pChild);
4727 #else
4728 return GTK_LABEL(find_label_widget(pButton));
4729 #endif
4732 GtkImage* get_image_widget(GtkWidget *pButton)
4734 #if !GTK_CHECK_VERSION(4, 0, 0)
4735 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
4737 if (GTK_IS_CONTAINER(pChild))
4738 pChild = find_image_widget(GTK_CONTAINER(pChild));
4739 else if (!GTK_IS_IMAGE(pChild))
4740 pChild = nullptr;
4742 return GTK_IMAGE(pChild);
4743 #else
4744 return GTK_IMAGE(find_image_widget(pButton));
4745 #endif
4748 OUString button_get_label(GtkButton* pButton)
4750 if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
4751 return ::get_label(pLabel);
4752 const gchar* pStr = gtk_button_get_label(pButton);
4753 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4756 void button_set_label(GtkButton* pButton, const OUString& rText)
4758 if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
4760 ::set_label(pLabel, rText);
4761 gtk_widget_set_visible(GTK_WIDGET(pLabel), true);
4762 return;
4764 gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
4767 #if GTK_CHECK_VERSION(4, 0, 0)
4768 OUString get_label(GtkCheckButton* pButton)
4770 const gchar* pStr = gtk_check_button_get_label(pButton);
4771 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4774 void set_label(GtkCheckButton* pButton, const OUString& rText)
4776 gtk_check_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
4778 #endif
4780 OUString get_title(GtkWindow* pWindow)
4782 const gchar* pStr = gtk_window_get_title(pWindow);
4783 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4786 void set_title(GtkWindow* pWindow, std::u16string_view rTitle)
4788 gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
4791 OUString get_primary_text(GtkMessageDialog* pMessageDialog)
4793 gchar* pText = nullptr;
4794 g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
4795 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
4798 void set_primary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
4800 g_object_set(G_OBJECT(pMessageDialog), "text",
4801 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
4802 nullptr);
4805 void set_secondary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
4807 g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
4808 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
4809 nullptr);
4812 OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
4814 gchar* pText = nullptr;
4815 g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
4816 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
4820 namespace
4822 GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
4824 auto nLength = rStream.TellEnd();
4825 if (!nLength)
4826 return nullptr;
4827 const guchar* pData = static_cast<const guchar*>(rStream.GetData());
4828 assert((*pData == 137 || *pData == '<') && "if we want to support more than png or svg this function must change");
4829 // if we know the image type, it's a little faster to hand the type over and skip the type detection.
4830 GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new_with_type(*pData == 137 ? "png" : "svg", nullptr);
4831 gdk_pixbuf_loader_write(pixbuf_loader, pData, nLength, nullptr);
4832 gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
4833 GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
4834 if (pixbuf)
4835 g_object_ref(pixbuf);
4836 g_object_unref(pixbuf_loader);
4837 return pixbuf;
4840 std::shared_ptr<SvMemoryStream> get_icon_stream_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
4842 return ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
4845 GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
4847 auto xMemStm = get_icon_stream_by_name_theme_lang(rIconName, rIconTheme, rUILang);
4848 if (!xMemStm)
4849 return nullptr;
4850 return load_icon_from_stream(*xMemStm);
4853 std::unique_ptr<utl::TempFileNamed> get_icon_stream_as_file_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
4855 uno::Reference<io::XInputStream> xInputStream = ImageTree::get().getImageXInputStream(rIconName, rIconTheme, rUILang);
4856 if (!xInputStream)
4857 return nullptr;
4859 std::unique_ptr<utl::TempFileNamed> xRet(new utl::TempFileNamed);
4860 xRet->EnableKillingFile(true);
4861 SvStream* pStream = xRet->GetStream(StreamMode::WRITE);
4863 for (;;)
4865 const sal_Int32 nSize(2048);
4866 uno::Sequence<sal_Int8> aData(nSize);
4867 sal_Int32 nRead = xInputStream->readBytes(aData, nSize);
4868 pStream->WriteBytes(aData.getConstArray(), nRead);
4869 if (nRead < nSize)
4870 break;
4872 xRet->CloseStream();
4874 return xRet;
4877 std::unique_ptr<utl::TempFileNamed> get_icon_stream_as_file(const OUString& rIconName)
4879 OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
4880 OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
4881 return get_icon_stream_as_file_by_name_theme_lang(rIconName, sIconTheme, sUILang);
4885 GdkPixbuf* load_icon_by_name(const OUString& rIconName)
4887 OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
4888 OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
4889 return load_icon_by_name_theme_lang(rIconName, sIconTheme, sUILang);
4892 namespace
4894 Image mirrorImage(const Image& rImage)
4896 BitmapEx aMirrBitmapEx(rImage.GetBitmapEx());
4897 aMirrBitmapEx.Mirror(BmpMirrorFlags::Horizontal);
4898 return Image(aMirrBitmapEx);
4901 GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
4903 Image aImage(rImage);
4905 OUString sStock(aImage.GetStock());
4906 if (!sStock.isEmpty())
4907 return load_icon_by_name(sStock);
4909 SvMemoryStream aMemStm;
4911 // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
4912 css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
4913 u"Compression"_ustr, sal_Int32(1)) };
4914 auto aBitmapEx = aImage.GetBitmapEx();
4915 vcl::PngImageWriter aWriter(aMemStm);
4916 aWriter.setParameters(aFilterData);
4917 aWriter.write(aBitmapEx);
4919 return load_icon_from_stream(aMemStm);
4922 // tdf#151898 as far as I can see only gtk_image_new_from_file (or gtk_image_new_from_resource) can support the use of a
4923 // scalable input format to create a hidpi GtkImage, rather than an upscaled lodpi one so forced to go via a file here
4924 std::unique_ptr<utl::TempFileNamed> getImageFile(const css::uno::Reference<css::graphic::XGraphic>& rImage, bool bMirror = false)
4926 Image aImage(rImage);
4927 if (bMirror)
4928 aImage = mirrorImage(aImage);
4930 OUString sStock(aImage.GetStock());
4931 if (!sStock.isEmpty())
4932 return get_icon_stream_as_file(sStock);
4934 std::unique_ptr<utl::TempFileNamed> xRet(new utl::TempFileNamed);
4935 xRet->EnableKillingFile(true);
4936 SvStream* pStream = xRet->GetStream(StreamMode::WRITE);
4938 // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
4939 css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
4940 u"Compression"_ustr, sal_Int32(1)) };
4941 auto aBitmapEx = aImage.GetBitmapEx();
4942 vcl::PngImageWriter aWriter(*pStream);
4943 aWriter.setParameters(aFilterData);
4944 aWriter.write(aBitmapEx);
4946 xRet->CloseStream();
4947 return xRet;
4950 GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
4952 Size aSize(rDevice.GetOutputSizePixel());
4953 cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
4954 double fXScale, fYScale;
4955 dl_cairo_surface_get_device_scale(orig_surface, &fXScale, &fYScale);
4957 cairo_surface_t* surface;
4958 if (fXScale != 1.0 || fYScale != -1)
4960 surface = cairo_surface_create_similar_image(orig_surface,
4961 CAIRO_FORMAT_ARGB32,
4962 aSize.Width(),
4963 aSize.Height());
4964 cairo_t* cr = cairo_create(surface);
4965 cairo_set_source_surface(cr, orig_surface, 0, 0);
4966 cairo_paint(cr);
4967 cairo_destroy(cr);
4969 else
4970 surface = orig_surface;
4972 GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
4974 if (surface != orig_surface)
4975 cairo_surface_destroy(surface);
4977 return pRet;
4980 #if GTK_CHECK_VERSION(4, 0, 0)
4981 cairo_surface_t* render_paintable_to_surface(GdkPaintable *paintable, int nWidth, int nHeight)
4983 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
4985 GtkSnapshot* snapshot = gtk_snapshot_new();
4986 gdk_paintable_snapshot(paintable, snapshot, nWidth, nHeight);
4987 GskRenderNode* node = gtk_snapshot_free_to_node(snapshot);
4989 cairo_t* cr = cairo_create(surface);
4990 gsk_render_node_draw(node, cr);
4991 cairo_destroy(cr);
4993 gsk_render_node_unref(node);
4995 return surface;
4997 #endif
4999 GdkPixbuf* getPixbuf(const OUString& rIconName)
5001 if (rIconName.isEmpty())
5002 return nullptr;
5004 GdkPixbuf* pixbuf = nullptr;
5005 if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
5007 assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
5008 "unknown stock image");
5010 #if GTK_CHECK_VERSION(4, 0, 0)
5011 GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
5012 GtkIconPaintable *icon = gtk_icon_theme_lookup_icon(icon_theme,
5013 OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
5014 nullptr,
5017 AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR,
5018 static_cast<GtkIconLookupFlags>(0));
5019 GdkPaintable* paintable = GDK_PAINTABLE(icon);
5020 int nWidth = gdk_paintable_get_intrinsic_width(paintable);
5021 int nHeight = gdk_paintable_get_intrinsic_height(paintable);
5022 cairo_surface_t* surface = render_paintable_to_surface(paintable, nWidth, nHeight);
5023 pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, nWidth, nHeight);
5024 cairo_surface_destroy(surface);
5025 #else
5026 GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
5027 GError *error = nullptr;
5028 pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
5029 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
5030 #endif
5032 else
5034 const AllSettings& rSettings = Application::GetSettings();
5035 pixbuf = load_icon_by_name_theme_lang(rIconName,
5036 rSettings.GetStyleSettings().DetermineIconTheme(),
5037 rSettings.GetUILanguageTag().getBcp47());
5039 return pixbuf;
5043 namespace
5045 #if GTK_CHECK_VERSION(4, 0, 0)
5046 SurfacePaintable* paintable_new_from_virtual_device(const VirtualDevice& rImageSurface)
5048 cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
5050 Size aSize(rImageSurface.GetOutputSizePixel());
5051 cairo_surface_t* target = cairo_surface_create_similar(surface,
5052 cairo_surface_get_content(surface),
5053 aSize.Width(),
5054 aSize.Height());
5055 cairo_t* cr = cairo_create(target);
5056 cairo_set_source_surface(cr, surface, 0, 0);
5057 cairo_paint(cr);
5058 cairo_destroy(cr);
5060 SurfacePaintable* pPaintable = SURFACE_PAINTABLE(g_object_new(surface_paintable_get_type(), nullptr));
5061 surface_paintable_set_source(pPaintable, target, aSize.Width(), aSize.Height());
5062 return pPaintable;
5065 GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
5067 SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
5068 return gtk_image_new_from_paintable(GDK_PAINTABLE(paintable));
5071 GtkWidget* picture_new_from_virtual_device(const VirtualDevice& rImageSurface)
5073 SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
5074 return gtk_picture_new_for_paintable(GDK_PAINTABLE(paintable));
5077 #else
5078 GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
5080 GtkWidget* pImage = nullptr;
5081 cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
5083 Size aSize(rImageSurface.GetOutputSizePixel());
5084 cairo_surface_t* target = cairo_surface_create_similar(surface,
5085 cairo_surface_get_content(surface),
5086 aSize.Width(),
5087 aSize.Height());
5088 cairo_t* cr = cairo_create(target);
5089 cairo_set_source_surface(cr, surface, 0, 0);
5090 cairo_paint(cr);
5091 cairo_destroy(cr);
5093 pImage = gtk_image_new_from_surface(target);
5094 cairo_surface_destroy(target);
5095 return pImage;
5097 #endif
5099 GtkWidget* image_new_from_xgraphic(const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
5101 GtkWidget* pImage = nullptr;
5102 if (auto xTempFile = getImageFile(rIcon, bMirror))
5103 pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5104 return pImage;
5107 GtkWidget* image_new_from_icon_name(const OUString& rIconName)
5109 GtkWidget* pImage = nullptr;
5110 if (auto xTempFile = get_icon_stream_as_file(rIconName))
5111 pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5112 return pImage;
5115 GtkWidget* image_new_from_icon_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
5117 GtkWidget* pImage = nullptr;
5118 if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
5119 pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5120 return pImage;
5123 void image_set_from_icon_name(GtkImage* pImage, const OUString& rIconName)
5125 if (auto xTempFile = get_icon_stream_as_file(rIconName))
5126 gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5127 else
5128 gtk_image_set_from_pixbuf(pImage, nullptr);
5131 void image_set_from_icon_name_theme_lang(GtkImage* pImage, const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
5133 if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
5134 gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5135 else
5136 gtk_image_set_from_pixbuf(pImage, nullptr);
5139 void image_set_from_virtual_device(GtkImage* pImage, const VirtualDevice* pDevice)
5141 #if GTK_CHECK_VERSION(4, 0, 0)
5142 gtk_image_set_from_paintable(pImage, pDevice ? GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)) : nullptr);
5143 #else
5144 gtk_image_set_from_surface(pImage, pDevice ? get_underlying_cairo_surface(*pDevice) : nullptr);
5145 #endif
5148 void image_set_from_xgraphic(GtkImage* pImage, const css::uno::Reference<css::graphic::XGraphic>& rImage)
5150 if (auto xTempFile = getImageFile(rImage, false))
5151 gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5152 else
5153 gtk_image_set_from_pixbuf(pImage, nullptr);
5156 #if GTK_CHECK_VERSION(4, 0, 0)
5157 void picture_set_from_icon_name(GtkPicture* pPicture, const OUString& rIconName)
5159 if (auto xTempFile = get_icon_stream_as_file(rIconName))
5160 gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5161 else
5162 gtk_picture_set_pixbuf(pPicture, nullptr);
5165 void picture_set_from_icon_name_theme_lang(GtkPicture* pPicture, const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
5167 if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
5168 gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5169 else
5170 gtk_picture_set_pixbuf(pPicture, nullptr);
5173 void picture_set_from_virtual_device(GtkPicture* pPicture, const VirtualDevice* pDevice)
5175 if (!pDevice)
5176 gtk_picture_set_paintable(pPicture, nullptr);
5177 else
5178 gtk_picture_set_paintable(pPicture, GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)));
5181 void picture_set_from_xgraphic(GtkPicture* pPicture, const css::uno::Reference<css::graphic::XGraphic>& rPicture)
5183 if (auto xTempFile = getImageFile(rPicture, false))
5184 gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
5185 else
5186 gtk_picture_set_pixbuf(pPicture, nullptr);
5188 #endif
5190 void button_set_from_icon_name(GtkButton* pButton, const OUString& rIconName)
5192 if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
5194 ::image_set_from_icon_name(pImage, rIconName);
5195 gtk_widget_set_visible(GTK_WIDGET(pImage), true);
5196 return;
5199 GtkWidget* pImage = image_new_from_icon_name(rIconName);
5200 #if GTK_CHECK_VERSION(4, 0, 0)
5201 gtk_button_set_child(pButton, pImage);
5202 #else
5203 gtk_button_set_image(pButton, pImage);
5204 #endif
5207 void button_set_image(GtkButton* pButton, const VirtualDevice* pDevice)
5209 #if !GTK_CHECK_VERSION(4, 0, 0)
5210 gtk_button_set_always_show_image(pButton, true);
5211 gtk_button_set_image_position(pButton, GTK_POS_LEFT);
5212 #endif
5213 GtkWidget* pImage = pDevice ? image_new_from_virtual_device(*pDevice) : nullptr;
5214 #if GTK_CHECK_VERSION(4, 0, 0)
5215 gtk_button_set_child(pButton, pImage);
5216 #else
5217 gtk_button_set_image(pButton, pImage);
5218 #endif
5221 void button_set_image(GtkButton* pButton, const css::uno::Reference<css::graphic::XGraphic>& rImage)
5223 if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
5225 ::image_set_from_xgraphic(pImage, rImage);
5226 gtk_widget_set_visible(GTK_WIDGET(pImage), true);
5227 return;
5230 GtkWidget* pImage = image_new_from_xgraphic(rImage, false);
5231 #if GTK_CHECK_VERSION(4, 0, 0)
5232 gtk_button_set_child(pButton, pImage);
5233 #else
5234 gtk_button_set_image(pButton, pImage);
5235 #endif
5239 class MenuHelper
5241 protected:
5242 #if !GTK_CHECK_VERSION(4, 0, 0)
5243 GtkMenu* m_pMenu;
5245 std::map<OUString, GtkMenuItem*> m_aMap;
5246 #else
5247 GtkPopoverMenu* m_pMenu;
5249 o3tl::sorted_vector<OString> m_aInsertedActions; // must outlive m_aActionEntries
5250 std::map<OUString, OString> m_aIdToAction;
5251 std::set<OUString> m_aHiddenIds;
5252 std::vector<GActionEntry> m_aActionEntries;
5253 GActionGroup* m_pActionGroup;
5254 // move 'invisible' entries to m_pHiddenActionGroup
5255 GActionGroup* m_pHiddenActionGroup;
5256 #endif
5257 bool m_bTakeOwnership;
5258 private:
5260 virtual void signal_item_activate(const OUString& rIdent) = 0;
5262 #if !GTK_CHECK_VERSION(4, 0, 0)
5263 static void collect(GtkWidget* pItem, gpointer widget)
5265 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
5266 if (GtkWidget* pSubMenu = gtk_menu_item_get_submenu(pMenuItem))
5267 gtk_container_foreach(GTK_CONTAINER(pSubMenu), collect, widget);
5268 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
5269 pThis->add_to_map(pMenuItem);
5272 static void signalActivate(GtkMenuItem* pItem, gpointer widget)
5274 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
5275 SolarMutexGuard aGuard;
5276 pThis->signal_item_activate(::get_buildable_id(GTK_BUILDABLE(pItem)));
5278 #else
5279 static std::pair<GMenuModel*, int> find_id(GMenuModel* pMenuModel, const OUString& rId)
5281 for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
5283 OUString sTarget;
5284 char *id;
5285 if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
5287 sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
5288 g_free(id);
5291 if (sTarget == rId)
5292 return std::make_pair(pMenuModel, i);
5294 if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
5296 std::pair<GMenuModel*, int> aRet = find_id(pSectionModel, rId);
5297 if (aRet.first)
5298 return aRet;
5300 if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
5302 std::pair<GMenuModel*, int> aRet = find_id(pSubMenuModel, rId);
5303 if (aRet.first)
5304 return aRet;
5308 return std::make_pair(nullptr, -1);
5311 void clear_actions()
5313 for (const auto& rAction : m_aActionEntries)
5315 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), rAction.name);
5316 g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), rAction.name);
5318 m_aActionEntries.clear();
5319 m_aInsertedActions.clear();
5320 m_aIdToAction.clear();
5323 static void action_activated(GSimpleAction*, GVariant* pParameter, gpointer widget)
5325 gsize nLength(0);
5326 const gchar* pStr = g_variant_get_string(pParameter, &nLength);
5327 OUString aStr(pStr, nLength, RTL_TEXTENCODING_UTF8);
5328 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
5329 SolarMutexGuard aGuard;
5330 pThis->signal_item_activate(aStr);
5332 #endif
5334 public:
5335 #if !GTK_CHECK_VERSION(4, 0, 0)
5336 MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
5337 #else
5338 MenuHelper(GtkPopoverMenu* pMenu, bool bTakeOwnership)
5339 #endif
5340 : m_pMenu(pMenu)
5341 , m_bTakeOwnership(bTakeOwnership)
5343 #if !GTK_CHECK_VERSION(4, 0, 0)
5344 if (!m_pMenu)
5345 return;
5346 gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
5347 #else
5348 m_pActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
5349 m_pHiddenActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
5350 #endif
5353 #if !GTK_CHECK_VERSION(4, 0, 0)
5354 void add_to_map(GtkMenuItem* pMenuItem)
5356 OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
5357 m_aMap[id] = pMenuItem;
5358 g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
5361 void remove_from_map(GtkMenuItem* pMenuItem)
5363 OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
5364 auto iter = m_aMap.find(id);
5365 g_signal_handlers_disconnect_by_data(pMenuItem, this);
5366 m_aMap.erase(iter);
5369 void disable_item_notify_events()
5371 for (auto& a : m_aMap)
5372 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
5375 void enable_item_notify_events()
5377 for (auto& a : m_aMap)
5378 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
5380 #endif
5382 #if GTK_CHECK_VERSION(4, 0, 0)
5383 /* LibreOffice likes to think of separators between menu entries, while gtk likes
5384 to think of sections of menus with separators drawn between sections. We always
5385 arrange to have a section in a menu so toplevel menumodels comprise of
5386 sections and we move entries between sections on pretending to insert separators */
5387 static std::pair<GMenuModel*, int> get_section_and_pos_for(GMenuModel* pMenuModel, int pos)
5389 int nSectionCount = g_menu_model_get_n_items(pMenuModel);
5390 assert(nSectionCount);
5392 GMenuModel* pSectionModel = nullptr;
5393 int nIndexWithinSection = 0;
5395 int nExternalPos = 0;
5396 for (int nSection = 0; nSection < nSectionCount; ++nSection)
5398 pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
5399 assert(pSectionModel);
5400 int nCount = g_menu_model_get_n_items(pSectionModel);
5401 for (nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
5403 if (pos == nExternalPos)
5404 break;
5405 ++nExternalPos;
5407 ++nExternalPos;
5410 return std::make_pair(pSectionModel, nIndexWithinSection);
5413 static int count_immediate_children(GMenuModel* pMenuModel)
5415 int nSectionCount = g_menu_model_get_n_items(pMenuModel);
5416 assert(nSectionCount);
5418 int nExternalPos = 0;
5419 for (int nSection = 0; nSection < nSectionCount; ++nSection)
5421 GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
5422 assert(pSectionModel);
5423 int nCount = g_menu_model_get_n_items(pSectionModel);
5424 for (int nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
5426 ++nExternalPos;
5428 ++nExternalPos;
5431 return nExternalPos - 1;
5433 #endif
5435 #if GTK_CHECK_VERSION(4, 0, 0)
5436 void process_menu_model(GMenuModel* pMenuModel)
5438 for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
5440 OString sAction;
5441 OUString sTarget;
5442 char *id;
5443 if (g_menu_model_get_item_attribute(pMenuModel, i, "action", "s", &id))
5445 assert(o3tl::starts_with(id, "menu."));
5447 sAction = OString(id + 5);
5449 auto res = m_aInsertedActions.insert(sAction);
5450 if (res.second)
5452 // the const char* arg isn't copied by anything so it must continue to exist for the life time of
5453 // the action group
5454 if (sAction.startsWith("radio."))
5455 m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", "'none'", nullptr, {}});
5456 else
5457 m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", nullptr, nullptr, {}});
5460 g_free(id);
5463 if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
5465 sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
5466 g_free(id);
5469 m_aIdToAction[sTarget] = sAction;
5471 if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
5472 process_menu_model(pSectionModel);
5473 if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
5474 process_menu_model(pSubMenuModel);
5478 // build an action group for the menu, "action" is the normal menu entry case
5479 // the others are radiogroups
5480 void update_action_group_from_popover_model()
5482 clear_actions();
5484 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5486 process_menu_model(pMenuModel);
5489 // move hidden entries to m_pHiddenActionGroup
5490 g_action_map_add_action_entries(G_ACTION_MAP(m_pActionGroup), m_aActionEntries.data(), m_aActionEntries.size(), this);
5491 for (const auto& id : m_aHiddenIds)
5493 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
5494 g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
5495 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
5498 #endif
5500 void insert_item(int pos, const OUString& rId, const OUString& rStr,
5501 const OUString* pIconName, const VirtualDevice* pImageSurface,
5502 TriState eCheckRadioFalse)
5504 #if !GTK_CHECK_VERSION(4, 0, 0)
5505 GtkWidget* pImage = nullptr;
5506 if (pIconName && !pIconName->isEmpty())
5507 pImage = image_new_from_icon_name(*pIconName);
5508 else if (pImageSurface)
5509 pImage = image_new_from_virtual_device(*pImageSurface);
5511 GtkWidget *pItem;
5512 if (pImage)
5514 GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
5515 GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
5516 gtk_label_set_xalign(GTK_LABEL(pLabel), 0.0);
5517 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
5518 gtk_box_pack_start(pBox, pImage, false, true, 0);
5519 gtk_box_pack_start(pBox, pLabel, true, true, 0);
5520 gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
5521 gtk_widget_show_all(pItem);
5523 else
5525 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
5526 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
5529 if (eCheckRadioFalse == TRISTATE_FALSE)
5530 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
5532 ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
5533 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
5534 gtk_widget_show(pItem);
5535 add_to_map(GTK_MENU_ITEM(pItem));
5536 if (pos != -1)
5537 gtk_menu_reorder_child(m_pMenu, pItem, pos);
5538 #else
5539 (void)pIconName; (void)pImageSurface;
5541 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5543 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
5544 GMenu* pMenu = G_MENU(aSectionAndPos.first);
5545 // action with a target value ... the action name and target value are separated by a double
5546 // colon ... For example: "app.action::target"
5547 OUString sActionAndTarget;
5548 if (eCheckRadioFalse == TRISTATE_INDET)
5549 sActionAndTarget = "menu.normal." + rId + "::" + rId;
5550 else
5551 sActionAndTarget = "menu.radio." + rId + "::" + rId;
5552 g_menu_insert(pMenu, aSectionAndPos.second, MapToGtkAccelerator(rStr).getStr(), sActionAndTarget.toUtf8().getStr());
5554 assert(eCheckRadioFalse == TRISTATE_INDET); // come back to this later
5556 // TODO not redo entire group
5557 update_action_group_from_popover_model();
5559 #endif
5562 void insert_separator(int pos, const OUString& rId)
5564 #if !GTK_CHECK_VERSION(4, 0, 0)
5565 GtkWidget* pItem = gtk_separator_menu_item_new();
5566 ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
5567 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
5568 gtk_widget_show(pItem);
5569 add_to_map(GTK_MENU_ITEM(pItem));
5570 if (pos != -1)
5571 gtk_menu_reorder_child(m_pMenu, pItem, pos);
5572 #else
5573 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5575 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
5577 for (int nSection = 0, nSectionCount = g_menu_model_get_n_items(pMenuModel); nSection < nSectionCount; ++nSection)
5579 GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
5580 assert(pSectionModel);
5581 if (aSectionAndPos.first == pSectionModel)
5583 GMenu* pNewSection = g_menu_new();
5584 GMenuItem* pSectionItem = g_menu_item_new_section(nullptr, G_MENU_MODEL(pNewSection));
5585 OUString sActionAndTarget = "menu.separator." + rId + "::" + rId;
5586 g_menu_item_set_detailed_action(pSectionItem, sActionAndTarget.toUtf8().getStr());
5587 g_menu_insert_item(G_MENU(pMenuModel), nSection + 1, pSectionItem);
5588 int nOldSectionCount = g_menu_model_get_n_items(pSectionModel);
5589 for (int i = nOldSectionCount - 1; i >= aSectionAndPos.second; --i)
5591 GMenuItem* pMenuItem = g_menu_item_new_from_model(pSectionModel, i);
5592 g_menu_prepend_item(pNewSection, pMenuItem);
5593 g_menu_remove(G_MENU(pSectionModel), i);
5594 g_object_unref(pMenuItem);
5596 g_object_unref(pSectionItem);
5597 g_object_unref(pNewSection);
5602 #endif
5605 void remove_item(const OUString& rIdent)
5607 #if !GTK_CHECK_VERSION(4, 0, 0)
5608 GtkMenuItem* pMenuItem = m_aMap[rIdent];
5609 remove_from_map(pMenuItem);
5610 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
5611 #else
5612 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5614 std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
5615 if (!aRes.first)
5616 return;
5617 g_menu_remove(G_MENU(aRes.first), aRes.second);
5619 #endif
5622 void set_item_sensitive(const OUString& rIdent, bool bSensitive)
5624 #if GTK_CHECK_VERSION(4, 0, 0)
5625 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5626 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction[rIdent].getStr());
5627 g_simple_action_set_enabled(G_SIMPLE_ACTION(pAction), bSensitive);
5628 #else
5629 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
5630 #endif
5633 bool get_item_sensitive(const OUString& rIdent) const
5635 #if GTK_CHECK_VERSION(4, 0, 0)
5636 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5637 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction.find(rIdent)->second.getStr());
5638 return g_action_get_enabled(pAction);
5639 #else
5640 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
5641 #endif
5644 void set_item_active(const OUString& rIdent, bool bActive)
5646 #if !GTK_CHECK_VERSION(4, 0, 0)
5647 disable_item_notify_events();
5648 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
5649 enable_item_notify_events();
5650 #else
5651 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5652 g_action_group_change_action_state(pActionGroup, m_aIdToAction[rIdent].getStr(),
5653 g_variant_new_string(bActive ? OUStringToOString(rIdent, RTL_TEXTENCODING_UTF8).getStr() : "'none'"));
5654 #endif
5657 bool get_item_active(const OUString& rIdent) const
5659 #if !GTK_CHECK_VERSION(4, 0, 0)
5660 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
5661 #else
5662 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5663 GVariant* pState = g_action_group_get_action_state(pActionGroup, m_aIdToAction.find(rIdent)->second.getStr());
5664 if (!pState)
5665 return false;
5666 const char *pStateString = g_variant_get_string(pState, nullptr);
5667 bool bInactive = g_strcmp0(pStateString, "'none'") == 0;
5668 g_variant_unref(pState);
5669 return bInactive;
5670 #endif
5673 void set_item_label(const OUString& rIdent, const OUString& rText)
5675 #if !GTK_CHECK_VERSION(4, 0, 0)
5676 gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
5677 #else
5678 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5680 std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
5681 if (!aRes.first)
5682 return;
5683 // clone the original item, remove the original, insert the replacement at
5684 // the original location
5685 GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
5686 g_menu_remove(G_MENU(aRes.first), aRes.second);
5687 g_menu_item_set_label(pMenuItem, MapToGtkAccelerator(rText).getStr());
5688 g_menu_insert_item(G_MENU(aRes.first), aRes.second, pMenuItem);
5689 g_object_unref(pMenuItem);
5691 #endif
5694 OUString get_item_label(const OUString& rIdent) const
5696 #if !GTK_CHECK_VERSION(4, 0, 0)
5697 const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
5698 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
5699 #else
5700 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5702 std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
5703 if (!aRes.first)
5704 return OUString();
5706 // clone the original item to query its label
5707 GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
5708 char *pLabel = nullptr;
5709 g_menu_item_get_attribute(pMenuItem, G_MENU_ATTRIBUTE_LABEL, "&s", &pLabel);
5710 OUString aRet(pLabel, pLabel ? strlen(pLabel) : 0, RTL_TEXTENCODING_UTF8);
5711 g_free(pLabel);
5712 g_object_unref(pMenuItem);
5713 return aRet;
5715 return OUString();
5716 #endif
5719 void set_item_visible(const OUString& rIdent, bool bShow)
5721 #if !GTK_CHECK_VERSION(4, 0, 0)
5722 GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
5723 if (bShow)
5724 gtk_widget_show(pWidget);
5725 else
5726 gtk_widget_hide(pWidget);
5727 #else
5728 bool bOldVisible = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end();
5729 if (bShow == bOldVisible)
5730 return;
5732 if (!bShow)
5734 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
5735 g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
5736 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
5737 m_aHiddenIds.insert(rIdent);
5739 else
5741 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
5742 g_action_map_add_action(G_ACTION_MAP(m_pActionGroup), pAction);
5743 g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
5744 m_aHiddenIds.erase(rIdent);
5746 #endif
5749 OUString get_item_id(int pos) const
5751 #if !GTK_CHECK_VERSION(4, 0, 0)
5752 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
5753 gpointer pMenuItem = g_list_nth_data(pChildren, pos);
5754 OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
5755 g_list_free(pChildren);
5756 return id;
5757 #else
5758 OUString sTarget;
5759 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5761 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
5762 char *id;
5763 if (g_menu_model_get_item_attribute(aSectionAndPos.first, aSectionAndPos.second, "target", "s", &id))
5765 sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
5766 g_free(id);
5769 return sTarget;
5770 #endif
5773 int get_n_children() const
5775 #if !GTK_CHECK_VERSION(4, 0, 0)
5776 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
5777 int nLen = g_list_length(pChildren);
5778 g_list_free(pChildren);
5779 return nLen;
5780 #else
5781 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5782 return count_immediate_children(pMenuModel);
5783 return 0;
5784 #endif
5787 void clear_items()
5789 #if !GTK_CHECK_VERSION(4, 0, 0)
5790 for (const auto& a : m_aMap)
5792 GtkMenuItem* pMenuItem = a.second;
5793 g_signal_handlers_disconnect_by_data(pMenuItem, this);
5794 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
5796 m_aMap.clear();
5797 #else
5798 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5800 GMenu* pMenu = G_MENU(pMenuModel);
5801 g_menu_remove_all(pMenu);
5802 g_menu_insert_section(pMenu, 0, nullptr, G_MENU_MODEL(g_menu_new()));
5803 m_aHiddenIds.clear();
5804 update_action_group_from_popover_model();
5806 #endif
5809 #if !GTK_CHECK_VERSION(4, 0, 0)
5810 GtkMenu* getMenu() const
5811 #else
5812 GtkPopoverMenu* getMenu() const
5813 #endif
5815 return m_pMenu;
5818 virtual ~MenuHelper()
5820 #if !GTK_CHECK_VERSION(4, 0, 0)
5821 for (auto& a : m_aMap)
5822 g_signal_handlers_disconnect_by_data(a.second, this);
5823 if (m_bTakeOwnership)
5824 gtk_widget_destroy(GTK_WIDGET(m_pMenu));
5825 #else
5826 g_object_unref(m_pActionGroup);
5827 g_object_unref(m_pHiddenActionGroup);
5828 #endif
5832 class GtkInstanceSizeGroup : public weld::SizeGroup
5834 private:
5835 GtkSizeGroup* m_pGroup;
5836 public:
5837 GtkInstanceSizeGroup()
5838 : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
5841 virtual void add_widget(weld::Widget* pWidget) override
5843 GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
5844 assert(pVclWidget);
5845 gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
5847 virtual void set_mode(VclSizeGroupMode eVclMode) override
5849 GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
5850 switch (eVclMode)
5852 case VclSizeGroupMode::NONE:
5853 eGtkMode = GTK_SIZE_GROUP_NONE;
5854 break;
5855 case VclSizeGroupMode::Horizontal:
5856 eGtkMode = GTK_SIZE_GROUP_HORIZONTAL;
5857 break;
5858 case VclSizeGroupMode::Vertical:
5859 eGtkMode = GTK_SIZE_GROUP_VERTICAL;
5860 break;
5861 case VclSizeGroupMode::Both:
5862 eGtkMode = GTK_SIZE_GROUP_BOTH;
5863 break;
5865 gtk_size_group_set_mode(m_pGroup, eGtkMode);
5867 virtual ~GtkInstanceSizeGroup() override
5869 g_object_unref(m_pGroup);
5873 class ChildFrame : public WorkWindow
5875 private:
5876 Idle maLayoutIdle;
5878 DECL_LINK(ImplHandleLayoutTimerHdl, Timer*, void);
5879 public:
5880 ChildFrame(vcl::Window* pParent, WinBits nStyle)
5881 : WorkWindow(pParent, nStyle)
5882 , maLayoutIdle( "ChildFrame maLayoutIdle" )
5884 maLayoutIdle.SetPriority(TaskPriority::RESIZE);
5885 maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
5888 virtual void dispose() override
5890 maLayoutIdle.Stop();
5891 WorkWindow::dispose();
5894 virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override
5896 WorkWindow::queue_resize(eReason);
5897 if (maLayoutIdle.IsActive())
5898 return;
5899 maLayoutIdle.Start();
5902 void Layout()
5904 if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
5905 pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
5908 virtual void Resize() override
5910 maLayoutIdle.Stop();
5911 Layout();
5912 WorkWindow::Resize();
5916 IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
5918 Layout();
5921 class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
5923 private:
5924 #if !GTK_CHECK_VERSION(4, 0, 0)
5925 GtkContainer* m_pContainer;
5926 #else
5927 GtkWidget* m_pContainer;
5928 #endif
5929 gulong m_nSetFocusChildSignalId;
5930 bool m_bChildHasFocus;
5932 void signal_set_focus_child(bool bChildHasFocus)
5934 if (m_bChildHasFocus != bChildHasFocus)
5936 m_bChildHasFocus = bChildHasFocus;
5937 signal_container_focus_changed();
5941 #if !GTK_CHECK_VERSION(4, 0, 0)
5942 static void signalSetFocusChild(GtkContainer*, GtkWidget* pChild, gpointer widget)
5944 GtkInstanceContainer* pThis = static_cast<GtkInstanceContainer*>(widget);
5945 pThis->signal_set_focus_child(pChild != nullptr);
5947 #endif
5949 public:
5950 #if !GTK_CHECK_VERSION(4, 0, 0)
5951 GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5952 : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
5953 #else
5954 GtkInstanceContainer(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5955 : GtkInstanceWidget(pContainer, pBuilder, bTakeOwnership)
5956 #endif
5957 , m_pContainer(pContainer)
5958 , m_nSetFocusChildSignalId(0)
5959 , m_bChildHasFocus(false)
5963 virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
5965 #if !GTK_CHECK_VERSION(4, 0, 0)
5966 if (!m_nSetFocusChildSignalId)
5967 m_nSetFocusChildSignalId = g_signal_connect(G_OBJECT(m_pContainer), "set-focus-child", G_CALLBACK(signalSetFocusChild), this);
5968 #endif
5969 weld::Container::connect_container_focus_changed(rLink);
5972 #if GTK_CHECK_VERSION(4, 0, 0)
5973 GtkWidget* getContainer() { return m_pContainer; }
5974 #else
5975 GtkContainer* getContainer() { return m_pContainer; }
5976 #endif
5978 virtual void child_grab_focus() override
5980 gtk_widget_grab_focus(m_pWidget);
5981 #if GTK_CHECK_VERSION(4, 0, 0)
5982 bool bHasFocusChild = gtk_widget_get_focus_child(GTK_WIDGET(m_pContainer));
5983 #else
5984 bool bHasFocusChild = gtk_container_get_focus_child(m_pContainer);
5985 #endif
5986 if (!bHasFocusChild)
5988 #if GTK_CHECK_VERSION(4, 0, 0)
5989 if (GtkWidget* pChild = gtk_widget_get_first_child(m_pContainer))
5991 gtk_widget_set_focus_child(m_pContainer, pChild);
5992 bHasFocusChild = true;
5994 #else
5995 GList* pChildren = gtk_container_get_children(m_pContainer);
5996 if (GList* pChild = g_list_first(pChildren))
5998 gtk_container_set_focus_child(m_pContainer, static_cast<GtkWidget*>(pChild->data));
5999 bHasFocusChild = true;
6001 g_list_free(pChildren);
6002 #endif
6005 if (bHasFocusChild)
6007 #if GTK_CHECK_VERSION(4, 0, 0)
6008 gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
6009 #else
6010 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
6011 #endif
6016 virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
6018 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
6019 assert(pGtkWidget);
6020 GtkWidget* pChild = pGtkWidget->getWidget();
6021 g_object_ref(pChild);
6022 auto pOldContainer = getContainer();
6023 container_remove(GTK_WIDGET(pOldContainer), pChild);
6025 GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
6026 assert(!pNewParent || pNewGtkParent);
6027 if (pNewGtkParent)
6029 auto pNewContainer = pNewGtkParent->getContainer();
6030 container_add(GTK_WIDGET(pNewContainer), pChild);
6032 g_object_unref(pChild);
6035 virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
6037 // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
6038 // will create a toplevel GtkEventBox window
6039 auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
6040 SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
6041 GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
6042 assert(pGtkFrame);
6044 // relocate that toplevel GtkEventBox into this widget
6045 GtkWidget* pWindow = pGtkFrame->getWindow();
6047 GtkWidget* pParent = gtk_widget_get_parent(pWindow);
6049 g_object_ref(pWindow);
6050 container_remove(pParent, pWindow);
6051 container_add(GTK_WIDGET(m_pContainer), pWindow);
6052 #if !GTK_CHECK_VERSION(4, 0, 0)
6053 gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
6054 #endif
6055 gtk_widget_set_hexpand(pWindow, true);
6056 gtk_widget_set_vexpand(pWindow, true);
6057 gtk_widget_realize(pWindow);
6058 gtk_widget_set_can_focus(pWindow, true);
6059 g_object_unref(pWindow);
6061 // NoActivate otherwise Show grab focus to this widget
6062 xEmbedWindow->Show(true, ShowFlags::NoActivate);
6063 css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
6064 return xWindow;
6067 virtual ~GtkInstanceContainer() override
6069 if (m_nSetFocusChildSignalId)
6070 g_signal_handler_disconnect(m_pContainer, m_nSetFocusChildSignalId);
6076 std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
6078 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
6079 if (!pParent)
6080 return nullptr;
6081 #if !GTK_CHECK_VERSION(4, 0, 0)
6082 return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
6083 #else
6084 return std::make_unique<GtkInstanceContainer>(pParent, m_pBuilder, false);
6085 #endif
6088 namespace {
6090 bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
6092 //order within groups according to platform rules
6093 return getButtonPriority(get_buildable_id(GTK_BUILDABLE(pA))) <
6094 getButtonPriority(get_buildable_id(GTK_BUILDABLE(pB)));
6097 void sort_native_button_order(GtkBox* pContainer)
6099 std::vector<GtkWidget*> aChildren;
6100 #if GTK_CHECK_VERSION(4, 0, 0)
6101 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pContainer));
6102 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6104 aChildren.push_back(pChild);
6106 #else
6107 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
6108 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
6109 aChildren.push_back(static_cast<GtkWidget*>(pChild->data));
6110 g_list_free(pChildren);
6111 #endif
6113 //sort child order within parent so that we match the platform button order
6114 std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
6116 #if GTK_CHECK_VERSION(4, 0, 0)
6117 for (size_t pos = 0; pos < aChildren.size(); ++pos)
6118 gtk_box_reorder_child_after(pContainer, aChildren[pos], pos ? aChildren[pos - 1] : nullptr);
6119 #else
6120 for (size_t pos = 0; pos < aChildren.size(); ++pos)
6121 gtk_box_reorder_child(pContainer, aChildren[pos], pos);
6122 #endif
6125 class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
6127 private:
6128 GtkBox* m_pBox;
6130 public:
6131 GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6132 #if !GTK_CHECK_VERSION(4, 0, 0)
6133 : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
6134 #else
6135 : GtkInstanceContainer(GTK_WIDGET(pBox), pBuilder, bTakeOwnership)
6136 #endif
6137 , m_pBox(pBox)
6141 virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
6143 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
6144 assert(pGtkWidget);
6145 GtkWidget* pChild = pGtkWidget->getWidget();
6147 #if !GTK_CHECK_VERSION(4, 0, 0)
6148 gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
6149 #else
6150 if (nNewPosition == 0)
6151 gtk_box_reorder_child_after(m_pBox, pChild, nullptr);
6152 else
6154 int nNewSiblingPos = nNewPosition - 1;
6155 int nChildPosition = 0;
6156 for (GtkWidget* pNewSibling = gtk_widget_get_first_child(GTK_WIDGET(m_pBox));
6157 pNewSibling; pNewSibling = gtk_widget_get_next_sibling(pNewSibling))
6159 if (nChildPosition == nNewSiblingPos)
6161 gtk_box_reorder_child_after(m_pBox, pChild, pNewSibling);
6162 break;
6164 ++nChildPosition;
6167 #endif
6170 virtual void sort_native_button_order() override
6172 ::sort_native_button_order(m_pBox);
6178 namespace
6180 Point get_csd_offset(GtkWidget* pTopLevel)
6182 // try and omit drawing CSD under wayland
6183 GtkWidget* pChild = widget_get_first_child(pTopLevel);
6185 gtk_coord x, y;
6186 gtk_widget_translate_coordinates(pChild, pTopLevel, 0, 0, &x, &y);
6188 #if !GTK_CHECK_VERSION(4, 0, 0)
6189 int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild));
6190 int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
6191 int totalborder = outerborder + innerborder;
6192 x -= totalborder;
6193 y -= totalborder;
6194 #endif
6196 return Point(x, y);
6199 void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
6201 GtkWidget* pTopLevel = widget_get_toplevel(pItem);
6203 gtk_coord x, y;
6204 gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
6206 Point aOffset = get_csd_offset(pTopLevel);
6208 GtkAllocation alloc;
6209 gtk_widget_get_allocation(pItem, &alloc);
6211 const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
6212 const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
6214 if (!aCurrentRange.isEmpty())
6216 weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
6217 pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
6220 #if GTK_CHECK_VERSION(4, 0, 0)
6221 for (GtkWidget* pChild = gtk_widget_get_first_child(pItem);
6222 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6224 do_collect_screenshot_data(pChild, data);
6226 #else
6227 if (GTK_IS_CONTAINER(pItem))
6228 gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
6229 #endif
6232 AbsoluteScreenPixelRectangle get_monitor_workarea(GtkWidget* pWindow)
6234 GdkRectangle aRect;
6235 #if !GTK_CHECK_VERSION(4, 0, 0)
6236 GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
6237 gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, widget_get_surface(pWindow));
6238 gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
6239 #else
6240 GdkDisplay* pDisplay = gtk_widget_get_display(pWindow);
6241 GdkSurface* gdkWindow = widget_get_surface(pWindow);
6242 GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
6243 gdk_monitor_get_geometry(pMonitor, &aRect);
6244 #endif
6245 return AbsoluteScreenPixelRectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height);
6249 class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
6251 private:
6252 GtkWindow* m_pWindow;
6253 rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
6254 gulong m_nToplevelFocusChangedSignalId;
6255 protected:
6256 std::optional<Point> m_aPosWhileInvis; //tdf#146648 store last known position when visible to return as pos if hidden
6258 #if !GTK_CHECK_VERSION(4, 0, 0)
6259 static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
6261 if (GTK_IS_BUTTON(pWidget))
6262 g_object_set(G_OBJECT(pWidget), "has-default", false, nullptr);
6263 if (GTK_IS_CONTAINER(pWidget))
6264 gtk_container_forall(GTK_CONTAINER(pWidget), implResetDefault, user_data);
6267 void recursively_unset_default_buttons()
6269 implResetDefault(GTK_WIDGET(m_pWindow), nullptr);
6271 #endif
6273 #if !GTK_CHECK_VERSION(4, 0, 0)
6274 static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
6276 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
6277 pThis->help();
6278 return true;
6280 #endif
6282 static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
6284 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
6285 pThis->signal_container_focus_changed();
6288 bool isPositioningAllowed() const
6290 // no X/Y positioning under Wayland
6291 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
6292 return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
6295 protected:
6296 void help();
6297 public:
6298 GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6299 #if !GTK_CHECK_VERSION(4, 0, 0)
6300 : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
6301 #else
6302 : GtkInstanceContainer(GTK_WIDGET(pWindow), pBuilder, bTakeOwnership)
6303 #endif
6304 , m_pWindow(pWindow)
6305 , m_nToplevelFocusChangedSignalId(0)
6307 #if !GTK_CHECK_VERSION(4, 0, 0)
6308 const bool bIsFrameWeld = pBuilder == nullptr;
6309 if (!bIsFrameWeld)
6311 //hook up F1 to show help
6312 GtkAccelGroup *pGroup = gtk_accel_group_new();
6313 GClosure* closure = g_cclosure_new(G_CALLBACK(help_pressed), this, nullptr);
6314 gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
6315 gtk_window_add_accel_group(pWindow, pGroup);
6317 #endif
6320 virtual void set_title(const OUString& rTitle) override
6322 ::set_title(m_pWindow, rTitle);
6325 virtual OUString get_title() const override
6327 return ::get_title(m_pWindow);
6330 virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
6332 if (!m_xWindow.is())
6333 m_xWindow.set(new SalGtkXWindow(this, m_pWidget));
6334 return m_xWindow;
6337 virtual void set_modal(bool bModal) override
6339 gtk_window_set_modal(m_pWindow, bModal);
6342 virtual bool get_modal() const override
6344 return gtk_window_get_modal(m_pWindow);
6347 virtual void resize_to_request() override
6349 #if GTK_CHECK_VERSION(4, 0, 0)
6350 gtk_window_set_default_size(m_pWindow, 1, 1);
6351 #else
6352 gtk_window_resize(m_pWindow, 1, 1);
6353 #endif
6356 virtual void window_move(int x, int y) override
6358 #if !GTK_CHECK_VERSION(4, 0, 0)
6359 gtk_window_move(m_pWindow, x, y);
6360 #else
6361 (void)x;
6362 (void)y;
6363 #endif
6366 virtual SystemEnvData get_system_data() const override
6368 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(GTK_WIDGET(m_pWindow));
6369 assert(pFrame && "nothing should call this impl, yet anyway, if ever, except on result of GetFrameWeld()");
6370 const SystemEnvData* pEnvData = pFrame->GetSystemData();
6371 assert(pEnvData);
6372 return *pEnvData;
6375 virtual Size get_size() const override
6377 int current_width, current_height;
6378 #if !GTK_CHECK_VERSION(4, 0, 0)
6379 gtk_window_get_size(m_pWindow, &current_width, &current_height);
6380 #else
6381 gtk_window_get_default_size(m_pWindow, &current_width, &current_height);
6382 #endif
6383 return Size(current_width, current_height);
6386 virtual Point get_position() const override
6388 if (m_aPosWhileInvis)
6390 assert(!get_visible());
6391 return *m_aPosWhileInvis;
6394 int current_x(0), current_y(0);
6395 #if !GTK_CHECK_VERSION(4, 0, 0)
6396 gtk_window_get_position(m_pWindow, &current_x, &current_y);
6397 #endif
6398 return Point(current_x, current_y);
6401 virtual void show() override
6403 m_aPosWhileInvis.reset();
6404 GtkInstanceContainer::show();
6407 virtual void hide() override
6409 if (is_visible())
6410 m_aPosWhileInvis = get_position();
6411 GtkInstanceContainer::hide();
6414 virtual AbsoluteScreenPixelRectangle get_monitor_workarea() const override
6416 return ::get_monitor_workarea(GTK_WIDGET(m_pWindow));
6419 virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
6421 #if !GTK_CHECK_VERSION(4, 0, 0)
6422 if (bTrackGeometryRequests)
6423 gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ALWAYS);
6424 else
6425 gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ON_PARENT);
6426 #else
6427 (void)bTrackGeometryRequests;
6428 #endif
6431 virtual bool get_resizable() const override
6433 return gtk_window_get_resizable(m_pWindow);
6436 virtual bool has_toplevel_focus() const override
6438 #if GTK_CHECK_VERSION(4, 0, 0)
6439 return gtk_window_is_active(m_pWindow);
6440 #else
6441 return gtk_window_has_toplevel_focus(m_pWindow);
6442 #endif
6445 virtual void present() override
6447 gtk_window_present(m_pWindow);
6450 virtual void change_default_widget(weld::Widget* pOld, weld::Widget* pNew) override
6452 GtkInstanceWidget* pGtkNew = dynamic_cast<GtkInstanceWidget*>(pNew);
6453 GtkWidget* pWidgetNew = pGtkNew ? pGtkNew->getWidget() : nullptr;
6454 #if GTK_CHECK_VERSION(4, 0, 0)
6455 gtk_window_set_default_widget(m_pWindow, pWidgetNew);
6456 (void)pOld;
6457 #else
6458 GtkInstanceWidget* pGtkOld = dynamic_cast<GtkInstanceWidget*>(pOld);
6459 GtkWidget* pWidgetOld = pGtkOld ? pGtkOld->getWidget() : nullptr;
6460 if (pWidgetOld)
6461 g_object_set(G_OBJECT(pWidgetOld), "has-default", false, nullptr);
6462 else
6463 recursively_unset_default_buttons();
6464 if (pWidgetNew)
6465 g_object_set(G_OBJECT(pWidgetNew), "has-default", true, nullptr);
6466 #endif
6469 virtual bool is_default_widget(const weld::Widget* pCandidate) const override
6471 const GtkInstanceWidget* pGtkCandidate = dynamic_cast<const GtkInstanceWidget*>(pCandidate);
6472 GtkWidget* pWidget = pGtkCandidate ? pGtkCandidate->getWidget() : nullptr;
6473 #if GTK_CHECK_VERSION(4, 0, 0)
6474 return pWidget && gtk_window_get_default_widget(m_pWindow) == pWidget;
6475 #else
6476 gboolean has_default(false);
6477 if (pWidget)
6478 g_object_get(G_OBJECT(pWidget), "has-default", &has_default, nullptr);
6479 return has_default;
6480 #endif
6483 virtual void set_window_state(const OUString& rStr) override
6485 const vcl::WindowData aData(rStr);
6486 const auto nMask = aData.mask();
6487 const auto nState = aData.state() & vcl::WindowState::SystemMask;
6489 if ((nMask & vcl::WindowDataMask::Size) == vcl::WindowDataMask::Size)
6491 gtk_window_set_default_size(m_pWindow, aData.width(), aData.height());
6493 if (nMask & vcl::WindowDataMask::State)
6495 if (nState & vcl::WindowState::Maximized)
6496 gtk_window_maximize(m_pWindow);
6497 else
6498 gtk_window_unmaximize(m_pWindow);
6501 #if !GTK_CHECK_VERSION(4, 0, 0)
6502 if (isPositioningAllowed() && ((nMask & vcl::WindowDataMask::Pos) == vcl::WindowDataMask::Pos))
6504 gtk_window_move(m_pWindow, aData.x(), aData.y());
6506 #endif
6509 virtual OUString get_window_state(vcl::WindowDataMask nMask) const override
6511 bool bPositioningAllowed = isPositioningAllowed();
6513 vcl::WindowData aData;
6514 vcl::WindowDataMask nAvailable = vcl::WindowDataMask::State | vcl::WindowDataMask::Size;
6515 if (bPositioningAllowed)
6516 nAvailable |= vcl::WindowDataMask::Pos;
6517 aData.setMask(nMask & nAvailable);
6519 if (nMask & vcl::WindowDataMask::State)
6521 vcl::WindowState nState = vcl::WindowState::Normal;
6522 if (gtk_window_is_maximized(m_pWindow))
6523 nState |= vcl::WindowState::Maximized;
6524 aData.setState(nState);
6527 if (bPositioningAllowed && (nMask & vcl::WindowDataMask::Pos))
6528 aData.setPos(get_position());
6530 if (nMask & vcl::WindowDataMask::Size)
6531 aData.setSize(get_size());
6533 return aData.toStr();
6536 virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
6538 if (!m_nToplevelFocusChangedSignalId)
6539 m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
6540 weld::Container::connect_container_focus_changed(rLink);
6543 virtual void disable_notify_events() override
6545 if (m_nToplevelFocusChangedSignalId)
6546 g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
6547 GtkInstanceContainer::disable_notify_events();
6550 virtual void enable_notify_events() override
6552 GtkInstanceContainer::enable_notify_events();
6553 if (m_nToplevelFocusChangedSignalId)
6554 g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
6557 virtual VclPtr<VirtualDevice> screenshot() override
6559 // detect if we have to manually setup its size
6560 bool bAlreadyRealized = gtk_widget_get_realized(GTK_WIDGET(m_pWindow));
6561 // has to be visible for draw to work
6562 bool bAlreadyVisible = gtk_widget_get_visible(GTK_WIDGET(m_pWindow));
6563 #if !GTK_CHECK_VERSION(4, 0, 0)
6564 if (!bAlreadyVisible)
6566 if (GTK_IS_DIALOG(m_pWindow))
6567 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow))));
6568 gtk_widget_show(GTK_WIDGET(m_pWindow));
6570 #endif
6572 if (!bAlreadyRealized)
6574 GtkAllocation allocation;
6575 gtk_widget_realize(GTK_WIDGET(m_pWindow));
6576 gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
6577 #if !GTK_CHECK_VERSION(4, 0, 0)
6578 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
6579 #else
6580 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation, 0);
6581 #endif
6584 VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
6585 xOutput->SetOutputSizePixel(get_size());
6586 cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
6587 cairo_t* cr = cairo_create(pSurface);
6589 Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
6591 cairo_translate(cr, -aOffset.X(), -aOffset.Y());
6593 #if !GTK_CHECK_VERSION(4, 0, 0)
6594 gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
6595 #else
6596 GtkSnapshot* pSnapshot = gtk_snapshot_new();
6597 GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(GTK_WIDGET(m_pWindow));
6598 pWidgetClass->snapshot(GTK_WIDGET(m_pWindow), pSnapshot);
6599 GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
6600 gsk_render_node_draw(pNode, cr);
6601 gsk_render_node_unref(pNode);
6602 #endif
6604 cairo_destroy(cr);
6606 if (!bAlreadyVisible)
6607 gtk_widget_hide(GTK_WIDGET(m_pWindow));
6608 if (!bAlreadyRealized)
6609 gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
6611 return xOutput;
6614 virtual weld::ScreenShotCollection collect_screenshot_data() override
6616 weld::ScreenShotCollection aRet;
6618 #if GTK_CHECK_VERSION(4, 0, 0)
6619 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pWindow));
6620 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6622 do_collect_screenshot_data(pChild, &aRet);
6624 #else
6625 gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
6626 #endif
6628 return aRet;
6631 virtual const vcl::ILibreOfficeKitNotifier* GetLOKNotifier() override
6633 // dummy implementation
6634 return nullptr;
6637 virtual ~GtkInstanceWindow() override
6639 if (m_nToplevelFocusChangedSignalId)
6640 g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
6641 if (m_xWindow.is())
6642 m_xWindow->clear();
6646 class GtkInstanceDialog;
6648 struct DialogRunner
6650 GtkWindow* m_pDialog;
6651 GtkInstanceDialog *m_pInstance;
6652 gint m_nResponseId;
6653 GMainLoop *m_pLoop;
6654 VclPtr<vcl::Window> m_xFrameWindow;
6655 int m_nModalDepth;
6657 DialogRunner(GtkWindow* pDialog, GtkInstanceDialog* pInstance)
6658 : m_pDialog(pDialog)
6659 , m_pInstance(pInstance)
6660 , m_nResponseId(GTK_RESPONSE_NONE)
6661 , m_pLoop(nullptr)
6662 , m_nModalDepth(0)
6664 GtkWindow* pParent = gtk_window_get_transient_for(m_pDialog);
6665 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
6666 m_xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
6669 bool loop_is_running() const
6671 return m_pLoop && g_main_loop_is_running(m_pLoop);
6674 void loop_quit()
6676 if (g_main_loop_is_running(m_pLoop))
6677 g_main_loop_quit(m_pLoop);
6680 static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
6681 static void signal_cancel(GtkAssistant*, gpointer data);
6683 #if !GTK_CHECK_VERSION(4, 0, 0)
6684 static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
6686 DialogRunner* pThis = static_cast<DialogRunner*>(data);
6687 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
6689 // An assistant isn't a dialog, but we want to treat it like one
6690 signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
6692 else
6693 pThis->loop_quit();
6694 return true; /* Do not destroy */
6696 #endif
6698 static void signal_destroy(GtkDialog*, gpointer data)
6700 DialogRunner* pThis = static_cast<DialogRunner*>(data);
6701 pThis->loop_quit();
6704 void inc_modal_count()
6706 if (m_xFrameWindow)
6708 m_xFrameWindow->IncModalCount();
6709 if (m_nModalDepth == 0)
6710 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
6711 ++m_nModalDepth;
6715 void dec_modal_count()
6717 if (m_xFrameWindow)
6719 m_xFrameWindow->DecModalCount();
6720 --m_nModalDepth;
6721 if (m_nModalDepth == 0)
6722 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
6726 // same as gtk_dialog_run except that unmap doesn't auto-respond
6727 // so we can hide the dialog and restore it without a response getting
6728 // triggered
6729 gint run()
6731 g_object_ref(m_pDialog);
6733 inc_modal_count();
6735 bool bWasModal = gtk_window_get_modal(m_pDialog);
6736 if (!bWasModal)
6737 gtk_window_set_modal(m_pDialog, true);
6739 if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
6740 gtk_widget_show(GTK_WIDGET(m_pDialog));
6742 gulong nSignalResponseId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signal_response), this) : 0;
6743 gulong nSignalCancelId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signal_cancel), this) : 0;
6744 #if !GTK_CHECK_VERSION(4, 0, 0)
6745 gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
6746 #endif
6747 gulong nSignalDestroyId = g_signal_connect(m_pDialog, "destroy", G_CALLBACK(signal_destroy), this);
6749 m_pLoop = g_main_loop_new(nullptr, false);
6750 m_nResponseId = GTK_RESPONSE_NONE;
6752 main_loop_run(m_pLoop);
6754 g_main_loop_unref(m_pLoop);
6756 m_pLoop = nullptr;
6758 if (!bWasModal)
6759 gtk_window_set_modal(m_pDialog, false);
6761 if (nSignalResponseId)
6762 g_signal_handler_disconnect(m_pDialog, nSignalResponseId);
6763 if (nSignalCancelId)
6764 g_signal_handler_disconnect(m_pDialog, nSignalCancelId);
6765 #if !GTK_CHECK_VERSION(4, 0, 0)
6766 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
6767 #endif
6768 g_signal_handler_disconnect(m_pDialog, nSignalDestroyId);
6770 dec_modal_count();
6772 g_object_unref(m_pDialog);
6774 return m_nResponseId;
6777 ~DialogRunner()
6779 if (m_xFrameWindow && m_nModalDepth)
6781 // if, like the calc validation dialog does, the modality was
6782 // toggled off during execution ensure that on cleanup the parent
6783 // is left in the state it was found
6784 while (m_nModalDepth++ < 0)
6785 m_xFrameWindow->IncModalCount();
6792 typedef std::set<GtkWidget*> winset;
6794 namespace
6796 #if GTK_CHECK_VERSION(4, 0, 0)
6797 void collectVisibleChildren(GtkWidget* pTop, winset& rVisibleWidgets)
6799 for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
6800 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6802 if (!gtk_widget_get_visible(pChild))
6803 continue;
6804 rVisibleWidgets.insert(pChild);
6805 collectVisibleChildren(pChild, rVisibleWidgets);
6808 #endif
6810 void hideUnless(GtkWidget* pTop, const winset& rVisibleWidgets,
6811 std::vector<GtkWidget*> &rWasVisibleWidgets)
6813 #if GTK_CHECK_VERSION(4, 0, 0)
6814 for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
6815 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6817 if (!gtk_widget_get_visible(pChild))
6818 continue;
6819 if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
6821 g_object_ref(pChild);
6822 rWasVisibleWidgets.emplace_back(pChild);
6823 gtk_widget_hide(pChild);
6825 else
6827 hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
6830 #else
6831 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTop));
6832 for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
6834 GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
6835 if (!gtk_widget_get_visible(pChild))
6836 continue;
6837 if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
6839 g_object_ref(pChild);
6840 rWasVisibleWidgets.emplace_back(pChild);
6841 gtk_widget_hide(pChild);
6843 else if (GTK_IS_CONTAINER(pChild))
6845 hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
6848 g_list_free(pChildren);
6849 #endif
6852 class GtkInstanceButton;
6854 class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
6856 private:
6857 GtkWindow* m_pDialog;
6858 DialogRunner m_aDialogRun;
6859 std::shared_ptr<weld::DialogController> m_xDialogController;
6860 // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
6861 std::shared_ptr<weld::Dialog> m_xRunAsyncSelf;
6862 std::function<void(sal_Int32)> m_aFunc;
6863 gulong m_nCloseSignalId;
6864 gulong m_nResponseSignalId;
6865 gulong m_nCancelSignalId;
6866 gulong m_nSignalDeleteId;
6868 // for calc ref dialog that shrink to range selection widgets and resize back
6869 GtkWidget* m_pRefEdit;
6870 std::vector<GtkWidget*> m_aHiddenWidgets; // vector of hidden Controls
6871 int m_nOldEditWidth; // Original width of the input field
6872 int m_nOldEditWidthReq; // Original width request of the input field
6873 #if !GTK_CHECK_VERSION(4, 0, 0)
6874 int m_nOldBorderWidth; // border width for expanded dialog
6875 #endif
6877 void signal_close()
6879 close(true);
6882 static void signalClose(GtkWidget*, gpointer widget)
6884 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6885 pThis->signal_close();
6888 static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
6890 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6891 pThis->asyncresponse(ret);
6894 static void signalAsyncCancel(GtkAssistant*, gpointer widget)
6896 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6897 // make esc in an assistant act as if cancel button was pressed
6898 pThis->close(false);
6901 #if !GTK_CHECK_VERSION(4, 0, 0)
6902 static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
6904 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6905 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
6907 // An assistant isn't a dialog, but we want to treat it like one
6908 signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
6910 return true; /* Do not destroy */
6912 #endif
6914 static int GtkToVcl(int ret)
6916 if (ret == GTK_RESPONSE_OK)
6917 ret = RET_OK;
6918 else if (ret == GTK_RESPONSE_CANCEL)
6919 ret = RET_CANCEL;
6920 else if (ret == GTK_RESPONSE_DELETE_EVENT)
6921 ret = RET_CANCEL;
6922 else if (ret == GTK_RESPONSE_CLOSE)
6923 ret = RET_CLOSE;
6924 else if (ret == GTK_RESPONSE_YES)
6925 ret = RET_YES;
6926 else if (ret == GTK_RESPONSE_NO)
6927 ret = RET_NO;
6928 else if (ret == GTK_RESPONSE_HELP)
6929 ret = RET_HELP;
6930 return ret;
6933 static int VclToGtk(int nResponse)
6935 if (nResponse == RET_OK)
6936 return GTK_RESPONSE_OK;
6937 else if (nResponse == RET_CANCEL)
6938 return GTK_RESPONSE_CANCEL;
6939 else if (nResponse == RET_CLOSE)
6940 return GTK_RESPONSE_CLOSE;
6941 else if (nResponse == RET_YES)
6942 return GTK_RESPONSE_YES;
6943 else if (nResponse == RET_NO)
6944 return GTK_RESPONSE_NO;
6945 else if (nResponse == RET_HELP)
6946 return GTK_RESPONSE_HELP;
6947 return nResponse;
6950 void asyncresponse(gint ret);
6952 #if !GTK_CHECK_VERSION(4, 0, 0)
6953 static void signalActivate(GtkMenuItem*, gpointer data)
6955 bool* pActivate = static_cast<bool*>(data);
6956 *pActivate = true;
6958 #endif
6960 #if !GTK_CHECK_VERSION(4, 0, 0)
6961 bool signal_screenshot_popup_menu(const GdkEventButton* pEvent)
6963 GtkWidget *pMenu = gtk_menu_new();
6965 GtkWidget* pMenuItem = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT)).getStr());
6966 gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
6967 bool bActivate(false);
6968 g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), &bActivate);
6969 gtk_widget_show(pMenuItem);
6971 int button, event_time;
6972 if (pEvent)
6974 button = pEvent->button;
6975 event_time = pEvent->time;
6977 else
6979 button = 0;
6980 event_time = gtk_get_current_event_time();
6983 gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
6985 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
6986 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
6988 gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
6990 if (g_main_loop_is_running(pLoop))
6991 main_loop_run(pLoop);
6993 g_main_loop_unref(pLoop);
6994 g_signal_handler_disconnect(pMenu, nSignalId);
6995 gtk_menu_detach(GTK_MENU(pMenu));
6997 if (bActivate)
6999 // open screenshot annotation dialog
7000 VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
7001 VclPtr<AbstractScreenshotAnnotationDlg> xTmp = pFact->CreateScreenshotAnnotationDlg(*this);
7002 ScopedVclPtr<AbstractScreenshotAnnotationDlg> xDialog(xTmp);
7003 xDialog->Execute();
7006 return false;
7008 #endif
7010 static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
7012 #if !GTK_CHECK_VERSION(4, 0, 0)
7013 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
7014 return pThis->signal_screenshot_popup_menu(nullptr);
7015 #else
7016 (void)widget;
7017 return false;
7018 #endif
7021 #if !GTK_CHECK_VERSION(4, 0, 0)
7022 static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
7024 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
7025 SolarMutexGuard aGuard;
7026 return pThis->signal_screenshot_button(pEvent);
7029 bool signal_screenshot_button(GdkEventButton* pEvent)
7031 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
7033 //if handled for context menu, stop processing
7034 return signal_screenshot_popup_menu(pEvent);
7036 return false;
7038 #endif
7040 public:
7041 GtkInstanceDialog(GtkWindow* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7042 : GtkInstanceWindow(pDialog, pBuilder, bTakeOwnership)
7043 , m_pDialog(pDialog)
7044 , m_aDialogRun(pDialog, this)
7045 , m_nResponseSignalId(0)
7046 , m_nCancelSignalId(0)
7047 , m_nSignalDeleteId(0)
7048 , m_pRefEdit(nullptr)
7049 , m_nOldEditWidth(0)
7050 , m_nOldEditWidthReq(0)
7051 #if !GTK_CHECK_VERSION(4, 0, 0)
7052 , m_nOldBorderWidth(0)
7053 #endif
7055 if (GTK_IS_DIALOG(m_pDialog) || GTK_IS_ASSISTANT(m_pDialog))
7056 m_nCloseSignalId = g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this);
7057 else
7058 m_nCloseSignalId = 0;
7059 const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
7060 if (bScreenshotMode)
7062 g_signal_connect(m_pDialog, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu), this);
7063 #if !GTK_CHECK_VERSION(4, 0, 0)
7064 g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
7065 #endif
7069 virtual bool runAsync(const std::shared_ptr<weld::DialogController>& rDialogController,
7070 const std::function<void(sal_Int32)>& func) override
7072 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
7074 m_xDialogController = rDialogController;
7075 m_aFunc = func;
7077 if (get_modal())
7078 m_aDialogRun.inc_modal_count();
7079 show();
7081 m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
7082 m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
7083 #if !GTK_CHECK_VERSION(4, 0, 0)
7084 m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
7085 #endif
7087 return true;
7090 virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
7092 assert( rxSelf.get() == this );
7093 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
7095 // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
7096 // which is that rxSelf enforces.
7097 m_xRunAsyncSelf = rxSelf;
7098 m_aFunc = func;
7100 if (get_modal())
7101 m_aDialogRun.inc_modal_count();
7102 show();
7104 m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
7105 m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
7106 #if !GTK_CHECK_VERSION(4, 0, 0)
7107 m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
7108 #endif
7110 return true;
7113 GtkInstanceButton* has_click_handler(int nResponse);
7115 virtual int run() override;
7117 virtual void show() override
7119 if (gtk_widget_get_visible(m_pWidget))
7120 return;
7121 #if !GTK_CHECK_VERSION(4, 0, 0)
7122 if (GTK_IS_DIALOG(m_pDialog))
7123 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
7124 #endif
7125 GtkInstanceWindow::show();
7128 virtual void set_modal(bool bModal) override
7130 if (get_modal() == bModal)
7131 return;
7132 GtkInstanceWindow::set_modal(bModal);
7133 /* if change the dialog modality while its running, then also change the parent LibreOffice window
7134 modal count, we typically expect the dialog modality to be restored to its original state
7136 This change modality while running case is for...
7138 a) the calc/chart dialogs which put up an extra range chooser
7139 dialog, hides the original, the user can select a range of cells and
7140 on completion the original dialog is restored
7142 b) the validity dialog in calc
7144 // tdf#135567 we know we are running in the sync case if loop_is_running is true
7145 // but for the async case we instead check for m_xDialogController which is set in
7146 // runAsync and cleared in asyncresponse
7147 if (m_aDialogRun.loop_is_running() || m_xDialogController)
7149 if (bModal)
7150 m_aDialogRun.inc_modal_count();
7151 else
7152 m_aDialogRun.dec_modal_count();
7156 virtual void response(int nResponse) override;
7158 virtual void add_button(const OUString& rText, int nResponse, const OUString& rHelpId) override
7160 GtkWidget* pWidget = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), MapToGtkAccelerator(rText).getStr(), VclToGtk(nResponse));
7161 if (!rHelpId.isEmpty())
7162 ::set_help_id(pWidget, rHelpId);
7165 virtual void set_default_response(int nResponse) override
7167 gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
7170 virtual GtkButton* get_widget_for_response(int nGtkResponse)
7172 return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
7175 virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
7177 virtual Container* weld_content_area() override
7179 #if !GTK_CHECK_VERSION(4, 0, 0)
7180 return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
7181 #else
7182 return new GtkInstanceContainer(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog)), m_pBuilder, false);
7183 #endif
7186 virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
7188 GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
7189 assert(pVclEdit);
7190 GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
7192 GtkWidget* pRefEdit = pVclEdit->getWidget();
7193 GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
7195 m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
7197 gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
7199 //We want just pRefBtn and pRefEdit to be shown
7200 //mark widgets we want to be visible, starting with pRefEdit
7201 //and all its direct parents.
7202 winset aVisibleWidgets;
7203 GtkWidget *pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog));
7204 for (GtkWidget *pCandidate = pRefEdit;
7205 pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
7206 pCandidate = gtk_widget_get_parent(pCandidate))
7208 aVisibleWidgets.insert(pCandidate);
7210 #if GTK_CHECK_VERSION(4, 0, 0)
7211 collectVisibleChildren(pRefEdit, aVisibleWidgets);
7212 #endif
7213 if (pRefBtn)
7215 #if GTK_CHECK_VERSION(4, 0, 0)
7216 collectVisibleChildren(pRefBtn, aVisibleWidgets);
7217 #endif
7218 //same again with pRefBtn, except stop if there's a
7219 //shared parent in the existing widgets
7220 for (GtkWidget *pCandidate = pRefBtn;
7221 pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
7222 pCandidate = gtk_widget_get_parent(pCandidate))
7224 if (aVisibleWidgets.insert(pCandidate).second)
7225 break;
7229 //hide everything except the aVisibleWidgets
7230 hideUnless(pContentArea, aVisibleWidgets, m_aHiddenWidgets);
7231 gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
7232 #if !GTK_CHECK_VERSION(4, 0, 0)
7233 m_nOldBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(m_pDialog));
7234 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), 0);
7235 if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
7236 gtk_widget_hide(pActionArea);
7237 gtk_widget_show_all(pRefEdit);
7238 if (pRefBtn)
7239 gtk_widget_show_all(pRefBtn);
7240 #else
7241 if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
7242 gtk_widget_hide(pActionArea);
7243 #endif
7245 // calc's insert->function is springing back to its original size if the ref-button
7246 // is used to shrink the dialog down and then the user clicks in the calc area to do
7247 // the selection
7248 bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
7249 if (bWorkaroundSizeSpringingBack)
7250 gtk_widget_unmap(GTK_WIDGET(m_pDialog));
7252 resize_to_request();
7254 if (bWorkaroundSizeSpringingBack)
7255 gtk_widget_map(GTK_WIDGET(m_pDialog));
7257 m_pRefEdit = pRefEdit;
7260 virtual void undo_collapse() override
7262 // All others: Show();
7263 for (GtkWidget* pWindow : m_aHiddenWidgets)
7265 gtk_widget_show(pWindow);
7266 g_object_unref(pWindow);
7268 m_aHiddenWidgets.clear();
7270 gtk_widget_set_size_request(m_pRefEdit, m_nOldEditWidthReq, -1);
7271 m_pRefEdit = nullptr;
7272 #if !GTK_CHECK_VERSION(4, 0, 0)
7273 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), m_nOldBorderWidth);
7274 if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
7275 gtk_widget_show(pActionArea);
7276 #else
7277 if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
7278 gtk_widget_show(pActionArea);
7279 #endif
7280 resize_to_request();
7281 present();
7284 void close(bool bCloseSignal);
7286 virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
7288 //not implemented for the gtk variant
7291 virtual ~GtkInstanceDialog() override
7293 if (!m_aHiddenWidgets.empty())
7295 for (GtkWidget* pWindow : m_aHiddenWidgets)
7296 g_object_unref(pWindow);
7297 m_aHiddenWidgets.clear();
7300 if (m_nCloseSignalId)
7301 g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
7302 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
7308 void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
7310 DialogRunner* pThis = static_cast<DialogRunner*>(data);
7312 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
7313 if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
7315 pThis->m_pInstance->close(false);
7316 return;
7319 pThis->m_nResponseId = nResponseId;
7320 pThis->loop_quit();
7323 void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
7325 DialogRunner* pThis = static_cast<DialogRunner*>(data);
7327 // make esc in an assistant act as if cancel button was pressed
7328 pThis->m_pInstance->close(false);
7331 namespace {
7333 class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
7335 private:
7336 GtkMessageDialog* m_pMessageDialog;
7337 public:
7338 GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7339 : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
7340 , m_pMessageDialog(pMessageDialog)
7344 virtual void set_primary_text(const OUString& rText) override
7346 ::set_primary_text(m_pMessageDialog, rText);
7349 virtual OUString get_primary_text() const override
7351 return ::get_primary_text(m_pMessageDialog);
7354 virtual void set_secondary_text(const OUString& rText) override
7356 ::set_secondary_text(m_pMessageDialog, rText);
7359 virtual OUString get_secondary_text() const override
7361 return ::get_secondary_text(m_pMessageDialog);
7364 virtual Container* weld_message_area() override
7366 #if !GTK_CHECK_VERSION(4, 0, 0)
7367 return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
7368 #else
7369 return new GtkInstanceContainer(gtk_message_dialog_get_message_area(m_pMessageDialog), m_pBuilder, false);
7370 #endif
7374 void set_label_wrap(GtkLabel* pLabel, bool bWrap)
7376 #if GTK_CHECK_VERSION(4, 0, 0)
7377 gtk_label_set_wrap(pLabel, bWrap);
7378 #else
7379 gtk_label_set_line_wrap(pLabel, bWrap);
7380 #endif
7383 class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
7385 private:
7386 GtkAssistant* m_pAssistant;
7387 GtkWidget* m_pSidebar;
7388 GtkWidget* m_pSidebarEventBox;
7389 #if !GTK_CHECK_VERSION(4, 0, 0)
7390 GtkButtonBox* m_pButtonBox;
7391 #else
7392 GtkBox* m_pButtonBox;
7393 GtkEventController* m_pSidebarClickController;
7394 #endif
7395 GtkButton* m_pHelp;
7396 GtkButton* m_pBack;
7397 GtkButton* m_pNext;
7398 GtkButton* m_pFinish;
7399 GtkButton* m_pCancel;
7400 gulong m_nButtonPressSignalId;
7401 std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
7402 std::map<OUString, bool> m_aNotClickable;
7404 int find_page(std::u16string_view ident) const
7406 int nPages = gtk_assistant_get_n_pages(m_pAssistant);
7407 for (int i = 0; i < nPages; ++i)
7409 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, i);
7410 OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pPage));
7411 if (sBuildableName == ident)
7412 return i;
7414 return -1;
7417 static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
7419 if (GTK_IS_LABEL(pWidget))
7421 ::set_label_wrap(GTK_LABEL(pWidget), true);
7422 gtk_label_set_width_chars(GTK_LABEL(pWidget), 22);
7423 gtk_label_set_max_width_chars(GTK_LABEL(pWidget), 22);
7427 static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
7429 OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
7430 if (sBuildableName == "sidebar")
7432 GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
7433 *ppSidebar = pWidget;
7435 #if !GTK_CHECK_VERSION(4, 0, 0)
7436 if (GTK_IS_CONTAINER(pWidget))
7437 gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
7438 #endif
7441 static void signalHelpClicked(GtkButton*, gpointer widget)
7443 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
7444 pThis->signal_help_clicked();
7447 void signal_help_clicked()
7449 help();
7452 #if GTK_CHECK_VERSION(4, 0, 0)
7453 static void signalButton(GtkGestureClick* /*pGesture*/, int /*n_press*/, gdouble x, gdouble y, gpointer widget)
7455 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
7456 SolarMutexGuard aGuard;
7457 pThis->signal_button(x, y);
7459 #else
7460 static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
7462 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
7463 SolarMutexGuard aGuard;
7464 return pThis->signal_button(pEvent->x, pEvent->y);
7466 #endif
7468 bool signal_button(gtk_coord event_x, gtk_coord event_y)
7470 int nNewCurrentPage = -1;
7472 GtkAllocation allocation;
7474 int nPageIndex = 0;
7476 #if GTK_CHECK_VERSION(4, 0, 0)
7477 for (GtkWidget* pWidget = gtk_widget_get_first_child(m_pSidebar);
7478 pWidget; pWidget = gtk_widget_get_next_sibling(pWidget))
7480 #else
7481 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pSidebar));
7482 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
7484 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
7485 #endif
7486 if (!gtk_widget_get_visible(pWidget))
7487 continue;
7489 gtk_widget_get_allocation(pWidget, &allocation);
7491 gtk_coord dest_x1, dest_y1;
7492 gtk_widget_translate_coordinates(pWidget,
7493 m_pSidebarEventBox,
7496 &dest_x1,
7497 &dest_y1);
7499 gtk_coord dest_x2, dest_y2;
7500 gtk_widget_translate_coordinates(pWidget,
7501 m_pSidebarEventBox,
7502 allocation.width,
7503 allocation.height,
7504 &dest_x2,
7505 &dest_y2);
7508 if (event_x >= dest_x1 && event_x <= dest_x2 && event_y >= dest_y1 && event_y <= dest_y2)
7510 nNewCurrentPage = nPageIndex;
7511 break;
7514 ++nPageIndex;
7516 #if !GTK_CHECK_VERSION(4, 0, 0)
7517 g_list_free(pChildren);
7518 #endif
7520 if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
7522 OUString sIdent = get_page_ident(nNewCurrentPage);
7523 if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
7524 set_current_page(nNewCurrentPage);
7527 return false;
7530 public:
7531 GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7532 : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
7533 , m_pAssistant(pAssistant)
7534 , m_pSidebar(nullptr)
7535 #if GTK_CHECK_VERSION(4, 0, 0)
7536 , m_pSidebarClickController(nullptr)
7537 #endif
7538 , m_nButtonPressSignalId(0)
7540 #if !GTK_CHECK_VERSION(4, 0, 0)
7541 m_pButtonBox = GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
7542 gtk_button_box_set_layout(m_pButtonBox, GTK_BUTTONBOX_END);
7543 gtk_box_set_spacing(GTK_BOX(m_pButtonBox), 6);
7544 #else
7545 m_pButtonBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
7546 #endif
7548 m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
7549 #if !GTK_CHECK_VERSION(4, 0, 0)
7550 gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
7551 #endif
7552 ::set_buildable_id(GTK_BUILDABLE(m_pBack), u"previous"_ustr);
7553 #if GTK_CHECK_VERSION(4, 0, 0)
7554 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack));
7555 #else
7556 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
7557 #endif
7559 m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
7560 #if !GTK_CHECK_VERSION(4, 0, 0)
7561 gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
7562 #endif
7563 ::set_buildable_id(GTK_BUILDABLE(m_pNext), u"next"_ustr);
7564 #if GTK_CHECK_VERSION(4, 0, 0)
7565 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext));
7566 #else
7567 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
7568 #endif
7570 m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
7571 #if !GTK_CHECK_VERSION(4, 0, 0)
7572 gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
7573 #endif
7574 #if GTK_CHECK_VERSION(4, 0, 0)
7575 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel));
7576 #else
7577 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
7578 #endif
7580 m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
7581 #if !GTK_CHECK_VERSION(4, 0, 0)
7582 gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
7583 #endif
7584 ::set_buildable_id(GTK_BUILDABLE(m_pFinish), u"finish"_ustr);
7585 #if GTK_CHECK_VERSION(4, 0, 0)
7586 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish));
7587 #else
7588 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
7589 #endif
7591 #if GTK_CHECK_VERSION(4, 0, 0)
7592 m_pHelp = GTK_BUTTON(gtk_button_new_from_icon_name("help-browser-symbolic"));
7593 #else
7594 m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
7595 #endif
7596 #if !GTK_CHECK_VERSION(4, 0, 0)
7597 gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
7598 #endif
7599 g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
7600 #if GTK_CHECK_VERSION(4, 0, 0)
7601 gtk_box_prepend(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp));
7602 gtk_widget_set_hexpand(GTK_WIDGET(m_pHelp), true);
7603 gtk_widget_set_halign(GTK_WIDGET(m_pHelp), GTK_ALIGN_START);
7604 #else
7605 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
7606 #endif
7608 gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
7609 #if !GTK_CHECK_VERSION(4, 0, 0)
7610 gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
7611 #endif
7612 gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
7614 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
7615 #if !GTK_CHECK_VERSION(4, 0, 0)
7616 gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
7617 #endif
7618 gtk_widget_set_halign(pParent, GTK_ALIGN_FILL);
7620 // Hide the built-in ones early so we get a nice optimal size for the width without
7621 // including the unused contents
7622 #if GTK_CHECK_VERSION(4, 0, 0)
7623 for (GtkWidget* pChild = gtk_widget_get_first_child(pParent);
7624 pChild; pChild = gtk_widget_get_next_sibling(pChild))
7626 gtk_widget_hide(pChild);
7628 #else
7629 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pParent));
7630 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
7632 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
7633 gtk_widget_hide(pWidget);
7635 g_list_free(pChildren);
7636 #endif
7638 #if !GTK_CHECK_VERSION(4, 0, 0)
7639 gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
7640 #else
7641 gtk_widget_show(GTK_WIDGET(m_pButtonBox));
7642 #endif
7644 find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
7646 m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
7647 if (m_pSidebarEventBox)
7649 #if GTK_CHECK_VERSION(4, 0, 0)
7650 GtkGesture *pClick = gtk_gesture_click_new();
7651 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
7652 m_pSidebarClickController = GTK_EVENT_CONTROLLER(pClick);
7653 gtk_widget_add_controller(m_pSidebarEventBox, m_pSidebarClickController);
7654 m_nButtonPressSignalId = g_signal_connect(m_pSidebarClickController, "pressed", G_CALLBACK(signalButton), this);
7655 #else
7656 m_nButtonPressSignalId = g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this);
7657 #endif
7661 virtual int get_current_page() const override
7663 return gtk_assistant_get_current_page(m_pAssistant);
7666 virtual int get_n_pages() const override
7668 return gtk_assistant_get_n_pages(m_pAssistant);
7671 virtual OUString get_page_ident(int nPage) const override
7673 const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
7674 return ::get_buildable_id(GTK_BUILDABLE(pWidget));
7677 virtual OUString get_current_page_ident() const override
7679 return get_page_ident(get_current_page());
7682 virtual void set_current_page(int nPage) override
7684 OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
7686 gtk_assistant_set_current_page(m_pAssistant, nPage);
7688 // if the page doesn't have a title, then the dialog will now have no
7689 // title, so restore the original title as a fallback
7690 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nPage);
7691 if (!gtk_assistant_get_page_title(m_pAssistant, pPage))
7692 gtk_window_set_title(GTK_WINDOW(m_pAssistant), sDialogTitle.getStr());
7695 virtual void set_current_page(const OUString& rIdent) override
7697 int nPage = find_page(rIdent);
7698 if (nPage == -1)
7699 return;
7700 set_current_page(nPage);
7703 virtual void set_page_title(const OUString& rIdent, const OUString& rTitle) override
7705 int nIndex = find_page(rIdent);
7706 if (nIndex == -1)
7707 return;
7708 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
7709 gtk_assistant_set_page_title(m_pAssistant, pPage,
7710 OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
7711 #if !GTK_CHECK_VERSION(4, 0, 0)
7712 gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
7713 #endif
7716 virtual OUString get_page_title(const OUString& rIdent) const override
7718 int nIndex = find_page(rIdent);
7719 if (nIndex == -1)
7720 return OUString();
7721 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
7722 const gchar* pStr = gtk_assistant_get_page_title(m_pAssistant, pPage);
7723 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7726 virtual void set_page_sensitive(const OUString& rIdent, bool bSensitive) override
7728 m_aNotClickable[rIdent] = !bSensitive;
7731 virtual void set_page_index(const OUString& rIdent, int nNewIndex) override
7733 int nOldIndex = find_page(rIdent);
7734 if (nOldIndex == -1)
7735 return;
7737 if (nOldIndex == nNewIndex)
7738 return;
7740 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
7742 g_object_ref(pPage);
7743 std::optional<OString> sTitle;
7744 if (auto const title = gtk_assistant_get_page_title(m_pAssistant, pPage)) {
7745 sTitle = title;
7747 gtk_assistant_remove_page(m_pAssistant, nOldIndex);
7748 gtk_assistant_insert_page(m_pAssistant, pPage, nNewIndex);
7749 gtk_assistant_set_page_type(m_pAssistant, pPage, GTK_ASSISTANT_PAGE_CUSTOM);
7750 gtk_assistant_set_page_title(m_pAssistant, pPage, sTitle ? sTitle->getStr() : nullptr);
7751 #if !GTK_CHECK_VERSION(4, 0, 0)
7752 gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
7753 #endif
7754 g_object_unref(pPage);
7757 virtual weld::Container* append_page(const OUString& rIdent) override
7759 disable_notify_events();
7761 GtkWidget *pChild = gtk_grid_new();
7762 ::set_buildable_id(GTK_BUILDABLE(pChild), rIdent);
7763 gtk_assistant_append_page(m_pAssistant, pChild);
7764 gtk_assistant_set_page_type(m_pAssistant, pChild, GTK_ASSISTANT_PAGE_CUSTOM);
7765 gtk_widget_show(pChild);
7767 enable_notify_events();
7769 #if !GTK_CHECK_VERSION(4, 0, 0)
7770 m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
7771 #else
7772 m_aPages.emplace_back(new GtkInstanceContainer(pChild, m_pBuilder, false));
7773 #endif
7775 return m_aPages.back().get();
7778 virtual void set_page_side_help_id(const OUString& rHelpId) override
7780 if (!m_pSidebar)
7781 return;
7782 ::set_help_id(m_pSidebar, rHelpId);
7785 virtual GtkButton* get_widget_for_response(int nGtkResponse) override
7787 GtkButton* pButton = nullptr;
7788 if (nGtkResponse == GTK_RESPONSE_YES)
7789 pButton = m_pNext;
7790 else if (nGtkResponse == GTK_RESPONSE_NO)
7791 pButton = m_pBack;
7792 else if (nGtkResponse == GTK_RESPONSE_OK)
7793 pButton = m_pFinish;
7794 else if (nGtkResponse == GTK_RESPONSE_CANCEL)
7795 pButton = m_pCancel;
7796 else if (nGtkResponse == GTK_RESPONSE_HELP)
7797 pButton = m_pHelp;
7798 return pButton;
7801 virtual void set_page_side_image(const OUString& /*rImage*/) override
7803 // Since GTK+ 3.2, sidebar images are not shown anymore
7806 virtual ~GtkInstanceAssistant() override
7808 if (m_nButtonPressSignalId)
7810 #if GTK_CHECK_VERSION(4, 0, 0)
7811 g_signal_handler_disconnect(m_pSidebarClickController, m_nButtonPressSignalId);
7812 #else
7813 g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
7814 #endif
7819 class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
7821 private:
7822 GtkFrame* m_pFrame;
7823 public:
7824 GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7825 #if !GTK_CHECK_VERSION(4, 0, 0)
7826 : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
7827 #else
7828 : GtkInstanceContainer(GTK_WIDGET(pFrame), pBuilder, bTakeOwnership)
7829 #endif
7830 , m_pFrame(pFrame)
7834 virtual void set_label(const OUString& rText) override
7836 gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
7839 virtual OUString get_label() const override
7841 const gchar* pStr = gtk_frame_get_label(m_pFrame);
7842 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7845 virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
7848 class GtkInstancePaned : public GtkInstanceContainer, public virtual weld::Paned
7850 private:
7851 GtkPaned* m_pPaned;
7852 public:
7853 GtkInstancePaned(GtkPaned* pPaned, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7854 #if !GTK_CHECK_VERSION(4, 0, 0)
7855 : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
7856 #else
7857 : GtkInstanceContainer(GTK_WIDGET(pPaned), pBuilder, bTakeOwnership)
7858 #endif
7859 , m_pPaned(pPaned)
7863 virtual void set_position(int nPos) override
7865 gtk_paned_set_position(m_pPaned, nPos);
7868 virtual int get_position() const override
7870 return gtk_paned_get_position(m_pPaned);
7876 static GType immobilized_viewport_get_type();
7877 static gpointer immobilized_viewport_parent_class;
7879 #ifndef NDEBUG
7880 # define IMMOBILIZED_TYPE_VIEWPORT (immobilized_viewport_get_type())
7881 # define IMMOBILIZED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), IMMOBILIZED_TYPE_VIEWPORT))
7882 #endif
7884 namespace {
7886 struct ImmobilizedViewportPrivate
7888 GtkAdjustment *hadjustment;
7889 GtkAdjustment *vadjustment;
7894 #define IMMOBILIZED_VIEWPORT_PRIVATE_DATA "ImmobilizedViewportPrivateData"
7896 enum
7898 PROP_0,
7899 PROP_HADJUSTMENT,
7900 PROP_VADJUSTMENT,
7901 PROP_HSCROLL_POLICY,
7902 PROP_VSCROLL_POLICY,
7903 PROP_SHADOW_TYPE
7906 static void viewport_set_adjustment(GtkViewport *viewport,
7907 GtkOrientation orientation,
7908 GtkAdjustment *adjustment)
7910 ImmobilizedViewportPrivate* priv =
7911 static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(G_OBJECT(viewport),
7912 IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
7914 if (!adjustment)
7915 adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
7917 if (orientation == GTK_ORIENTATION_HORIZONTAL)
7919 if (priv->hadjustment)
7920 g_object_unref(priv->hadjustment);
7921 priv->hadjustment = adjustment;
7923 else
7925 if (priv->vadjustment)
7926 g_object_unref(priv->vadjustment);
7927 priv->vadjustment = adjustment;
7930 g_object_ref_sink(adjustment);
7933 static void
7934 immobilized_viewport_set_property(GObject* object,
7935 guint prop_id,
7936 const GValue* value,
7937 GParamSpec* /*pspec*/)
7939 GtkViewport *viewport = GTK_VIEWPORT(object);
7941 switch (prop_id)
7943 case PROP_HADJUSTMENT:
7944 viewport_set_adjustment(viewport, GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(g_value_get_object(value)));
7945 break;
7946 case PROP_VADJUSTMENT:
7947 viewport_set_adjustment(viewport, GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(g_value_get_object(value)));
7948 break;
7949 case PROP_HSCROLL_POLICY:
7950 case PROP_VSCROLL_POLICY:
7951 break;
7952 default:
7953 SAL_WARN( "vcl.gtk", "unknown property\n");
7954 break;
7958 static void
7959 immobilized_viewport_get_property(GObject* object,
7960 guint prop_id,
7961 GValue* value,
7962 GParamSpec* /*pspec*/)
7964 ImmobilizedViewportPrivate* priv =
7965 static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(object,
7966 IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
7968 switch (prop_id)
7970 case PROP_HADJUSTMENT:
7971 g_value_set_object(value, priv->hadjustment);
7972 break;
7973 case PROP_VADJUSTMENT:
7974 g_value_set_object(value, priv->vadjustment);
7975 break;
7976 case PROP_HSCROLL_POLICY:
7977 g_value_set_enum(value, GTK_SCROLL_MINIMUM);
7978 break;
7979 case PROP_VSCROLL_POLICY:
7980 g_value_set_enum(value, GTK_SCROLL_MINIMUM);
7981 break;
7982 default:
7983 SAL_WARN( "vcl.gtk", "unknown property\n");
7984 break;
7988 static ImmobilizedViewportPrivate*
7989 immobilized_viewport_new_private_data()
7991 ImmobilizedViewportPrivate* priv = g_slice_new0(ImmobilizedViewportPrivate);
7992 priv->hadjustment = nullptr;
7993 priv->vadjustment = nullptr;
7994 return priv;
7997 static void
7998 immobilized_viewport_instance_init(GTypeInstance *instance, gpointer /*klass*/)
8000 GObject* object = G_OBJECT(instance);
8001 g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA,
8002 immobilized_viewport_new_private_data());
8005 static void
8006 immobilized_viewport_finalize(GObject* object)
8008 void* priv = g_object_get_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA);
8009 if (priv)
8011 g_slice_free(ImmobilizedViewportPrivate, priv);
8012 g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA, nullptr);
8014 G_OBJECT_CLASS(immobilized_viewport_parent_class)->finalize(object);
8017 static void immobilized_viewport_class_init(gpointer klass_, gpointer)
8019 auto const klass = static_cast<GtkWidgetClass*>(klass_);
8020 immobilized_viewport_parent_class = g_type_class_peek_parent(klass);
8022 GObjectClass* o_class = G_OBJECT_CLASS(klass);
8024 /* GObject signals */
8025 o_class->finalize = immobilized_viewport_finalize;
8026 o_class->set_property = immobilized_viewport_set_property;
8027 o_class->get_property = immobilized_viewport_get_property;
8029 /* Properties */
8030 g_object_class_override_property(o_class, PROP_HADJUSTMENT, "hadjustment");
8031 g_object_class_override_property(o_class, PROP_VADJUSTMENT, "vadjustment");
8032 g_object_class_override_property(o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
8033 g_object_class_override_property(o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
8036 GType immobilized_viewport_get_type()
8038 static GType type = 0;
8040 if (!type)
8042 GTypeQuery query;
8043 g_type_query(gtk_viewport_get_type(), &query);
8045 static const GTypeInfo tinfo =
8047 static_cast<guint16>(query.class_size),
8048 nullptr, /* base init */
8049 nullptr, /* base finalize */
8050 immobilized_viewport_class_init, /* class init */
8051 nullptr, /* class finalize */
8052 nullptr, /* class data */
8053 static_cast<guint16>(query.instance_size), /* instance size */
8054 0, /* nb preallocs */
8055 immobilized_viewport_instance_init, /* instance init */
8056 nullptr /* value table */
8059 type = g_type_register_static(GTK_TYPE_VIEWPORT, "ImmobilizedViewport",
8060 &tinfo, GTypeFlags(0));
8063 return type;
8066 static VclPolicyType GtkToVcl(GtkPolicyType eType)
8068 VclPolicyType eRet(VclPolicyType::NEVER);
8069 switch (eType)
8071 case GTK_POLICY_ALWAYS:
8072 eRet = VclPolicyType::ALWAYS;
8073 break;
8074 case GTK_POLICY_AUTOMATIC:
8075 eRet = VclPolicyType::AUTOMATIC;
8076 break;
8077 case GTK_POLICY_EXTERNAL:
8078 case GTK_POLICY_NEVER:
8079 eRet = VclPolicyType::NEVER;
8080 break;
8082 return eRet;
8085 static GtkPolicyType VclToGtk(VclPolicyType eType)
8087 GtkPolicyType eRet(GTK_POLICY_ALWAYS);
8088 switch (eType)
8090 case VclPolicyType::ALWAYS:
8091 eRet = GTK_POLICY_ALWAYS;
8092 break;
8093 case VclPolicyType::AUTOMATIC:
8094 eRet = GTK_POLICY_AUTOMATIC;
8095 break;
8096 case VclPolicyType::NEVER:
8097 eRet = GTK_POLICY_NEVER;
8098 break;
8100 return eRet;
8103 static GtkMessageType VclToGtk(VclMessageType eType)
8105 GtkMessageType eRet(GTK_MESSAGE_INFO);
8106 switch (eType)
8108 case VclMessageType::Info:
8109 eRet = GTK_MESSAGE_INFO;
8110 break;
8111 case VclMessageType::Warning:
8112 eRet = GTK_MESSAGE_WARNING;
8113 break;
8114 case VclMessageType::Question:
8115 eRet = GTK_MESSAGE_QUESTION;
8116 break;
8117 case VclMessageType::Error:
8118 eRet = GTK_MESSAGE_ERROR;
8119 break;
8120 case VclMessageType::Other:
8121 eRet = GTK_MESSAGE_OTHER;
8122 break;
8124 return eRet;
8127 static GtkButtonsType VclToGtk(VclButtonsType eType)
8129 GtkButtonsType eRet(GTK_BUTTONS_NONE);
8130 switch (eType)
8132 case VclButtonsType::NONE:
8133 eRet = GTK_BUTTONS_NONE;
8134 break;
8135 case VclButtonsType::Ok:
8136 eRet = GTK_BUTTONS_OK;
8137 break;
8138 case VclButtonsType::Close:
8139 eRet = GTK_BUTTONS_CLOSE;
8140 break;
8141 case VclButtonsType::Cancel:
8142 eRet = GTK_BUTTONS_CANCEL;
8143 break;
8144 case VclButtonsType::YesNo:
8145 eRet = GTK_BUTTONS_YES_NO;
8146 break;
8147 case VclButtonsType::OkCancel:
8148 eRet = GTK_BUTTONS_OK_CANCEL;
8149 break;
8151 return eRet;
8154 static GtkSelectionMode VclToGtk(SelectionMode eType)
8156 GtkSelectionMode eRet(GTK_SELECTION_NONE);
8157 switch (eType)
8159 case SelectionMode::NONE:
8160 eRet = GTK_SELECTION_NONE;
8161 break;
8162 case SelectionMode::Single:
8163 eRet = GTK_SELECTION_SINGLE;
8164 break;
8165 case SelectionMode::Range:
8166 eRet = GTK_SELECTION_BROWSE;
8167 break;
8168 case SelectionMode::Multiple:
8169 eRet = GTK_SELECTION_MULTIPLE;
8170 break;
8172 return eRet;
8175 namespace {
8177 class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
8179 private:
8180 GtkScrolledWindow* m_pScrolledWindow;
8181 GtkWidget *m_pOrigViewport;
8182 GtkCssProvider* m_pScrollBarCssProvider;
8183 GtkAdjustment* m_pVAdjustment;
8184 GtkAdjustment* m_pHAdjustment;
8185 gulong m_nVAdjustChangedSignalId;
8186 gulong m_nHAdjustChangedSignalId;
8188 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
8190 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
8191 SolarMutexGuard aGuard;
8192 pThis->signal_vadjustment_changed();
8195 static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
8197 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
8198 SolarMutexGuard aGuard;
8199 pThis->signal_hadjustment_changed();
8202 public:
8203 GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership, bool bUserManagedScrolling)
8204 #if !GTK_CHECK_VERSION(4, 0, 0)
8205 : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
8206 #else
8207 : GtkInstanceContainer(GTK_WIDGET(pScrolledWindow), pBuilder, bTakeOwnership)
8208 #endif
8209 , m_pScrolledWindow(pScrolledWindow)
8210 , m_pOrigViewport(nullptr)
8211 , m_pScrollBarCssProvider(nullptr)
8212 , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
8213 , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
8214 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
8215 , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
8217 if (bUserManagedScrolling)
8218 set_user_managed_scrolling();
8221 void set_user_managed_scrolling()
8223 disable_notify_events();
8224 //remove the original viewport and replace it with our bodged one which
8225 //doesn't do any scrolling and expects its child to figure it out somehow
8226 assert(!m_pOrigViewport);
8227 #if GTK_CHECK_VERSION(4, 0, 0)
8228 GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
8229 #else
8230 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
8231 #endif
8232 assert(GTK_IS_VIEWPORT(pViewport));
8233 #if GTK_CHECK_VERSION(4, 0, 0)
8234 GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
8235 #else
8236 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
8237 #endif
8238 g_object_ref(pChild);
8239 #if GTK_CHECK_VERSION(4, 0, 0)
8240 gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
8241 #else
8242 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
8243 #endif
8244 g_object_ref(pViewport);
8245 #if GTK_CHECK_VERSION(4, 0, 0)
8246 gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
8247 #else
8248 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
8249 #endif
8250 GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(immobilized_viewport_get_type(), nullptr));
8251 gtk_widget_show(pNewViewport);
8252 #if GTK_CHECK_VERSION(4, 0, 0)
8253 gtk_scrolled_window_set_child(m_pScrolledWindow, pNewViewport);
8254 gtk_viewport_set_child(GTK_VIEWPORT(pNewViewport), pChild);
8255 #else
8256 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
8257 gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
8258 #endif
8259 g_object_unref(pChild);
8260 m_pOrigViewport = pViewport;
8261 enable_notify_events();
8264 virtual void hadjustment_configure(int value, int lower, int upper,
8265 int step_increment, int page_increment,
8266 int page_size) override
8268 disable_notify_events();
8269 if (SwapForRTL())
8270 value = upper - (value - lower + page_size);
8271 gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
8272 enable_notify_events();
8275 virtual int hadjustment_get_value() const override
8277 int value = gtk_adjustment_get_value(m_pHAdjustment);
8279 if (SwapForRTL())
8281 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
8282 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
8283 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
8284 value = lower + (upper - value - page_size);
8287 return value;
8290 virtual void hadjustment_set_value(int value) override
8292 disable_notify_events();
8294 if (SwapForRTL())
8296 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
8297 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
8298 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
8299 value = upper - (value - lower + page_size);
8302 gtk_adjustment_set_value(m_pHAdjustment, value);
8303 enable_notify_events();
8306 virtual int hadjustment_get_upper() const override
8308 return gtk_adjustment_get_upper(m_pHAdjustment);
8311 virtual void hadjustment_set_upper(int upper) override
8313 disable_notify_events();
8314 gtk_adjustment_set_upper(m_pHAdjustment, upper);
8315 enable_notify_events();
8318 virtual int hadjustment_get_page_size() const override
8320 return gtk_adjustment_get_page_size(m_pHAdjustment);
8323 virtual void hadjustment_set_page_size(int size) override
8325 gtk_adjustment_set_page_size(m_pHAdjustment, size);
8328 virtual void hadjustment_set_page_increment(int size) override
8330 gtk_adjustment_set_page_increment(m_pHAdjustment, size);
8333 virtual void hadjustment_set_step_increment(int size) override
8335 gtk_adjustment_set_step_increment(m_pHAdjustment, size);
8338 virtual void set_hpolicy(VclPolicyType eHPolicy) override
8340 GtkPolicyType eGtkVPolicy;
8341 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
8342 gtk_scrolled_window_set_policy(m_pScrolledWindow, VclToGtk(eHPolicy), eGtkVPolicy);
8345 virtual VclPolicyType get_hpolicy() const override
8347 GtkPolicyType eGtkHPolicy;
8348 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
8349 return GtkToVcl(eGtkHPolicy);
8352 virtual void vadjustment_configure(int value, int lower, int upper,
8353 int step_increment, int page_increment,
8354 int page_size) override
8356 disable_notify_events();
8357 gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
8358 enable_notify_events();
8361 virtual int vadjustment_get_value() const override
8363 return gtk_adjustment_get_value(m_pVAdjustment);
8366 virtual void vadjustment_set_value(int value) override
8368 disable_notify_events();
8369 gtk_adjustment_set_value(m_pVAdjustment, value);
8370 enable_notify_events();
8373 virtual int vadjustment_get_upper() const override
8375 return gtk_adjustment_get_upper(m_pVAdjustment);
8378 virtual void vadjustment_set_upper(int upper) override
8380 disable_notify_events();
8381 gtk_adjustment_set_upper(m_pVAdjustment, upper);
8382 enable_notify_events();
8385 virtual int vadjustment_get_lower() const override
8387 return gtk_adjustment_get_lower(m_pVAdjustment);
8390 virtual void vadjustment_set_lower(int lower) override
8392 disable_notify_events();
8393 gtk_adjustment_set_lower(m_pVAdjustment, lower);
8394 enable_notify_events();
8397 virtual int vadjustment_get_page_size() const override
8399 return gtk_adjustment_get_page_size(m_pVAdjustment);
8402 virtual void vadjustment_set_page_size(int size) override
8404 gtk_adjustment_set_page_size(m_pVAdjustment, size);
8407 virtual void vadjustment_set_page_increment(int size) override
8409 gtk_adjustment_set_page_increment(m_pVAdjustment, size);
8412 virtual void vadjustment_set_step_increment(int size) override
8414 gtk_adjustment_set_step_increment(m_pVAdjustment, size);
8417 virtual void set_vpolicy(VclPolicyType eVPolicy) override
8419 GtkPolicyType eGtkHPolicy;
8420 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
8421 gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
8424 virtual VclPolicyType get_vpolicy() const override
8426 GtkPolicyType eGtkVPolicy;
8427 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
8428 return GtkToVcl(eGtkVPolicy);
8431 virtual int get_scroll_thickness() const override
8433 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
8434 return 0;
8435 GtkRequisition size;
8436 gtk_widget_get_preferred_size(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow), nullptr, &size);
8437 return size.width;
8440 virtual void set_scroll_thickness(int nThickness) override
8442 GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
8443 GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
8444 gtk_widget_set_size_request(pHorzBar, -1, nThickness);
8445 gtk_widget_set_size_request(pVertBar, nThickness, -1);
8448 virtual void disable_notify_events() override
8450 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
8451 g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
8452 GtkInstanceContainer::disable_notify_events();
8455 virtual void enable_notify_events() override
8457 GtkInstanceContainer::enable_notify_events();
8458 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
8459 g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
8462 virtual void customize_scrollbars(const Color& rBackgroundColor,
8463 const Color& rShadowColor,
8464 const Color& rFaceColor) override
8466 GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
8467 GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
8468 GtkStyleContext *pHorzContext = gtk_widget_get_style_context(pHorzBar);
8469 GtkStyleContext *pVertContext = gtk_widget_get_style_context(pVertBar);
8470 if (m_pScrollBarCssProvider)
8472 gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8473 gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8476 m_pScrollBarCssProvider = gtk_css_provider_new();
8477 // intentionally 'trough' a long, narrow open container.
8478 OUString aBuffer = "scrollbar contents trough { background-color: #" + rBackgroundColor.AsRGBHexString() + "; } "
8479 "scrollbar contents trough slider { background-color: #" + rShadowColor.AsRGBHexString() + "; } "
8480 "scrollbar contents button { background-color: #" + rFaceColor.AsRGBHexString() + "; } "
8481 "scrollbar contents button { color: #000000; } "
8482 "scrollbar contents button:disabled { color: #7f7f7f; }";
8483 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
8484 css_provider_load_from_data(m_pScrollBarCssProvider, aResult.getStr(), aResult.getLength());
8486 gtk_style_context_add_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
8487 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8488 gtk_style_context_add_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
8489 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8492 virtual ~GtkInstanceScrolledWindow() override
8494 // we use GtkInstanceContainer::[disable|enable]_notify_events later on
8495 // to avoid touching these removed handlers
8496 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
8497 g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
8499 if (m_pScrollBarCssProvider)
8501 GtkStyleContext *pHorzContext = gtk_widget_get_style_context(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
8502 GtkStyleContext *pVertContext = gtk_widget_get_style_context(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
8503 gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8504 gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8505 m_pScrollBarCssProvider = nullptr;
8508 //put it back the way it was
8509 if (!m_pOrigViewport)
8510 return;
8512 GtkInstanceContainer::disable_notify_events();
8514 // force in new adjustment to drop the built-in handlers on value-changed
8515 // which are getting called eventually by the gtk_container_add call
8516 // and which access the scrolled window indicators which, in the case
8517 // of user-managed scrolling windows in toolbar popups during popdown
8518 // are nullptr causing crashes when the scrolling windows is not at its
8519 // initial 0,0 position
8520 GtkAdjustment *pVAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
8521 gtk_scrolled_window_set_vadjustment(m_pScrolledWindow, pVAdjustment);
8522 GtkAdjustment *pHAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
8523 gtk_scrolled_window_set_hadjustment(m_pScrolledWindow, pHAdjustment);
8525 #if GTK_CHECK_VERSION(4, 0, 0)
8526 GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
8527 #else
8528 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
8529 #endif
8530 assert(IMMOBILIZED_IS_VIEWPORT(pViewport));
8531 #if GTK_CHECK_VERSION(4, 0, 0)
8532 GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
8533 #else
8534 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
8535 #endif
8536 g_object_ref(pChild);
8537 #if GTK_CHECK_VERSION(4, 0, 0)
8538 gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
8539 #else
8540 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
8541 #endif
8542 g_object_ref(pViewport);
8543 #if GTK_CHECK_VERSION(4, 0, 0)
8544 gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
8545 #else
8546 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
8547 #endif
8549 #if GTK_CHECK_VERSION(4, 0, 0)
8550 gtk_scrolled_window_set_child(m_pScrolledWindow, m_pOrigViewport);
8551 #else
8552 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
8553 #endif
8554 // coverity[freed_arg : FALSE] - this does not free m_pOrigViewport, it is reffed by m_pScrolledWindow
8555 g_object_unref(m_pOrigViewport);
8556 #if GTK_CHECK_VERSION(4, 0, 0)
8557 gtk_viewport_set_child(GTK_VIEWPORT(m_pOrigViewport), pChild);
8558 #else
8559 gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
8560 #endif
8561 g_object_unref(pChild);
8562 #if !GTK_CHECK_VERSION(4, 0, 0)
8563 gtk_widget_destroy(pViewport);
8564 #endif
8565 g_object_unref(pViewport);
8566 m_pOrigViewport = nullptr;
8567 GtkInstanceContainer::enable_notify_events();
8571 class GtkInstanceScrollbar final : public GtkInstanceWidget, public virtual weld::Scrollbar
8573 private:
8574 GtkScrollbar* m_pScrollbar;
8575 GtkAdjustment* m_pAdjustment;
8576 GtkCssProvider* m_pThicknessCssProvider;
8577 gulong m_nAdjustChangedSignalId;
8579 static void signalAdjustValueChanged(GtkAdjustment*, gpointer widget)
8581 GtkInstanceScrollbar* pThis = static_cast<GtkInstanceScrollbar*>(widget);
8582 SolarMutexGuard aGuard;
8583 pThis->signal_adjustment_changed();
8586 #if GTK_CHECK_VERSION(4, 0, 0)
8587 // if the widget is inside a GtkSalFrame then ensure the event is processed by the GtkSalFrame and not the
8588 // GtkScrollbar
8589 static gboolean signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer widget)
8591 GtkInstanceScrollbar* pThis = static_cast<GtkInstanceScrollbar*>(widget);
8593 GtkWidget* pParent = widget_get_toplevel(GTK_WIDGET(pThis->m_pScrollbar));
8594 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
8596 return pFrame && pFrame->event_controller_scroll_forward(pController, delta_x, delta_y);
8598 #else
8599 static gboolean signalScroll(GtkWidget* pWidget, GdkEventScroll* /*pEvent*/, gpointer widget)
8601 GtkInstanceScrollbar* pThis = static_cast<GtkInstanceScrollbar*>(widget);
8603 GtkWidget* pParent = widget_get_toplevel(GTK_WIDGET(pThis->m_pScrollbar));
8604 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
8606 if (pFrame)
8607 g_signal_stop_emission_by_name(pWidget, "scroll-event");
8609 return false;
8611 #endif
8613 public:
8614 GtkInstanceScrollbar(GtkScrollbar* pScrollbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8615 : GtkInstanceWidget(GTK_WIDGET(pScrollbar), pBuilder, bTakeOwnership)
8616 , m_pScrollbar(pScrollbar)
8617 #if GTK_CHECK_VERSION(4, 0, 0)
8618 , m_pAdjustment(gtk_scrollbar_get_adjustment(m_pScrollbar))
8619 #else
8620 , m_pAdjustment(gtk_range_get_adjustment(GTK_RANGE(m_pScrollbar)))
8621 #endif
8622 , m_pThicknessCssProvider(nullptr)
8623 , m_nAdjustChangedSignalId(g_signal_connect(m_pAdjustment, "value-changed", G_CALLBACK(signalAdjustValueChanged), this))
8625 #if GTK_CHECK_VERSION(4, 0, 0)
8626 GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
8627 gtk_event_controller_set_propagation_phase(pScrollController, GTK_PHASE_CAPTURE);
8628 g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
8629 gtk_widget_add_controller(GTK_WIDGET(pScrollbar), pScrollController);
8630 #else
8631 g_signal_connect(pScrollbar, "scroll-event", G_CALLBACK(signalScroll), this);
8632 #endif
8635 virtual void adjustment_configure(int value, int lower, int upper,
8636 int step_increment, int page_increment,
8637 int page_size) override
8639 disable_notify_events();
8640 gtk_adjustment_configure(m_pAdjustment, value, lower, upper, step_increment, page_increment, page_size);
8641 enable_notify_events();
8644 virtual int adjustment_get_value() const override
8646 return gtk_adjustment_get_value(m_pAdjustment);
8649 virtual void adjustment_set_value(int value) override
8651 disable_notify_events();
8652 gtk_adjustment_set_value(m_pAdjustment, value);
8653 enable_notify_events();
8656 virtual int adjustment_get_upper() const override
8658 return gtk_adjustment_get_upper(m_pAdjustment);
8661 virtual void adjustment_set_upper(int upper) override
8663 disable_notify_events();
8664 gtk_adjustment_set_upper(m_pAdjustment, upper);
8665 enable_notify_events();
8668 virtual int adjustment_get_lower() const override
8670 return gtk_adjustment_get_lower(m_pAdjustment);
8673 virtual void adjustment_set_lower(int lower) override
8675 disable_notify_events();
8676 gtk_adjustment_set_lower(m_pAdjustment, lower);
8677 enable_notify_events();
8680 virtual int adjustment_get_page_size() const override
8682 return gtk_adjustment_get_page_size(m_pAdjustment);
8685 virtual void adjustment_set_page_size(int size) override
8687 gtk_adjustment_set_page_size(m_pAdjustment, size);
8690 virtual int adjustment_get_page_increment() const override
8692 return gtk_adjustment_get_page_increment(m_pAdjustment);
8695 virtual void adjustment_set_page_increment(int size) override
8697 gtk_adjustment_set_page_increment(m_pAdjustment, size);
8700 virtual int adjustment_get_step_increment() const override
8702 return gtk_adjustment_get_step_increment(m_pAdjustment);
8705 virtual void adjustment_set_step_increment(int size) override
8707 gtk_adjustment_set_step_increment(m_pAdjustment, size);
8710 virtual void disable_notify_events() override
8712 g_signal_handler_block(m_pAdjustment, m_nAdjustChangedSignalId);
8713 GtkInstanceWidget::disable_notify_events();
8716 virtual void enable_notify_events() override
8718 GtkInstanceWidget::enable_notify_events();
8719 g_signal_handler_unblock(m_pAdjustment, m_nAdjustChangedSignalId);
8722 virtual ScrollType get_scroll_type() const override
8724 // tdf#153049 want a mousewheel spin to be treated as DontKnow
8725 return has_grab() ? ScrollType::Drag : ScrollType::DontKnow;
8728 virtual int get_scroll_thickness() const override
8730 if (gtk_orientable_get_orientation(GTK_ORIENTABLE(m_pScrollbar)) == GTK_ORIENTATION_HORIZONTAL)
8731 return gtk_widget_get_allocated_height(GTK_WIDGET(m_pScrollbar));
8732 return gtk_widget_get_allocated_width(GTK_WIDGET(m_pScrollbar));
8735 virtual void set_scroll_thickness(int nThickness) override
8737 GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pScrollbar));
8739 if (m_pThicknessCssProvider)
8741 gtk_style_context_remove_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pThicknessCssProvider));
8742 m_pThicknessCssProvider = nullptr;
8745 m_pThicknessCssProvider = gtk_css_provider_new();
8746 int nSlider = nThickness > 6 ? nThickness - 6 : 1;
8747 const OString sData = "slider { min-height: " + OString::number(nSlider) + "px;"
8748 " min-width: " + OString::number(nSlider) + "px; }";
8749 css_provider_load_from_data(m_pThicknessCssProvider, sData.getStr(), sData.getLength());
8750 gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pThicknessCssProvider),
8751 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8753 if (gtk_orientable_get_orientation(GTK_ORIENTABLE(m_pScrollbar)) == GTK_ORIENTATION_HORIZONTAL)
8754 gtk_widget_set_size_request(GTK_WIDGET(m_pScrollbar), -1, nThickness);
8755 else
8756 gtk_widget_set_size_request(GTK_WIDGET(m_pScrollbar), nThickness, -1);
8759 virtual void set_scroll_swap_arrows(bool /* bSwap */) override
8761 // Related: tdf#93352 do nothing since GtkScrollbar has no arrows
8764 virtual ~GtkInstanceScrollbar() override
8766 g_signal_handler_disconnect(m_pAdjustment, m_nAdjustChangedSignalId);
8767 if (m_pThicknessCssProvider)
8769 GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pScrollbar));
8770 gtk_style_context_remove_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pThicknessCssProvider));
8777 namespace {
8779 class GtkInstanceNotebook : public GtkInstanceWidget, public virtual weld::Notebook
8781 private:
8782 GtkNotebook* m_pNotebook;
8783 GtkBox* m_pOverFlowBox;
8784 GtkNotebook* m_pOverFlowNotebook;
8785 gulong m_nSwitchPageSignalId;
8786 gulong m_nOverFlowSwitchPageSignalId;
8787 #if GTK_CHECK_VERSION(4, 0, 0)
8788 NotifyingLayout* m_pLayout;
8789 #else
8790 gulong m_nNotebookSizeAllocateSignalId;
8791 gulong m_nFocusSignalId;
8792 #endif
8793 gulong m_nChangeCurrentPageId;
8794 guint m_nLaunchSplitTimeoutId;
8795 bool m_bOverFlowBoxActive;
8796 bool m_bOverFlowBoxIsStart;
8797 bool m_bInternalPageChange;
8798 int m_nStartTabCount;
8799 int m_nEndTabCount;
8800 mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
8802 static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
8804 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
8805 SolarMutexGuard aGuard;
8806 pThis->signal_switch_page(nNewPage);
8809 static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
8811 SolarMutexGuard aGuard;
8812 pThis->signal_overflow_switch_page();
8813 return false;
8816 static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
8818 g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
8821 void signal_switch_page(int nNewPage)
8823 if (m_bOverFlowBoxIsStart)
8825 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
8826 // add count of overflow pages, minus the extra tab
8827 nNewPage += nOverFlowLen;
8830 bool bAllow = m_bInternalPageChange || !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
8831 if (!bAllow)
8833 g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
8834 return;
8836 if (m_bOverFlowBoxActive)
8837 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
8838 OUString sNewIdent(get_page_ident(nNewPage));
8839 if (!m_bInternalPageChange)
8840 m_aEnterPageHdl.Call(sNewIdent);
8843 void unsplit_notebooks()
8845 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
8846 int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
8847 int nPageIndex = 0;
8848 if (!m_bOverFlowBoxIsStart)
8849 nPageIndex += nMainPages;
8851 // take the overflow pages, and put them back at the end of the normal one
8852 int i = nMainPages;
8853 while (nOverFlowPages)
8855 OUString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
8856 OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
8857 remove_page(m_pOverFlowNotebook, sIdent);
8859 GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
8860 insert_page(m_pNotebook, sIdent, sLabel, pPage, -1);
8862 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
8863 gtk_notebook_get_nth_page(m_pNotebook, i));
8864 gtk_widget_set_hexpand(pTabWidget, true);
8865 --nOverFlowPages;
8866 ++i;
8867 ++nPageIndex;
8870 // remove the dangling placeholder tab page
8871 remove_page(m_pOverFlowNotebook, u"useless");
8874 // a tab has been selected on the overflow notebook
8875 void signal_overflow_switch_page()
8877 int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
8878 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
8879 if (nNewPage == nOverFlowPages)
8881 // the useless tab which is there because there has to be an active tab
8882 return;
8885 // check if we are allowed leave before attempting to resplit the notebooks
8886 bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
8887 if (!bAllow)
8888 return;
8890 disable_notify_events();
8892 // take the overflow pages, and put them back at the end of the normal one
8893 unsplit_notebooks();
8895 // now redo the split, the pages will be split the other way around this time
8896 std::swap(m_nStartTabCount, m_nEndTabCount);
8897 split_notebooks();
8899 // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
8900 gtk_notebook_set_current_page(m_pNotebook, nNewPage);
8902 enable_notify_events();
8904 // trigger main notebook switch-page callback
8905 OUString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
8906 m_aEnterPageHdl.Call(sNewIdent);
8909 static OUString get_page_ident(GtkNotebook *pNotebook, guint nPage)
8911 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
8912 return ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
8915 static gint get_page_number(GtkNotebook *pNotebook, std::u16string_view ident)
8917 gint nPages = gtk_notebook_get_n_pages(pNotebook);
8918 for (gint i = 0; i < nPages; ++i)
8920 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
8921 OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
8922 if (sBuildableName == ident)
8923 return i;
8925 return -1;
8928 int remove_page(GtkNotebook *pNotebook, std::u16string_view ident)
8930 disable_notify_events();
8931 int nPageNumber = get_page_number(pNotebook, ident);
8932 assert(nPageNumber != -1 && "asked to remove page that doesn't exist");
8933 gtk_notebook_remove_page(pNotebook, nPageNumber);
8934 enable_notify_events();
8935 return nPageNumber;
8938 static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
8940 const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
8941 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
8944 static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
8946 OString sUtf8(rText.toUtf8());
8948 GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
8950 // tdf#128241 if there's already a label here, reuse it so the buildable
8951 // name remains the same, gtk_notebook_set_tab_label_text will replace
8952 // the label widget with a new one
8953 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
8954 if (pTabWidget && GTK_IS_LABEL(pTabWidget))
8956 gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
8957 return;
8960 gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
8963 void append_useless_page(GtkNotebook *pNotebook)
8965 disable_notify_events();
8967 GtkWidget *pTabWidget = gtk_fixed_new();
8968 ::set_buildable_id(GTK_BUILDABLE(pTabWidget), u"useless"_ustr);
8970 GtkWidget *pChild = gtk_grid_new();
8971 gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
8972 gtk_widget_show(pChild);
8973 gtk_widget_show(pTabWidget);
8975 enable_notify_events();
8978 void insert_page(GtkNotebook *pNotebook, const OUString& rIdent, const OUString& rLabel, GtkWidget *pChild, int nPos)
8980 disable_notify_events();
8982 GtkWidget *pTabWidget = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
8983 ::set_buildable_id(GTK_BUILDABLE(pTabWidget), rIdent);
8984 gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
8985 gtk_widget_show(pChild);
8986 gtk_widget_show(pTabWidget);
8988 if (nPos != -1)
8990 unsigned int nPageIndex = static_cast<unsigned int>(nPos);
8991 if (nPageIndex < m_aPages.size())
8992 m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
8995 enable_notify_events();
8998 void make_overflow_boxes()
9000 m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
9001 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
9002 container_add(pParent, GTK_WIDGET(m_pOverFlowBox));
9003 #if GTK_CHECK_VERSION(4, 0, 0)
9004 gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook));
9005 #else
9006 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
9007 #endif
9008 g_object_ref(m_pNotebook);
9009 container_remove(pParent, GTK_WIDGET(m_pNotebook));
9010 #if GTK_CHECK_VERSION(4, 0, 0)
9011 gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pNotebook));
9012 #else
9013 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
9014 #endif
9015 // coverity[freed_arg : FALSE] - this does not free m_pNotebook , it is reffed by pParent
9016 g_object_unref(m_pNotebook);
9017 gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
9020 void split_notebooks()
9022 // get the original preferred size for the notebook, the sane width
9023 // expected here depends on the notebooks all initially having
9024 // scrollable tabs enabled
9025 GtkAllocation alloc;
9026 gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
9028 // toggle the direction of the split since the last time
9029 m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
9030 if (!m_pOverFlowBox)
9031 make_overflow_boxes();
9033 // don't scroll the tabs anymore
9034 // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
9035 gtk_notebook_set_scrollable(m_pNotebook, false);
9037 #if !GTK_CHECK_VERSION(4, 0, 0)
9038 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
9039 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
9040 #else
9041 g_object_freeze_notify(G_OBJECT(m_pNotebook));
9042 g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
9043 #endif
9045 gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
9047 gint nPages;
9049 GtkRequisition size1, size2;
9051 if (!m_nStartTabCount && !m_nEndTabCount)
9053 nPages = gtk_notebook_get_n_pages(m_pNotebook);
9055 std::vector<int> aLabelWidths;
9056 //move tabs to the overflow notebook
9057 for (int i = 0; i < nPages; ++i)
9059 OUString sLabel(get_tab_label_text(m_pNotebook, i));
9060 aLabelWidths.push_back(get_pixel_size(sLabel).Width());
9062 int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
9063 int count = 0;
9064 for (int i = 0; i < nPages; ++i)
9066 count += aLabelWidths[i];
9067 if (count >= row_width)
9069 m_nStartTabCount = i;
9070 break;
9074 m_nEndTabCount = nPages - m_nStartTabCount;
9077 //move the tabs to the overflow notebook
9078 int i = 0;
9079 int nOverFlowPages = m_nStartTabCount;
9080 while (nOverFlowPages)
9082 OUString sIdent(get_page_ident(m_pNotebook, 0));
9083 OUString sLabel(get_tab_label_text(m_pNotebook, 0));
9084 remove_page(m_pNotebook, sIdent);
9085 insert_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new(), -1);
9086 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
9087 gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
9088 gtk_widget_set_hexpand(pTabWidget, true);
9090 --nOverFlowPages;
9091 ++i;
9094 for (i = 0; i < m_nEndTabCount; ++i)
9096 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
9097 gtk_notebook_get_nth_page(m_pNotebook, i));
9098 gtk_widget_set_hexpand(pTabWidget, true);
9101 // have to have some tab as the active tab of the overflow notebook
9102 append_useless_page(m_pOverFlowNotebook);
9103 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
9104 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
9105 gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
9107 // add this temporarily to the normal notebook to measure how wide
9108 // the row would be if switched to the other notebook
9109 append_useless_page(m_pNotebook);
9111 gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
9112 gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
9114 auto nWidth = std::max(size1.width, size2.width);
9115 gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
9116 gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
9118 // remove it once we've measured it
9119 remove_page(m_pNotebook, u"useless");
9121 #if !GTK_CHECK_VERSION(4, 0, 0)
9122 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
9123 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
9124 #else
9125 g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
9126 g_object_thaw_notify(G_OBJECT(m_pNotebook));
9127 #endif
9129 m_bOverFlowBoxActive = true;
9132 static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
9134 int nCurrentPage = pThis->get_current_page();
9135 pThis->split_notebooks();
9136 pThis->set_current_page(nCurrentPage);
9137 pThis->m_nLaunchSplitTimeoutId = 0;
9138 return false;
9141 // tdf#120371
9142 // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
9143 // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
9144 // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
9145 // tabs in a single row when they would fit
9146 void signal_notebook_size_allocate()
9148 if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
9149 return;
9150 disable_notify_events();
9151 gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
9152 if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
9154 for (gint i = 0; i < nPages; ++i)
9156 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
9157 #if GTK_CHECK_VERSION(4, 0, 0)
9158 bool bTabVisible = gtk_widget_get_child_visible(gtk_widget_get_parent(pTabWidget));
9159 #else
9160 bool bTabVisible = gtk_widget_get_child_visible(pTabWidget);
9161 #endif
9162 if (!bTabVisible)
9164 m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
9165 break;
9169 enable_notify_events();
9172 #if GTK_CHECK_VERSION(4, 0, 0)
9173 DECL_LINK(SizeAllocateHdl, void*, void);
9174 #else
9175 static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
9177 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
9178 pThis->signal_notebook_size_allocate();
9180 #endif
9182 bool signal_focus(GtkDirectionType direction)
9184 if (!m_bOverFlowBoxActive)
9185 return false;
9187 int nPage = gtk_notebook_get_current_page(m_pNotebook);
9188 if (direction == GTK_DIR_LEFT && nPage == 0)
9190 auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
9191 gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
9192 return true;
9194 else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
9196 gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
9197 return true;
9200 return false;
9203 #if !GTK_CHECK_VERSION(4, 0, 0)
9204 static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
9206 // if the notebook widget itself has focus
9207 if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
9209 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
9210 return pThis->signal_focus(direction);
9212 return false;
9214 #endif
9216 // ctrl + page_up/ page_down
9217 bool signal_change_current_page(gint arg1)
9219 bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
9220 if (bHandled)
9221 g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
9222 return false;
9225 static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
9227 if (arg1 == 0)
9228 return true;
9229 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
9230 return pThis->signal_change_current_page(arg1);
9233 public:
9234 GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9235 : GtkInstanceWidget(GTK_WIDGET(pNotebook), pBuilder, bTakeOwnership)
9236 , m_pNotebook(pNotebook)
9237 , m_pOverFlowBox(nullptr)
9238 , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
9239 , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
9240 , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
9241 #if GTK_CHECK_VERSION(4, 0, 0)
9242 , m_pLayout(nullptr)
9243 #else
9244 , m_nNotebookSizeAllocateSignalId(0)
9245 , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
9246 #endif
9247 , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
9248 , m_nLaunchSplitTimeoutId(0)
9249 , m_bOverFlowBoxActive(false)
9250 , m_bOverFlowBoxIsStart(false)
9251 , m_bInternalPageChange(false)
9252 , m_nStartTabCount(0)
9253 , m_nEndTabCount(0)
9255 #if !GTK_CHECK_VERSION(4, 0, 0)
9256 gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
9257 #endif
9258 gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
9259 if (nPages > 6)
9261 #if !GTK_CHECK_VERSION(4, 0, 0)
9262 m_nNotebookSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
9263 #else
9264 m_pLayout = NOTIFYING_LAYOUT(g_object_new(notifying_layout_get_type(), nullptr));
9265 notifying_layout_start_watch(m_pLayout, GTK_WIDGET(pNotebook), LINK(this, GtkInstanceNotebook, SizeAllocateHdl));
9266 #endif
9268 gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
9270 // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
9271 // the unwanted tab into invisibility via the 'overflow' class themed by global CreateStyleProvider
9272 GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
9273 gtk_style_context_add_class(pNotebookContext, "overflow");
9276 virtual int get_current_page() const override
9278 int nPage = gtk_notebook_get_current_page(m_pNotebook);
9279 if (nPage == -1)
9280 return nPage;
9281 if (m_bOverFlowBoxIsStart)
9283 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
9284 // add count of overflow pages, minus the extra tab
9285 nPage += nOverFlowLen;
9287 return nPage;
9290 virtual OUString get_page_ident(int nPage) const override
9292 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
9293 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
9294 if (m_bOverFlowBoxIsStart)
9296 if (nPage < nOverFlowLen)
9297 return get_page_ident(m_pOverFlowNotebook, nPage);
9298 nPage -= nOverFlowLen;
9299 return get_page_ident(m_pNotebook, nPage);
9301 else
9303 if (nPage < nMainLen)
9304 return get_page_ident(m_pNotebook, nPage);
9305 nPage -= nMainLen;
9306 return get_page_ident(m_pOverFlowNotebook, nPage);
9310 virtual OUString get_current_page_ident() const override
9312 const int nPage = get_current_page();
9313 return nPage != -1 ? get_page_ident(nPage) : OUString();
9316 virtual int get_page_index(const OUString& rIdent) const override
9318 auto nMainIndex = get_page_number(m_pNotebook, rIdent);
9319 auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
9321 if (nMainIndex == -1 && nOverFlowIndex == -1)
9322 return -1;
9324 if (m_bOverFlowBoxIsStart)
9326 if (nOverFlowIndex != -1)
9327 return nOverFlowIndex;
9328 else
9330 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
9331 return nMainIndex + nOverFlowLen;
9334 else
9336 if (nMainIndex != -1)
9337 return nMainIndex;
9338 else
9340 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
9341 return nOverFlowIndex + nMainLen;
9346 virtual weld::Container* get_page(const OUString& rIdent) const override
9348 int nPage = get_page_index(rIdent);
9349 if (nPage < 0)
9350 return nullptr;
9352 GtkWidget* pChild;
9353 if (m_bOverFlowBoxIsStart)
9355 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
9356 if (nPage < nOverFlowLen)
9357 pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
9358 else
9360 nPage -= nOverFlowLen;
9361 pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
9364 else
9366 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
9367 if (nPage < nMainLen)
9368 pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
9369 else
9371 nPage -= nMainLen;
9372 pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
9376 unsigned int nPageIndex = static_cast<unsigned int>(nPage);
9377 if (m_aPages.size() < nPageIndex + 1)
9378 m_aPages.resize(nPageIndex + 1);
9379 #if !GTK_CHECK_VERSION(4, 0, 0)
9380 if (!m_aPages[nPageIndex])
9381 m_aPages[nPageIndex].reset(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
9382 #else
9383 if (!m_aPages[nPageIndex])
9384 m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
9385 #endif
9386 return m_aPages[nPageIndex].get();
9389 virtual void set_current_page(int nPage) override
9391 // normally we'd call disable_notify_events/enable_notify_events here,
9392 // but the notebook is complicated by the need to support the
9393 // double-decker hackery so for simplicity just flag that the page
9394 // change is not a directly user-triggered one
9395 bool bInternalPageChange = m_bInternalPageChange;
9396 m_bInternalPageChange = true;
9398 if (m_bOverFlowBoxIsStart)
9400 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
9401 if (nPage < nOverFlowLen)
9402 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
9403 else
9405 nPage -= nOverFlowLen;
9406 gtk_notebook_set_current_page(m_pNotebook, nPage);
9409 else
9411 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
9412 if (nPage < nMainLen)
9413 gtk_notebook_set_current_page(m_pNotebook, nPage);
9414 else
9416 nPage -= nMainLen;
9417 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
9421 m_bInternalPageChange = bInternalPageChange;
9424 virtual void set_current_page(const OUString& rIdent) override
9426 gint nPage = get_page_index(rIdent);
9427 set_current_page(nPage);
9430 virtual int get_n_pages() const override
9432 int nLen = gtk_notebook_get_n_pages(m_pNotebook);
9433 if (m_bOverFlowBoxActive)
9434 nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
9435 return nLen;
9438 virtual OUString get_tab_label_text(const OUString& rIdent) const override
9440 gint nPageNum = get_page_number(m_pNotebook, rIdent);
9441 if (nPageNum != -1)
9442 return get_tab_label_text(m_pNotebook, nPageNum);
9443 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
9444 if (nPageNum != -1)
9445 return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
9446 return OUString();
9449 virtual void set_tab_label_text(const OUString& rIdent, const OUString& rText) override
9451 gint nPageNum = get_page_number(m_pNotebook, rIdent);
9452 if (nPageNum != -1)
9454 set_tab_label_text(m_pNotebook, nPageNum, rText);
9455 return;
9457 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
9458 if (nPageNum != -1)
9460 set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
9464 virtual void set_show_tabs(bool bShow) override
9466 if (m_bOverFlowBoxActive)
9468 unsplit_notebooks();
9469 reset_split_data();
9472 gtk_notebook_set_show_tabs(m_pNotebook, bShow);
9473 gtk_notebook_set_show_tabs(m_pOverFlowNotebook, bShow);
9476 virtual void disable_notify_events() override
9478 g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
9479 #if !GTK_CHECK_VERSION(4, 0, 0)
9480 g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
9481 #endif
9482 g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
9483 g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
9484 #if !GTK_CHECK_VERSION(4, 0, 0)
9485 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
9486 #endif
9487 g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
9488 GtkInstanceWidget::disable_notify_events();
9491 virtual void enable_notify_events() override
9493 GtkInstanceWidget::enable_notify_events();
9494 g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
9495 #if !GTK_CHECK_VERSION(4, 0, 0)
9496 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
9497 #endif
9498 g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
9499 g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
9500 #if !GTK_CHECK_VERSION(4, 0, 0)
9501 g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
9502 #endif
9503 g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
9506 void reset_split_data()
9508 // reset overflow and allow it to be recalculated if necessary
9509 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
9510 m_bOverFlowBoxActive = false;
9511 m_nStartTabCount = 0;
9512 m_nEndTabCount = 0;
9515 virtual void remove_page(const OUString& rIdent) override
9517 if (m_bOverFlowBoxActive)
9519 unsplit_notebooks();
9520 reset_split_data();
9523 unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
9524 if (nPageIndex < m_aPages.size())
9525 m_aPages.erase(m_aPages.begin() + nPageIndex);
9528 virtual void insert_page(const OUString& rIdent, const OUString& rLabel, int nPos) override
9530 if (m_bOverFlowBoxActive)
9532 unsplit_notebooks();
9533 reset_split_data();
9536 // reset overflow and allow it to be recalculated if necessary
9537 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
9538 m_bOverFlowBoxActive = false;
9540 insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos);
9543 virtual ~GtkInstanceNotebook() override
9545 if (m_nLaunchSplitTimeoutId)
9546 g_source_remove(m_nLaunchSplitTimeoutId);
9547 #if !GTK_CHECK_VERSION(4, 0, 0)
9548 if (m_nNotebookSizeAllocateSignalId)
9549 g_signal_handler_disconnect(m_pNotebook, m_nNotebookSizeAllocateSignalId);
9550 #else
9551 if (m_pLayout)
9553 // put it back how we found it initially
9554 notifying_layout_stop_watch(m_pLayout);
9556 #endif
9557 g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
9558 #if !GTK_CHECK_VERSION(4, 0, 0)
9559 g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
9560 #endif
9561 g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
9562 g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
9563 #if !GTK_CHECK_VERSION(4, 0, 0)
9564 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
9565 #else
9566 GtkWidget* pOverFlowWidget = GTK_WIDGET(m_pOverFlowNotebook);
9567 g_clear_pointer(&pOverFlowWidget, gtk_widget_unparent);
9568 #endif
9569 if (!m_pOverFlowBox)
9570 return;
9572 // put it back to how we found it initially
9573 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
9574 g_object_ref(m_pNotebook);
9575 container_remove(GTK_WIDGET(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
9576 container_add(GTK_WIDGET(pParent), GTK_WIDGET(m_pNotebook));
9577 g_object_unref(m_pNotebook);
9579 #if !GTK_CHECK_VERSION(4, 0, 0)
9580 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
9581 #else
9582 GtkWidget* pOverFlowBox = GTK_WIDGET(m_pOverFlowBox);
9583 g_clear_pointer(&pOverFlowBox, gtk_widget_unparent);
9584 #endif
9588 #if GTK_CHECK_VERSION(4, 0, 0)
9589 IMPL_LINK_NOARG(GtkInstanceNotebook, SizeAllocateHdl, void*, void)
9591 signal_notebook_size_allocate();
9593 #endif
9596 OUString vcl_font_to_css(const vcl::Font& rFont)
9598 OUStringBuffer sCSS(
9599 "font-family: \"" + rFont.GetFamilyName() + "\"; "
9600 "font-size: " + OUString::number(rFont.GetFontSize().Height()) + "pt; ");
9601 switch (rFont.GetItalic())
9603 case ITALIC_NONE:
9604 sCSS.append("font-style: normal; ");
9605 break;
9606 case ITALIC_NORMAL:
9607 sCSS.append("font-style: italic; ");
9608 break;
9609 case ITALIC_OBLIQUE:
9610 sCSS.append("font-style: oblique; ");
9611 break;
9612 default:
9613 break;
9615 switch (rFont.GetWeight())
9617 case WEIGHT_ULTRALIGHT:
9618 sCSS.append("font-weight: 200; ");
9619 break;
9620 case WEIGHT_LIGHT:
9621 sCSS.append("font-weight: 300; ");
9622 break;
9623 case WEIGHT_NORMAL:
9624 sCSS.append("font-weight: 400; ");
9625 break;
9626 case WEIGHT_BOLD:
9627 sCSS.append("font-weight: 700; ");
9628 break;
9629 case WEIGHT_ULTRABOLD:
9630 sCSS.append("font-weight: 800; ");
9631 break;
9632 default:
9633 break;
9635 switch (rFont.GetWidthType())
9637 case WIDTH_ULTRA_CONDENSED:
9638 sCSS.append("font-stretch: ultra-condensed; ");
9639 break;
9640 case WIDTH_EXTRA_CONDENSED:
9641 sCSS.append("font-stretch: extra-condensed; ");
9642 break;
9643 case WIDTH_CONDENSED:
9644 sCSS.append("font-stretch: condensed; ");
9645 break;
9646 case WIDTH_SEMI_CONDENSED:
9647 sCSS.append("font-stretch: semi-condensed; ");
9648 break;
9649 case WIDTH_NORMAL:
9650 sCSS.append("font-stretch: normal; ");
9651 break;
9652 case WIDTH_SEMI_EXPANDED:
9653 sCSS.append("font-stretch: semi-expanded; ");
9654 break;
9655 case WIDTH_EXPANDED:
9656 sCSS.append("font-stretch: expanded; ");
9657 break;
9658 case WIDTH_EXTRA_EXPANDED:
9659 sCSS.append("font-stretch: extra-expanded; ");
9660 break;
9661 case WIDTH_ULTRA_EXPANDED:
9662 sCSS.append("font-stretch: ultra-expanded; ");
9663 break;
9664 default:
9665 break;
9667 return sCSS.toString();
9670 void update_attr_list(PangoAttrList* pAttrList, const vcl::Font& rFont)
9672 pango_attr_list_change(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
9673 pango_attr_list_change(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
9675 switch (rFont.GetItalic())
9677 case ITALIC_NONE:
9678 pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
9679 break;
9680 case ITALIC_NORMAL:
9681 pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
9682 break;
9683 case ITALIC_OBLIQUE:
9684 pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
9685 break;
9686 default:
9687 break;
9689 switch (rFont.GetWeight())
9691 case WEIGHT_ULTRALIGHT:
9692 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
9693 break;
9694 case WEIGHT_LIGHT:
9695 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
9696 break;
9697 case WEIGHT_NORMAL:
9698 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
9699 break;
9700 case WEIGHT_BOLD:
9701 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
9702 break;
9703 case WEIGHT_ULTRABOLD:
9704 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
9705 break;
9706 default:
9707 break;
9709 switch (rFont.GetWidthType())
9711 case WIDTH_ULTRA_CONDENSED:
9712 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
9713 break;
9714 case WIDTH_EXTRA_CONDENSED:
9715 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
9716 break;
9717 case WIDTH_CONDENSED:
9718 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
9719 break;
9720 case WIDTH_SEMI_CONDENSED:
9721 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
9722 break;
9723 case WIDTH_NORMAL:
9724 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
9725 break;
9726 case WIDTH_SEMI_EXPANDED:
9727 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
9728 break;
9729 case WIDTH_EXPANDED:
9730 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
9731 break;
9732 case WIDTH_EXTRA_EXPANDED:
9733 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
9734 break;
9735 case WIDTH_ULTRA_EXPANDED:
9736 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
9737 break;
9738 default:
9739 break;
9743 gboolean filter_pango_attrs(PangoAttribute *attr, gpointer data)
9745 PangoAttrType* pFilterAttrs = static_cast<PangoAttrType*>(data);
9746 while (*pFilterAttrs)
9748 if (attr->klass->type == *pFilterAttrs)
9749 return true;
9750 ++pFilterAttrs;
9752 return false;
9755 void set_font(GtkLabel* pLabel, const vcl::Font& rFont)
9757 PangoAttrList* pOrigList = gtk_label_get_attributes(pLabel);
9758 PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
9760 if (pOrigList)
9762 // tdf#143443 remove both PANGO_ATTR_ABSOLUTE_SIZE and PANGO_ATTR_SIZE
9763 // because pango_attr_list_change(..., pango_attr_size_new...) isn't
9764 // sufficient on its own to ensure a new size sticks.
9765 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_ABSOLUTE_SIZE, PANGO_ATTR_SIZE, PANGO_ATTR_INVALID};
9766 PangoAttrList* pRemovedAttrs = pango_attr_list_filter(pAttrList, filter_pango_attrs, &aFilterAttrs);
9767 pango_attr_list_unref(pRemovedAttrs);
9770 update_attr_list(pAttrList, rFont);
9771 gtk_label_set_attributes(pLabel, pAttrList);
9772 pango_attr_list_unref(pAttrList);
9777 namespace {
9779 class WidgetBackground
9781 private:
9782 GtkWidget* m_pWidget;
9783 GtkCssProvider* m_pCustomCssProvider;
9784 std::unique_ptr<utl::TempFileNamed> m_xCustomImage;
9786 public:
9787 // See: https://developer.gnome.org/Buttons/
9788 void use_custom_content(const VirtualDevice* pDevice)
9790 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
9792 if (m_pCustomCssProvider)
9794 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider));
9795 m_pCustomCssProvider = nullptr;
9798 m_xCustomImage.reset();
9800 if (!pDevice)
9801 return;
9803 m_xCustomImage.reset(new utl::TempFileNamed);
9804 m_xCustomImage->EnableKillingFile(true);
9806 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
9807 Size aSize = pDevice->GetOutputSizePixel();
9808 cairo_surface_write_to_png(surface, OUStringToOString(m_xCustomImage->GetFileName(), osl_getThreadTextEncoding()).getStr());
9810 m_pCustomCssProvider = gtk_css_provider_new();
9811 OUString aBuffer = "* { background-image: url(\"" + m_xCustomImage->GetURL() + "\"); "
9812 "background-size: " + OUString::number(aSize.Width()) + "px " + OUString::number(aSize.Height()) + "px; "
9813 "border-radius: 0; border-width: 0; }";
9814 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
9815 css_provider_load_from_data(m_pCustomCssProvider, aResult.getStr(), aResult.getLength());
9816 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider),
9817 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
9820 public:
9821 WidgetBackground(GtkWidget* pWidget)
9822 : m_pWidget(pWidget)
9823 , m_pCustomCssProvider(nullptr)
9827 ~WidgetBackground()
9829 if (m_pCustomCssProvider)
9830 use_custom_content(nullptr);
9831 assert(!m_pCustomCssProvider);
9835 class WidgetFont
9837 private:
9838 GtkWidget* m_pWidget;
9839 GtkCssProvider* m_pFontCssProvider;
9840 std::unique_ptr<vcl::Font> m_xFont;
9841 public:
9842 WidgetFont(GtkWidget* pWidget)
9843 : m_pWidget(pWidget)
9844 , m_pFontCssProvider(nullptr)
9848 void use_custom_font(const vcl::Font* pFont, std::u16string_view rCSSSelector)
9850 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
9851 if (m_pFontCssProvider)
9853 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider));
9854 m_pFontCssProvider = nullptr;
9857 m_xFont.reset();
9859 if (!pFont)
9860 return;
9862 m_xFont.reset(new vcl::Font(*pFont));
9863 m_pFontCssProvider = gtk_css_provider_new();
9864 OUString aBuffer = rCSSSelector + OUString::Concat(" { ") + vcl_font_to_css(*pFont) + OUString::Concat(" }");
9865 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
9866 css_provider_load_from_data(m_pFontCssProvider, aResult.getStr(), aResult.getLength());
9867 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider),
9868 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
9871 const vcl::Font* get_custom_font() const
9873 return m_xFont.get();
9876 ~WidgetFont()
9878 if (m_pFontCssProvider)
9879 use_custom_font(nullptr, u"");
9880 assert(!m_pFontCssProvider);
9884 class GtkInstanceButton : public GtkInstanceWidget, public virtual weld::Button
9886 private:
9887 GtkButton* m_pButton;
9888 gulong m_nSignalId;
9889 std::optional<vcl::Font> m_xFont;
9890 WidgetBackground m_aCustomBackground;
9892 static void signalClicked(GtkButton*, gpointer widget)
9894 GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
9895 SolarMutexGuard aGuard;
9896 pThis->signal_clicked();
9899 virtual void ensureMouseEventWidget() override
9901 // The GtkButton is sufficient to get mouse events without an intermediate GtkEventBox
9902 if (!m_pMouseEventBox)
9903 m_pMouseEventBox = m_pWidget;
9906 public:
9907 GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9908 : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
9909 , m_pButton(pButton)
9910 , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
9911 , m_aCustomBackground(GTK_WIDGET(pButton))
9913 g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
9916 virtual void set_label(const OUString& rText) override
9918 ::button_set_label(m_pButton, rText);
9921 virtual void set_image(VirtualDevice* pDevice) override
9923 ::button_set_image(m_pButton, pDevice);
9926 virtual void set_from_icon_name(const OUString& rIconName) override
9928 ::button_set_from_icon_name(m_pButton, rIconName);
9931 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
9933 ::button_set_image(m_pButton, rImage);
9936 virtual void set_custom_button(VirtualDevice* pDevice) override
9938 m_aCustomBackground.use_custom_content(pDevice);
9941 virtual OUString get_label() const override
9943 return ::button_get_label(m_pButton);
9946 virtual void set_font(const vcl::Font& rFont) override
9948 m_xFont = rFont;
9949 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pButton));
9950 ::set_font(pChild, rFont);
9953 virtual vcl::Font get_font() override
9955 if (m_xFont)
9956 return *m_xFont;
9957 return GtkInstanceWidget::get_font();
9960 // allow us to block buttons with click handlers making dialogs return a response
9961 bool has_click_handler() const
9963 return m_aClickHdl.IsSet();
9966 void clear_click_handler()
9968 m_aClickHdl = Link<Button&, void>();
9971 virtual void disable_notify_events() override
9973 g_signal_handler_block(m_pButton, m_nSignalId);
9974 GtkInstanceWidget::disable_notify_events();
9977 virtual void enable_notify_events() override
9979 GtkInstanceWidget::enable_notify_events();
9980 g_signal_handler_unblock(m_pButton, m_nSignalId);
9983 virtual ~GtkInstanceButton() override
9985 g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
9986 g_signal_handler_disconnect(m_pButton, m_nSignalId);
9992 void GtkInstanceDialog::asyncresponse(gint ret)
9994 SolarMutexGuard aGuard;
9996 if (ret == GTK_RESPONSE_HELP)
9998 help();
9999 return;
10002 GtkInstanceButton* pClickHandler = has_click_handler(ret);
10003 if (pClickHandler)
10005 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
10006 if (ret == GTK_RESPONSE_DELETE_EVENT)
10007 close(false);
10008 return;
10011 if (get_modal())
10012 m_aDialogRun.dec_modal_count();
10013 hide();
10015 // move the self pointer, otherwise it might be de-allocated by time we try to reset it
10016 auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
10017 auto xDialogController = std::move(m_xDialogController);
10018 auto aFunc = std::move(m_aFunc);
10020 auto nResponseSignalId = m_nResponseSignalId;
10021 auto nCancelSignalId = m_nCancelSignalId;
10022 auto nSignalDeleteId = m_nSignalDeleteId;
10023 m_nResponseSignalId = 0;
10024 m_nCancelSignalId = 0;
10025 m_nSignalDeleteId = 0;
10027 if (aFunc)
10028 aFunc(GtkToVcl(ret));
10030 if (nResponseSignalId)
10031 g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
10032 if (nCancelSignalId)
10033 g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
10034 if (nSignalDeleteId)
10035 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
10037 xDialogController.reset();
10038 xRunAsyncSelf.reset();
10041 int GtkInstanceDialog::run()
10043 // tdf#150723 "run" will make the dialog visible so drop m_aPosWhileInvis like show
10044 m_aPosWhileInvis.reset();
10046 #if !GTK_CHECK_VERSION(4, 0, 0)
10047 if (GTK_IS_DIALOG(m_pDialog))
10048 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
10049 #endif
10050 int ret;
10051 while (true)
10053 ret = m_aDialogRun.run();
10054 if (ret == GTK_RESPONSE_HELP)
10056 help();
10057 continue;
10059 else if (has_click_handler(ret))
10060 continue;
10061 break;
10063 hide();
10064 return GtkToVcl(ret);
10067 weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
10069 GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
10070 if (!pButton)
10071 return nullptr;
10072 return new GtkInstanceButton(pButton, m_pBuilder, false);
10075 void GtkInstanceDialog::response(int nResponse)
10077 int nGtkResponse = VclToGtk(nResponse);
10078 //unblock this response now when activated through code
10079 if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
10081 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
10082 GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
10083 if (pButton)
10084 pButton->clear_click_handler();
10086 if (GTK_IS_DIALOG(m_pDialog))
10087 gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
10088 else if (GTK_IS_ASSISTANT(m_pDialog))
10090 if (!m_aDialogRun.loop_is_running())
10091 asyncresponse(nGtkResponse);
10092 else
10094 m_aDialogRun.m_nResponseId = nGtkResponse;
10095 m_aDialogRun.loop_quit();
10100 void GtkInstanceDialog::close(bool bCloseSignal)
10102 GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
10103 if (pClickHandler)
10105 if (bCloseSignal)
10106 g_signal_stop_emission_by_name(m_pDialog, "close");
10107 // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
10108 // act as if cancel button was pressed
10109 pClickHandler->clicked();
10110 return;
10112 response(RET_CANCEL);
10115 GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
10117 GtkInstanceButton* pButton = nullptr;
10118 // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
10119 nResponse = VclToGtk(GtkToVcl(nResponse));
10120 if (GtkButton* pWidget = get_widget_for_response(nResponse))
10122 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
10123 pButton = static_cast<GtkInstanceButton*>(pData);
10124 if (pButton && !pButton->has_click_handler())
10125 pButton = nullptr;
10127 return pButton;
10130 namespace {
10132 class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
10134 protected:
10135 GtkToggleButton* m_pToggleButton;
10136 gulong m_nToggledSignalId;
10137 private:
10138 static void signalToggled(GtkToggleButton*, gpointer widget)
10140 GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
10141 SolarMutexGuard aGuard;
10142 pThis->signal_toggled();
10144 public:
10145 GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10146 : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
10147 , m_pToggleButton(pButton)
10148 , m_nToggledSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
10152 virtual void set_active(bool active) override
10154 disable_notify_events();
10155 set_inconsistent(false);
10156 gtk_toggle_button_set_active(m_pToggleButton, active);
10157 enable_notify_events();
10160 virtual bool get_active() const override
10162 return gtk_toggle_button_get_active(m_pToggleButton);
10165 virtual void set_inconsistent(bool inconsistent) override
10167 #if GTK_CHECK_VERSION(4, 0, 0)
10168 if (inconsistent)
10169 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT, false);
10170 else
10171 gtk_widget_unset_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT);
10172 #else
10173 gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
10174 #endif
10177 virtual bool get_inconsistent() const override
10179 #if GTK_CHECK_VERSION(4, 0, 0)
10180 return gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)) & GTK_STATE_FLAG_INCONSISTENT;
10181 #else
10182 return gtk_toggle_button_get_inconsistent(m_pToggleButton);
10183 #endif
10186 virtual void disable_notify_events() override
10188 g_signal_handler_block(m_pToggleButton, m_nToggledSignalId);
10189 GtkInstanceButton::disable_notify_events();
10192 virtual void enable_notify_events() override
10194 GtkInstanceButton::enable_notify_events();
10195 g_signal_handler_unblock(m_pToggleButton, m_nToggledSignalId);
10198 virtual ~GtkInstanceToggleButton() override
10200 g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
10206 #if !GTK_CHECK_VERSION(4, 0, 0)
10208 namespace {
10210 void do_grab(GtkWidget* pWidget)
10212 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
10213 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
10214 gdk_seat_grab(pSeat, widget_get_surface(pWidget),
10215 GDK_SEAT_CAPABILITY_KEYBOARD, true, nullptr, nullptr, nullptr, nullptr);
10218 void do_ungrab(GtkWidget* pWidget)
10220 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
10221 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
10222 gdk_seat_ungrab(pSeat);
10225 GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
10226 weld::Placement ePlace, bool bTryShrink)
10228 //place the toplevel just below its launcher button
10229 GtkWidget* pToplevel = widget_get_toplevel(pMenuButton);
10230 gtk_coord x, y, absx, absy;
10231 gtk_widget_translate_coordinates(pMenuButton, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
10232 GdkSurface* pWindow = widget_get_surface(pToplevel);
10233 gdk_window_get_position(pWindow, &absx, &absy);
10235 x += absx;
10236 y += absy;
10238 gint nButtonHeight = rAnchor.height;
10239 gint nButtonWidth = rAnchor.width;
10240 if (ePlace == weld::Placement::Under)
10241 y += nButtonHeight;
10242 else
10243 x += nButtonWidth;
10245 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
10246 gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
10248 gint nMenuWidth, nMenuHeight;
10249 gtk_widget_get_size_request(GTK_WIDGET(pMenu), &nMenuWidth, &nMenuHeight);
10251 if (nMenuWidth == -1 || nMenuHeight == -1)
10253 GtkRequisition req;
10254 gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
10255 if (nMenuWidth == -1)
10256 nMenuWidth = req.width;
10257 if (nMenuHeight == -1)
10258 nMenuHeight = req.height;
10261 bool bSwapForRTL = SwapForRTL(pMenuButton);
10262 if (bSwapForRTL)
10264 if (ePlace == weld::Placement::Under)
10265 x += nButtonWidth;
10266 else
10267 x -= nButtonWidth;
10268 x -= nMenuWidth;
10271 tools::Rectangle aWorkArea(::get_monitor_workarea(pMenuButton));
10273 // shrink it a little, I find it reassuring to see a little margin with a
10274 // long menu to know the menu is fully on screen
10275 aWorkArea.AdjustTop(8);
10276 aWorkArea.AdjustBottom(-8);
10277 aWorkArea.AdjustLeft(8);
10278 aWorkArea.AdjustRight(-8);
10280 GtkPositionType ePosUsed;
10282 if (ePlace == weld::Placement::Under)
10284 gint endx = x + nMenuWidth;
10285 if (endx > aWorkArea.Right())
10286 x -= endx - aWorkArea.Right();
10287 if (x < 0)
10288 x = 0;
10290 ePosUsed = GTK_POS_BOTTOM;
10291 gint endy = y + nMenuHeight;
10292 gint nMissingBelow = endy - aWorkArea.Bottom();
10293 if (nMissingBelow > 0)
10295 gint nNewY = y - (nButtonHeight + nMenuHeight);
10296 gint nMissingAbove = aWorkArea.Top() - nNewY;
10297 if (nMissingAbove > 0)
10299 if (bTryShrink)
10301 if (nMissingBelow <= nMissingAbove)
10302 nMenuHeight -= nMissingBelow;
10303 else
10305 nMenuHeight -= nMissingAbove;
10306 y = aWorkArea.Top();
10307 ePosUsed = GTK_POS_TOP;
10309 gtk_widget_set_size_request(GTK_WIDGET(pMenu), nMenuWidth, nMenuHeight);
10311 else
10313 if (nMissingBelow <= nMissingAbove)
10314 y -= nMissingBelow;
10315 else
10317 y = aWorkArea.Top();
10318 ePosUsed = GTK_POS_TOP;
10322 else
10324 y = nNewY;
10325 ePosUsed = GTK_POS_TOP;
10329 else
10331 if (!bSwapForRTL)
10333 ePosUsed = GTK_POS_RIGHT;
10334 gint endx = x + nMenuWidth;
10335 gint nMissingAfter = endx - aWorkArea.Right();
10336 if (nMissingAfter > 0)
10338 gint nNewX = x - (nButtonWidth + nMenuWidth);
10339 if (nNewX >= aWorkArea.Left())
10341 x = nNewX;
10342 ePosUsed = GTK_POS_LEFT;
10346 else
10348 ePosUsed = GTK_POS_LEFT;
10349 gint startx = x;
10350 gint nMissingBefore = aWorkArea.Left() - startx;
10351 if (nMissingBefore > 0)
10353 gint nNewX = x + (nButtonWidth + nMenuWidth);
10354 if (nNewX + nMenuWidth < aWorkArea.Right())
10356 x = nNewX;
10357 ePosUsed = GTK_POS_RIGHT;
10363 gtk_window_move(pMenu, x, y);
10365 return ePosUsed;
10368 bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu, const GdkRectangle &rAnchor,
10369 weld::Placement ePlace, bool bTryShrink)
10371 static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
10372 GdkGravity, GdkAnchorHints, gint, gint)>(
10373 dlsym(nullptr, "gdk_window_move_to_rect"));
10374 if (!window_move_to_rect)
10375 return false;
10377 // under wayland gdk_window_move_to_rect works great for me, but in my current
10378 // gtk 3.24 under X it leaves part of long menus outside the work area
10379 GdkDisplay *pDisplay = gtk_widget_get_display(pComboBox);
10380 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
10381 return false;
10383 //place the toplevel just below its launcher button
10384 GtkWidget* pToplevel = widget_get_toplevel(pComboBox);
10385 gtk_coord x, y;
10386 gtk_widget_translate_coordinates(pComboBox, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
10388 gtk_widget_realize(GTK_WIDGET(pMenu));
10389 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
10390 gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
10392 bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
10394 GdkGravity rect_anchor;
10395 GdkGravity menu_anchor;
10397 if (ePlace == weld::Placement::Under)
10399 rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
10400 menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
10402 else
10404 rect_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_EAST : GDK_GRAVITY_NORTH_WEST;
10405 menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
10408 GdkAnchorHints anchor_hints = static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE);
10409 if (bTryShrink)
10410 anchor_hints = static_cast<GdkAnchorHints>(anchor_hints | GDK_ANCHOR_RESIZE);
10411 GdkRectangle rect {x, y, rAnchor.width, rAnchor.height};
10412 GdkSurface* toplevel = widget_get_surface(GTK_WIDGET(pMenu));
10414 window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor, anchor_hints,
10415 0, 0);
10417 return true;
10420 GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
10421 weld::Placement ePlace, bool bTryShrink)
10423 // we only use ePosUsed in the replacement-for-X-popover case of a
10424 // MenuButton, so we only need it when show_menu_older_gtk is used
10425 GtkPositionType ePosUsed = GTK_POS_BOTTOM;
10427 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
10428 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
10429 // implemented as popups, which means that we cannot show any popup if there is a
10430 // visible tooltip.
10431 GtkWidget* pParent = widget_get_toplevel(pMenuButton);
10432 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
10433 if (pFrame)
10435 // hide any current tooltip
10436 pFrame->HideTooltip();
10437 // don't allow any more to appear until menu is dismissed
10438 pFrame->BlockTooltip();
10441 // try with gdk_window_move_to_rect, but if that's not available, try without
10442 if (!show_menu_newer_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink))
10443 ePosUsed = show_menu_older_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink);
10444 gtk_widget_show_all(GTK_WIDGET(pMenu));
10445 gtk_widget_grab_focus(GTK_WIDGET(pMenu));
10446 do_grab(GTK_WIDGET(pMenu));
10448 return ePosUsed;
10452 #endif
10454 namespace {
10456 #if !GTK_CHECK_VERSION(4, 0, 0)
10457 bool button_event_is_outside(GtkWidget* pMenuHack, GdkEventButton* pEvent)
10459 //we want to pop down if the button was released outside our popup
10460 gdouble x = pEvent->x_root;
10461 gdouble y = pEvent->y_root;
10463 gint window_x, window_y;
10464 GdkSurface* pWindow = widget_get_surface(pMenuHack);
10465 gdk_window_get_position(pWindow, &window_x, &window_y);
10467 GtkAllocation alloc;
10468 gtk_widget_get_allocation(pMenuHack, &alloc);
10469 gint x1 = window_x;
10470 gint y1 = window_y;
10471 gint x2 = x1 + alloc.width;
10472 gint y2 = y1 + alloc.height;
10474 if (x > x1 && x < x2 && y > y1 && y < y2)
10475 return false;
10477 return true;
10480 GtkPositionType MovePopoverContentsToWindow(GtkWidget* pPopover, GtkWindow* pMenuHack, GtkWidget* pAnchor,
10481 const GdkRectangle& rAnchor, weld::Placement ePlace)
10483 //set border width
10484 gtk_container_set_border_width(GTK_CONTAINER(pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(pPopover)));
10486 //steal popover contents and smuggle into toplevel display window
10487 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pPopover));
10488 g_object_ref(pChild);
10489 gtk_container_remove(GTK_CONTAINER(pPopover), pChild);
10490 gtk_container_add(GTK_CONTAINER(pMenuHack), pChild);
10491 g_object_unref(pChild);
10493 GtkPositionType eRet = show_menu(pAnchor, pMenuHack, rAnchor, ePlace, false);
10495 gtk_grab_add(GTK_WIDGET(pMenuHack));
10497 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
10498 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
10500 return eRet;
10503 void MoveWindowContentsToPopover(GtkWindow* pMenuHack, GtkWidget* pPopover, GtkWidget* pAnchor)
10505 bool bHadFocus = gtk_window_has_toplevel_focus(pMenuHack);
10507 do_ungrab(GTK_WIDGET(pMenuHack));
10509 gtk_grab_remove(GTK_WIDGET(pMenuHack));
10511 gtk_widget_hide(GTK_WIDGET(pMenuHack));
10512 //put contents back from where the came from
10513 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pMenuHack));
10514 g_object_ref(pChild);
10515 gtk_container_remove(GTK_CONTAINER(pMenuHack), pChild);
10516 gtk_container_add(GTK_CONTAINER(pPopover), pChild);
10517 g_object_unref(pChild);
10519 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
10520 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
10522 // so gdk_window_move_to_rect will work again the next time
10523 gtk_widget_unrealize(GTK_WIDGET(pMenuHack));
10525 gtk_widget_set_size_request(GTK_WIDGET(pMenuHack), -1, -1);
10527 // undo show_menu tooltip blocking
10528 GtkWidget* pParent = widget_get_toplevel(pAnchor);
10529 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
10530 if (pFrame)
10531 pFrame->UnblockTooltip();
10533 if (bHadFocus)
10535 GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
10536 void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
10537 if (pParentIsPopover)
10538 do_grab(pAnchor);
10539 gtk_widget_grab_focus(pAnchor);
10543 // tdf#153885/tdf#152438 for wayland if the popover window is the application
10544 // window, constrain it within the application window so it won't be cut off
10545 // screen. Leave dialog hosted ones alone, like format, watermark, which are
10546 // likely presented in the middle of the screen and are too small to constrain
10547 // the popover inside.
10548 void ConstrainApplicationWindowPopovers(GtkWidget* pItem)
10550 #if defined(GDK_WINDOWING_WAYLAND)
10551 GdkDisplay *pDisplay = gtk_widget_get_display(pItem);
10552 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay) && GTK_IS_MENU_BUTTON(pItem))
10554 GtkMenuButton* pMenuButton = GTK_MENU_BUTTON(pItem);
10555 if (GtkPopover* pPopover = gtk_menu_button_get_popover(pMenuButton))
10557 if (gtk_popover_get_constrain_to(pPopover) == GTK_POPOVER_CONSTRAINT_NONE)
10559 GtkWidget* pTopLevel = widget_get_toplevel(pItem);
10560 GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr;
10561 if (pFrame)
10563 // the toplevel is an application window
10564 gtk_popover_set_constrain_to(pPopover, GTK_POPOVER_CONSTRAINT_WINDOW);
10569 #else
10570 (void)pItem;
10571 #endif
10574 #endif
10576 /* four types of uses of this
10577 a) textual menubutton, always with pan-down symbol, e.g. math, format, font, modify
10578 b) image + text, always with additional pan-down symbol, e.g. writer, format, watermark
10579 c) gear menu, never with text and without pan-down symbol where there is a replacement
10580 icon for pan-down, e.g. file, new, templates
10581 d) image, always with additional pan-down symbol, e.g. calc, insert, header/footer */
10582 #if !GTK_CHECK_VERSION(4, 0, 0)
10583 class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
10584 #else
10585 class GtkInstanceMenuButton : public GtkInstanceWidget, public MenuHelper, public virtual weld::MenuButton
10586 #endif
10588 protected:
10589 GtkMenuButton* m_pMenuButton;
10590 private:
10591 GtkBox* m_pBox;
10592 #if !GTK_CHECK_VERSION(4, 0, 0)
10593 GtkImage* m_pImage;
10594 #else
10595 GtkPicture* m_pImage;
10596 GtkToggleButton* m_pMenuButtonToggleButton;
10597 #endif
10598 GtkWidget* m_pLabel;
10599 #if !GTK_CHECK_VERSION(4, 0, 0)
10600 //popover cannot escape dialog under X so stick up own window instead
10601 GtkWindow* m_pMenuHack;
10602 //when doing so, if it's a toolbar menubutton align the menu to the full toolitem
10603 GtkWidget* m_pMenuHackAlign;
10604 bool m_nButtonPressSeen;
10605 gulong m_nSignalId;
10606 #endif
10607 GtkWidget* m_pPopover;
10608 #if GTK_CHECK_VERSION(4, 0, 0)
10609 gulong m_nToggledSignalId;
10610 std::optional<vcl::Font> m_xFont;
10611 WidgetBackground m_aCustomBackground;
10612 #endif
10614 #if !GTK_CHECK_VERSION(4, 0, 0)
10615 static void signalMenuButtonToggled(GtkWidget* pItem, gpointer widget)
10617 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10618 if (!pThis->m_pMenuHack)
10620 ConstrainApplicationWindowPopovers(pItem);
10621 return;
10623 SolarMutexGuard aGuard;
10624 pThis->menu_toggled();
10626 #endif
10628 #if !GTK_CHECK_VERSION(4, 0, 0)
10629 void menu_toggled()
10631 if (!get_active())
10633 m_nButtonPressSeen = false;
10634 MoveWindowContentsToPopover(m_pMenuHack, m_pPopover, GTK_WIDGET(m_pMenuButton));
10636 else
10638 GtkWidget* pAnchor = m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton);
10639 GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pAnchor), gtk_widget_get_allocated_height(pAnchor) };
10640 GtkPositionType ePosUsed = MovePopoverContentsToWindow(m_pPopover, m_pMenuHack, pAnchor, aAnchor, weld::Placement::Under);
10641 // tdf#132540 keep the placeholder popover on this same side as the replacement menu
10642 gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton), ePosUsed);
10645 #endif
10647 #if !GTK_CHECK_VERSION(4, 0, 0)
10648 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
10650 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10651 pThis->grab_broken(pEvent);
10654 void grab_broken(const GdkEventGrabBroken *event)
10656 if (event->grab_window == nullptr)
10658 set_active(false);
10660 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
10662 //try and regrab, so when we lose the grab to the menu of the color palette
10663 //combobox we regain it so the color palette doesn't itself disappear on next
10664 //click on the color palette combobox
10665 do_grab(GTK_WIDGET(m_pMenuHack));
10669 static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
10671 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10672 pThis->m_nButtonPressSeen = true;
10673 return false;
10676 static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
10678 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10679 if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
10680 pThis->set_active(false);
10681 return false;
10684 static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
10686 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10687 return pThis->key_press(pEvent);
10690 bool key_press(const GdkEventKey* pEvent)
10692 if (pEvent->keyval == GDK_KEY_Escape)
10694 set_active(false);
10695 return true;
10697 return false;
10699 #endif
10701 void ensure_image_widget()
10703 if (m_pImage)
10704 return;
10706 #if !GTK_CHECK_VERSION(4, 0, 0)
10707 m_pImage = GTK_IMAGE(gtk_image_new());
10708 gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
10709 gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
10710 #else
10711 m_pImage = GTK_PICTURE(gtk_picture_new());
10712 gtk_widget_set_halign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
10713 gtk_widget_set_valign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
10714 gtk_box_prepend(m_pBox, GTK_WIDGET(m_pImage));
10715 gtk_widget_set_halign(m_pLabel, GTK_ALIGN_START);
10716 #endif
10717 gtk_widget_show(GTK_WIDGET(m_pImage));
10720 static void signalFlagsChanged(GtkToggleButton* pToggleButton, GtkStateFlags flags, gpointer widget)
10722 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10723 bool bOldChecked = flags & GTK_STATE_FLAG_CHECKED;
10724 bool bNewChecked = gtk_widget_get_state_flags(GTK_WIDGET(pToggleButton)) & GTK_STATE_FLAG_CHECKED;
10725 if (bOldChecked == bNewChecked)
10726 return;
10727 if (bOldChecked && gtk_widget_get_focus_on_click(GTK_WIDGET(pToggleButton)))
10729 // grab focus back to the toggle button if the menu was popped down
10730 gtk_widget_grab_focus(GTK_WIDGET(pToggleButton));
10732 SolarMutexGuard aGuard;
10733 pThis->signal_toggled();
10736 public:
10737 #if !GTK_CHECK_VERSION(4, 0, 0)
10738 GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10739 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
10740 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
10741 #else
10742 GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10743 : GtkInstanceWidget(GTK_WIDGET(pMenuButton), pBuilder, bTakeOwnership)
10744 , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
10745 #endif
10746 , m_pMenuButton(pMenuButton)
10747 , m_pImage(nullptr)
10748 #if !GTK_CHECK_VERSION(4, 0, 0)
10749 , m_pMenuHack(nullptr)
10750 , m_pMenuHackAlign(pMenuAlign)
10751 , m_nButtonPressSeen(true)
10752 , m_nSignalId(0)
10753 #endif
10754 , m_pPopover(nullptr)
10755 #if GTK_CHECK_VERSION(4, 0, 0)
10756 , m_aCustomBackground(GTK_WIDGET(pMenuButton))
10757 #endif
10759 #if !GTK_CHECK_VERSION(4, 0, 0)
10760 // tdf#142924 "toggled" is too late to use to populate changes to the menu,
10761 // so use "state-flag-changed" on GTK_STATE_FLAG_CHECKED instead which
10762 // happens before "toggled"
10763 g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
10764 m_nToggledSignalId = g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
10766 m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
10767 m_pImage = get_image_widget(GTK_WIDGET(m_pMenuButton));
10768 m_pBox = formatMenuButton(m_pLabel);
10769 #else
10770 GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
10771 assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
10772 m_pMenuButtonToggleButton = GTK_TOGGLE_BUTTON(pToggleButton);
10773 m_nToggledSignalId = g_signal_connect(m_pMenuButtonToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
10774 GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(pToggleButton));
10775 m_pBox = GTK_IS_BOX(pChild) ? GTK_BOX(pChild) : nullptr;
10776 m_pLabel = m_pBox ? gtk_widget_get_first_child(GTK_WIDGET(m_pBox)) : nullptr;
10777 (void)pMenuAlign;
10778 #endif
10780 #if GTK_CHECK_VERSION(4, 0, 0)
10781 gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton), "menu", m_pActionGroup);
10783 update_action_group_from_popover_model();
10784 #endif
10787 virtual void set_size_request(int nWidth, int nHeight) override
10789 // tweak the label to get a narrower size to stick
10790 if (GTK_IS_LABEL(m_pLabel))
10791 gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
10792 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
10795 virtual void set_label(const OUString& rText) override
10797 ::set_label(GTK_LABEL(m_pLabel), rText);
10800 virtual OUString get_label() const override
10802 return ::get_label(GTK_LABEL(m_pLabel));
10805 virtual void set_image(VirtualDevice* pDevice) override
10807 ensure_image_widget();
10808 #if GTK_CHECK_VERSION(4, 0, 0)
10809 picture_set_from_virtual_device(m_pImage, pDevice);
10810 #else
10811 image_set_from_virtual_device(m_pImage, pDevice);
10812 #endif
10815 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
10817 ensure_image_widget();
10818 #if GTK_CHECK_VERSION(4, 0, 0)
10819 picture_set_from_xgraphic(m_pImage, rImage);
10820 #else
10821 image_set_from_xgraphic(m_pImage, rImage);
10822 #endif
10825 #if GTK_CHECK_VERSION(4, 0, 0)
10826 virtual void set_from_icon_name(const OUString& rIconName) override
10828 ensure_image_widget();
10829 picture_set_from_icon_name(m_pImage, rIconName);
10832 virtual void set_custom_button(VirtualDevice* pDevice) override
10834 m_aCustomBackground.use_custom_content(pDevice);
10837 virtual void set_inconsistent(bool inconsistent) override
10839 if (inconsistent)
10840 gtk_widget_set_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT, false);
10841 else
10842 gtk_widget_unset_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT);
10845 virtual bool get_inconsistent() const override
10847 return gtk_widget_get_state_flags(GTK_WIDGET(m_pMenuButton)) & GTK_STATE_FLAG_INCONSISTENT;
10850 virtual void set_active(bool active) override
10852 disable_notify_events();
10853 set_inconsistent(false);
10854 if (active)
10855 gtk_menu_button_popup(m_pMenuButton);
10856 else
10857 gtk_menu_button_popdown(m_pMenuButton);
10858 enable_notify_events();
10861 virtual bool get_active() const override
10863 GtkPopover* pPopover = gtk_menu_button_get_popover(m_pMenuButton);
10864 return pPopover && gtk_widget_get_visible(GTK_WIDGET(pPopover));
10867 virtual void set_font(const vcl::Font& rFont) override
10869 m_xFont = rFont;
10870 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pMenuButton));
10871 ::set_font(pChild, rFont);
10874 virtual vcl::Font get_font() override
10876 if (m_xFont)
10877 return *m_xFont;
10878 return GtkInstanceWidget::get_font();
10880 #else
10881 virtual void set_active(bool bActive) override
10883 bool bWasActive = get_active();
10884 GtkInstanceToggleButton::set_active(bActive);
10885 if (bWasActive && !bActive && gtk_widget_get_focus_on_click(GTK_WIDGET(m_pMenuButton)))
10887 // grab focus back to the toggle button if the menu was popped down
10888 gtk_widget_grab_focus(GTK_WIDGET(m_pMenuButton));
10891 #endif
10893 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
10894 const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
10896 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
10899 virtual void insert_separator(int pos, const OUString& rId) override
10901 MenuHelper::insert_separator(pos, rId);
10904 virtual void remove_item(const OUString& rId) override
10906 MenuHelper::remove_item(rId);
10909 virtual void clear() override
10911 MenuHelper::clear_items();
10914 virtual void set_item_active(const OUString& rIdent, bool bActive) override
10916 MenuHelper::set_item_active(rIdent, bActive);
10919 virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override
10921 MenuHelper::set_item_sensitive(rIdent, bSensitive);
10924 virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override
10926 MenuHelper::set_item_label(rIdent, rLabel);
10929 virtual OUString get_item_label(const OUString& rIdent) const override
10931 return MenuHelper::get_item_label(rIdent);
10934 virtual void set_item_visible(const OUString& rIdent, bool bVisible) override
10936 MenuHelper::set_item_visible(rIdent, bVisible);
10939 virtual void signal_item_activate(const OUString& rIdent) override
10941 signal_selected(rIdent);
10944 virtual void set_popover(weld::Widget* pPopover) override
10946 GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
10947 m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
10949 #if GTK_CHECK_VERSION(4, 0, 0)
10950 gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
10951 update_action_group_from_popover_model();
10952 return;
10953 #else
10955 if (!m_pPopover)
10957 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
10958 return;
10961 m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalMenuButtonToggled), this);
10963 if (!m_pMenuHack)
10965 //under wayland a Popover will work to "escape" the parent dialog, not
10966 //so under X, so come up with this hack to use a raw GtkWindow
10967 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
10968 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay) && gtk_popover_get_constrain_to(GTK_POPOVER(m_pPopover)) == GTK_POPOVER_CONSTRAINT_NONE)
10970 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
10971 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
10972 // See writer "format, watermark" for true here. Can't interact with the replacement popover otherwise.
10973 gtk_window_set_modal(m_pMenuHack, true);
10974 gtk_window_set_resizable(m_pMenuHack, false);
10975 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
10976 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
10977 g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
10978 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
10982 if (m_pMenuHack)
10984 GtkWidget* pPlaceHolder = gtk_popover_new(GTK_WIDGET(m_pMenuButton));
10985 gtk_popover_set_transitions_enabled(GTK_POPOVER(pPlaceHolder), false);
10987 // tdf#132540 theme the unwanted popover into invisibility
10988 GtkStyleContext *pPopoverContext = gtk_widget_get_style_context(pPlaceHolder);
10989 GtkCssProvider *pProvider = gtk_css_provider_new();
10990 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; }";
10991 css_provider_load_from_data(pProvider, data, -1);
10992 gtk_style_context_add_provider(pPopoverContext, GTK_STYLE_PROVIDER(pProvider),
10993 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
10995 gtk_menu_button_set_popover(m_pMenuButton, pPlaceHolder);
10997 else
10999 gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
11000 gtk_widget_show_all(m_pPopover);
11002 #endif
11005 void set_menu(weld::Menu* pMenu);
11007 static GtkBox* formatMenuButton(GtkWidget* pLabel)
11009 // format the GtkMenuButton "manually" so we can have the dropdown image in GtkMenuButtons shown
11010 // on the right at the same time as an image is shown on the left
11011 g_object_ref(pLabel);
11012 GtkWidget* pContainer = gtk_widget_get_parent(pLabel);
11013 #if !GTK_CHECK_VERSION(4, 0, 0)
11014 gtk_container_remove(GTK_CONTAINER(pContainer), pLabel);
11015 #else
11016 gtk_box_remove(GTK_BOX(pContainer), pLabel);
11017 #endif
11019 gint nImageSpacing(2);
11020 #if !GTK_CHECK_VERSION(4, 0, 0)
11021 GtkStyleContext *pContext = gtk_widget_get_style_context(pContainer);
11022 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
11023 #endif
11024 GtkBox* pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
11026 #if !GTK_CHECK_VERSION(4, 0, 0)
11027 gtk_box_pack_start(pBox, pLabel, true, true, 0);
11028 #else
11029 gtk_widget_set_halign(pLabel, GTK_ALIGN_START);
11030 gtk_box_prepend(pBox, pLabel);
11031 #endif
11032 g_object_unref(pLabel);
11034 #if !GTK_CHECK_VERSION(4, 0, 0)
11035 if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(pContainer)))
11036 gtk_box_pack_end(pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
11037 #endif
11039 #if !GTK_CHECK_VERSION(4, 0, 0)
11040 gtk_container_add(GTK_CONTAINER(pContainer), GTK_WIDGET(pBox));
11041 #else
11042 gtk_box_prepend(GTK_BOX(pContainer), GTK_WIDGET(pBox));
11043 #endif
11044 #if !GTK_CHECK_VERSION(4, 0, 0)
11045 gtk_widget_show_all(GTK_WIDGET(pBox));
11046 #else
11047 gtk_widget_show(GTK_WIDGET(pBox));
11048 #endif
11050 return pBox;
11053 #if GTK_CHECK_VERSION(4, 0, 0)
11054 virtual void disable_notify_events() override
11056 g_signal_handler_block(m_pMenuButtonToggleButton, m_nToggledSignalId);
11057 GtkInstanceWidget::disable_notify_events();
11060 virtual void enable_notify_events() override
11062 GtkInstanceWidget::enable_notify_events();
11063 g_signal_handler_unblock(m_pMenuButtonToggleButton, m_nToggledSignalId);
11065 #endif
11067 virtual ~GtkInstanceMenuButton() override
11069 #if GTK_CHECK_VERSION(4, 0, 0)
11070 g_signal_handler_disconnect(m_pMenuButtonToggleButton, m_nToggledSignalId);
11071 gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton), "menu", nullptr);
11072 #else
11073 if (m_pMenuHack)
11075 g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
11076 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
11077 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
11079 #endif
11083 class GtkInstanceMenuToggleButton : public GtkInstanceToggleButton, public MenuHelper
11084 , public virtual weld::MenuToggleButton
11086 private:
11087 GtkBox* m_pContainer;
11088 GtkButton* m_pToggleMenuButton;
11089 GtkMenuButton* m_pMenuButton;
11090 gulong m_nMenuBtnClickedId;
11091 gulong m_nToggleStateFlagsChangedId;
11092 gulong m_nMenuBtnStateFlagsChangedId;
11094 static void signalToggleStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
11096 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
11097 // mirror togglebutton state to menubutton
11098 gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleMenuButton), gtk_widget_get_state_flags(pWidget), true);
11101 static void signalMenuBtnStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
11103 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
11104 // mirror menubutton to togglebutton, keeping depressed state of menubutton
11105 GtkStateFlags eToggleFlags = gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pToggleButton));
11106 GtkStateFlags eFlags = gtk_widget_get_state_flags(pWidget);
11107 GtkStateFlags eFinalFlags = static_cast<GtkStateFlags>((eFlags & ~GTK_STATE_FLAG_ACTIVE) |
11108 (eToggleFlags & GTK_STATE_FLAG_ACTIVE));
11109 gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleButton), eFinalFlags, true);
11112 static void signalMenuBtnClicked(GtkButton*, gpointer widget)
11114 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
11115 pThis->launch_menu();
11118 void launch_menu()
11120 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleMenuButton), gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)), true);
11121 GtkWidget* pWidget = GTK_WIDGET(m_pToggleButton);
11123 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
11124 //it during DispatchCommand, returning now to the outer loop causes the
11125 //launching PopupMenu to be destroyed, instead run the subloop here
11126 //until the gtk menu is destroyed
11127 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
11129 #if GTK_CHECK_VERSION(4, 0, 0)
11130 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
11132 g_object_ref(m_pMenu);
11133 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
11134 gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
11135 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
11136 gtk_popover_popup(GTK_POPOVER(m_pMenu));
11137 #else
11138 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
11140 #if GTK_CHECK_VERSION(3,22,0)
11141 if (gtk_check_version(3, 22, 0) == nullptr)
11143 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
11144 // before trying to launch the menu
11145 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
11146 // Fixed in GTK 3.24
11147 GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
11148 gtk_main_do_event(pKeyEvent);
11150 GdkEvent *pTriggerEvent = gtk_get_current_event();
11151 bool bEventOwnership = true;
11152 if (!pTriggerEvent)
11154 pTriggerEvent = pKeyEvent;
11155 bEventOwnership = false;
11158 gtk_menu_popup_at_widget(m_pMenu, pWidget, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
11159 if (bEventOwnership)
11160 gdk_event_free(pTriggerEvent);
11162 gdk_event_free(pKeyEvent);
11164 else
11165 #endif
11167 guint nButton;
11168 guint32 nTime;
11170 //typically there is an event, and we can then distinguish if this was
11171 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
11172 //doesn't)
11173 GdkEvent *pEvent = gtk_get_current_event();
11174 if (pEvent)
11176 gdk_event_get_button(pEvent, &nButton);
11177 nTime = gdk_event_get_time(pEvent);
11178 gdk_event_free(pEvent);
11180 else
11182 nButton = 0;
11183 nTime = GtkSalFrame::GetLastInputEventTime();
11186 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
11188 #endif
11190 if (g_main_loop_is_running(pLoop))
11191 main_loop_run(pLoop);
11193 g_main_loop_unref(pLoop);
11194 g_signal_handler_disconnect(m_pMenu, nSignalId);
11196 #if GTK_CHECK_VERSION(4, 0, 0)
11197 gtk_widget_unparent(GTK_WIDGET(m_pMenu));
11198 gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
11199 g_object_unref(m_pMenu);
11200 #endif
11204 static gboolean signalMenuToggleButton(GtkWidget*, gboolean bGroupCycling, gpointer widget)
11206 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
11207 return gtk_widget_mnemonic_activate(GTK_WIDGET(pThis->m_pToggleButton), bGroupCycling);
11210 public:
11211 GtkInstanceMenuToggleButton(GtkBuilder* pMenuToggleButtonBuilder, GtkMenuButton* pMenuButton,
11212 GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
11213 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "togglebutton")),
11214 pBuilder, bTakeOwnership)
11215 #if !GTK_CHECK_VERSION(4, 0, 0)
11216 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
11217 #else
11218 , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
11219 #endif
11220 , m_pContainer(GTK_BOX(gtk_builder_get_object(pMenuToggleButtonBuilder, "box")))
11221 , m_pToggleMenuButton(GTK_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "menubutton")))
11222 , m_pMenuButton(pMenuButton)
11223 , m_nMenuBtnClickedId(g_signal_connect(m_pToggleMenuButton, "clicked", G_CALLBACK(signalMenuBtnClicked), this))
11224 , m_nToggleStateFlagsChangedId(g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalToggleStateFlagsChanged), this))
11225 , m_nMenuBtnStateFlagsChangedId(g_signal_connect(m_pToggleMenuButton, "state-flags-changed", G_CALLBACK(signalMenuBtnStateFlagsChanged), this))
11227 #if !GTK_CHECK_VERSION(4, 0, 0)
11228 GtkInstanceMenuButton::formatMenuButton(gtk_bin_get_child(GTK_BIN(m_pMenuButton)));
11229 #endif
11231 insertAsParent(GTK_WIDGET(m_pMenuButton), GTK_WIDGET(m_pContainer));
11232 gtk_widget_hide(GTK_WIDGET(m_pMenuButton));
11234 // move the first GtkMenuButton child, as created by GtkInstanceMenuButton ctor, into the GtkToggleButton
11235 // instead, leaving just the indicator behind in the GtkMenuButton
11236 #if !GTK_CHECK_VERSION(4, 0, 0)
11237 GtkWidget* pButtonBox = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
11238 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pButtonBox));
11239 int nGroup = 0;
11240 for (GList* pChild = g_list_first(pChildren); pChild && nGroup < 2; pChild = g_list_next(pChild), ++nGroup)
11242 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
11243 g_object_ref(pWidget);
11244 gtk_container_remove(GTK_CONTAINER(pButtonBox), pWidget);
11245 if (nGroup == 0)
11246 gtk_container_add(GTK_CONTAINER(m_pToggleButton), pWidget);
11247 else
11248 gtk_container_add(GTK_CONTAINER(m_pToggleMenuButton), pWidget);
11249 gtk_widget_show_all(pWidget);
11250 g_object_unref(pWidget);
11252 g_list_free(pChildren);
11253 #else
11254 GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
11255 pChild = gtk_widget_get_first_child(pChild);
11256 pChild = gtk_widget_get_first_child(pChild);
11258 g_object_ref(pChild);
11259 gtk_widget_unparent(pChild);
11260 gtk_button_set_child(GTK_BUTTON(m_pToggleButton), pChild);
11261 g_object_unref(pChild);
11262 #endif
11264 // match the GtkToggleButton relief to the GtkMenuButton
11265 #if !GTK_CHECK_VERSION(4, 0, 0)
11266 const GtkReliefStyle eStyle = gtk_button_get_relief(GTK_BUTTON(m_pMenuButton));
11267 gtk_button_set_relief(GTK_BUTTON(m_pToggleButton), eStyle);
11268 gtk_button_set_relief(GTK_BUTTON(m_pToggleMenuButton), eStyle);
11269 #else
11270 const bool bStyle = gtk_menu_button_get_has_frame(GTK_MENU_BUTTON(m_pMenuButton));
11271 gtk_button_set_has_frame(GTK_BUTTON(m_pToggleButton), bStyle);
11272 gtk_button_set_has_frame(GTK_BUTTON(m_pToggleMenuButton), bStyle);
11273 #endif
11275 // move the GtkMenuButton margins up to the new parent
11276 gtk_widget_set_margin_top(GTK_WIDGET(m_pContainer),
11277 gtk_widget_get_margin_top(GTK_WIDGET(m_pMenuButton)));
11278 gtk_widget_set_margin_bottom(GTK_WIDGET(m_pContainer),
11279 gtk_widget_get_margin_bottom(GTK_WIDGET(m_pMenuButton)));
11280 gtk_widget_set_margin_start(GTK_WIDGET(m_pContainer),
11281 gtk_widget_get_margin_start(GTK_WIDGET(m_pMenuButton)));
11282 gtk_widget_set_margin_end(GTK_WIDGET(m_pContainer),
11283 gtk_widget_get_margin_end(GTK_WIDGET(m_pMenuButton)));
11285 #if !GTK_CHECK_VERSION(4, 0, 0)
11286 gtk_menu_detach(m_pMenu);
11287 gtk_menu_attach_to_widget(m_pMenu, GTK_WIDGET(m_pToggleButton), nullptr);
11288 #else
11289 gtk_widget_insert_action_group(GTK_WIDGET(m_pContainer), "menu", m_pActionGroup);
11291 update_action_group_from_popover_model();
11292 #endif
11294 g_signal_connect(m_pContainer, "mnemonic-activate", G_CALLBACK(signalMenuToggleButton), this);
11297 virtual void disable_notify_events() override
11299 g_signal_handler_block(m_pToggleMenuButton, m_nMenuBtnClickedId);
11300 GtkInstanceToggleButton::disable_notify_events();
11303 virtual void enable_notify_events() override
11305 GtkInstanceToggleButton::enable_notify_events();
11306 g_signal_handler_unblock(m_pToggleMenuButton, m_nMenuBtnClickedId);
11309 virtual ~GtkInstanceMenuToggleButton()
11311 g_signal_handler_disconnect(m_pToggleButton, m_nToggleStateFlagsChangedId);
11312 g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnStateFlagsChangedId);
11313 g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnClickedId);
11315 #if GTK_CHECK_VERSION(4, 0, 0)
11316 GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(m_pToggleButton));
11317 g_object_ref(pChild);
11318 gtk_button_set_child(GTK_BUTTON(m_pToggleButton), nullptr);
11319 gtk_widget_unparent(pChild);
11320 gtk_widget_set_parent(pChild, GTK_WIDGET(m_pMenuButton));
11321 g_object_unref(pChild);
11322 #endif
11325 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
11326 const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
11328 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
11331 virtual void insert_separator(int pos, const OUString& rId) override
11333 MenuHelper::insert_separator(pos, rId);
11336 virtual void remove_item(const OUString& rId) override
11338 MenuHelper::remove_item(rId);
11341 virtual void clear() override
11343 MenuHelper::clear_items();
11346 virtual void set_item_active(const OUString& rIdent, bool bActive) override
11348 MenuHelper::set_item_active(rIdent, bActive);
11351 virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override
11353 MenuHelper::set_item_sensitive(rIdent, bSensitive);
11356 virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override
11358 MenuHelper::set_item_label(rIdent, rLabel);
11361 virtual OUString get_item_label(const OUString& rIdent) const override
11363 return MenuHelper::get_item_label(rIdent);
11366 virtual void set_item_visible(const OUString& rIdent, bool bVisible) override
11368 MenuHelper::set_item_visible(rIdent, bVisible);
11371 virtual void signal_item_activate(const OUString& rIdent) override
11373 signal_selected(rIdent);
11376 virtual void set_popover(weld::Widget* /*pPopover*/) override
11378 assert(false && "not implemented");
11382 class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
11384 protected:
11385 #if !GTK_CHECK_VERSION(4, 0, 0)
11386 std::vector<GtkMenuItem*> m_aExtraItems;
11387 #endif
11388 OUString m_sActivated;
11389 #if !GTK_CHECK_VERSION(4, 0, 0)
11390 MenuHelper* m_pTopLevelMenuHelper;
11391 #endif
11393 private:
11394 virtual void signal_item_activate(const OUString& rIdent) override
11396 m_sActivated = rIdent;
11397 weld::Menu::signal_activate(m_sActivated);
11400 #if !GTK_CHECK_VERSION(4, 0, 0)
11401 void clear_extras()
11403 if (m_aExtraItems.empty())
11404 return;
11405 if (m_pTopLevelMenuHelper)
11407 for (auto a : m_aExtraItems)
11408 m_pTopLevelMenuHelper->remove_from_map(a);
11410 m_aExtraItems.clear();
11412 #endif
11414 public:
11415 #if !GTK_CHECK_VERSION(4, 0, 0)
11416 GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
11417 #else
11418 GtkInstanceMenu(GtkPopoverMenu* pMenu, bool bTakeOwnership)
11419 #endif
11420 : MenuHelper(pMenu, bTakeOwnership)
11421 #if !GTK_CHECK_VERSION(4, 0, 0)
11422 , m_pTopLevelMenuHelper(nullptr)
11423 #endif
11425 g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
11426 #if !GTK_CHECK_VERSION(4, 0, 0)
11427 // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
11428 // then find that MenuButton parent so that when adding items to this
11429 // menu we can inform the MenuButton of their addition
11430 GtkMenu* pTopLevelMenu = pMenu;
11431 while (true)
11433 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
11434 if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
11435 break;
11436 GtkWidget* pParent = gtk_widget_get_parent(pAttached);
11437 if (!pParent || !GTK_IS_MENU(pParent))
11438 break;
11439 pTopLevelMenu = GTK_MENU(pParent);
11441 if (pTopLevelMenu == pMenu)
11442 return;
11444 // maybe the toplevel is a menubutton
11445 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
11446 if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
11448 void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
11449 m_pTopLevelMenuHelper = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
11451 // or maybe a menu
11452 if (!m_pTopLevelMenuHelper)
11454 void* pData = g_object_get_data(G_OBJECT(pTopLevelMenu), "g-lo-GtkInstanceMenu");
11455 m_pTopLevelMenuHelper = static_cast<GtkInstanceMenu*>(pData);
11457 #else
11458 update_action_group_from_popover_model();
11459 #endif
11462 virtual OUString popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
11464 m_sActivated.clear();
11466 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
11467 assert(pGtkWidget);
11468 GtkWidget* pWidget = pGtkWidget->getWidget();
11470 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
11471 //it during DispatchCommand, returning now to the outer loop causes the
11472 //launching PopupMenu to be destroyed, instead run the subloop here
11473 //until the gtk menu is destroyed
11474 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
11476 #if GTK_CHECK_VERSION(4, 0, 0)
11477 gtk_widget_insert_action_group(pWidget, "menu", m_pActionGroup);
11479 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
11481 GdkRectangle aRect;
11482 pWidget = getPopupRect(pWidget, rRect, aRect);
11484 GtkWidget* pOrigParent = gtk_widget_get_parent(GTK_WIDGET(m_pMenu));
11485 gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
11486 gtk_popover_set_pointing_to(GTK_POPOVER(m_pMenu), &aRect);
11487 if (ePlace == weld::Placement::Under)
11488 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
11489 else
11491 if (SwapForRTL(pWidget))
11492 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_LEFT);
11493 else
11494 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_RIGHT);
11496 gtk_popover_popup(GTK_POPOVER(m_pMenu));
11497 #else
11498 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
11500 #if GTK_CHECK_VERSION(3,22,0)
11501 if (gtk_check_version(3, 22, 0) == nullptr)
11503 GdkRectangle aRect;
11504 pWidget = getPopupRect(pWidget, rRect, aRect);
11505 gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
11507 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
11508 // before trying to launch the menu
11509 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
11510 // Fixed in GTK 3.24
11511 GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
11512 gtk_main_do_event(pKeyEvent);
11514 GdkEvent *pTriggerEvent = gtk_get_current_event();
11515 bool bEventOwnership = true;
11516 if (!pTriggerEvent)
11518 pTriggerEvent = pKeyEvent;
11519 bEventOwnership = false;
11522 bool bSwapForRTL = SwapForRTL(pWidget);
11524 if (ePlace == weld::Placement::Under)
11526 if (bSwapForRTL)
11527 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
11528 else
11529 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
11531 else
11533 if (bSwapForRTL)
11534 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
11535 else
11536 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
11538 if (bEventOwnership)
11539 gdk_event_free(pTriggerEvent);
11541 gdk_event_free(pKeyEvent);
11543 else
11544 #else
11545 (void) rRect;
11546 #endif
11548 gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
11550 guint nButton;
11551 guint32 nTime;
11553 //typically there is an event, and we can then distinguish if this was
11554 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
11555 //doesn't)
11556 GdkEvent *pEvent = gtk_get_current_event();
11557 if (pEvent)
11559 if (!gdk_event_get_button(pEvent, &nButton))
11560 nButton = 0;
11561 nTime = gdk_event_get_time(pEvent);
11562 gdk_event_free(pEvent);
11564 else
11566 nButton = 0;
11567 nTime = GtkSalFrame::GetLastInputEventTime();
11570 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
11572 #endif
11574 if (g_main_loop_is_running(pLoop))
11575 main_loop_run(pLoop);
11577 g_main_loop_unref(pLoop);
11578 g_signal_handler_disconnect(m_pMenu, nSignalId);
11580 #if GTK_CHECK_VERSION(4, 0, 0)
11581 if (!pOrigParent)
11582 gtk_widget_unparent(GTK_WIDGET(m_pMenu));
11583 else
11584 gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pOrigParent);
11586 gtk_widget_insert_action_group(pWidget, "menu", nullptr);
11587 #else
11588 gtk_menu_detach(m_pMenu);
11589 #endif
11591 return m_sActivated;
11594 virtual void set_sensitive(const OUString& rIdent, bool bSensitive) override
11596 set_item_sensitive(rIdent, bSensitive);
11599 virtual bool get_sensitive(const OUString& rIdent) const override
11601 return get_item_sensitive(rIdent);
11604 virtual void set_active(const OUString& rIdent, bool bActive) override
11606 set_item_active(rIdent, bActive);
11609 virtual bool get_active(const OUString& rIdent) const override
11611 return get_item_active(rIdent);
11614 virtual void set_visible(const OUString& rIdent, bool bShow) override
11616 set_item_visible(rIdent, bShow);
11619 virtual void set_label(const OUString& rIdent, const OUString& rLabel) override
11621 set_item_label(rIdent, rLabel);
11624 virtual OUString get_label(const OUString& rIdent) const override
11626 return get_item_label(rIdent);
11629 virtual void insert_separator(int pos, const OUString& rId) override
11631 MenuHelper::insert_separator(pos, rId);
11634 virtual void clear() override
11636 #if !GTK_CHECK_VERSION(4, 0, 0)
11637 clear_extras();
11638 #endif
11639 MenuHelper::clear_items();
11642 virtual void insert(int pos, const OUString& rId, const OUString& rStr,
11643 const OUString* pIconName, VirtualDevice* pImageSurface,
11644 const css::uno::Reference<css::graphic::XGraphic>& rGraphic,
11645 TriState eCheckRadioFalse) override
11647 #if !GTK_CHECK_VERSION(4, 0, 0)
11648 GtkWidget* pImage = nullptr;
11649 if (pIconName)
11650 pImage = image_new_from_icon_name(*pIconName);
11651 else if (pImageSurface)
11653 pImage = image_new_from_virtual_device(*pImageSurface);
11655 else if (rGraphic)
11657 pImage = image_new_from_xgraphic(rGraphic, false);
11660 GtkWidget *pItem;
11661 if (pImage)
11663 GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
11664 GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
11665 gtk_label_set_xalign(GTK_LABEL(pLabel), 0.0);
11666 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
11667 gtk_box_pack_start(pBox, pImage, false, true, 0);
11668 gtk_box_pack_start(pBox, pLabel, true, true, 0);
11669 gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
11670 gtk_widget_show_all(pItem);
11672 else
11674 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
11675 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
11678 if (eCheckRadioFalse == TRISTATE_FALSE)
11679 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
11681 ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
11682 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
11683 gtk_widget_show(pItem);
11684 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
11685 m_aExtraItems.push_back(pMenuItem);
11686 add_to_map(pMenuItem);
11687 if (m_pTopLevelMenuHelper)
11688 m_pTopLevelMenuHelper->add_to_map(pMenuItem);
11689 if (pos != -1)
11690 gtk_menu_reorder_child(m_pMenu, pItem, pos);
11691 #else
11692 SAL_WARN("vcl.gtk", "needs to be implemented for gtk4");
11693 (void)pIconName;
11694 (void)pImageSurface;
11695 (void)rGraphic;
11697 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
11699 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
11700 GMenu* pMenu = G_MENU(aSectionAndPos.first);
11701 // action with a target value ... the action name and target value are separated by a double
11702 // colon ... For example: "app.action::target"
11703 OUString sActionAndTarget;
11704 if (eCheckRadioFalse == TRISTATE_INDET)
11705 sActionAndTarget = "menu.normal." + rId + "::" + rId;
11706 else
11707 sActionAndTarget = "menu.radio." + rId + "::" + rId;
11708 g_menu_insert(pMenu, aSectionAndPos.second, MapToGtkAccelerator(rStr).getStr(), sActionAndTarget.toUtf8().getStr());
11710 assert(eCheckRadioFalse == TRISTATE_INDET); // come back to this later
11712 // TODO not redo entire group
11713 update_action_group_from_popover_model();
11716 #endif
11719 virtual OUString get_id(int pos) const override
11721 return get_item_id(pos);
11724 virtual int n_children() const override
11726 return get_n_children();
11729 virtual void set_item_help_id(const OUString& rIdent, const OUString& rHelpId) override
11731 #if !GTK_CHECK_VERSION(4, 0, 0)
11732 ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
11733 #else
11734 (void)rIdent;
11735 (void)rHelpId;
11736 #endif
11739 void remove(const OUString& rIdent) override
11741 #if !GTK_CHECK_VERSION(4, 0, 0)
11742 if (!m_aExtraItems.empty())
11744 GtkMenuItem* pMenuItem = m_aMap[rIdent];
11745 auto iter = std::find(m_aExtraItems.begin(), m_aExtraItems.end(), pMenuItem);
11746 if (iter != m_aExtraItems.end())
11748 if (m_pTopLevelMenuHelper)
11749 m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
11750 m_aExtraItems.erase(iter);
11753 #endif
11754 MenuHelper::remove_item(rIdent);
11757 virtual ~GtkInstanceMenu() override
11759 #if !GTK_CHECK_VERSION(4, 0, 0)
11760 clear_extras();
11761 #endif
11762 g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
11766 #if !GTK_CHECK_VERSION(4, 0, 0)
11767 vcl::ImageType GtkToVcl(GtkIconSize eSize)
11769 vcl::ImageType eRet;
11770 switch (eSize)
11772 #if !GTK_CHECK_VERSION(4, 0, 0)
11773 case GTK_ICON_SIZE_MENU:
11774 case GTK_ICON_SIZE_SMALL_TOOLBAR:
11775 case GTK_ICON_SIZE_BUTTON:
11776 eRet = vcl::ImageType::Size16;
11777 break;
11778 case GTK_ICON_SIZE_LARGE_TOOLBAR:
11779 eRet = vcl::ImageType::Size26;
11780 break;
11781 case GTK_ICON_SIZE_DND:
11782 case GTK_ICON_SIZE_DIALOG:
11783 eRet = vcl::ImageType::Size32;
11784 break;
11785 default:
11786 case GTK_ICON_SIZE_INVALID:
11787 eRet = vcl::ImageType::Small;
11788 break;
11789 #else
11790 case GTK_ICON_SIZE_LARGE:
11791 eRet = vcl::ImageType::Size32;
11792 break;
11793 case GTK_ICON_SIZE_NORMAL:
11794 default:
11795 eRet = vcl::ImageType::Size16;
11796 break;
11797 #endif
11799 return eRet;
11802 GtkIconSize VclToGtk(vcl::ImageType eSize)
11804 GtkIconSize eRet;
11805 #if !GTK_CHECK_VERSION(4, 0, 0)
11806 switch (eSize)
11808 case vcl::ImageType::Size16:
11809 eRet = GTK_ICON_SIZE_SMALL_TOOLBAR;
11810 break;
11811 case vcl::ImageType::Size26:
11812 eRet = GTK_ICON_SIZE_LARGE_TOOLBAR;
11813 break;
11814 case vcl::ImageType::Size32:
11815 eRet = GTK_ICON_SIZE_DIALOG;
11816 break;
11817 default:
11818 O3TL_UNREACHABLE;
11820 #else
11821 switch (eSize)
11823 case vcl::ImageType::Size26:
11824 case vcl::ImageType::Size32:
11825 eRet = GTK_ICON_SIZE_LARGE;
11826 break;
11827 case vcl::ImageType::Size16:
11828 default:
11829 eRet = GTK_ICON_SIZE_NORMAL;
11830 break;
11832 #endif
11833 return eRet;
11835 #endif
11838 void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
11840 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
11841 m_pPopover = nullptr;
11842 m_pMenu = pPopoverWidget ? pPopoverWidget->getMenu() : nullptr;
11844 #if !GTK_CHECK_VERSION(4, 0, 0)
11845 gtk_menu_button_set_popup(m_pMenuButton, GTK_WIDGET(m_pMenu));
11846 #else
11847 gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
11848 update_action_group_from_popover_model();
11849 #endif
11852 namespace {
11854 class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
11856 private:
11857 #if !GTK_CHECK_VERSION(4, 0, 0)
11858 GtkToolbar* m_pToolbar;
11859 #else
11860 GtkBox* m_pToolbar;
11861 vcl::ImageType m_eImageType;
11862 #endif
11863 GtkCssProvider *m_pMenuButtonProvider;
11865 std::map<OUString, GtkWidget*> m_aMap;
11866 std::map<OUString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
11867 std::map<OUString, bool> m_aMirroredMap;
11869 #if !GTK_CHECK_VERSION(4, 0, 0)
11870 // at the time of writing there is no gtk_menu_tool_button_set_popover available
11871 // though there will be in the future
11872 // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
11873 static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
11875 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
11877 GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
11878 *ppToggleButton = pWidget;
11880 else if (GTK_IS_CONTAINER(pWidget))
11881 gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
11884 static void find_menupeer_button(GtkWidget *pWidget, gpointer user_data)
11886 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkButton") == 0)
11888 GtkWidget **ppButton = static_cast<GtkWidget**>(user_data);
11889 *ppButton = pWidget;
11891 else if (GTK_IS_CONTAINER(pWidget))
11892 gtk_container_forall(GTK_CONTAINER(pWidget), find_menupeer_button, user_data);
11894 #endif
11896 static void collect(GtkWidget* pItem, gpointer widget)
11898 #if !GTK_CHECK_VERSION(4, 0, 0)
11899 if (!GTK_IS_TOOL_ITEM(pItem))
11900 return;
11901 #endif
11902 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
11904 GtkMenuButton* pMenuButton = nullptr;
11905 #if !GTK_CHECK_VERSION(4, 0, 0)
11906 if (GTK_IS_MENU_TOOL_BUTTON(pItem))
11907 find_menu_button(pItem, &pMenuButton);
11908 #else
11909 if (GTK_IS_MENU_BUTTON(pItem))
11910 pMenuButton = GTK_MENU_BUTTON(pItem);
11911 #endif
11913 pThis->add_to_map(pItem, pMenuButton);
11916 void add_to_map(GtkWidget* pToolItem, GtkMenuButton* pMenuButton)
11918 OUString id = ::get_buildable_id(GTK_BUILDABLE(pToolItem));
11919 m_aMap[id] = pToolItem;
11920 if (pMenuButton)
11922 m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, GTK_WIDGET(pToolItem), m_pBuilder, false);
11923 // so that, e.g. with focus initially in writer main document then
11924 // after clicking the heading menu in the writer navigator focus is
11925 // left in the main document and not in the toolbar
11926 #if !GTK_CHECK_VERSION(4, 0, 0)
11927 gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
11928 g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
11929 #else
11930 gtk_widget_set_focus_on_click(GTK_WIDGET(pMenuButton), false);
11932 GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(pMenuButton));
11933 assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
11934 g_signal_connect(pToggleButton, "toggled", G_CALLBACK(signalItemToggled), this);
11935 #endif
11937 // by default the GtkMenuButton down arrow button is as wide as
11938 // a normal button and LibreOffice's original ones are very
11939 // narrow, that assumption is fairly baked into the toolbar and
11940 // sidebar designs, try and minimize the width of the dropdown
11941 // zone.
11942 GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pMenuButton));
11944 if (!m_pMenuButtonProvider)
11946 m_pMenuButtonProvider = gtk_css_provider_new();
11947 static const gchar data[] = "* { "
11948 "padding: 0;"
11949 "margin-left: 0px;"
11950 "margin-right: 0px;"
11951 "min-width: 4px;"
11952 "}";
11953 css_provider_load_from_data(m_pMenuButtonProvider, data, -1);
11956 gtk_style_context_add_provider(pButtonContext,
11957 GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
11958 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
11960 #if !GTK_CHECK_VERSION(4, 0, 0)
11961 if (!GTK_IS_TOOL_BUTTON(pToolItem))
11962 #else
11963 if (!GTK_IS_BUTTON(pToolItem))
11964 #endif
11966 return;
11968 g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
11971 #if !GTK_CHECK_VERSION(4, 0, 0)
11972 static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
11973 #else
11974 static void signalItemClicked(GtkButton* pItem, gpointer widget)
11975 #endif
11977 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
11978 SolarMutexGuard aGuard;
11979 pThis->signal_item_clicked(pItem);
11982 #if !GTK_CHECK_VERSION(4, 0, 0)
11983 void signal_item_clicked(GtkToolButton* pItem)
11984 #else
11985 void signal_item_clicked(GtkButton* pItem)
11986 #endif
11988 signal_clicked(::get_buildable_id(GTK_BUILDABLE(pItem)));
11991 static void signalItemToggled(GtkToggleButton* pItem, gpointer widget)
11993 #if !GTK_CHECK_VERSION(4, 0, 0)
11994 ConstrainApplicationWindowPopovers(GTK_WIDGET(pItem));
11995 #endif
11996 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
11997 SolarMutexGuard aGuard;
11998 pThis->signal_item_toggled(pItem);
12001 void signal_item_toggled(GtkToggleButton* pItem)
12003 for (const auto& a : m_aMenuButtonMap)
12005 #if !GTK_CHECK_VERSION(4, 0, 0)
12006 if (a.second->getWidget() == GTK_WIDGET(pItem))
12007 #else
12008 if (a.second->getWidget() == gtk_widget_get_parent(GTK_WIDGET(pItem)))
12009 #endif
12011 signal_toggle_menu(a.first);
12012 break;
12017 #if GTK_CHECK_VERSION(4, 0, 0)
12018 static void set_item_image(GtkWidget* pItem, GtkWidget* pImage)
12020 if (GTK_IS_BUTTON(pItem))
12021 gtk_button_set_child(GTK_BUTTON(pItem), pImage);
12022 else if (GTK_IS_MENU_BUTTON(pItem))
12024 // TODO after gtk 4.6 is released require that version and drop this
12025 static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
12026 if (menu_button_set_child)
12027 menu_button_set_child(GTK_MENU_BUTTON(pItem), pImage);
12029 // versions of gtk4 > 4.2.1 might do this on their own
12030 gtk_widget_remove_css_class(pItem, "text-button");
12032 #endif
12034 #if !GTK_CHECK_VERSION(4, 0, 0)
12035 static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
12036 #else
12037 static void set_item_image(GtkWidget* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
12038 #endif
12040 GtkWidget* pImage = image_new_from_xgraphic(rIcon, bMirror);
12041 if (pImage)
12042 gtk_widget_show(pImage);
12043 #if !GTK_CHECK_VERSION(4, 0, 0)
12044 gtk_tool_button_set_icon_widget(pItem, pImage);
12045 #else
12046 set_item_image(pItem, pImage);
12047 #endif
12050 #if !GTK_CHECK_VERSION(4, 0, 0)
12051 void set_item_image(GtkToolButton* pItem, const VirtualDevice* pDevice)
12052 #else
12053 void set_item_image(GtkWidget* pItem, const VirtualDevice* pDevice)
12054 #endif
12056 GtkWidget* pImage = nullptr;
12058 if (pDevice)
12060 #if GTK_CHECK_VERSION(4, 0, 0)
12061 pImage = picture_new_from_virtual_device(*pDevice);
12062 #else
12063 pImage = image_new_from_virtual_device(*pDevice);
12064 #endif
12065 gtk_widget_show(pImage);
12068 #if !GTK_CHECK_VERSION(4, 0, 0)
12069 gtk_tool_button_set_icon_widget(pItem, pImage);
12070 #else
12071 set_item_image(pItem, pImage);
12072 #endif
12073 gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
12076 #if !GTK_CHECK_VERSION(4, 0, 0)
12077 GtkWidget* toolbar_get_nth_item(int nIndex) const
12079 return GTK_WIDGET(gtk_toolbar_get_nth_item(m_pToolbar, nIndex));
12081 #else
12082 GtkWidget* toolbar_get_nth_item(int nIndex) const
12084 int i = 0;
12085 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
12086 pChild; pChild = gtk_widget_get_next_sibling(pChild))
12088 if (i == nIndex)
12089 return pChild;
12090 ++i;
12092 return nullptr;
12094 #endif
12095 public:
12096 #if !GTK_CHECK_VERSION(4, 0, 0)
12097 GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12098 #else
12099 GtkInstanceToolbar(GtkBox* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12100 #endif
12101 : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
12102 , m_pToolbar(pToolbar)
12103 #if GTK_CHECK_VERSION(4, 0, 0)
12104 , m_eImageType(vcl::ImageType::Size16)
12105 #endif
12106 , m_pMenuButtonProvider(nullptr)
12108 #if GTK_CHECK_VERSION(4, 0, 0)
12109 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pToolbar));
12110 pChild; pChild = gtk_widget_get_next_sibling(pChild))
12112 collect(pChild, this);
12114 #else
12115 gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
12116 #endif
12119 void disable_item_notify_events()
12121 for (auto& a : m_aMap)
12123 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
12127 void enable_item_notify_events()
12129 for (auto& a : m_aMap)
12131 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
12135 virtual void set_item_sensitive(const OUString& rIdent, bool bSensitive) override
12137 disable_item_notify_events();
12138 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
12139 enable_item_notify_events();
12142 virtual bool get_item_sensitive(const OUString& rIdent) const override
12144 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
12147 virtual void set_item_visible(const OUString& rIdent, bool bVisible) override
12149 disable_item_notify_events();
12150 gtk_widget_set_visible(GTK_WIDGET(m_aMap[rIdent]), bVisible);
12151 enable_item_notify_events();
12154 virtual void set_item_help_id(const OUString& rIdent, const OUString& rHelpId) override
12156 ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
12159 virtual bool get_item_visible(const OUString& rIdent) const override
12161 return gtk_widget_get_visible(GTK_WIDGET(m_aMap.find(rIdent)->second));
12164 virtual void set_item_active(const OUString& rIdent, bool bActive) override
12166 disable_item_notify_events();
12168 GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
12170 #if !GTK_CHECK_VERSION(4, 0, 0)
12171 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
12172 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
12173 else
12175 GtkButton* pButton = nullptr;
12176 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
12177 // to emulate one
12178 find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
12179 if (pButton)
12181 auto eState = gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & ~GTK_STATE_FLAG_CHECKED;
12182 if (bActive)
12183 eState |= GTK_STATE_FLAG_CHECKED;
12184 gtk_widget_set_state_flags(GTK_WIDGET(pButton), static_cast<GtkStateFlags>(eState), true);
12187 #else
12188 GtkWidget* pWidget;
12189 if (GTK_IS_MENU_BUTTON(pToolButton))
12191 pWidget = gtk_widget_get_first_child(pToolButton);
12192 assert(GTK_IS_TOGGLE_BUTTON(pWidget));
12194 else
12195 pWidget = pToolButton;
12196 auto eState = gtk_widget_get_state_flags(pWidget) & ~GTK_STATE_FLAG_CHECKED;
12197 if (bActive)
12198 eState |= GTK_STATE_FLAG_CHECKED;
12199 gtk_widget_set_state_flags(pWidget, static_cast<GtkStateFlags>(eState), true);
12200 #endif
12202 enable_item_notify_events();
12205 virtual bool get_item_active(const OUString& rIdent) const override
12207 GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
12209 #if !GTK_CHECK_VERSION(4, 0, 0)
12210 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
12211 return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
12212 else
12214 GtkButton* pButton = nullptr;
12215 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
12216 // to emulate one
12217 find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
12218 if (pButton)
12220 return gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & GTK_STATE_FLAG_CHECKED;
12223 #else
12224 GtkWidget* pWidget;
12225 if (GTK_IS_MENU_BUTTON(pToolButton))
12227 pWidget = gtk_widget_get_first_child(pToolButton);
12228 assert(GTK_IS_TOGGLE_BUTTON(pWidget));
12230 else
12231 pWidget = pToolButton;
12232 return gtk_widget_get_state_flags(pWidget) & GTK_STATE_FLAG_CHECKED;
12233 #endif
12235 return false;
12238 virtual void set_menu_item_active(const OUString& rIdent, bool bActive) override
12240 disable_item_notify_events();
12242 auto aFind = m_aMenuButtonMap.find(rIdent);
12243 assert (aFind != m_aMenuButtonMap.end());
12244 aFind->second->set_active(bActive);
12246 enable_item_notify_events();
12249 virtual bool get_menu_item_active(const OUString& rIdent) const override
12251 auto aFind = m_aMenuButtonMap.find(rIdent);
12252 assert (aFind != m_aMenuButtonMap.end());
12253 return aFind->second->get_active();
12256 virtual void insert_item(int pos, const OUString& rId) override
12258 #if !GTK_CHECK_VERSION(4, 0, 0)
12259 GtkToolItem* pItem = gtk_tool_button_new(nullptr, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
12260 #else
12261 GtkWidget* pItem = gtk_button_new();
12262 #endif
12263 ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
12264 #if !GTK_CHECK_VERSION(4, 0, 0)
12265 gtk_toolbar_insert(m_pToolbar, pItem, pos);
12266 #else
12267 gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
12268 #endif
12269 gtk_widget_show(GTK_WIDGET(pItem));
12270 add_to_map(GTK_WIDGET(pItem), nullptr);
12273 virtual void insert_separator(int pos, const OUString& rId) override
12275 #if !GTK_CHECK_VERSION(4, 0, 0)
12276 GtkToolItem* pItem = gtk_separator_tool_item_new();
12277 #else
12278 GtkWidget* pItem = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
12279 #endif
12280 ::set_buildable_id(GTK_BUILDABLE(pItem), rId);
12281 #if !GTK_CHECK_VERSION(4, 0, 0)
12282 gtk_toolbar_insert(m_pToolbar, pItem, pos);
12283 #else
12284 gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
12285 #endif
12286 gtk_widget_show(GTK_WIDGET(pItem));
12289 virtual void set_item_popover(const OUString& rIdent, weld::Widget* pPopover) override
12291 m_aMenuButtonMap[rIdent]->set_popover(pPopover);
12294 virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
12296 m_aMenuButtonMap[rIdent]->set_menu(pMenu);
12299 virtual int get_n_items() const override
12301 #if !GTK_CHECK_VERSION(4, 0, 0)
12302 return gtk_toolbar_get_n_items(m_pToolbar);
12303 #else
12304 int n_items = 0;
12305 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
12306 pChild; pChild = gtk_widget_get_next_sibling(pChild))
12308 ++n_items;
12310 return n_items;
12311 #endif
12314 virtual OUString get_item_ident(int nIndex) const override
12316 auto* pItem = toolbar_get_nth_item(nIndex);
12317 return ::get_buildable_id(GTK_BUILDABLE(pItem));
12320 virtual void set_item_ident(int nIndex, const OUString& rIdent) override
12322 OUString sOldIdent(get_item_ident(nIndex));
12323 m_aMap.erase(m_aMap.find(sOldIdent));
12325 auto* pItem = toolbar_get_nth_item(nIndex);
12326 ::set_buildable_id(GTK_BUILDABLE(pItem), rIdent);
12328 // to keep the ids unique, if the new id is already in use by an item,
12329 // change the id of that item to the now unused old ident of this item
12330 auto aFind = m_aMap.find(rIdent);
12331 if (aFind != m_aMap.end())
12333 GtkWidget* pDupIdItem = aFind->second;
12334 ::set_buildable_id(GTK_BUILDABLE(pDupIdItem), sOldIdent);
12335 m_aMap[sOldIdent] = pDupIdItem;
12338 m_aMap[rIdent] = pItem;
12341 virtual void set_item_label(int nIndex, const OUString& rLabel) override
12343 auto* pItem = toolbar_get_nth_item(nIndex);
12344 #if !GTK_CHECK_VERSION(4, 0, 0)
12345 if (!GTK_IS_TOOL_BUTTON(pItem))
12346 return;
12347 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
12348 #else
12349 if (!GTK_IS_BUTTON(pItem))
12350 return;
12351 ::button_set_label(GTK_BUTTON(pItem), rLabel);
12352 #endif
12355 virtual void set_item_label(const OUString& rIdent, const OUString& rLabel) override
12357 GtkWidget* pItem = m_aMap[rIdent];
12358 #if !GTK_CHECK_VERSION(4, 0, 0)
12359 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
12360 return;
12361 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
12362 #else
12363 if (!pItem || !GTK_IS_BUTTON(pItem))
12364 return;
12365 ::button_set_label(GTK_BUTTON(pItem), rLabel);
12366 #endif
12369 OUString get_item_label(const OUString& rIdent) const override
12371 #if !GTK_CHECK_VERSION(4, 0, 0)
12372 const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
12373 #else
12374 const gchar* pText = gtk_button_get_label(GTK_BUTTON(m_aMap.find(rIdent)->second));
12375 #endif
12376 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
12379 virtual void set_item_icon_name(const OUString& rIdent, const OUString& rIconName) override
12381 GtkWidget* pItem = m_aMap[rIdent];
12382 #if !GTK_CHECK_VERSION(4, 0, 0)
12383 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
12384 return;
12385 #else
12386 if (!pItem || !GTK_IS_BUTTON(pItem))
12387 return;
12388 #endif
12390 GtkWidget* pImage = image_new_from_icon_name(rIconName);
12391 if (pImage)
12392 gtk_widget_show(pImage);
12394 #if !GTK_CHECK_VERSION(4, 0, 0)
12395 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
12396 #else
12397 gtk_button_set_child(GTK_BUTTON(pItem), pImage);
12398 // versions of gtk4 > 4.2.1 might do this on their own
12399 gtk_widget_remove_css_class(GTK_WIDGET(pItem), "text-button");
12400 #endif
12403 virtual void set_item_image_mirrored(const OUString& rIdent, bool bMirrored) override
12405 m_aMirroredMap[rIdent] = bMirrored;
12408 virtual void set_item_image(const OUString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
12410 GtkWidget* pItem = m_aMap[rIdent];
12411 auto it = m_aMirroredMap.find(rIdent);
12412 bool bMirrored = it != m_aMirroredMap.end() && it->second;
12413 #if !GTK_CHECK_VERSION(4, 0, 0)
12414 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
12415 return;
12416 set_item_image(GTK_TOOL_BUTTON(pItem), rIcon, bMirrored);
12417 #else
12418 if (!pItem)
12419 return;
12420 set_item_image(pItem, rIcon, bMirrored);
12421 #endif
12424 virtual void set_item_image(const OUString& rIdent, VirtualDevice* pDevice) override
12426 GtkWidget* pItem = m_aMap[rIdent];
12427 #if !GTK_CHECK_VERSION(4, 0, 0)
12428 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
12429 return;
12430 set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
12431 #else
12432 if (!pItem)
12433 return;
12434 set_item_image(pItem, pDevice);
12435 #endif
12438 virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
12440 auto* pItem = toolbar_get_nth_item(nIndex);
12441 #if !GTK_CHECK_VERSION(4, 0, 0)
12442 if (!GTK_IS_TOOL_BUTTON(pItem))
12443 return;
12444 set_item_image(GTK_TOOL_BUTTON(pItem), rIcon, false);
12445 #else
12446 set_item_image(pItem, rIcon, false);
12447 #endif
12450 virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
12452 auto* pItem = toolbar_get_nth_item(nIndex);
12453 gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
12456 virtual void set_item_tooltip_text(const OUString& rIdent, const OUString& rTip) override
12458 GtkWidget* pItem = GTK_WIDGET(m_aMap[rIdent]);
12459 gtk_widget_set_tooltip_text(pItem, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
12462 virtual void set_item_accessible_name(int nIndex, const OUString& rName) override
12464 GtkWidget* pItem = toolbar_get_nth_item(nIndex);
12465 #if !GTK_CHECK_VERSION(4, 0, 0)
12466 AtkObject* pAccessible = gtk_widget_get_accessible(pItem);
12467 assert(pAccessible);
12468 atk_object_set_name(pAccessible, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
12469 #else
12470 gtk_accessible_update_property(GTK_ACCESSIBLE(pItem), GTK_ACCESSIBLE_PROPERTY_LABEL,
12471 OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
12472 #endif
12475 virtual void set_item_accessible_name(const OUString& rIdent, const OUString& rName) override
12477 GtkWidget* pItem = GTK_WIDGET(m_aMap[rIdent]);
12478 #if !GTK_CHECK_VERSION(4, 0, 0)
12479 AtkObject* pAccessible = gtk_widget_get_accessible(pItem);
12480 assert(pAccessible);
12481 atk_object_set_name(pAccessible, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
12482 #else
12483 gtk_accessible_update_property(GTK_ACCESSIBLE(pItem), GTK_ACCESSIBLE_PROPERTY_LABEL,
12484 OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
12485 #endif
12488 virtual OUString get_item_tooltip_text(const OUString& rIdent) const override
12490 GtkWidget* pItem = GTK_WIDGET(m_aMap.find(rIdent)->second);
12491 const gchar* pStr = gtk_widget_get_tooltip_text(pItem);
12492 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12495 virtual vcl::ImageType get_icon_size() const override
12497 #if GTK_CHECK_VERSION(4, 0, 0)
12498 return m_eImageType;
12499 #else
12500 return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
12501 #endif
12504 virtual void set_icon_size(vcl::ImageType eType) override
12506 #if GTK_CHECK_VERSION(4, 0, 0)
12507 m_eImageType = eType;
12508 #else
12509 gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
12510 #endif
12513 virtual sal_uInt16 get_modifier_state() const override
12515 #if GTK_CHECK_VERSION(4, 0, 0)
12516 GdkDisplay* pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pToolbar));
12517 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
12518 GdkDevice* pDevice = gdk_seat_get_keyboard(pSeat);
12519 guint nState = gdk_device_get_modifier_state(pDevice);
12520 #else
12521 GdkKeymap* pKeymap = gdk_keymap_get_default();
12522 guint nState = gdk_keymap_get_modifier_state(pKeymap);
12523 #endif
12524 return GtkSalFrame::GetKeyModCode(nState);
12527 virtual int get_drop_index(const Point& rPoint) const override
12529 #if !GTK_CHECK_VERSION(4, 0, 0)
12530 return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
12531 #else
12532 GtkWidget* pToolbar = GTK_WIDGET(m_pToolbar);
12533 GtkWidget* pTarget = gtk_widget_pick(pToolbar, rPoint.X(), rPoint.Y(), GTK_PICK_DEFAULT);
12534 if (!pTarget || pTarget == pToolbar)
12535 return -1;
12536 int i = 0;
12537 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
12538 pChild; pChild = gtk_widget_get_next_sibling(pChild))
12540 if (pChild == pTarget)
12541 return i;
12542 ++i;
12544 return -1;
12545 #endif
12548 virtual bool has_focus() const override
12550 if (gtk_widget_has_focus(m_pWidget))
12551 return true;
12553 GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
12554 if (!GTK_IS_WINDOW(pTopLevel))
12555 return false;
12556 GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pTopLevel));
12557 if (!pFocus)
12558 return false;
12559 return gtk_widget_is_ancestor(pFocus, m_pWidget);
12562 virtual void grab_focus() override
12564 if (has_focus())
12565 return;
12566 gtk_widget_grab_focus(m_pWidget);
12567 #if GTK_CHECK_VERSION(4, 0, 0)
12568 bool bHasFocusChild = gtk_widget_get_focus_child(m_pWidget);
12569 #else
12570 bool bHasFocusChild = gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget));
12571 #endif
12572 if (!bHasFocusChild)
12574 if (auto* pItem = toolbar_get_nth_item(0))
12576 #if GTK_CHECK_VERSION(4, 0, 0)
12577 gtk_widget_set_focus_child(m_pWidget, GTK_WIDGET(pItem));
12578 #else
12579 gtk_container_set_focus_child(GTK_CONTAINER(m_pWidget), GTK_WIDGET(pItem));
12580 #endif
12581 bHasFocusChild = true;
12584 if (bHasFocusChild)
12586 #if GTK_CHECK_VERSION(4, 0, 0)
12587 gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
12588 #else
12589 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
12590 #endif
12594 virtual ~GtkInstanceToolbar() override
12596 for (auto& a : m_aMap)
12597 g_signal_handlers_disconnect_by_data(a.second, this);
12603 namespace {
12605 class GtkInstanceLinkButton : public GtkInstanceWidget, public virtual weld::LinkButton
12607 private:
12608 GtkLinkButton* m_pButton;
12609 gulong m_nSignalId;
12611 static bool signalActivateLink(GtkButton*, gpointer widget)
12613 GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
12614 SolarMutexGuard aGuard;
12615 return pThis->signal_activate_link();
12618 public:
12619 GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12620 : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
12621 , m_pButton(pButton)
12622 , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
12626 virtual void set_label(const OUString& rText) override
12628 ::button_set_label(GTK_BUTTON(m_pButton), rText);
12631 virtual OUString get_label() const override
12633 return ::button_get_label(GTK_BUTTON(m_pButton));
12636 virtual void set_uri(const OUString& rText) override
12638 gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12641 virtual void set_label_wrap(bool bWrap) override
12643 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pButton));
12644 ::set_label_wrap(pChild, bWrap);
12645 gtk_label_set_max_width_chars(pChild, 1);
12648 virtual OUString get_uri() const override
12650 const gchar* pStr = gtk_link_button_get_uri(m_pButton);
12651 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12654 virtual void disable_notify_events() override
12656 g_signal_handler_block(m_pButton, m_nSignalId);
12657 GtkInstanceWidget::disable_notify_events();
12660 virtual void enable_notify_events() override
12662 GtkInstanceWidget::enable_notify_events();
12663 g_signal_handler_unblock(m_pButton, m_nSignalId);
12666 virtual ~GtkInstanceLinkButton() override
12668 g_signal_handler_disconnect(m_pButton, m_nSignalId);
12674 namespace {
12676 class GtkInstanceCheckButton : public GtkInstanceWidget, public virtual weld::CheckButton
12678 private:
12679 GtkCheckButton* m_pCheckButton;
12680 gulong m_nSignalId;
12682 static void signalToggled(void*, gpointer widget)
12684 GtkInstanceCheckButton* pThis = static_cast<GtkInstanceCheckButton*>(widget);
12685 SolarMutexGuard aGuard;
12686 pThis->signal_toggled();
12689 public:
12690 GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12691 : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
12692 , m_pCheckButton(pButton)
12693 , m_nSignalId(g_signal_connect(m_pCheckButton, "toggled", G_CALLBACK(signalToggled), this))
12697 virtual void set_active(bool active) override
12699 disable_notify_events();
12700 #if GTK_CHECK_VERSION(4, 0, 0)
12701 gtk_check_button_set_inconsistent(m_pCheckButton, false);
12702 gtk_check_button_set_active(m_pCheckButton, active);
12703 #else
12704 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), false);
12705 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pCheckButton), active);
12706 #endif
12707 enable_notify_events();
12710 virtual bool get_active() const override
12712 #if GTK_CHECK_VERSION(4, 0, 0)
12713 return gtk_check_button_get_active(m_pCheckButton);
12714 #else
12715 return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pCheckButton));
12716 #endif
12719 virtual void set_inconsistent(bool inconsistent) override
12721 #if GTK_CHECK_VERSION(4, 0, 0)
12722 gtk_check_button_set_inconsistent(m_pCheckButton, inconsistent);
12723 #else
12724 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), inconsistent);
12725 #endif
12728 virtual bool get_inconsistent() const override
12730 #if GTK_CHECK_VERSION(4, 0, 0)
12731 return gtk_check_button_get_inconsistent(m_pCheckButton);
12732 #else
12733 return gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton));
12734 #endif
12737 virtual void set_label(const OUString& rText) override
12739 #if GTK_CHECK_VERSION(4, 0, 0)
12740 gtk_check_button_set_label(m_pCheckButton, MapToGtkAccelerator(rText).getStr());
12741 #else
12742 ::button_set_label(GTK_BUTTON(m_pCheckButton), rText);
12743 #endif
12746 virtual OUString get_label() const override
12748 #if GTK_CHECK_VERSION(4, 0, 0)
12749 const gchar* pStr = gtk_check_button_get_label(m_pCheckButton);
12750 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12751 #else
12752 return ::button_get_label(GTK_BUTTON(m_pCheckButton));
12753 #endif
12756 virtual void set_label_wrap(bool bWrap) override
12758 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pCheckButton));
12759 ::set_label_wrap(pChild, bWrap);
12762 virtual void disable_notify_events() override
12764 g_signal_handler_block(m_pCheckButton, m_nSignalId);
12765 GtkInstanceWidget::disable_notify_events();
12768 virtual void enable_notify_events() override
12770 GtkInstanceWidget::enable_notify_events();
12771 g_signal_handler_unblock(m_pCheckButton, m_nSignalId);
12774 virtual ~GtkInstanceCheckButton() override
12776 g_signal_handler_disconnect(m_pCheckButton, m_nSignalId);
12780 class GtkInstanceRadioButton : public GtkInstanceCheckButton, public virtual weld::RadioButton
12782 public:
12783 #if GTK_CHECK_VERSION(4, 0, 0)
12784 GtkInstanceRadioButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12785 : GtkInstanceCheckButton(pButton, pBuilder, bTakeOwnership)
12786 #else
12787 GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12788 : GtkInstanceCheckButton(GTK_CHECK_BUTTON(pButton), pBuilder, bTakeOwnership)
12789 #endif
12796 namespace {
12798 class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
12800 private:
12801 GtkScale* m_pScale;
12802 gulong m_nValueChangedSignalId;
12804 static void signalValueChanged(GtkScale*, gpointer widget)
12806 GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
12807 SolarMutexGuard aGuard;
12808 pThis->signal_value_changed();
12811 public:
12812 GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12813 : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
12814 , m_pScale(pScale)
12815 , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
12819 virtual void disable_notify_events() override
12821 g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
12822 GtkInstanceWidget::disable_notify_events();
12825 virtual void enable_notify_events() override
12827 GtkInstanceWidget::enable_notify_events();
12828 g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
12831 virtual void set_value(int value) override
12833 disable_notify_events();
12834 gtk_range_set_value(GTK_RANGE(m_pScale), value);
12835 enable_notify_events();
12838 virtual void set_range(int min, int max) override
12840 disable_notify_events();
12841 gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
12842 enable_notify_events();
12845 virtual void set_increments(int step, int page) override
12847 disable_notify_events();
12848 gtk_range_set_increments(GTK_RANGE(m_pScale), step, page);
12849 enable_notify_events();
12852 virtual void get_increments(int& step, int& page) const override
12854 GtkAdjustment* pAdjustment = gtk_range_get_adjustment(GTK_RANGE(m_pScale));
12855 step = gtk_adjustment_get_step_increment(pAdjustment);
12856 page = gtk_adjustment_get_page_increment(pAdjustment);
12859 virtual int get_value() const override
12861 return gtk_range_get_value(GTK_RANGE(m_pScale));
12864 virtual ~GtkInstanceScale() override
12866 g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
12870 class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
12872 private:
12873 GtkProgressBar* m_pProgressBar;
12875 public:
12876 GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12877 : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
12878 , m_pProgressBar(pProgressBar)
12882 virtual void set_percentage(int value) override
12884 gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
12887 virtual OUString get_text() const override
12889 const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
12890 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
12891 return sRet;
12894 virtual void set_text(const OUString& rText) override
12896 gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12900 class GtkInstanceLevelBar : public GtkInstanceWidget, public virtual weld::LevelBar
12902 private:
12903 GtkLevelBar* m_pLevelBar;
12905 public:
12906 GtkInstanceLevelBar(GtkLevelBar* pLevelBar, GtkInstanceBuilder* pBuilder,
12907 bool bTakeOwnership)
12908 : GtkInstanceWidget(GTK_WIDGET(pLevelBar), pBuilder, bTakeOwnership)
12909 , m_pLevelBar(pLevelBar)
12913 virtual void set_percentage(double fPercentage) override
12915 gtk_level_bar_set_value(m_pLevelBar, fPercentage / 100.0);
12919 class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
12921 private:
12922 GtkSpinner* m_pSpinner;
12924 public:
12925 GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12926 : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
12927 , m_pSpinner(pSpinner)
12931 virtual void start() override
12933 gtk_spinner_start(m_pSpinner);
12936 virtual void stop() override
12938 gtk_spinner_stop(m_pSpinner);
12942 class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
12944 private:
12945 GtkImage* m_pImage;
12947 public:
12948 GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12949 : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
12950 , m_pImage(pImage)
12954 virtual void set_from_icon_name(const OUString& rIconName) override
12956 image_set_from_icon_name(m_pImage, rIconName);
12959 virtual void set_image(VirtualDevice* pDevice) override
12961 image_set_from_virtual_device(m_pImage, pDevice);
12964 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
12966 image_set_from_xgraphic(m_pImage, rImage);
12970 #if GTK_CHECK_VERSION(4, 0, 0)
12971 class GtkInstancePicture: public GtkInstanceWidget, public virtual weld::Image
12973 private:
12974 GtkPicture* m_pPicture;
12976 public:
12977 GtkInstancePicture(GtkPicture* pPicture, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12978 : GtkInstanceWidget(GTK_WIDGET(pPicture), pBuilder, bTakeOwnership)
12979 , m_pPicture(pPicture)
12981 gtk_picture_set_can_shrink(m_pPicture, true);
12984 virtual void set_from_icon_name(const OUString& rIconName) override
12986 picture_set_from_icon_name(m_pPicture, rIconName);
12989 virtual void set_image(VirtualDevice* pDevice) override
12991 picture_set_from_virtual_device(m_pPicture, pDevice);
12994 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rPicture) override
12996 picture_set_from_xgraphic(m_pPicture, rPicture);
12999 #endif
13001 class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
13003 private:
13004 GtkCalendar* m_pCalendar;
13005 #if GTK_CHECK_VERSION(4, 0, 0)
13006 GtkEventController* m_pKeyController;
13007 #endif
13008 gulong m_nDaySelectedSignalId;
13009 gulong m_nDaySelectedDoubleClickSignalId;
13010 gulong m_nKeyPressEventSignalId;
13011 #if !GTK_CHECK_VERSION(4, 0, 0)
13012 gulong m_nButtonPressEventSignalId;
13013 #endif
13015 static void signalDaySelected(GtkCalendar*, gpointer widget)
13017 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
13018 SolarMutexGuard aGuard;
13019 pThis->signal_selected();
13022 static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
13024 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
13025 SolarMutexGuard aGuard;
13026 pThis->signal_activated();
13029 bool signal_key_press(guint nKeyVal)
13031 if (nKeyVal == GDK_KEY_Return || nKeyVal == GDK_KEY_KP_Enter)
13033 SolarMutexGuard aGuard;
13034 signal_activated();
13035 return true;
13037 return false;
13040 #if GTK_CHECK_VERSION(4, 0, 0)
13041 static gboolean signalKeyPress(GtkEventControllerKey*, guint nKeyVal, guint /*nKeyCode*/, GdkModifierType, gpointer widget)
13043 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
13044 return pThis->signal_key_press(nKeyVal);
13046 #else
13047 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
13049 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
13050 return pThis->signal_key_press(pEvent->keyval);
13052 #endif
13054 #if !GTK_CHECK_VERSION(4, 0, 0)
13055 static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
13057 // don't let button press get to parent window, for the case of the
13058 // ImplCFieldFloatWin floating window belonging to CalendarField where
13059 // the click on the calendar continues to the parent GtkWindow and
13060 // closePopup is called by GtkSalFrame::signalButton because the click
13061 // window isn't that of the floating parent GtkWindow
13062 return true;
13064 #endif
13066 public:
13067 GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
13068 : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
13069 , m_pCalendar(pCalendar)
13070 #if GTK_CHECK_VERSION(4, 0, 0)
13071 , m_pKeyController(gtk_event_controller_key_new())
13072 #endif
13073 , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
13074 , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
13075 #if GTK_CHECK_VERSION(4, 0, 0)
13076 , m_nKeyPressEventSignalId(g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this))
13077 #else
13078 , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
13079 , m_nButtonPressEventSignalId(g_signal_connect_after(pCalendar, "button-press-event", G_CALLBACK(signalButton), this))
13080 #endif
13082 #if GTK_CHECK_VERSION(4, 0, 0)
13083 gtk_widget_add_controller(GTK_WIDGET(m_pCalendar), m_pKeyController);
13084 #endif
13087 virtual void set_date(const Date& rDate) override
13089 if (!rDate.IsValidAndGregorian())
13090 return;
13092 disable_notify_events();
13093 #if GTK_CHECK_VERSION(4, 0, 0)
13094 GDateTime* pDateTime = g_date_time_new_local(rDate.GetYear(), rDate.GetMonth(), rDate.GetDay(), 0, 0, 0);
13095 gtk_calendar_select_day(m_pCalendar, pDateTime);
13096 g_date_time_unref(pDateTime);
13097 #else
13098 gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
13099 gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
13100 #endif
13101 enable_notify_events();
13104 virtual Date get_date() const override
13106 #if GTK_CHECK_VERSION(4, 0, 0)
13107 GDateTime* pDateTime = gtk_calendar_get_date(m_pCalendar);
13108 Date aDate(g_date_time_get_day_of_month(pDateTime),
13109 g_date_time_get_month(pDateTime),
13110 g_date_time_get_year(pDateTime));
13111 g_date_time_unref(pDateTime);
13112 return aDate;
13113 #else
13114 guint year, month, day;
13115 gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
13116 return Date(day, month + 1, year);
13117 #endif
13120 virtual void disable_notify_events() override
13122 g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
13123 g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
13124 GtkInstanceWidget::disable_notify_events();
13127 virtual void enable_notify_events() override
13129 GtkInstanceWidget::enable_notify_events();
13130 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
13131 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
13134 virtual ~GtkInstanceCalendar() override
13136 #if GTK_CHECK_VERSION(4, 0, 0)
13137 g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
13138 #else
13139 g_signal_handler_disconnect(m_pCalendar, m_nButtonPressEventSignalId);
13140 g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
13141 #endif
13142 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
13143 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
13149 namespace
13151 // CSS nodes: entry[.flat][.warning][.error]
13152 void set_widget_css_message_type(GtkWidget* pWidget, weld::EntryMessageType eType)
13154 #if GTK_CHECK_VERSION(4, 0, 0)
13155 gtk_widget_remove_css_class(pWidget, "error");
13156 gtk_widget_remove_css_class(pWidget, "warning");
13157 #else
13158 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(pWidget);
13159 gtk_style_context_remove_class(pWidgetContext, "error");
13160 gtk_style_context_remove_class(pWidgetContext, "warning");
13161 #endif
13163 switch (eType)
13165 case weld::EntryMessageType::Normal:
13166 break;
13167 case weld::EntryMessageType::Warning:
13168 #if GTK_CHECK_VERSION(4, 0, 0)
13169 gtk_widget_add_css_class(pWidget, "warning");
13170 #else
13171 gtk_style_context_add_class(pWidgetContext, "warning");
13172 #endif
13173 break;
13174 case weld::EntryMessageType::Error:
13175 #if GTK_CHECK_VERSION(4, 0, 0)
13176 gtk_widget_add_css_class(pWidget, "error");
13177 #else
13178 gtk_style_context_add_class(pWidgetContext, "error");
13179 #endif
13180 break;
13184 void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
13186 set_widget_css_message_type(GTK_WIDGET(pEntry), eType);
13187 switch (eType)
13189 case weld::EntryMessageType::Normal:
13190 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
13191 break;
13192 case weld::EntryMessageType::Warning:
13193 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
13194 break;
13195 case weld::EntryMessageType::Error:
13196 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
13197 break;
13202 namespace
13205 class GtkInstanceEditable : public GtkInstanceWidget, public virtual weld::Entry
13207 protected:
13208 GtkEditable* m_pEditable;
13209 GtkWidget* m_pDelegate;
13210 WidgetFont m_aCustomFont;
13211 private:
13212 gulong m_nChangedSignalId;
13213 gulong m_nInsertTextSignalId;
13214 gulong m_nCursorPosSignalId;
13215 gulong m_nSelectionPosSignalId;
13216 gulong m_nActivateSignalId;
13218 static void signalChanged(GtkEditable*, gpointer widget)
13220 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
13221 SolarMutexGuard aGuard;
13222 pThis->signal_changed();
13225 static void signalInsertText(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength,
13226 gint* position, gpointer widget)
13228 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
13229 SolarMutexGuard aGuard;
13230 pThis->signal_insert_text(pEditable, pNewText, nNewTextLength, position);
13233 void signal_insert_text(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength, gint* position)
13235 if (!m_aInsertTextHdl.IsSet())
13236 return;
13237 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
13238 const bool bContinue = m_aInsertTextHdl.Call(sText);
13239 if (bContinue && !sText.isEmpty())
13241 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
13242 g_signal_handlers_block_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
13243 gtk_editable_insert_text(pEditable, sFinalText.getStr(), sFinalText.getLength(), position);
13244 g_signal_handlers_unblock_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
13246 g_signal_stop_emission_by_name(pEditable, "insert-text");
13249 static void signalCursorPosition(void*, GParamSpec*, gpointer widget)
13251 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
13252 pThis->signal_cursor_position();
13255 static void signalActivate(void*, gpointer widget)
13257 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
13258 pThis->signal_activate();
13261 virtual void ensureMouseEventWidget() override
13263 // The GtkEntry is sufficient to get mouse events without an intermediate GtkEventBox
13264 if (!m_pMouseEventBox)
13265 m_pMouseEventBox = m_pDelegate;
13268 protected:
13270 virtual void signal_activate()
13272 if (m_aActivateHdl.IsSet())
13274 SolarMutexGuard aGuard;
13275 if (m_aActivateHdl.Call(*this))
13276 g_signal_stop_emission_by_name(m_pDelegate, "activate");
13280 PangoAttrList* get_attributes()
13282 #if GTK_CHECK_VERSION(4, 0, 0)
13283 return gtk_text_get_attributes(GTK_TEXT(m_pDelegate));
13284 #else
13285 return gtk_entry_get_attributes(GTK_ENTRY(m_pDelegate));
13286 #endif
13289 void set_attributes(PangoAttrList* pAttrs)
13291 #if GTK_CHECK_VERSION(4, 0, 0)
13292 gtk_text_set_attributes(GTK_TEXT(m_pDelegate), pAttrs);
13293 #else
13294 gtk_entry_set_attributes(GTK_ENTRY(m_pDelegate), pAttrs);
13295 #endif
13298 public:
13299 GtkInstanceEditable(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
13300 : GtkInstanceWidget(pWidget, pBuilder, bTakeOwnership)
13301 , m_pEditable(GTK_EDITABLE(pWidget))
13302 #if GTK_CHECK_VERSION(4, 0, 0)
13303 , m_pDelegate(GTK_WIDGET(gtk_editable_get_delegate(m_pEditable)))
13304 #else
13305 , m_pDelegate(pWidget)
13306 #endif
13307 , m_aCustomFont(m_pWidget)
13308 , m_nChangedSignalId(g_signal_connect(m_pEditable, "changed", G_CALLBACK(signalChanged), this))
13309 , m_nInsertTextSignalId(g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalInsertText), this))
13310 , m_nCursorPosSignalId(g_signal_connect(m_pEditable, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
13311 , m_nSelectionPosSignalId(g_signal_connect(m_pEditable, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
13312 , m_nActivateSignalId(g_signal_connect(m_pDelegate, "activate", G_CALLBACK(signalActivate), this))
13316 virtual void set_text(const OUString& rText) override
13318 disable_notify_events();
13319 #if GTK_CHECK_VERSION(4, 0, 0)
13320 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
13321 #else
13322 gtk_entry_set_text(GTK_ENTRY(m_pDelegate), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
13323 #endif
13324 enable_notify_events();
13327 virtual OUString get_text() const override
13329 #if GTK_CHECK_VERSION(4, 0, 0)
13330 const gchar* pText = gtk_editable_get_text(m_pEditable);
13331 #else
13332 const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pDelegate));
13333 #endif
13334 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
13335 return sRet;
13338 virtual void set_width_chars(int nChars) override
13340 disable_notify_events();
13341 #if GTK_CHECK_VERSION(4, 0, 0)
13342 gtk_editable_set_width_chars(m_pEditable, nChars);
13343 gtk_editable_set_max_width_chars(m_pEditable, nChars);
13344 #else
13345 gtk_entry_set_width_chars(GTK_ENTRY(m_pDelegate), nChars);
13346 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pDelegate), nChars);
13347 #endif
13348 enable_notify_events();
13351 virtual int get_width_chars() const override
13353 #if GTK_CHECK_VERSION(4, 0, 0)
13354 return gtk_editable_get_width_chars(m_pEditable);
13355 #else
13356 return gtk_entry_get_width_chars(GTK_ENTRY(m_pDelegate));
13357 #endif
13360 virtual void set_max_length(int nChars) override
13362 disable_notify_events();
13363 #if GTK_CHECK_VERSION(4, 0, 0)
13364 gtk_text_set_max_length(GTK_TEXT(m_pDelegate), nChars);
13365 #else
13366 gtk_entry_set_max_length(GTK_ENTRY(m_pDelegate), nChars);
13367 #endif
13368 enable_notify_events();
13371 virtual void select_region(int nStartPos, int nEndPos) override
13373 disable_notify_events();
13374 gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
13375 enable_notify_events();
13378 bool get_selection_bounds(int& rStartPos, int& rEndPos) override
13380 return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
13383 virtual void replace_selection(const OUString& rText) override
13385 disable_notify_events();
13386 gtk_editable_delete_selection(m_pEditable);
13387 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
13388 gint position = gtk_editable_get_position(m_pEditable);
13389 gtk_editable_insert_text(m_pEditable, sText.getStr(), sText.getLength(),
13390 &position);
13391 enable_notify_events();
13394 virtual void set_position(int nCursorPos) override
13396 disable_notify_events();
13397 gtk_editable_set_position(m_pEditable, nCursorPos);
13398 enable_notify_events();
13401 virtual int get_position() const override
13403 return gtk_editable_get_position(m_pEditable);
13406 virtual void set_editable(bool bEditable) override
13408 gtk_editable_set_editable(m_pEditable, bEditable);
13411 virtual bool get_editable() const override
13413 return gtk_editable_get_editable(m_pEditable);
13416 virtual void set_overwrite_mode(bool bOn) override
13418 #if GTK_CHECK_VERSION(4, 0, 0)
13419 gtk_text_set_overwrite_mode(GTK_TEXT(m_pDelegate), bOn);
13420 #else
13421 gtk_entry_set_overwrite_mode(GTK_ENTRY(m_pDelegate), bOn);
13422 #endif
13425 virtual bool get_overwrite_mode() const override
13427 #if GTK_CHECK_VERSION(4, 0, 0)
13428 return gtk_text_get_overwrite_mode(GTK_TEXT(m_pDelegate));
13429 #else
13430 return gtk_entry_get_overwrite_mode(GTK_ENTRY(m_pDelegate));
13431 #endif
13434 virtual void set_message_type(weld::EntryMessageType eType) override
13436 #if GTK_CHECK_VERSION(4, 0, 0)
13437 if (!GTK_IS_ENTRY(m_pDelegate))
13439 ::set_widget_css_message_type(m_pDelegate, eType);
13440 return;
13442 #endif
13443 ::set_entry_message_type(GTK_ENTRY(m_pDelegate), eType);
13446 virtual void disable_notify_events() override
13448 g_signal_handler_block(m_pDelegate, m_nActivateSignalId);
13449 g_signal_handler_block(m_pEditable, m_nSelectionPosSignalId);
13450 g_signal_handler_block(m_pEditable, m_nCursorPosSignalId);
13451 g_signal_handler_block(m_pEditable, m_nInsertTextSignalId);
13452 g_signal_handler_block(m_pEditable, m_nChangedSignalId);
13453 GtkInstanceWidget::disable_notify_events();
13456 virtual void enable_notify_events() override
13458 GtkInstanceWidget::enable_notify_events();
13459 g_signal_handler_unblock(m_pEditable, m_nChangedSignalId);
13460 g_signal_handler_unblock(m_pEditable, m_nInsertTextSignalId);
13461 g_signal_handler_unblock(m_pEditable, m_nCursorPosSignalId);
13462 g_signal_handler_unblock(m_pEditable, m_nSelectionPosSignalId);
13463 g_signal_handler_unblock(m_pDelegate, m_nActivateSignalId);
13466 virtual vcl::Font get_font() override
13468 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
13469 return *pFont;
13470 return GtkInstanceWidget::get_font();
13473 void set_font_color(const Color& rColor) override
13475 PangoAttrList* pOrigList = get_attributes();
13476 if (rColor == COL_AUTO && !pOrigList) // nothing to do
13477 return;
13479 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_INVALID};
13481 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
13482 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
13484 if (rColor != COL_AUTO)
13485 pango_attr_list_insert(pAttrs, pango_attr_foreground_new(rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0));
13487 set_attributes(pAttrs);
13488 pango_attr_list_unref(pAttrs);
13489 pango_attr_list_unref(pRemovedAttrs);
13492 void fire_signal_changed()
13494 signal_changed();
13497 virtual void cut_clipboard() override
13499 #if GTK_CHECK_VERSION(4, 0, 0)
13500 gtk_widget_activate_action(m_pDelegate, "cut.clipboard", nullptr);
13501 #else
13502 gtk_editable_cut_clipboard(m_pEditable);
13503 #endif
13506 virtual void copy_clipboard() override
13508 #if GTK_CHECK_VERSION(4, 0, 0)
13509 gtk_widget_activate_action(m_pDelegate, "copy.clipboard", nullptr);
13510 #else
13511 gtk_editable_copy_clipboard(m_pEditable);
13512 #endif
13515 virtual void paste_clipboard() override
13517 #if GTK_CHECK_VERSION(4, 0, 0)
13518 gtk_widget_activate_action(m_pDelegate, "paste.clipboard", nullptr);
13519 #else
13520 gtk_editable_paste_clipboard(m_pEditable);
13521 #endif
13524 virtual void set_placeholder_text(const OUString& rText) override
13526 #if GTK_CHECK_VERSION(4, 0, 0)
13527 gtk_text_set_placeholder_text(GTK_TEXT(m_pDelegate), rText.toUtf8().getStr());
13528 #else
13529 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pDelegate), rText.toUtf8().getStr());
13530 #endif
13533 virtual void grab_focus() override
13535 if (has_focus())
13536 return;
13537 #if GTK_CHECK_VERSION(4, 0, 0)
13538 gtk_text_grab_focus_without_selecting(GTK_TEXT(m_pDelegate));
13539 #else
13540 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(m_pDelegate));
13541 #endif
13544 virtual void set_alignment(TxtAlign eXAlign) override
13546 gfloat xalign = 0;
13547 switch (eXAlign)
13549 case TxtAlign::Left:
13550 xalign = 0.0;
13551 break;
13552 case TxtAlign::Center:
13553 xalign = 0.5;
13554 break;
13555 case TxtAlign::Right:
13556 xalign = 1.0;
13557 break;
13559 #if GTK_CHECK_VERSION(4, 0, 0)
13560 gtk_editable_set_alignment(m_pEditable, xalign);
13561 #else
13562 gtk_entry_set_alignment(GTK_ENTRY(m_pDelegate), xalign);
13563 #endif
13566 virtual ~GtkInstanceEditable() override
13568 g_signal_handler_disconnect(m_pDelegate, m_nActivateSignalId);
13569 g_signal_handler_disconnect(m_pEditable, m_nSelectionPosSignalId);
13570 g_signal_handler_disconnect(m_pEditable, m_nCursorPosSignalId);
13571 g_signal_handler_disconnect(m_pEditable, m_nInsertTextSignalId);
13572 g_signal_handler_disconnect(m_pEditable, m_nChangedSignalId);
13576 class GtkInstanceEntry : public GtkInstanceEditable
13578 private:
13579 #if !GTK_CHECK_VERSION(4, 0, 0)
13580 GtkEntry* m_pEntry;
13581 GtkOverlay* m_pPlaceHolderReplacement;
13582 GtkLabel* m_pPlaceHolderLabel;
13583 gulong m_nEntryFocusInSignalId;
13584 gulong m_nEntryFocusOutSignalId;
13585 gulong m_nEntryTextLengthSignalId;
13586 gulong m_nEntryScrollOffsetSignalId;
13587 guint m_nUpdatePlaceholderReplacementIdle;
13589 static gboolean do_update_placeholder_replacement(gpointer widget)
13591 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
13592 pThis->update_placeholder_replacement();
13593 return false;
13596 void update_placeholder_replacement()
13598 m_nUpdatePlaceholderReplacementIdle = 0;
13600 const char* placeholder_text = gtk_entry_get_placeholder_text(m_pEntry);
13601 const bool bShow = placeholder_text && !gtk_entry_get_text_length(m_pEntry) &&
13602 gtk_widget_has_focus(GTK_WIDGET(m_pEntry));
13603 if (bShow)
13605 GdkRectangle text_area;
13606 gtk_entry_get_text_area(m_pEntry, &text_area);
13607 gint x;
13608 gtk_entry_get_layout_offsets(m_pEntry, &x, nullptr);
13609 gtk_widget_set_margin_start(GTK_WIDGET(m_pPlaceHolderLabel), x);
13610 gtk_widget_set_margin_end(GTK_WIDGET(m_pPlaceHolderLabel), x);
13611 gtk_label_set_text(m_pPlaceHolderLabel, placeholder_text);
13612 gtk_widget_show(GTK_WIDGET(m_pPlaceHolderLabel));
13614 else
13615 gtk_widget_hide(GTK_WIDGET(m_pPlaceHolderLabel));
13618 void launch_update_placeholder_replacement()
13620 // do it in the next event cycle so the GtkEntry has done its layout
13621 // and gtk_entry_get_layout_offsets returns the right results
13622 if (m_nUpdatePlaceholderReplacementIdle)
13623 return;
13624 // G_PRIORITY_LOW so gtk's idles are run before this
13625 m_nUpdatePlaceholderReplacementIdle = g_idle_add_full(G_PRIORITY_LOW, do_update_placeholder_replacement, this, nullptr);
13628 static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
13630 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
13631 pThis->launch_update_placeholder_replacement();
13632 return false;
13635 static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
13637 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
13638 pThis->launch_update_placeholder_replacement();
13639 return false;
13642 static void signalEntryTextLength(void*, GParamSpec*, gpointer widget)
13644 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
13645 pThis->launch_update_placeholder_replacement();
13648 static void signalEntryScrollOffset(void*, GParamSpec*, gpointer widget)
13650 // this property affects the x-position of the text area
13651 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
13652 pThis->launch_update_placeholder_replacement();
13655 #endif
13657 public:
13658 GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
13659 : GtkInstanceEditable(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
13660 #if !GTK_CHECK_VERSION(4, 0, 0)
13661 , m_pEntry(pEntry)
13662 , m_pPlaceHolderReplacement(nullptr)
13663 , m_pPlaceHolderLabel(nullptr)
13664 , m_nEntryFocusInSignalId(0)
13665 , m_nEntryFocusOutSignalId(0)
13666 , m_nEntryTextLengthSignalId(0)
13667 , m_nEntryScrollOffsetSignalId(0)
13668 , m_nUpdatePlaceholderReplacementIdle(0)
13669 #endif
13671 #if !GTK_CHECK_VERSION(4, 0, 0)
13672 // tdf#150810 fake getting placeholders visible even when GtkEntry has focus in gtk3.
13673 // In gtk4 this works out of the box, for gtk3 fake it by having a GtkLabel in an
13674 // overlay and show that label if the placeholder would be shown if there was
13675 // no focus
13676 const char* pPlaceHolderText = gtk_entry_get_placeholder_text(m_pEntry);
13677 if (pPlaceHolderText ? strlen(pPlaceHolderText) : 0)
13679 m_pPlaceHolderReplacement = GTK_OVERLAY(gtk_overlay_new());
13680 m_pPlaceHolderLabel = GTK_LABEL(gtk_label_new(nullptr));
13682 GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pEntry));
13683 GdkRGBA fg = { 0.5, 0.5, 0.5, 0.0 };
13684 gtk_style_context_lookup_color(pStyleContext, "placeholder_text_color", &fg);
13686 auto red = std::clamp(fg.red * 65535 + 0.5, 0.0, 65535.0);
13687 auto green = std::clamp(fg.green * 65535 + 0.5, 0.0, 65535.0);
13688 auto blue = std::clamp(fg.blue * 65535 + 0.5, 0.0, 65535.0);
13690 PangoAttribute *pAttr = pango_attr_foreground_new(red, green, blue);
13691 pAttr->start_index = 0;
13692 pAttr->end_index = G_MAXINT;
13693 PangoAttrList* pAttrList = pango_attr_list_new();
13694 pango_attr_list_insert(pAttrList, pAttr);
13695 gtk_label_set_attributes(m_pPlaceHolderLabel, pAttrList);
13696 pango_attr_list_unref(pAttrList);
13698 // The GtkEntry will have the placeholder as the text to analyze here, assumes there is no initial text, just placeholder
13699 const bool bRTL = PANGO_DIRECTION_RTL == pango_context_get_base_dir(pango_layout_get_context(gtk_entry_get_layout(m_pEntry)));
13700 SAL_WARN_IF(gtk_entry_get_text_length(m_pEntry), "vcl.gtk", "don't have a placeholder set, but also initial text");
13701 gtk_label_set_xalign(m_pPlaceHolderLabel, bRTL ? 1.0 : 0.0);
13703 gtk_overlay_add_overlay(m_pPlaceHolderReplacement, GTK_WIDGET(m_pPlaceHolderLabel));
13704 insertAsParent(GTK_WIDGET(m_pEntry), GTK_WIDGET(m_pPlaceHolderReplacement));
13705 m_nEntryFocusInSignalId = g_signal_connect_after(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
13706 m_nEntryFocusOutSignalId = g_signal_connect_after(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
13707 m_nEntryTextLengthSignalId = g_signal_connect(m_pEntry, "notify::text-length", G_CALLBACK(signalEntryTextLength), this);
13708 m_nEntryScrollOffsetSignalId = g_signal_connect(m_pEntry, "notify::scroll-offset", G_CALLBACK(signalEntryScrollOffset), this);
13710 #endif
13713 virtual void set_font(const vcl::Font& rFont) override
13715 m_aCustomFont.use_custom_font(&rFont, u"entry");
13718 #if !GTK_CHECK_VERSION(4, 0, 0)
13720 virtual void show() override
13722 GtkInstanceEditable::show();
13723 if (m_pPlaceHolderReplacement)
13724 gtk_widget_show(GTK_WIDGET(m_pPlaceHolderReplacement));
13727 virtual void hide() override
13729 if (m_pPlaceHolderReplacement)
13730 gtk_widget_hide(GTK_WIDGET(m_pPlaceHolderReplacement));
13731 GtkInstanceEditable::hide();
13734 virtual ~GtkInstanceEntry() override
13736 if (m_nUpdatePlaceholderReplacementIdle)
13737 g_source_remove(m_nUpdatePlaceholderReplacementIdle);
13738 if (m_nEntryFocusInSignalId)
13739 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
13740 if (m_nEntryFocusOutSignalId)
13741 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
13742 if (m_nEntryTextLengthSignalId)
13743 g_signal_handler_disconnect(m_pEntry, m_nEntryTextLengthSignalId);
13744 if (m_nEntryScrollOffsetSignalId)
13745 g_signal_handler_disconnect(m_pEntry, m_nEntryScrollOffsetSignalId);
13747 #endif
13752 namespace
13755 struct Search
13757 OString str;
13758 int index;
13759 int col;
13760 Search(std::u16string_view rText, int nCol)
13761 : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
13762 , index(-1)
13763 , col(nCol)
13768 gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
13770 Search* search = static_cast<Search*>(data);
13771 gchar *pStr = nullptr;
13772 gtk_tree_model_get(model, iter, search->col, &pStr, -1);
13773 bool found = strcmp(pStr, search->str.getStr()) == 0;
13774 if (found)
13776 gint depth;
13777 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
13778 search->index = indices[depth-1];
13780 g_free(pStr);
13781 return found;
13784 void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, std::u16string_view rText, const OUString* pIconName, const VirtualDevice* pDevice)
13786 if (!pIconName && !pDevice)
13788 gtk_list_store_insert_with_values(pListStore, &iter, pos,
13789 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
13790 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
13791 -1);
13793 else
13795 if (pIconName)
13797 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
13799 gtk_list_store_insert_with_values(pListStore, &iter, pos,
13800 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
13801 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
13802 2, pixbuf,
13803 -1);
13805 if (pixbuf)
13806 g_object_unref(pixbuf);
13808 else
13810 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
13812 Size aSize(pDevice->GetOutputSizePixel());
13813 cairo_surface_t* target = cairo_surface_create_similar(surface,
13814 cairo_surface_get_content(surface),
13815 aSize.Width(),
13816 aSize.Height());
13818 cairo_t* cr = cairo_create(target);
13819 cairo_set_source_surface(cr, surface, 0, 0);
13820 cairo_paint(cr);
13821 cairo_destroy(cr);
13823 gtk_list_store_insert_with_values(pListStore, &iter, pos,
13824 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
13825 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
13826 3, target,
13827 -1);
13828 cairo_surface_destroy(target);
13834 namespace
13836 gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
13838 comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
13839 gchar* pName1;
13840 gchar* pName2;
13841 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
13842 gint sort_column_id(0);
13843 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
13844 gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
13845 gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
13846 gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
13847 OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
13848 g_free(pName1);
13849 g_free(pName2);
13850 return ret;
13853 int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
13855 GtkTreeIter iter;
13856 if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
13857 return -1;
13859 const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
13860 int nRet = nStartRow;
13863 gchar* pStr;
13864 gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
13865 OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
13866 g_free(pStr);
13867 const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
13868 if (bMatch)
13869 return nRet;
13870 ++nRet;
13871 } while (gtk_tree_model_iter_next(pTreeModel, &iter));
13873 return -1;
13876 struct GtkInstanceTreeIter : public weld::TreeIter
13878 GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
13880 if (pOrig)
13881 iter = pOrig->iter;
13882 else
13883 memset(&iter, 0, sizeof(iter));
13885 GtkInstanceTreeIter(const GtkTreeIter& rOrig)
13887 memcpy(&iter, &rOrig, sizeof(iter));
13889 virtual bool equal(const TreeIter& rOther) const override
13891 return memcmp(&iter, &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
13893 GtkTreeIter iter;
13896 class GtkInstanceTreeView;
13900 static GtkInstanceTreeView* g_DragSource;
13902 namespace {
13904 struct CompareGtkTreePath
13906 bool operator()(const GtkTreePath* lhs, const GtkTreePath* rhs) const
13908 return gtk_tree_path_compare(lhs, rhs) < 0;
13912 int get_height_row(GtkTreeView* pTreeView, GList* pColumns)
13914 gint nMaxRowHeight = 0;
13915 for (GList* pEntry = g_list_first(pColumns); pEntry; pEntry = g_list_next(pEntry))
13917 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
13918 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
13919 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
13921 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
13922 gint nRowHeight;
13923 gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(pTreeView), nullptr, &nRowHeight);
13924 nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
13926 g_list_free(pRenderers);
13928 return nMaxRowHeight;
13931 int get_height_row_separator(GtkTreeView* pTreeView)
13933 // gtk4: _TREE_VIEW_VERTICAL_SEPARATOR define in gtk/gtktreeview.c
13934 gint nVerticalSeparator = 2;
13935 #if !GTK_CHECK_VERSION(4, 0, 0)
13936 gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
13937 #else
13938 (void)pTreeView;
13939 #endif
13940 return nVerticalSeparator;
13943 int get_height_rows(GtkTreeView* pTreeView, GList* pColumns, int nRows)
13945 gint nMaxRowHeight = get_height_row(pTreeView, pColumns);
13946 gint nVerticalSeparator = get_height_row_separator(pTreeView);
13947 return (nMaxRowHeight * nRows) + (nVerticalSeparator * nRows) / 2;
13950 #if !GTK_CHECK_VERSION(4, 0, 0)
13951 int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
13953 return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
13955 #endif
13957 tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePath* pPath)
13959 tools::Rectangle aRet;
13961 GdkRectangle aRect;
13962 for (GList* pEntry = g_list_last(pColumns); pEntry; pEntry = g_list_previous(pEntry))
13964 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
13965 gtk_tree_view_get_cell_area(pTreeView, pPath, pColumn, &aRect);
13966 aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height));
13969 return aRet;
13972 struct GtkTreeRowReferenceDeleter
13974 void operator()(GtkTreeRowReference* p) const
13976 gtk_tree_row_reference_free(p);
13980 bool separator_function(const GtkTreePath* path, const std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>>& rSeparatorRows)
13982 bool bFound = false;
13983 for (auto& a : rSeparatorRows)
13985 GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get());
13986 if (seppath)
13988 bFound = gtk_tree_path_compare(path, seppath) == 0;
13989 gtk_tree_path_free(seppath);
13991 if (bFound)
13992 break;
13994 return bFound;
13997 void tree_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
13999 va_list args;
14001 va_start(args, pIter);
14002 gtk_tree_store_set_valist(GTK_TREE_STORE(pTreeModel), pIter, args);
14003 va_end(args);
14006 void list_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
14008 va_list args;
14010 va_start(args, pIter);
14011 gtk_list_store_set_valist(GTK_LIST_STORE(pTreeModel), pIter, args);
14012 va_end(args);
14015 void tree_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
14016 gint nTextCol, const gchar* pText,
14017 gint nIdCol, const gchar* pId)
14019 gtk_tree_store_insert_with_values(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPos,
14020 nTextCol, pText, nIdCol, pId, -1);
14023 void list_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
14024 gint nTextCol, const gchar* pText,
14025 gint nIdCol, const gchar* pId)
14027 assert(!pParent); (void)pParent;
14028 gtk_list_store_insert_with_values(GTK_LIST_STORE(pTreeModel), pIter, nPos,
14029 nTextCol, pText, nIdCol, pId, -1);
14032 void tree_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
14034 gtk_tree_store_prepend(GTK_TREE_STORE(pTreeModel), pIter, pParent);
14037 void list_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
14039 assert(!pParent); (void)pParent;
14040 gtk_list_store_prepend(GTK_LIST_STORE(pTreeModel), pIter);
14043 void tree_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
14045 gtk_tree_store_insert(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPosition);
14048 void list_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
14050 assert(!pParent); (void)pParent;
14051 gtk_list_store_insert(GTK_LIST_STORE(pTreeModel), pIter, nPosition);
14054 void tree_store_clear(GtkTreeModel* pTreeModel)
14056 gtk_tree_store_clear(GTK_TREE_STORE(pTreeModel));
14059 void list_store_clear(GtkTreeModel* pTreeModel)
14061 gtk_list_store_clear(GTK_LIST_STORE(pTreeModel));
14064 bool tree_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
14066 return gtk_tree_store_remove(GTK_TREE_STORE(pTreeModel), pIter);
14069 bool list_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
14071 return gtk_list_store_remove(GTK_LIST_STORE(pTreeModel), pIter);
14074 void tree_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
14076 gtk_tree_store_swap(GTK_TREE_STORE(pTreeModel), pIter1, pIter2);
14079 void list_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
14081 gtk_list_store_swap(GTK_LIST_STORE(pTreeModel), pIter1, pIter2);
14084 void tree_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
14086 gtk_tree_store_set_value(GTK_TREE_STORE(pTreeModel), pIter, nColumn, pValue);
14089 void list_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
14091 gtk_list_store_set_value(GTK_LIST_STORE(pTreeModel), pIter, nColumn, pValue);
14094 int promote_arg(bool bArg)
14096 return static_cast<int>(bArg);
14099 class GtkInstanceTreeView : public GtkInstanceWidget, public virtual weld::TreeView
14101 private:
14102 GtkTreeView* m_pTreeView;
14103 GtkTreeModel* m_pTreeModel;
14105 typedef void(*setterFnc)(GtkTreeModel*, GtkTreeIter*, ...);
14106 setterFnc m_Setter;
14108 typedef void(*insertWithValuesFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint, gint, const gchar*, gint, const gchar*);
14109 insertWithValuesFnc m_InsertWithValues;
14111 typedef void(*insertFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint);
14112 insertFnc m_Insert;
14114 typedef void(*prependFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
14115 prependFnc m_Prepend;
14117 typedef void(*clearFnc)(GtkTreeModel*);
14118 clearFnc m_Clear;
14120 typedef bool(*removeFnc)(GtkTreeModel*, GtkTreeIter*);
14121 removeFnc m_Remove;
14123 typedef void(*swapFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
14124 swapFnc m_Swap;
14126 typedef void(*setValueFnc)(GtkTreeModel*, GtkTreeIter*, gint, GValue*);
14127 setValueFnc m_SetValue;
14129 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
14130 GList *m_pColumns;
14131 std::vector<gulong> m_aColumnSignalIds;
14132 // map from toggle column to toggle visibility column
14133 std::map<int, int> m_aToggleVisMap;
14134 // map from toggle column to tristate column
14135 std::map<int, int> m_aToggleTriStateMap;
14136 // map from text column to text weight column
14137 std::map<int, int> m_aWeightMap;
14138 // map from text column to sensitive column
14139 std::map<int, int> m_aSensitiveMap;
14140 // map from text column to indent column
14141 std::map<int, int> m_aIndentMap;
14142 // map from text column to text align column
14143 std::map<int, int> m_aAlignMap;
14144 // currently expanding parent that logically, but not currently physically,
14145 // contain placeholders
14146 o3tl::sorted_vector<GtkTreePath*, CompareGtkTreePath> m_aExpandingPlaceHolderParents;
14147 // which rows are separators (rare)
14148 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
14149 std::vector<GtkSortType> m_aSavedSortTypes;
14150 std::vector<int> m_aSavedSortColumns;
14151 bool m_bWorkAroundBadDragRegion;
14152 bool m_bInDrag;
14153 bool m_bChangedByMouse;
14154 gint m_nTextCol;
14155 gint m_nTextView;
14156 gint m_nImageCol;
14157 gint m_nExpanderToggleCol;
14158 gint m_nExpanderImageCol;
14159 gint m_nIdCol;
14160 int m_nPendingVAdjustment;
14161 gulong m_nChangedSignalId;
14162 gulong m_nRowActivatedSignalId;
14163 gulong m_nTestExpandRowSignalId;
14164 gulong m_nTestCollapseRowSignalId;
14165 gulong m_nVAdjustmentChangedSignalId;
14166 gulong m_nRowDeletedSignalId;
14167 gulong m_nRowInsertedSignalId;
14168 #if !GTK_CHECK_VERSION(4, 0, 0)
14169 gulong m_nPopupMenuSignalId;
14170 gulong m_nKeyPressSignalId;
14171 gulong m_nCrossingSignalid;
14172 #endif
14173 gulong m_nQueryTooltipSignalId;
14174 GtkAdjustment* m_pVAdjustment;
14175 ImplSVEvent* m_pChangeEvent;
14177 DECL_LINK(async_signal_changed, void*, void);
14179 void launch_signal_changed()
14181 //tdf#117991 selection change is sent before the focus change, and focus change
14182 //is what will cause a spinbutton that currently has the focus to set its contents
14183 //as the spin button value. So any LibreOffice callbacks on
14184 //signal-change would happen before the spinbutton value-change occurs.
14185 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
14186 //has been processed
14187 if (m_pChangeEvent)
14188 Application::RemoveUserEvent(m_pChangeEvent);
14190 #if !GTK_CHECK_VERSION(4, 0, 0)
14191 GdkEvent *pEvent = gtk_get_current_event();
14192 m_bChangedByMouse = pEvent && categorizeEvent(pEvent) == VclInputFlags::MOUSE;
14193 if (pEvent)
14194 gdk_event_free(pEvent);
14195 #else
14196 //TODO maybe iterate over gtk_widget_observe_controllers looking for a motion controller
14197 #endif
14199 m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
14202 static void signalChanged(GtkTreeView*, gpointer widget)
14204 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14205 pThis->launch_signal_changed();
14208 void handle_row_activated()
14210 if (signal_row_activated())
14211 return;
14212 GtkInstanceTreeIter aIter(nullptr);
14213 if (!get_cursor(&aIter))
14214 return;
14215 if (gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter))
14216 get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
14219 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
14221 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14222 SolarMutexGuard aGuard;
14223 pThis->handle_row_activated();
14226 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
14228 return m_aPopupMenuHdl.Call(rCEvt);
14231 void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
14232 const OUString* pIconName, const VirtualDevice* pDevice)
14234 m_InsertWithValues(m_pTreeModel, &iter, const_cast<GtkTreeIter*>(parent), pos,
14235 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
14236 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr());
14238 if (pIconName)
14240 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
14241 m_Setter(m_pTreeModel, &iter, m_nImageCol, pixbuf, -1);
14242 if (pixbuf)
14243 g_object_unref(pixbuf);
14245 else if (pDevice)
14247 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
14249 Size aSize(pDevice->GetOutputSizePixel());
14250 cairo_surface_t* target = cairo_surface_create_similar(surface,
14251 cairo_surface_get_content(surface),
14252 aSize.Width(),
14253 aSize.Height());
14255 cairo_t* cr = cairo_create(target);
14256 cairo_set_source_surface(cr, surface, 0, 0);
14257 cairo_paint(cr);
14258 cairo_destroy(cr);
14260 m_Setter(m_pTreeModel, &iter, m_nImageCol, target, -1);
14261 cairo_surface_destroy(target);
14265 bool separator_function(const GtkTreePath* path)
14267 return ::separator_function(path, m_aSeparatorRows);
14270 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
14272 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14273 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
14274 bool bRet = pThis->separator_function(path);
14275 gtk_tree_path_free(path);
14276 return bRet;
14279 OUString get(const GtkTreeIter& iter, int col) const
14281 gchar* pStr;
14282 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
14283 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
14284 g_free(pStr);
14285 return sRet;
14288 OUString get(int pos, int col) const
14290 OUString sRet;
14291 GtkTreeIter iter;
14292 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14293 sRet = get(iter, col);
14294 return sRet;
14297 gint get_int(const GtkTreeIter& iter, int col) const
14299 gint nRet(-1);
14300 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
14301 return nRet;
14304 gint get_int(int pos, int col) const
14306 gint nRet(-1);
14307 GtkTreeIter iter;
14308 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14309 nRet = get_int(iter, col);
14310 gtk_tree_model_get(m_pTreeModel, &iter, col, &nRet, -1);
14311 return nRet;
14314 bool get_bool(const GtkTreeIter& iter, int col) const
14316 gboolean bRet(false);
14317 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
14318 return bRet;
14321 bool get_bool(int pos, int col) const
14323 bool bRet(false);
14324 GtkTreeIter iter;
14325 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14326 bRet = get_bool(iter, col);
14327 return bRet;
14330 void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
14332 if (col == -1)
14333 col = m_nExpanderToggleCol;
14334 else
14335 col = to_internal_model(col);
14337 if (eState == TRISTATE_INDET)
14339 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
14340 m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
14341 m_aToggleTriStateMap[col], promote_arg(true), // tristate on
14342 -1);
14344 else
14346 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
14347 m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
14348 m_aToggleTriStateMap[col], promote_arg(false), // tristate off
14349 col, promote_arg(eState == TRISTATE_TRUE), // set toggle state
14350 -1);
14354 void set(const GtkTreeIter& iter, int col, std::u16string_view rText)
14356 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
14357 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
14360 void set(int pos, int col, std::u16string_view rText)
14362 GtkTreeIter iter;
14363 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14364 set(iter, col, rText);
14367 void set(const GtkTreeIter& iter, int col, bool bOn)
14369 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, promote_arg(bOn), -1);
14372 void set(int pos, int col, bool bOn)
14374 GtkTreeIter iter;
14375 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14376 set(iter, col, bOn);
14379 void set(const GtkTreeIter& iter, int col, gint bInt)
14381 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
14384 void set(int pos, int col, gint bInt)
14386 GtkTreeIter iter;
14387 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14388 set(iter, col, bInt);
14391 void set(const GtkTreeIter& iter, int col, double fValue)
14393 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
14396 void set(int pos, int col, double fValue)
14398 GtkTreeIter iter;
14399 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14400 set(iter, col, fValue);
14403 static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
14405 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14406 return !pThis->signal_test_expand_row(*iter);
14409 static gboolean signalTestCollapseRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
14411 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14412 return !pThis->signal_test_collapse_row(*iter);
14415 bool child_is_placeholder(GtkInstanceTreeIter& rGtkIter) const
14417 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &rGtkIter.iter);
14418 bool bExpanding = m_aExpandingPlaceHolderParents.count(pPath);
14419 gtk_tree_path_free(pPath);
14420 if (bExpanding)
14421 return true;
14423 bool bPlaceHolder = false;
14424 GtkTreeIter tmp;
14425 if (gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter))
14427 rGtkIter.iter = tmp;
14428 if (get_text(rGtkIter, -1) == "<dummy>")
14430 bPlaceHolder = true;
14433 return bPlaceHolder;
14436 bool signal_test_expand_row(GtkTreeIter& iter)
14438 disable_notify_events();
14440 // if there's a preexisting placeholder child, required to make this
14441 // potentially expandable in the first place, now we remove it
14442 GtkInstanceTreeIter aIter(iter);
14443 GtkTreePath* pPlaceHolderPath = nullptr;
14444 bool bPlaceHolder = child_is_placeholder(aIter);
14445 if (bPlaceHolder)
14447 m_Remove(m_pTreeModel, &aIter.iter);
14449 pPlaceHolderPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
14450 m_aExpandingPlaceHolderParents.insert(pPlaceHolderPath);
14453 aIter.iter = iter;
14454 bool bRet = signal_expanding(aIter);
14456 if (bPlaceHolder)
14458 //expand disallowed, restore placeholder
14459 if (!bRet)
14461 GtkTreeIter subiter;
14462 OUString sDummy(u"<dummy>"_ustr);
14463 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
14465 m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
14466 gtk_tree_path_free(pPlaceHolderPath);
14469 enable_notify_events();
14470 return bRet;
14473 bool signal_test_collapse_row(const GtkTreeIter& iter)
14475 disable_notify_events();
14477 GtkInstanceTreeIter aIter(iter);
14478 bool bRet = signal_collapsing(aIter);
14480 enable_notify_events();
14481 return bRet;
14484 static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
14486 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14487 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
14488 pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
14491 void signal_cell_toggled(const gchar *path, int nCol)
14493 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
14495 // additionally set the cursor into the row the toggled element is in
14496 gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
14498 GtkTreeIter iter;
14499 gtk_tree_model_get_iter(m_pTreeModel, &iter, tree_path);
14501 gboolean bRet(false);
14502 gtk_tree_model_get(m_pTreeModel, &iter, nCol, &bRet, -1);
14503 bRet = !bRet;
14504 m_Setter(m_pTreeModel, &iter, nCol, bRet, -1);
14506 set(iter, m_aToggleTriStateMap[nCol], false);
14508 signal_toggled(iter_col(GtkInstanceTreeIter(iter), to_external_model(nCol)));
14510 gtk_tree_path_free(tree_path);
14513 DECL_LINK(async_stop_cell_editing, void*, void);
14515 static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
14517 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14518 if (!pThis->signal_cell_editing_started(path))
14519 Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
14522 bool signal_cell_editing_started(const gchar *path)
14524 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
14526 GtkInstanceTreeIter aGtkIter(nullptr);
14527 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
14528 gtk_tree_path_free(tree_path);
14530 return signal_editing_started(aGtkIter);
14533 static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
14535 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14536 pThis->signal_cell_edited(pCell, path, pNewText);
14539 static void restoreNonEditable(GObject* pCell)
14541 if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
14543 g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
14544 g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
14548 void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
14550 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
14552 GtkInstanceTreeIter aGtkIter(nullptr);
14553 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
14554 gtk_tree_path_free(tree_path);
14556 OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
14557 if (signal_editing_done(iter_string(aGtkIter, sText)))
14559 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
14560 set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
14563 restoreNonEditable(G_OBJECT(pCell));
14566 static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
14568 restoreNonEditable(G_OBJECT(pCell));
14571 void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
14573 int nIndex(0);
14574 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
14576 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14577 if (pColumn == pClickedColumn)
14579 TreeView::signal_column_clicked(nIndex);
14580 break;
14582 ++nIndex;
14586 static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
14588 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14589 pThis->signal_column_clicked(pColumn);
14592 static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
14594 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14595 pThis->signal_visible_range_changed();
14598 // The outside concept of a column maps to a gtk CellRenderer, rather than
14599 // a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
14600 // and/or a leading Image Renderer, those are considered special expander
14601 // columns and precede index 0 and can be accessed via outside index -1
14602 int to_external_model(int modelcol) const
14604 if (m_nExpanderToggleCol != -1)
14605 --modelcol;
14606 if (m_nExpanderImageCol != -1)
14607 --modelcol;
14608 return modelcol;
14611 int to_internal_model(int modelcol) const
14613 if (m_nExpanderToggleCol != -1)
14614 ++modelcol;
14615 if (m_nExpanderImageCol != -1)
14616 ++modelcol;
14617 return modelcol;
14620 void set_column_editable(int nCol, bool bEditable)
14622 nCol = to_internal_model(nCol);
14624 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
14626 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14627 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
14628 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
14630 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
14631 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
14632 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
14634 g_object_set(G_OBJECT(pCellRenderer), "editable", bEditable, "editable-set", true, nullptr);
14635 break;
14638 g_list_free(pRenderers);
14642 static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
14644 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14645 pThis->signal_model_changed();
14648 static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
14650 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14651 pThis->signal_model_changed();
14654 static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
14656 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14657 return pThis->sort_func(pModel, a, b);
14660 gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
14662 if (m_aCustomSort)
14663 return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
14664 return default_sort_func(pModel, a, b, m_xSorter.get());
14667 #if !GTK_CHECK_VERSION(4, 0, 0)
14668 bool signal_key_press(GdkEventKey* pEvent)
14670 if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
14671 return false;
14673 GtkInstanceTreeIter aIter(nullptr);
14674 if (!get_cursor(&aIter))
14675 return false;
14677 bool bHasChild = gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter);
14679 if (pEvent->keyval == GDK_KEY_Right)
14681 if (bHasChild && !get_row_expanded(aIter))
14683 expand_row(aIter);
14684 return true;
14686 return false;
14689 if (bHasChild && get_row_expanded(aIter))
14691 collapse_row(aIter);
14692 return true;
14695 if (iter_parent(aIter))
14697 unselect_all();
14698 set_cursor(aIter);
14699 select(aIter);
14700 return true;
14703 return false;
14706 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
14708 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14709 return pThis->signal_key_press(pEvent);
14711 #endif
14713 static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
14714 gboolean keyboard_tip, GtkTooltip *tooltip,
14715 gpointer widget)
14717 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14718 GtkTreeIter iter;
14719 GtkTreeView *pTreeView = pThis->m_pTreeView;
14720 GtkTreeModel *pModel = gtk_tree_view_get_model(pTreeView);
14721 GtkTreePath *pPath = nullptr;
14722 #if GTK_CHECK_VERSION(4, 0, 0)
14723 if (!gtk_tree_view_get_tooltip_context(pTreeView, x, y, keyboard_tip, &pModel, &pPath, &iter))
14724 return false;
14725 #else
14726 if (!gtk_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
14727 return false;
14728 #endif
14729 OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
14730 if (!aTooltip.isEmpty())
14732 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
14733 gtk_tree_view_set_tooltip_row(pTreeView, tooltip, pPath);
14735 gtk_tree_path_free(pPath);
14736 return !aTooltip.isEmpty();
14739 void last_child(GtkTreeModel* pModel, GtkTreeIter* result, GtkTreeIter* pParent, int nChildren) const
14741 gtk_tree_model_iter_nth_child(pModel, result, pParent, nChildren - 1);
14742 nChildren = gtk_tree_model_iter_n_children(pModel, result);
14743 if (nChildren)
14745 GtkTreeIter newparent(*result);
14746 last_child(pModel, result, &newparent, nChildren);
14750 GtkTreePath* get_path_of_last_entry(GtkTreeModel *pModel)
14752 GtkTreePath *lastpath;
14753 // find the last entry in the model for comparison
14754 int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
14755 if (!nChildren)
14756 lastpath = gtk_tree_path_new_from_indices(0, -1);
14757 else
14759 GtkTreeIter iter;
14760 last_child(pModel, &iter, nullptr, nChildren);
14761 lastpath = gtk_tree_model_get_path(pModel, &iter);
14763 return lastpath;
14766 void set_font_color(const GtkTreeIter& iter, const Color& rColor)
14768 if (rColor == COL_AUTO)
14769 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
14770 else
14772 GdkRGBA aColor{rColor.GetRed()/255.0f, rColor.GetGreen()/255.0f, rColor.GetBlue()/255.0f, 0};
14773 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
14777 int get_expander_size() const
14779 // gtk4: _TREE_VIEW_EXPANDER_SIZE define in gtk/gtktreeview.c
14780 gint nExpanderSize = 16;
14781 // gtk4: _TREE_VIEW_HORIZONTAL_SEPARATOR define in gtk/gtktreeview.c
14782 gint nHorizontalSeparator = 4;
14784 #if !GTK_CHECK_VERSION(4, 0, 0)
14785 gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
14786 "expander-size", &nExpanderSize,
14787 "horizontal-separator", &nHorizontalSeparator,
14788 nullptr);
14789 #endif
14791 return nExpanderSize + (nHorizontalSeparator/ 2);
14794 void real_vadjustment_set_value(int value)
14796 disable_notify_events();
14797 gtk_adjustment_set_value(m_pVAdjustment, value);
14798 enable_notify_events();
14801 static gboolean setAdjustmentCallback(GtkWidget*, GdkFrameClock*, gpointer widget)
14803 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14804 if (pThis->m_nPendingVAdjustment != -1)
14806 pThis->real_vadjustment_set_value(pThis->m_nPendingVAdjustment);
14807 pThis->m_nPendingVAdjustment = -1;
14809 return false;
14812 bool iter_next(weld::TreeIter& rIter, bool bOnlyExpanded) const
14814 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
14815 GtkTreeIter tmp;
14816 GtkTreeIter iter = rGtkIter.iter;
14818 bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &iter);
14819 if (ret && bOnlyExpanded && !get_row_expanded(rGtkIter))
14820 ret = false;
14821 rGtkIter.iter = tmp;
14822 if (ret)
14824 //on-demand dummy entry doesn't count
14825 if (get_text(rGtkIter, -1) == "<dummy>")
14826 return iter_next(rGtkIter, bOnlyExpanded);
14827 return true;
14830 tmp = iter;
14831 if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
14833 rGtkIter.iter = tmp;
14834 //on-demand dummy entry doesn't count
14835 if (get_text(rGtkIter, -1) == "<dummy>")
14836 return iter_next(rGtkIter, bOnlyExpanded);
14837 return true;
14839 // Move up level(s) until we find the level where the next node exists.
14840 while (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
14842 iter = tmp;
14843 if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
14845 rGtkIter.iter = tmp;
14846 //on-demand dummy entry doesn't count
14847 if (get_text(rGtkIter, -1) == "<dummy>")
14848 return iter_next(rGtkIter, bOnlyExpanded);
14849 return true;
14852 return false;
14855 #if !GTK_CHECK_VERSION(4, 0, 0)
14856 // tdf#154565 ignore the crossing event if it was triggered ultimately by a
14857 // key stroke which is likely from exiting the search box. This way we can
14858 // avoid the problem that with hover-selection that after return is used in
14859 // the search box, selecting a matching row, that during teardown of the
14860 // widget the box is hidden, and the crossing notification triggers
14861 // selection of a different row under the mouse. If needs be this could be
14862 // refined further to only happen for a specific key or other details of
14863 // the triggering event
14864 static gboolean signalCrossing(GtkWidget*, GdkEventCrossing*, gpointer)
14866 if (GdkEvent *pEvent = gtk_get_current_event())
14868 const bool bCrossingTriggeredByKeyStroke = gdk_event_get_event_type(pEvent) == GDK_KEY_PRESS;
14869 gdk_event_free(pEvent);
14870 return bCrossingTriggeredByKeyStroke;
14873 return false;
14875 #endif
14877 static gboolean search_equal_func(GtkTreeModel *model,
14878 int column,
14879 const char *key,
14880 GtkTreeIter *iter,
14881 gpointer /*user_data*/)
14883 GValue aValue = G_VALUE_INIT;
14884 gtk_tree_model_get_value(model, iter, column, &aValue);
14886 GValue aStringValue = G_VALUE_INIT;
14887 g_value_init(&aStringValue, G_TYPE_STRING);
14888 const bool fail = !g_value_transform(&aValue, &aStringValue);
14889 g_value_unset(&aValue);
14890 if (fail)
14891 return true;
14893 bool bNoMatch(true);
14894 if (const char *str = g_value_get_string(&aStringValue))
14896 const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetLocaleI18nHelper();
14897 bNoMatch = !rI18nHelper.MatchString(OUString::fromUtf8(key), OUString::fromUtf8(str));
14899 g_value_unset(&aStringValue);
14900 return bNoMatch;
14903 public:
14904 GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
14905 : GtkInstanceWidget(GTK_WIDGET(pTreeView), pBuilder, bTakeOwnership)
14906 , m_pTreeView(pTreeView)
14907 , m_pTreeModel(gtk_tree_view_get_model(m_pTreeView))
14908 , m_bWorkAroundBadDragRegion(false)
14909 , m_bInDrag(false)
14910 , m_bChangedByMouse(false)
14911 , m_nTextCol(-1)
14912 , m_nTextView(-1)
14913 , m_nImageCol(-1)
14914 , m_nExpanderToggleCol(-1)
14915 , m_nExpanderImageCol(-1)
14916 , m_nPendingVAdjustment(-1)
14917 , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
14918 G_CALLBACK(signalChanged), this))
14919 , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
14920 , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
14921 , m_nTestCollapseRowSignalId(g_signal_connect(pTreeView, "test-collapse-row", G_CALLBACK(signalTestCollapseRow), this))
14922 , m_nVAdjustmentChangedSignalId(0)
14923 #if !GTK_CHECK_VERSION(4, 0, 0)
14924 , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
14925 , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
14926 , m_nCrossingSignalid(g_signal_connect(pTreeView, "enter-notify-event", G_CALLBACK(signalCrossing), this))
14927 #endif
14928 , m_nQueryTooltipSignalId(0)
14929 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
14930 , m_pChangeEvent(nullptr)
14932 if (GTK_IS_TREE_STORE(m_pTreeModel))
14934 m_Setter = tree_store_set;
14935 m_InsertWithValues = tree_store_insert_with_values;
14936 m_Insert = tree_store_insert;
14937 m_Prepend = tree_store_prepend;
14938 m_Remove = tree_store_remove;
14939 m_Swap = tree_store_swap;
14940 m_SetValue = tree_store_set_value;
14941 m_Clear = tree_store_clear;
14943 else
14946 tdf#136559 see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2693
14947 If we only need a list and not a tree we can get a performance boost from using a ListStore
14949 assert(!gtk_tree_view_get_show_expanders(m_pTreeView) && "a liststore can only be used if no tree structure is needed");
14950 m_Setter = list_store_set;
14951 m_InsertWithValues = list_store_insert_with_values;
14952 m_Insert = list_store_insert;
14953 m_Prepend = list_store_prepend;
14954 m_Remove = list_store_remove;
14955 m_Swap = list_store_swap;
14956 m_SetValue = list_store_set_value;
14957 m_Clear = list_store_clear;
14960 /* The outside concept of a column maps to a gtk CellRenderer, rather than
14961 a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
14962 and/or a leading Image Renderer, those are considered special expander
14963 columns and precede index 0 and can be accessed via outside index -1
14965 m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
14966 int nIndex(0);
14967 int nViewColumn(0);
14968 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
14970 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14971 m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
14972 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
14973 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
14975 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
14976 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
14978 if (m_nTextCol == -1)
14980 m_nTextCol = nIndex;
14981 m_nTextView = nViewColumn;
14983 m_aWeightMap[nIndex] = -1;
14984 m_aSensitiveMap[nIndex] = -1;
14985 m_aIndentMap[nIndex] = -1;
14986 m_aAlignMap[nIndex] = -1;
14987 g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
14988 g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
14989 g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
14991 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
14993 const bool bExpander = nIndex == 0 || (nIndex == 1 && m_nExpanderImageCol == 0);
14994 if (bExpander)
14995 m_nExpanderToggleCol = nIndex;
14996 g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
14997 m_aToggleVisMap[nIndex] = -1;
14998 m_aToggleTriStateMap[nIndex] = -1;
15000 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
15002 const bool bExpander = g_list_next(pRenderer) != nullptr;
15003 if (bExpander && m_nExpanderImageCol == -1)
15004 m_nExpanderImageCol = nIndex;
15005 else if (m_nImageCol == -1)
15006 m_nImageCol = nIndex;
15008 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
15009 ++nIndex;
15011 g_list_free(pRenderers);
15012 ++nViewColumn;
15015 m_nIdCol = nIndex++;
15017 for (auto& a : m_aToggleVisMap)
15018 a.second = nIndex++;
15019 for (auto& a : m_aToggleTriStateMap)
15020 a.second = nIndex++;
15021 for (auto& a : m_aWeightMap)
15022 a.second = nIndex++;
15023 for (auto& a : m_aSensitiveMap)
15024 a.second = nIndex++;
15025 for (auto& a : m_aIndentMap)
15026 a.second = nIndex++;
15027 for (auto& a : m_aAlignMap)
15028 a.second = nIndex++;
15030 ensure_drag_begin_end();
15032 m_nRowDeletedSignalId = g_signal_connect(m_pTreeModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
15033 m_nRowInsertedSignalId = g_signal_connect(m_pTreeModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
15035 // tdf#160028 LibreOffice embeds RTL/LTR direction markers in currency strings, which defeats the
15036 // default gtk search mechanism, so switch in our one here
15037 gtk_tree_view_set_search_equal_func(m_pTreeView, search_equal_func, nullptr, nullptr);
15040 virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
15042 weld::TreeView::connect_query_tooltip(rLink);
15043 m_nQueryTooltipSignalId = g_signal_connect(m_pTreeView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
15046 virtual void columns_autosize() override
15048 gtk_tree_view_columns_autosize(m_pTreeView);
15051 virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
15053 GList* pEntry = g_list_first(m_pColumns);
15054 for (auto nWidth : rWidths)
15056 assert(pEntry && "wrong count");
15057 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
15058 gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
15059 pEntry = g_list_next(pEntry);
15063 virtual void set_column_editables(const std::vector<bool>& rEditables) override
15065 size_t nTabCount = rEditables.size();
15066 for (size_t i = 0 ; i < nTabCount; ++i)
15067 set_column_editable(i, rEditables[i]);
15070 virtual void set_centered_column(int nCol) override
15072 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
15074 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
15075 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
15076 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
15078 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
15079 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
15080 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
15082 g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
15083 break;
15086 g_list_free(pRenderers);
15090 virtual int get_column_width(int nColumn) const override
15092 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
15093 assert(pColumn && "wrong count");
15094 int nWidth = gtk_tree_view_column_get_width(pColumn);
15095 // https://github.com/exaile/exaile/issues/580
15096 // after setting fixed_width on a column and requesting width before
15097 // gtk has a chance to do its layout of the column means that the width
15098 // request hasn't come into effect
15099 if (!nWidth)
15100 nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
15101 return nWidth;
15104 virtual OUString get_column_title(int nColumn) const override
15106 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
15107 assert(pColumn && "wrong count");
15108 const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
15109 OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
15110 return sRet;
15113 virtual void set_column_title(int nColumn, const OUString& rTitle) override
15115 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
15116 assert(pColumn && "wrong count");
15117 gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
15120 virtual void set_column_custom_renderer(int nColumn, bool bEnable) override
15122 assert(n_children() == 0 && "tree must be empty");
15123 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
15124 assert(pColumn && "wrong count");
15126 GtkCellRenderer* pExpander = nullptr;
15127 GtkCellRenderer* pToggle = nullptr;
15129 // migrate existing editable setting to the new renderer
15130 gboolean is_editable(false);
15131 void* pEditCellData(nullptr);
15132 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
15133 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
15135 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
15137 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
15138 auto nCellIndex = reinterpret_cast<sal_IntPtr>(pData);
15140 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
15142 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
15143 pEditCellData = pData;
15144 break;
15146 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
15148 if (nCellIndex == m_nExpanderToggleCol)
15150 pToggle = pCellRenderer;
15151 g_object_ref(pToggle);
15154 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
15156 if (nCellIndex == m_nExpanderImageCol)
15158 pExpander = pCellRenderer;
15159 g_object_ref(pExpander);
15164 g_list_free(pRenderers);
15166 GtkCellRenderer* pRenderer;
15168 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
15169 if (pExpander)
15171 gtk_tree_view_column_pack_start(pColumn, pExpander, false);
15172 gtk_tree_view_column_add_attribute(pColumn, pExpander, "pixbuf", m_nExpanderImageCol);
15173 g_object_unref(pExpander);
15175 if (pToggle)
15177 gtk_tree_view_column_pack_start(pColumn, pToggle, false);
15178 gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
15179 gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
15180 gtk_tree_view_column_add_attribute(pColumn, pToggle, "visible", m_aToggleTriStateMap[m_nExpanderToggleCol]);
15181 g_object_unref(pToggle);
15184 if (bEnable)
15186 pRenderer = custom_cell_renderer_new();
15187 GValue value = G_VALUE_INIT;
15188 g_value_init(&value, G_TYPE_POINTER);
15189 g_value_set_pointer(&value, static_cast<gpointer>(this));
15190 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
15191 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
15192 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
15193 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
15195 else
15197 pRenderer = gtk_cell_renderer_text_new();
15198 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
15199 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
15202 if (is_editable)
15204 g_object_set(pRenderer, "editable", true, "editable-set", true, nullptr);
15205 g_object_set_data(G_OBJECT(pRenderer), "g-lo-CellIndex", pEditCellData);
15206 g_signal_connect(pRenderer, "editing-started", G_CALLBACK(signalCellEditingStarted), this);
15207 g_signal_connect(pRenderer, "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
15208 g_signal_connect(pRenderer, "edited", G_CALLBACK(signalCellEdited), this);
15212 virtual void queue_draw() override
15214 gtk_widget_queue_draw(GTK_WIDGET(m_pTreeView));
15217 virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
15218 VirtualDevice* pImageSurface,
15219 bool bChildrenOnDemand, weld::TreeIter* pRet) override
15221 disable_notify_events();
15222 GtkTreeIter iter;
15223 const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
15224 insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface);
15225 if (bChildrenOnDemand)
15227 GtkTreeIter subiter;
15228 OUString sDummy(u"<dummy>"_ustr);
15229 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
15231 if (pRet)
15233 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
15234 pGtkRetIter->iter = iter;
15236 enable_notify_events();
15239 virtual void insert_separator(int pos, const OUString& rId) override
15241 disable_notify_events();
15242 GtkTreeIter iter;
15243 if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
15244 gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
15245 insert_row(iter, nullptr, pos, &rId, nullptr, nullptr, nullptr);
15246 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
15247 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
15248 gtk_tree_path_free(pPath);
15249 enable_notify_events();
15252 virtual void set_font_color(int pos, const Color& rColor) override
15254 GtkTreeIter iter;
15255 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
15256 set_font_color(iter, rColor);
15259 virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override
15261 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15262 set_font_color(rGtkIter.iter, rColor);
15265 virtual void remove(int pos) override
15267 disable_notify_events();
15268 GtkTreeIter iter;
15269 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
15270 m_Remove(m_pTreeModel, &iter);
15271 enable_notify_events();
15274 virtual int find_text(const OUString& rText) const override
15276 Search aSearch(rText, m_nTextCol);
15277 gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
15278 return aSearch.index;
15281 virtual int find_id(const OUString& rId) const override
15283 Search aSearch(rId, m_nIdCol);
15284 gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
15285 return aSearch.index;
15288 virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
15289 const weld::TreeIter* pParent,
15290 const std::vector<int>* pFixedWidths) override
15292 GtkInstanceTreeIter* pGtkIter = const_cast<GtkInstanceTreeIter*>(static_cast<const GtkInstanceTreeIter*>(pParent));
15294 freeze();
15295 if (!pGtkIter)
15296 clear();
15297 else
15299 GtkTreeIter restore(pGtkIter->iter);
15301 if (iter_children(*pGtkIter))
15302 while (m_Remove(m_pTreeModel, &pGtkIter->iter));
15304 pGtkIter->iter = restore;
15306 GtkInstanceTreeIter aGtkIter(nullptr);
15308 if (pFixedWidths)
15309 set_column_fixed_widths(*pFixedWidths);
15311 while (nSourceCount)
15313 // tdf#125241 inserting backwards is massively faster
15314 m_Prepend(m_pTreeModel, &aGtkIter.iter, pGtkIter ? &pGtkIter->iter : nullptr);
15315 func(aGtkIter, --nSourceCount);
15318 thaw();
15321 virtual void swap(int pos1, int pos2) override
15323 disable_notify_events();
15325 GtkTreeIter iter1;
15326 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter1, nullptr, pos1);
15328 GtkTreeIter iter2;
15329 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter2, nullptr, pos2);
15331 m_Swap(m_pTreeModel, &iter1, &iter2);
15333 enable_notify_events();
15336 virtual void clear() override
15338 disable_notify_events();
15339 gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
15340 m_aSeparatorRows.clear();
15341 m_Clear(m_pTreeModel);
15342 enable_notify_events();
15345 virtual void make_sorted() override
15347 // thaw wants to restore sort state of freeze
15348 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15349 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
15350 ::comphelper::getProcessComponentContext(),
15351 Application::GetSettings().GetUILanguageTag().getLocale()));
15352 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15353 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
15354 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
15357 virtual void make_unsorted() override
15359 m_xSorter.reset();
15360 int nSortColumn;
15361 GtkSortType eSortType;
15362 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15363 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
15364 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
15367 virtual void set_sort_order(bool bAscending) override
15369 GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
15371 gint sort_column_id(0);
15372 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15373 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
15374 gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
15377 virtual bool get_sort_order() const override
15379 int nSortColumn;
15380 GtkSortType eSortType;
15382 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15383 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
15384 return nSortColumn != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID && eSortType == GTK_SORT_ASCENDING;
15387 virtual void set_sort_indicator(TriState eState, int col) override
15389 assert(col >= 0 && "cannot sort on expander column");
15391 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
15392 assert(pColumn && "wrong count");
15393 if (eState == TRISTATE_INDET)
15394 gtk_tree_view_column_set_sort_indicator(pColumn, false);
15395 else
15397 gtk_tree_view_column_set_sort_indicator(pColumn, true);
15398 GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
15399 gtk_tree_view_column_set_sort_order(pColumn, eSortType);
15403 virtual TriState get_sort_indicator(int col) const override
15405 assert(col >= 0 && "cannot sort on expander column");
15407 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
15408 if (!gtk_tree_view_column_get_sort_indicator(pColumn))
15409 return TRISTATE_INDET;
15410 return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
15413 virtual int get_sort_column() const override
15415 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15416 gint sort_column_id(0);
15417 if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
15418 return -1;
15419 return to_external_model(sort_column_id);
15422 virtual void set_sort_column(int nColumn) override
15424 if (nColumn == -1)
15426 make_unsorted();
15427 return;
15429 GtkSortType eSortType;
15430 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15431 gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
15432 int nSortCol = to_internal_model(nColumn);
15433 gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
15434 gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
15437 virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
15439 weld::TreeView::set_sort_func(func);
15440 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15441 gtk_tree_sortable_sort_column_changed(pSortable);
15444 virtual int n_children() const override
15446 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
15449 virtual int iter_n_children(const weld::TreeIter& rIter) const override
15451 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15452 return gtk_tree_model_iter_n_children(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15455 virtual void select(int pos) 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 if (pos == -1 || (pos == 0 && n_children() == 0))
15461 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
15463 else
15465 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
15466 gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
15467 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
15468 gtk_tree_path_free(path);
15470 enable_notify_events();
15473 virtual void set_cursor(int pos) override
15475 disable_notify_events();
15476 GtkTreePath* path;
15477 if (pos != -1)
15479 path = gtk_tree_path_new_from_indices(pos, -1);
15480 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
15482 else
15483 path = gtk_tree_path_new_from_indices(G_MAXINT, -1);
15484 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
15485 gtk_tree_path_free(path);
15486 enable_notify_events();
15489 virtual void scroll_to_row(int pos) override
15491 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15492 disable_notify_events();
15493 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
15494 gtk_tree_view_expand_to_path(m_pTreeView, path);
15495 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, true, 0, 0);
15496 gtk_tree_path_free(path);
15497 enable_notify_events();
15500 virtual bool is_selected(int pos) const override
15502 GtkTreeIter iter;
15503 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
15504 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
15507 virtual void unselect(int pos) override
15509 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15510 disable_notify_events();
15511 if (pos == -1 || (pos == 0 && n_children() == 0))
15513 gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
15515 else
15517 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
15518 gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
15519 gtk_tree_path_free(path);
15521 enable_notify_events();
15524 virtual std::vector<int> get_selected_rows() const override
15526 std::vector<int> aRows;
15528 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
15529 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
15531 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
15533 gint depth;
15534 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
15535 int nRow = indices[depth-1];
15537 aRows.push_back(nRow);
15539 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
15541 return aRows;
15544 virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
15546 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
15548 GtkInstanceTreeIter aGtkIter(nullptr);
15549 if (get_iter_first(aGtkIter))
15553 if (func(aGtkIter))
15554 break;
15555 } while (iter_next(aGtkIter));
15558 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
15561 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
15563 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
15565 GtkInstanceTreeIter aGtkIter(nullptr);
15567 GtkTreeModel* pModel;
15568 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
15569 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
15571 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
15572 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
15573 if (func(aGtkIter))
15574 break;
15576 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
15578 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
15581 virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
15583 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
15585 GtkTreePath* start_path;
15586 GtkTreePath* end_path;
15588 if (!gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
15590 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
15591 return;
15594 GtkInstanceTreeIter aGtkIter(nullptr);
15595 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, start_path);
15599 if (func(aGtkIter))
15600 break;
15601 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &aGtkIter.iter);
15602 bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
15603 gtk_tree_path_free(path);
15604 if (!bContinue)
15605 break;
15606 if (!iter_next(aGtkIter))
15607 break;
15608 } while(true);
15610 gtk_tree_path_free(start_path);
15611 gtk_tree_path_free(end_path);
15613 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
15616 virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
15618 weld::TreeView::connect_visible_range_changed(rLink);
15619 if (!m_nVAdjustmentChangedSignalId)
15621 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
15622 m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
15626 virtual bool is_selected(const weld::TreeIter& rIter) const override
15628 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15629 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
15632 virtual OUString get_text(int pos, int col) const override
15634 if (col == -1)
15635 col = m_nTextCol;
15636 else
15637 col = to_internal_model(col);
15638 return get(pos, col);
15641 virtual void set_text(int pos, const OUString& rText, int col) override
15643 if (col == -1)
15644 col = m_nTextCol;
15645 else
15646 col = to_internal_model(col);
15647 set(pos, col, rText);
15650 virtual TriState get_toggle(int pos, int col) const override
15652 if (col == -1)
15653 col = m_nExpanderToggleCol;
15654 else
15655 col = to_internal_model(col);
15657 const auto iter = m_aToggleTriStateMap.find(col);
15658 assert(iter != m_aToggleTriStateMap.end());
15659 if (get_bool(pos, iter->second))
15660 return TRISTATE_INDET;
15661 return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
15664 virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
15666 if (col == -1)
15667 col = m_nExpanderToggleCol;
15668 else
15669 col = to_internal_model(col);
15671 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15672 const auto iter = m_aToggleTriStateMap.find(col);
15673 assert(iter != m_aToggleTriStateMap.end());
15674 if (get_bool(rGtkIter.iter, iter->second))
15675 return TRISTATE_INDET;
15676 return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
15679 virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
15681 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15682 set_toggle(rGtkIter.iter, eState, col);
15685 virtual void set_toggle(int pos, TriState eState, int col) override
15687 GtkTreeIter iter;
15688 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
15689 set_toggle(iter, eState, col);
15692 virtual void enable_toggle_buttons(weld::ColumnToggleType eType) override
15694 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
15696 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
15697 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
15698 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
15700 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
15701 if (!GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
15702 continue;
15703 GtkCellRendererToggle* pToggle = GTK_CELL_RENDERER_TOGGLE(pCellRenderer);
15704 gtk_cell_renderer_toggle_set_radio(pToggle, eType == weld::ColumnToggleType::Radio);
15706 g_list_free(pRenderers);
15710 virtual void set_clicks_to_toggle(int /*nToggleBehavior*/) override
15714 virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override
15716 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15717 set(rGtkIter.iter, m_aIndentMap[m_nTextCol], nIndentLevel * get_expander_size());
15720 virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
15722 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15723 auto weight = bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
15724 if (col == -1)
15726 for (const auto& elem : m_aWeightMap)
15727 set(rGtkIter.iter, elem.second, weight);
15728 return;
15730 col = to_internal_model(col);
15731 set(rGtkIter.iter, m_aWeightMap[col], weight);
15734 virtual void set_text_emphasis(int pos, bool bOn, int col) override
15736 auto weight = bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
15737 if (col == -1)
15739 for (const auto& elem : m_aWeightMap)
15740 set(pos, elem.second, weight);
15741 return;
15743 col = to_internal_model(col);
15744 set(pos, m_aWeightMap[col], weight);
15747 virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
15749 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15750 col = to_internal_model(col);
15751 const auto iter = m_aWeightMap.find(col);
15752 assert(iter != m_aWeightMap.end());
15753 return get_int(rGtkIter.iter, iter->second) == PANGO_WEIGHT_BOLD;
15756 virtual bool get_text_emphasis(int pos, int col) const override
15758 col = to_internal_model(col);
15759 const auto iter = m_aWeightMap.find(col);
15760 assert(iter != m_aWeightMap.end());
15761 return get_int(pos, iter->second) == PANGO_WEIGHT_BOLD;
15764 virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override
15766 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15767 col = to_internal_model(col);
15768 set(rGtkIter.iter, m_aAlignMap[col], fAlign);
15771 virtual void set_text_align(int pos, double fAlign, int col) override
15773 col = to_internal_model(col);
15774 set(pos, m_aAlignMap[col], fAlign);
15777 using GtkInstanceWidget::set_sensitive;
15778 using GtkInstanceWidget::get_sensitive;
15780 virtual void set_sensitive(int pos, bool bSensitive, int col) override
15782 if (col == -1)
15784 for (const auto& elem : m_aSensitiveMap)
15785 set(pos, elem.second, bSensitive);
15787 else
15789 col = to_internal_model(col);
15790 set(pos, m_aSensitiveMap[col], bSensitive);
15794 virtual bool get_sensitive(int pos, int col) const override
15796 col = to_internal_model(col);
15797 const auto iter = m_aSensitiveMap.find(col);
15798 assert(iter != m_aSensitiveMap.end());
15799 return get_bool(pos, iter->second);
15802 virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
15804 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15805 if (col == -1)
15807 for (const auto& elem : m_aSensitiveMap)
15808 set(rGtkIter.iter, elem.second, bSensitive);
15810 else
15812 col = to_internal_model(col);
15813 set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
15817 virtual bool get_sensitive(const weld::TreeIter& rIter, int col) const override
15819 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15820 col = to_internal_model(col);
15821 const auto iter = m_aSensitiveMap.find(col);
15822 assert(iter != m_aSensitiveMap.end());
15823 return get_bool(rGtkIter.iter, iter->second);
15826 void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
15828 if (col == -1)
15829 col = m_nExpanderImageCol;
15830 else
15831 col = to_internal_model(col);
15832 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
15833 if (pixbuf)
15834 g_object_unref(pixbuf);
15837 void set_image(int pos, GdkPixbuf* pixbuf, int col)
15839 GtkTreeIter iter;
15840 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
15842 set_image(iter, col, pixbuf);
15846 virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
15848 set_image(pos, getPixbuf(rImage), col);
15851 virtual void set_image(int pos, const OUString& rImage, int col) override
15853 set_image(pos, getPixbuf(rImage), col);
15856 virtual void set_image(int pos, VirtualDevice& rImage, int col) override
15858 set_image(pos, getPixbuf(rImage), col);
15861 virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
15863 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15864 set_image(rGtkIter.iter, col, getPixbuf(rImage));
15867 virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
15869 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15870 set_image(rGtkIter.iter, col, getPixbuf(rImage));
15873 virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
15875 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15876 set_image(rGtkIter.iter, col, getPixbuf(rImage));
15879 virtual OUString get_id(int pos) const override
15881 return get(pos, m_nIdCol);
15884 virtual void set_id(int pos, const OUString& rId) override
15886 return set(pos, m_nIdCol, rId);
15889 virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
15891 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15893 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15895 gint depth;
15896 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
15897 int nRet = indices[depth-1];
15899 gtk_tree_path_free(path);
15901 return nRet;
15904 virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
15906 const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
15907 const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
15909 GtkTreePath* pathA = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
15910 GtkTreePath* pathB = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
15912 int nRet = gtk_tree_path_compare(pathA, pathB);
15914 gtk_tree_path_free(pathB);
15915 gtk_tree_path_free(pathA);
15917 return nRet;
15920 // by copy and delete of old copy
15921 void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
15923 int nCols = gtk_tree_model_get_n_columns(m_pTreeModel);
15924 GValue value;
15926 GtkTreeIter toiter;
15927 m_Insert(m_pTreeModel, &toiter, pGtkParentIter, nIndexInNewParent);
15929 for (int i = 0; i < nCols; ++i)
15931 memset(&value, 0, sizeof(GValue));
15932 gtk_tree_model_get_value(m_pTreeModel, &rFromIter, i, &value);
15933 m_SetValue(m_pTreeModel, &toiter, i, &value);
15934 g_value_unset(&value);
15937 GtkTreeIter tmpfromiter;
15938 if (gtk_tree_model_iter_children(m_pTreeModel, &tmpfromiter, &rFromIter))
15940 int j = 0;
15943 move_subtree(tmpfromiter, &toiter, j++);
15944 } while (gtk_tree_model_iter_next(m_pTreeModel, &tmpfromiter));
15947 m_Remove(m_pTreeModel, &rFromIter);
15950 virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
15952 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
15953 const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
15954 move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
15957 virtual int get_selected_index() const override
15959 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
15960 int nRet = -1;
15961 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
15962 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
15964 GtkTreeIter iter;
15965 GtkTreeModel* pModel;
15966 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
15968 GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
15970 gint depth;
15971 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
15972 nRet = indices[depth-1];
15974 gtk_tree_path_free(path);
15977 else
15979 auto vec = get_selected_rows();
15980 return vec.empty() ? -1 : vec[0];
15982 return nRet;
15985 bool get_selected_iterator(GtkTreeIter* pIter) const
15987 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
15988 bool bRet = false;
15989 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
15990 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
15991 bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
15992 else
15994 GtkTreeModel* pModel;
15995 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
15996 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
15998 if (pIter)
16000 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
16001 gtk_tree_model_get_iter(pModel, pIter, path);
16003 bRet = true;
16004 break;
16006 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
16008 return bRet;
16011 virtual OUString get_selected_text() const override
16013 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
16014 GtkTreeIter iter;
16015 if (get_selected_iterator(&iter))
16016 return get(iter, m_nTextCol);
16017 return OUString();
16020 virtual OUString get_selected_id() const override
16022 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
16023 GtkTreeIter iter;
16024 if (get_selected_iterator(&iter))
16025 return get(iter, m_nIdCol);
16026 return OUString();
16029 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
16031 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
16034 virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
16036 const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
16037 GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
16038 rGtkDest.iter = rGtkSource.iter;
16041 virtual bool get_selected(weld::TreeIter* pIter) const override
16043 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
16044 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
16047 virtual bool get_cursor(weld::TreeIter* pIter) const override
16049 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
16050 GtkTreePath* path;
16051 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
16052 if (pGtkIter && path)
16054 gtk_tree_model_get_iter(m_pTreeModel, &pGtkIter->iter, path);
16056 if (!path)
16057 return false;
16058 gtk_tree_path_free(path);
16059 return true;
16062 virtual int get_cursor_index() const override
16064 int nRet = -1;
16066 GtkTreePath* path;
16067 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
16068 if (path)
16070 gint depth;
16071 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
16072 nRet = indices[depth-1];
16073 gtk_tree_path_free(path);
16076 return nRet;
16079 virtual void set_cursor(const weld::TreeIter& rIter) override
16081 disable_notify_events();
16082 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16083 GtkTreeIter Iter;
16084 if (gtk_tree_model_iter_parent(m_pTreeModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
16086 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &Iter);
16087 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
16088 gtk_tree_view_expand_to_path(m_pTreeView, path);
16089 gtk_tree_path_free(path);
16091 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16092 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
16093 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
16094 gtk_tree_path_free(path);
16095 enable_notify_events();
16098 virtual bool get_iter_first(weld::TreeIter& rIter) const override
16100 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16101 return gtk_tree_model_get_iter_first(m_pTreeModel, &rGtkIter.iter);
16104 virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
16106 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16107 return gtk_tree_model_iter_next(m_pTreeModel, &rGtkIter.iter);
16110 virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
16112 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16113 return gtk_tree_model_iter_previous(m_pTreeModel, &rGtkIter.iter);
16116 virtual bool iter_next(weld::TreeIter& rIter) const override
16118 return iter_next(rIter, false);
16121 virtual bool iter_previous(weld::TreeIter& rIter) const override
16123 bool ret = false;
16124 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16125 GtkTreeIter iter = rGtkIter.iter;
16126 GtkTreeIter tmp = iter;
16127 if (gtk_tree_model_iter_previous(m_pTreeModel, &tmp))
16129 // Move down level(s) until we find the level where the last node exists.
16130 int nChildren = gtk_tree_model_iter_n_children(m_pTreeModel, &tmp);
16131 if (!nChildren)
16132 rGtkIter.iter = tmp;
16133 else
16134 last_child(m_pTreeModel, &rGtkIter.iter, &tmp, nChildren);
16135 ret = true;
16137 else
16139 // Move up level
16140 if (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
16142 rGtkIter.iter = tmp;
16143 ret = true;
16147 if (ret)
16149 //on-demand dummy entry doesn't count
16150 if (get_text(rGtkIter, -1) == "<dummy>")
16151 return iter_previous(rGtkIter);
16152 return true;
16155 return false;
16158 virtual bool iter_children(weld::TreeIter& rIter) const override
16160 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16161 GtkTreeIter tmp;
16162 bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter);
16163 rGtkIter.iter = tmp;
16164 if (ret)
16166 //on-demand dummy entry doesn't count
16167 return get_text(rGtkIter, -1) != "<dummy>";
16169 return ret;
16172 virtual bool iter_parent(weld::TreeIter& rIter) const override
16174 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16175 GtkTreeIter tmp;
16176 bool ret = gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &rGtkIter.iter);
16177 rGtkIter.iter = tmp;
16178 return ret;
16181 virtual void remove(const weld::TreeIter& rIter) override
16183 disable_notify_events();
16184 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16185 m_Remove(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16186 enable_notify_events();
16189 virtual void remove_selection() override
16191 disable_notify_events();
16193 std::vector<GtkTreeIter> aIters;
16194 GtkTreeModel* pModel;
16195 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
16196 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
16198 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
16199 aIters.emplace_back();
16200 gtk_tree_model_get_iter(pModel, &aIters.back(), path);
16202 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
16204 for (auto& iter : aIters)
16205 m_Remove(m_pTreeModel, &iter);
16207 enable_notify_events();
16210 virtual void select(const weld::TreeIter& rIter) override
16212 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16213 disable_notify_events();
16214 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16215 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
16216 enable_notify_events();
16219 virtual void scroll_to_row(const weld::TreeIter& rIter) override
16221 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16222 disable_notify_events();
16223 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16224 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16225 gtk_tree_view_expand_to_path(m_pTreeView, path);
16226 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, true, 0, 0);
16227 gtk_tree_path_free(path);
16228 enable_notify_events();
16231 virtual void unselect(const weld::TreeIter& rIter) override
16233 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16234 disable_notify_events();
16235 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16236 gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
16237 enable_notify_events();
16240 virtual int get_iter_depth(const weld::TreeIter& rIter) const override
16242 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16243 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16244 int ret = gtk_tree_path_get_depth(path) - 1;
16245 gtk_tree_path_free(path);
16246 return ret;
16249 virtual bool iter_has_child(const weld::TreeIter& rIter) const override
16251 GtkInstanceTreeIter aTempCopy(static_cast<const GtkInstanceTreeIter*>(&rIter));
16252 return iter_children(aTempCopy);
16255 virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
16257 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16258 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16259 bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
16260 gtk_tree_path_free(path);
16261 return ret;
16264 virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override
16266 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16267 GtkInstanceTreeIter aIter(&rGtkIter);
16268 return child_is_placeholder(aIter);
16271 virtual void set_children_on_demand(const weld::TreeIter& rIter, bool bChildrenOnDemand) override
16273 disable_notify_events();
16275 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16276 GtkInstanceTreeIter aPlaceHolderIter(&rGtkIter);
16278 bool bPlaceHolder = child_is_placeholder(aPlaceHolderIter);
16280 if (bChildrenOnDemand && !bPlaceHolder)
16282 GtkTreeIter subiter;
16283 OUString sDummy(u"<dummy>"_ustr);
16284 insert_row(subiter, &rGtkIter.iter, -1, nullptr, &sDummy, nullptr, nullptr);
16286 else if (!bChildrenOnDemand && bPlaceHolder)
16287 remove(aPlaceHolderIter);
16289 enable_notify_events();
16292 virtual void expand_row(const weld::TreeIter& rIter) override
16294 assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
16296 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16297 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16298 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
16299 gtk_tree_view_expand_to_path(m_pTreeView, path);
16300 gtk_tree_path_free(path);
16303 virtual void collapse_row(const weld::TreeIter& rIter) override
16305 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16306 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16307 if (gtk_tree_view_row_expanded(m_pTreeView, path))
16308 gtk_tree_view_collapse_row(m_pTreeView, path);
16309 gtk_tree_path_free(path);
16312 virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
16314 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16315 if (col == -1)
16316 col = m_nTextCol;
16317 else
16318 col = to_internal_model(col);
16319 return get(rGtkIter.iter, col);
16322 virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
16324 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16325 if (col == -1)
16326 col = m_nTextCol;
16327 else
16328 col = to_internal_model(col);
16329 set(rGtkIter.iter, col, rText);
16332 virtual OUString get_id(const weld::TreeIter& rIter) const override
16334 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16335 return get(rGtkIter.iter, m_nIdCol);
16338 virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
16340 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16341 set(rGtkIter.iter, m_nIdCol, rId);
16344 virtual void freeze() override
16346 disable_notify_events();
16347 bool bIsFirstFreeze = IsFirstFreeze();
16348 GtkInstanceWidget::freeze();
16349 if (bIsFirstFreeze)
16351 g_object_ref(m_pTreeModel);
16352 gtk_tree_view_set_model(m_pTreeView, nullptr);
16353 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
16354 if (m_xSorter)
16356 int nSortColumn;
16357 GtkSortType eSortType;
16358 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
16359 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
16360 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
16362 m_aSavedSortColumns.push_back(nSortColumn);
16363 m_aSavedSortTypes.push_back(eSortType);
16366 enable_notify_events();
16369 virtual void thaw() override
16371 disable_notify_events();
16372 if (IsLastThaw())
16374 if (m_xSorter)
16376 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
16377 gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
16378 m_aSavedSortTypes.pop_back();
16379 m_aSavedSortColumns.pop_back();
16381 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
16382 gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeModel));
16383 g_object_unref(m_pTreeModel);
16385 GtkInstanceWidget::thaw();
16386 enable_notify_events();
16389 virtual int get_height_rows(int nRows) const override
16391 return ::get_height_rows(m_pTreeView, m_pColumns, nRows);
16394 virtual Size get_size_request() const override
16396 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16397 if (GTK_IS_SCROLLED_WINDOW(pParent))
16399 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
16400 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
16402 int nWidth, nHeight;
16403 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
16404 return Size(nWidth, nHeight);
16407 virtual Size get_preferred_size() const override
16409 Size aRet(-1, -1);
16410 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16411 if (GTK_IS_SCROLLED_WINDOW(pParent))
16413 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
16414 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
16416 GtkRequisition size;
16417 #if !GTK_CHECK_VERSION(4, 0, 0)
16418 // sometimes gtk gives a bad outcome for gtk_widget_get_preferred_size if GtkTreeView's
16419 // do_validate_rows hasn't been run before querying the preferred size, if we call
16420 // gtk_widget_get_preferred_width first, we can guarantee do_validate_rows gets called
16421 gtk_widget_get_preferred_width(m_pWidget, nullptr, &size.width);
16422 #endif
16423 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
16424 if (aRet.Width() == -1)
16425 aRet.setWidth(size.width);
16426 if (aRet.Height() == -1)
16427 aRet.setHeight(size.height);
16428 return aRet;
16431 virtual void show() override
16433 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16434 if (GTK_IS_SCROLLED_WINDOW(pParent))
16435 gtk_widget_show(pParent);
16436 gtk_widget_show(m_pWidget);
16439 virtual void hide() override
16441 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16442 if (GTK_IS_SCROLLED_WINDOW(pParent))
16443 gtk_widget_hide(pParent);
16444 gtk_widget_hide(m_pWidget);
16447 virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
16449 do_enable_drag_source(rHelper, eDNDConstants);
16452 #if !GTK_CHECK_VERSION(4, 0, 0)
16453 virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
16455 if (rGtkTargets.empty() && !eDragAction)
16456 gtk_tree_view_unset_rows_drag_source(m_pTreeView);
16457 else
16458 gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
16460 #endif
16462 virtual void set_selection_mode(SelectionMode eMode) override
16464 disable_notify_events();
16465 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
16466 enable_notify_events();
16469 virtual int count_selected_rows() const override
16471 return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
16474 int starts_with(const OUString& rStr, int nStartRow, bool bCaseSensitive)
16476 return ::starts_with(m_pTreeModel, rStr, m_nTextCol, nStartRow, bCaseSensitive);
16479 virtual void disable_notify_events() override
16481 g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
16482 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
16484 g_signal_handler_block(m_pTreeModel, m_nRowDeletedSignalId);
16485 g_signal_handler_block(m_pTreeModel, m_nRowInsertedSignalId);
16487 GtkInstanceWidget::disable_notify_events();
16490 virtual void enable_notify_events() override
16492 GtkInstanceWidget::enable_notify_events();
16494 g_signal_handler_unblock(m_pTreeModel, m_nRowDeletedSignalId);
16495 g_signal_handler_unblock(m_pTreeModel, m_nRowInsertedSignalId);
16497 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
16498 g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
16501 virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
16503 ensureButtonPressSignal();
16504 weld::TreeView::connect_popup_menu(rLink);
16507 virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode, bool bAutoScroll) override
16509 if (rPos.X() < 0 || rPos.Y() < 0)
16511 // short-circuit to avoid "gtk_tree_view_get_dest_row_at_pos: assertion 'drag_x >= 0'" g_assert
16512 return false;
16515 const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
16517 // to keep it simple we'll default to always drop before the current row
16518 // except for the special edge cases
16519 GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
16521 // unhighlight current highlighted row
16522 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
16524 if (m_bWorkAroundBadDragRegion)
16526 #if GTK_CHECK_VERSION(4, 0, 0)
16527 gtk_widget_unset_state_flags(GTK_WIDGET(m_pTreeView), GTK_STATE_FLAG_DROP_ACTIVE);
16528 #else
16529 gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
16530 #endif
16533 GtkTreePath *path = nullptr;
16534 GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
16535 bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
16536 &path, &gtkpos);
16538 // find the last entry in the model for comparison
16539 GtkTreePath *lastpath = get_path_of_last_entry(m_pTreeModel);
16541 if (!ret)
16543 // empty space, draw an indicator at the last entry
16544 assert(!path);
16545 path = gtk_tree_path_copy(lastpath);
16546 pos = GTK_TREE_VIEW_DROP_AFTER;
16548 else if (bDnDMode && gtk_tree_path_compare(path, lastpath) == 0)
16550 // if we're on the last entry, see if gtk thinks
16551 // the drop should be before or after it, and if
16552 // its after, treat it like a drop into empty
16553 // space, i.e. append it
16554 if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
16555 gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
16557 ret = false;
16558 pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
16562 if (ret && pResult)
16564 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
16565 gtk_tree_model_get_iter(m_pTreeModel, &rGtkIter.iter, path);
16568 if (m_bInDrag && bDnDMode)
16570 // highlight the row
16571 gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
16574 assert(path);
16575 gtk_tree_path_free(path);
16576 gtk_tree_path_free(lastpath);
16578 if (bAutoScroll)
16580 // auto scroll if we're close to the edges
16581 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
16582 double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
16583 if (rPos.Y() < fStep)
16585 double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
16586 if (fValue < 0)
16587 fValue = 0.0;
16588 gtk_adjustment_set_value(pVAdjustment, fValue);
16590 else
16592 GdkRectangle aRect;
16593 gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
16594 if (rPos.Y() > aRect.height - fStep)
16596 double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
16597 double fMax = gtk_adjustment_get_upper(pVAdjustment);
16598 if (fValue > fMax)
16599 fValue = fMax;
16600 gtk_adjustment_set_value(pVAdjustment, fValue);
16605 return ret;
16608 virtual void unset_drag_dest_row() override
16610 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
16613 virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override
16615 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16616 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16617 tools::Rectangle aRet = ::get_row_area(m_pTreeView, m_pColumns, pPath);
16618 gtk_tree_path_free(pPath);
16619 return aRet;
16622 virtual void start_editing(const weld::TreeIter& rIter) override
16624 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16625 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16627 GtkTreeViewColumn* pColumn = nullptr;
16629 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
16631 GtkTreeViewColumn* pTestColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
16633 // see if this column is editable
16634 gboolean is_editable(false);
16635 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pTestColumn));
16636 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
16638 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
16639 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
16641 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
16642 if (is_editable)
16644 pColumn = pTestColumn;
16645 break;
16649 g_list_free(pRenderers);
16651 if (is_editable)
16652 break;
16655 // if nothing explicit editable, allow editing of cells which are not
16656 // usually editable, so we can have double click do its usual
16657 // row-activate but if we explicitly want to edit (remote files dialog)
16658 // we can still do that
16659 if (!pColumn)
16661 pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, m_nTextView));
16662 assert(pColumn && "wrong column");
16664 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
16665 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
16667 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
16668 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
16670 g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
16671 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
16672 break;
16675 g_list_free(pRenderers);
16678 gtk_tree_view_scroll_to_cell(m_pTreeView, path, pColumn, false, 0, 0);
16679 gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
16681 gtk_tree_path_free(path);
16684 virtual void end_editing() override
16686 GtkTreeViewColumn *focus_column = nullptr;
16687 gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
16688 if (focus_column)
16689 gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
16692 virtual TreeView* get_drag_source() const override
16694 return g_DragSource;
16697 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
16699 if (m_aDragBeginHdl.Call(rUnsetDragIcon))
16700 return true;
16701 g_DragSource = this;
16702 return false;
16705 #if GTK_CHECK_VERSION(4, 0, 0)
16706 virtual void drag_set_icon(GtkDragSource*) override
16709 #else
16710 virtual void drag_set_icon(GdkDragContext* context) override
16712 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
16713 if (gtk_tree_selection_get_mode(selection) == GTK_SELECTION_MULTIPLE)
16715 int nWidth = 0;
16716 int nHeight = 0;
16718 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
16719 std::vector<cairo_surface_t*> surfaces;
16720 std::vector<int> heights;
16721 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
16723 GtkTreePath* pPath = static_cast<GtkTreePath*>(pItem->data);
16725 surfaces.push_back(gtk_tree_view_create_row_drag_icon(m_pTreeView, pPath));
16727 double x1, x2, y1, y2;
16728 cairo_t* cr = cairo_create(surfaces.back());
16729 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
16730 cairo_destroy(cr);
16732 heights.push_back(y2 - y1);
16734 nWidth = std::max(nWidth, static_cast<int>(x2 - x1));
16735 nHeight += heights.back();
16737 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
16739 // if it's just one, then don't do anything and leave the default dnd icon as-is
16740 if (surfaces.size() > 1)
16742 cairo_surface_t* target = cairo_surface_create_similar(surfaces[0],
16743 cairo_surface_get_content(surfaces[0]),
16744 nWidth,
16745 nHeight);
16747 cairo_t* cr = cairo_create(target);
16749 double y_pos = 0;
16750 for (size_t i = 0; i < surfaces.size(); ++i)
16752 cairo_set_source_surface(cr, surfaces[i], 2, y_pos + 2);
16753 cairo_rectangle(cr, 0, y_pos, nWidth, heights[i]);
16754 cairo_fill(cr);
16755 y_pos += heights[i];
16758 cairo_destroy(cr);
16760 double fXScale, fYScale;
16761 dl_cairo_surface_get_device_scale(target, &fXScale, &fYScale);
16762 cairo_surface_set_device_offset(target,
16763 - m_nPressStartX * fXScale,
16766 gtk_drag_set_icon_surface(context, target);
16767 cairo_surface_destroy(target);
16770 for (auto surface : surfaces)
16771 cairo_surface_destroy(surface);
16774 #endif
16776 virtual void do_signal_drag_end() override
16778 g_DragSource = nullptr;
16781 // Under gtk 3.24.8 dragging into the TreeView is not highlighting
16782 // entire TreeView widget, just the rectangle which has no entries
16783 // in it, so as a workaround highlight the parent container
16784 // on drag start, and undo it on drag end, and trigger removal
16785 // of the treeview's highlight effort
16786 virtual void drag_started() override
16788 m_bInDrag = true;
16789 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
16790 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
16791 if (GTK_IS_SCROLLED_WINDOW(pParent))
16793 #if GTK_CHECK_VERSION(4, 0, 0)
16794 gtk_widget_unset_state_flags(pWidget, GTK_STATE_FLAG_DROP_ACTIVE);
16795 gtk_widget_set_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE, false);
16796 #else
16797 gtk_drag_unhighlight(pWidget);
16798 gtk_drag_highlight(pParent);
16799 #endif
16800 m_bWorkAroundBadDragRegion = true;
16804 virtual void drag_ended() override
16806 m_bInDrag = false;
16807 if (m_bWorkAroundBadDragRegion)
16809 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
16810 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
16811 #if GTK_CHECK_VERSION(4, 0, 0)
16812 gtk_widget_unset_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE);
16813 #else
16814 gtk_drag_unhighlight(pParent);
16815 #endif
16816 m_bWorkAroundBadDragRegion = false;
16818 // unhighlight the row
16819 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
16822 virtual int vadjustment_get_value() const override
16824 if (m_nPendingVAdjustment != -1)
16825 return m_nPendingVAdjustment;
16826 return gtk_adjustment_get_value(m_pVAdjustment);
16829 virtual void vadjustment_set_value(int value) override
16831 disable_notify_events();
16833 /* This rube goldberg device is to remove flicker from setting the
16834 scroll position of a GtkTreeView directly after clearing it and
16835 filling it. As a specific example the writer navigator with ~100
16836 tables, scroll to the end, right click on an entry near the end
16837 and rename it, the tree is cleared and refilled and an attempt
16838 made to set the scroll position of the freshly refilled tree to
16839 the same point as before the clear.
16842 // This forces the tree to recalculate now its preferred size
16843 // after being cleared
16844 GtkRequisition size;
16845 gtk_widget_get_preferred_size(GTK_WIDGET(m_pTreeView), nullptr, &size);
16847 m_nPendingVAdjustment = value;
16849 // The value set here just has to be different to the final value
16850 // set later so that isn't a no-op
16851 gtk_adjustment_set_value(m_pVAdjustment, value - 0.0001);
16853 // This will set the desired m_nPendingVAdjustment value right
16854 // before the tree gets drawn
16855 gtk_widget_add_tick_callback(GTK_WIDGET(m_pTreeView), setAdjustmentCallback, this, nullptr);
16857 enable_notify_events();
16860 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
16862 signal_custom_render(rOutput, rRect, bSelected, rId);
16865 Size call_signal_custom_get_size(VirtualDevice& rOutput, const OUString& rId)
16867 return signal_custom_get_size(rOutput, rId);
16870 virtual void set_show_expanders(bool bShow) override
16872 gtk_tree_view_set_show_expanders(m_pTreeView, bShow);
16875 virtual bool changed_by_hover() const override
16877 return m_bChangedByMouse;
16880 virtual ~GtkInstanceTreeView() override
16882 if (m_pChangeEvent)
16883 Application::RemoveUserEvent(m_pChangeEvent);
16884 if (m_nQueryTooltipSignalId)
16885 g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
16886 #if !GTK_CHECK_VERSION(4, 0, 0)
16887 g_signal_handler_disconnect(m_pTreeView, m_nCrossingSignalid);
16888 g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
16889 g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
16890 #endif
16891 g_signal_handler_disconnect(m_pTreeModel, m_nRowDeletedSignalId);
16892 g_signal_handler_disconnect(m_pTreeModel, m_nRowInsertedSignalId);
16894 if (m_nVAdjustmentChangedSignalId)
16896 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
16897 g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
16900 g_signal_handler_disconnect(m_pTreeView, m_nTestCollapseRowSignalId);
16901 g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
16902 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
16903 g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
16905 if (g_DragSource == this)
16906 g_DragSource = nullptr;
16908 GValue value = G_VALUE_INIT;
16909 g_value_init(&value, G_TYPE_POINTER);
16910 g_value_set_pointer(&value, static_cast<gpointer>(nullptr));
16912 for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
16914 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
16915 g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
16916 m_aColumnSignalIds.pop_back();
16918 // unset "instance" to avoid dangling "instance" points in any CustomCellRenderers
16919 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
16920 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
16922 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
16923 if (!CUSTOM_IS_CELL_RENDERER(pCellRenderer))
16924 continue;
16925 g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
16927 g_list_free(pRenderers);
16929 g_list_free(m_pColumns);
16935 IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
16937 m_pChangeEvent = nullptr;
16938 signal_changed();
16939 m_bChangedByMouse = false;
16942 IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
16944 end_editing();
16947 namespace {
16949 class GtkInstanceIconView : public GtkInstanceWidget, public virtual weld::IconView
16951 private:
16952 GtkIconView* m_pIconView;
16953 GtkTreeStore* m_pTreeStore;
16954 gint m_nTextCol;
16955 gint m_nImageCol;
16956 gint m_nIdCol;
16957 gulong m_nSelectionChangedSignalId;
16958 gulong m_nItemActivatedSignalId;
16959 #if !GTK_CHECK_VERSION(4, 0, 0)
16960 gulong m_nPopupMenu;
16961 #endif
16962 gulong m_nQueryTooltipSignalId = 0;
16963 ImplSVEvent* m_pSelectionChangeEvent;
16965 DECL_LINK(async_signal_selection_changed, void*, void);
16967 bool signal_command(const CommandEvent& rCEvt)
16969 return m_aCommandHdl.Call(rCEvt);
16972 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
16974 return signal_command(rCEvt);
16977 void launch_signal_selection_changed()
16979 //tdf#117991 selection change is sent before the focus change, and focus change
16980 //is what will cause a spinbutton that currently has the focus to set its contents
16981 //as the spin button value. So any LibreOffice callbacks on
16982 //signal-change would happen before the spinbutton value-change occurs.
16983 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
16984 //has been processed
16985 if (m_pSelectionChangeEvent)
16986 Application::RemoveUserEvent(m_pSelectionChangeEvent);
16987 m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
16990 static void signalSelectionChanged(GtkIconView*, gpointer widget)
16992 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
16993 pThis->launch_signal_selection_changed();
16996 void handle_item_activated()
16998 if (signal_item_activated())
16999 return;
17002 static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
17004 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
17005 SolarMutexGuard aGuard;
17006 pThis->handle_item_activated();
17009 static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
17010 gboolean keyboard_tip, GtkTooltip* tooltip,
17011 gpointer widget)
17013 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
17014 GtkTreeIter iter;
17015 GtkIconView* pIconView = pThis->m_pIconView;
17016 GtkTreeModel* pModel = gtk_icon_view_get_model(pIconView);
17017 GtkTreePath* pPath = nullptr;
17018 #if GTK_CHECK_VERSION(4, 0, 0)
17019 if (!gtk_icon_view_get_tooltip_context(pIconView, x, y, keyboard_tip, &pModel, &pPath, &iter))
17020 return false;
17021 #else
17022 if (!gtk_icon_view_get_tooltip_context(pIconView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
17023 return false;
17024 #endif
17025 OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
17026 if (!aTooltip.isEmpty())
17028 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
17029 gtk_icon_view_set_tooltip_item(pIconView, tooltip, pPath);
17031 gtk_tree_path_free(pPath);
17032 return !aTooltip.isEmpty();
17035 /* Set the item's tooltip text as its accessible description as well. */
17036 void set_item_accessible_description_from_tooltip(GtkTreeIter& iter)
17038 #if GTK_CHECK_VERSION(4, 0, 0)
17039 (void)iter;
17040 #else
17041 AtkObject* pAtkObject = gtk_widget_get_accessible(GTK_WIDGET(m_pIconView));
17042 assert(pAtkObject);
17043 GtkTreePath* pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(m_pTreeStore), &iter);
17044 assert(gtk_tree_path_get_depth(pPath) == 1);
17045 int* indices = gtk_tree_path_get_indices(pPath);
17046 const int nIndex = indices[0];
17047 assert(nIndex < atk_object_get_n_accessible_children(pAtkObject)
17048 && "item index too high for ItemView's accessible child count");
17050 const OUString sTooltipText = signal_query_tooltip(GtkInstanceTreeIter(iter));
17051 AtkObject* pChild = atk_object_ref_accessible_child(pAtkObject, nIndex);
17052 atk_object_set_description(pChild,
17053 OUStringToOString(sTooltipText, RTL_TEXTENCODING_UTF8).getStr());
17054 g_object_unref(pChild);
17055 gtk_tree_path_free(pPath);
17057 #endif
17060 void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
17062 // m_nTextCol may be -1, so pass it last, to not terminate the sequence before the Id value
17063 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
17064 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
17065 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
17066 -1);
17067 if (pIconName)
17069 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
17070 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
17071 if (pixbuf)
17072 g_object_unref(pixbuf);
17075 set_item_accessible_description_from_tooltip(iter);
17078 void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const VirtualDevice* pIcon)
17080 // m_nTextCol may be -1, so pass it last, to not terminate the sequence before the Id value
17081 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
17082 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
17083 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
17084 -1);
17085 if (pIcon)
17087 GdkPixbuf* pixbuf = getPixbuf(*pIcon);
17088 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
17089 if (pixbuf)
17090 g_object_unref(pixbuf);
17093 set_item_accessible_description_from_tooltip(iter);
17096 OUString get(const GtkTreeIter& iter, int col) const
17098 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
17099 gchar* pStr;
17100 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
17101 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
17102 g_free(pStr);
17103 return sRet;
17106 bool get_selected_iterator(GtkTreeIter* pIter) const
17108 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
17109 bool bRet = false;
17111 GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
17112 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
17113 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
17115 if (pIter)
17117 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
17118 gtk_tree_model_get_iter(pModel, pIter, path);
17120 bRet = true;
17121 break;
17123 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
17125 return bRet;
17128 public:
17129 GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
17130 : GtkInstanceWidget(GTK_WIDGET(pIconView), pBuilder, bTakeOwnership)
17131 , m_pIconView(pIconView)
17132 , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
17133 , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView)) // May be -1
17134 , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
17135 , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
17136 G_CALLBACK(signalSelectionChanged), this))
17137 , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
17138 #if !GTK_CHECK_VERSION(4, 0, 0)
17139 , m_nPopupMenu(g_signal_connect(pIconView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
17140 #endif
17141 , m_pSelectionChangeEvent(nullptr)
17143 m_nIdCol = std::max(m_nTextCol, m_nImageCol) + 1;
17146 virtual int get_item_width() const override
17148 return gtk_icon_view_get_item_width(m_pIconView);
17151 virtual void set_item_width(int width) override
17153 gtk_icon_view_set_item_width(m_pIconView, width);
17156 virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
17158 disable_notify_events();
17159 GtkTreeIter iter;
17160 insert_item(iter, pos, pId, pText, pIconName);
17161 if (pRet)
17163 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
17164 pGtkRetIter->iter = iter;
17166 enable_notify_events();
17169 virtual void insert(int pos, const OUString* pText, const OUString* pId, const VirtualDevice* pIcon, weld::TreeIter* pRet) override
17171 disable_notify_events();
17172 GtkTreeIter iter;
17173 insert_item(iter, pos, pId, pText, pIcon);
17174 if (pRet)
17176 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
17177 pGtkRetIter->iter = iter;
17179 enable_notify_events();
17182 virtual void insert_separator(int /* pos */, const OUString* /* pId */) override
17184 // TODO: can't just copy from GtkInstanceTreeView, since there's
17185 // no IconView analog for gtk_tree_view_get_row_separator_func
17188 virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
17190 weld::IconView::connect_query_tooltip(rLink);
17191 m_nQueryTooltipSignalId = g_signal_connect(m_pIconView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
17192 gtk_widget_set_has_tooltip(GTK_WIDGET(m_pIconView), true);
17195 virtual void connect_get_image(const Link<const weld::encoded_image_query&, bool>& /*rLink*/) override
17197 //not implemented for the gtk variant
17200 virtual OUString get_selected_id() const override
17202 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
17203 GtkTreeIter iter;
17204 if (get_selected_iterator(&iter))
17205 return get(iter, m_nIdCol);
17206 return OUString();
17209 virtual void clear() override
17211 disable_notify_events();
17212 gtk_tree_store_clear(m_pTreeStore);
17213 enable_notify_events();
17216 virtual void freeze() override
17218 disable_notify_events();
17219 bool bIsFirstFreeze = IsFirstFreeze();
17220 GtkInstanceWidget::freeze();
17221 if (bIsFirstFreeze)
17222 g_object_freeze_notify(G_OBJECT(m_pTreeStore));
17223 enable_notify_events();
17226 virtual void thaw() override
17228 disable_notify_events();
17229 if (IsLastThaw())
17230 g_object_thaw_notify(G_OBJECT(m_pTreeStore));
17231 GtkInstanceWidget::thaw();
17232 enable_notify_events();
17235 virtual Size get_size_request() const override
17237 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17238 if (GTK_IS_SCROLLED_WINDOW(pParent))
17240 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
17241 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
17243 int nWidth, nHeight;
17244 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
17245 return Size(nWidth, nHeight);
17248 virtual Size get_preferred_size() const override
17250 Size aRet(-1, -1);
17251 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17252 if (GTK_IS_SCROLLED_WINDOW(pParent))
17254 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
17255 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
17257 GtkRequisition size;
17258 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
17259 if (aRet.Width() == -1)
17260 aRet.setWidth(size.width);
17261 if (aRet.Height() == -1)
17262 aRet.setHeight(size.height);
17263 return aRet;
17266 virtual void show() override
17268 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17269 if (GTK_IS_SCROLLED_WINDOW(pParent))
17270 gtk_widget_show(pParent);
17271 gtk_widget_show(m_pWidget);
17274 virtual void hide() override
17276 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17277 if (GTK_IS_SCROLLED_WINDOW(pParent))
17278 gtk_widget_hide(pParent);
17279 gtk_widget_hide(m_pWidget);
17282 virtual OUString get_selected_text() const override
17284 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
17285 GtkTreeIter iter;
17286 if (get_selected_iterator(&iter))
17287 return get(iter, m_nTextCol);
17288 return OUString();
17291 virtual int count_selected_items() const override
17293 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
17294 int nRet = g_list_length(pList);
17295 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
17296 return nRet;
17299 virtual void select(int pos) override
17301 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
17302 disable_notify_events();
17303 if (pos == -1 || (pos == 0 && n_children() == 0))
17305 gtk_icon_view_unselect_all(m_pIconView);
17307 else
17309 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
17310 gtk_icon_view_select_path(m_pIconView, path);
17311 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
17312 gtk_tree_path_free(path);
17314 enable_notify_events();
17317 virtual void unselect(int pos) override
17319 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
17320 disable_notify_events();
17321 if (pos == -1 || (pos == 0 && n_children() == 0))
17323 gtk_icon_view_select_all(m_pIconView);
17325 else
17327 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
17328 gtk_icon_view_select_path(m_pIconView, path);
17329 gtk_tree_path_free(path);
17331 enable_notify_events();
17334 virtual bool get_selected(weld::TreeIter* pIter) const override
17336 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
17337 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
17340 virtual bool get_cursor(weld::TreeIter* pIter) const override
17342 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
17343 GtkTreePath* path;
17344 gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
17345 if (pGtkIter && path)
17347 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
17348 gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
17350 return path != nullptr;
17353 virtual void set_cursor(const weld::TreeIter& rIter) override
17355 disable_notify_events();
17356 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
17357 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
17358 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
17359 gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
17360 gtk_tree_path_free(path);
17361 enable_notify_events();
17364 virtual bool get_iter_first(weld::TreeIter& rIter) const override
17366 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
17367 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
17368 return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
17371 virtual void scroll_to_item(const weld::TreeIter& rIter) override
17373 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
17374 disable_notify_events();
17375 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
17376 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
17377 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
17378 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
17379 gtk_tree_path_free(path);
17380 enable_notify_events();
17383 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
17385 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
17388 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
17390 GtkInstanceTreeIter aGtkIter(nullptr);
17392 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
17393 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
17394 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
17396 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
17397 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
17398 if (func(aGtkIter))
17399 break;
17401 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
17404 virtual int n_children() const override
17406 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
17409 virtual OUString get_id(const weld::TreeIter& rIter) const override
17411 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
17412 return get(rGtkIter.iter, m_nIdCol);
17415 virtual OUString get_text(const weld::TreeIter& rIter) const override
17417 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
17418 return get(rGtkIter.iter, m_nTextCol);
17421 virtual void disable_notify_events() override
17423 g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
17424 g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
17426 GtkInstanceWidget::disable_notify_events();
17429 virtual void enable_notify_events() override
17431 GtkInstanceWidget::enable_notify_events();
17433 g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
17434 g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
17437 virtual ~GtkInstanceIconView() override
17439 if (m_pSelectionChangeEvent)
17440 Application::RemoveUserEvent(m_pSelectionChangeEvent);
17442 if (m_nQueryTooltipSignalId)
17443 g_signal_handler_disconnect(m_pIconView, m_nQueryTooltipSignalId);
17445 g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
17446 g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
17447 #if !GTK_CHECK_VERSION(4, 0, 0)
17448 g_signal_handler_disconnect(m_pIconView, m_nPopupMenu);
17449 #endif
17455 IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
17457 m_pSelectionChangeEvent = nullptr;
17458 signal_selection_changed();
17461 namespace {
17463 void signalDestroyFlag(GtkWidget*, gpointer destroyed)
17465 bool* pDestroyed = static_cast<bool*>(destroyed);
17466 *pDestroyed = true;
17469 class GtkInstanceSpinButton : public GtkInstanceEditable, public virtual weld::SpinButton
17471 private:
17472 GtkSpinButton* m_pButton;
17473 gulong m_nValueChangedSignalId;
17474 gulong m_nOutputSignalId;
17475 gulong m_nInputSignalId;
17476 bool m_bFormatting;
17477 bool m_bBlockOutput;
17478 bool m_bBlank;
17480 static void signalValueChanged(GtkSpinButton*, gpointer widget)
17482 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
17483 SolarMutexGuard aGuard;
17484 pThis->m_bBlank = false;
17485 pThis->signal_value_changed();
17488 bool guarded_signal_output()
17490 if (m_bBlockOutput)
17491 return true;
17492 m_bFormatting = true;
17493 bool bRet = signal_output();
17494 m_bFormatting = false;
17495 return bRet;
17498 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
17500 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
17501 SolarMutexGuard aGuard;
17502 return pThis->guarded_signal_output();
17505 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
17507 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
17508 SolarMutexGuard aGuard;
17509 int result;
17510 TriState eHandled = pThis->signal_input(&result);
17511 if (eHandled == TRISTATE_INDET)
17512 return 0;
17513 if (eHandled == TRISTATE_TRUE)
17515 *new_value = pThis->toGtk(result);
17516 return 1;
17518 return GTK_INPUT_ERROR;
17521 virtual void signal_activate() override
17523 bool bActivateDestroy(false);
17524 gulong nDestroySignalId = g_signal_connect(m_pButton, "destroy", G_CALLBACK(signalDestroyFlag), &bActivateDestroy);
17525 gtk_spin_button_update(m_pButton);
17526 if (bActivateDestroy)
17527 return;
17528 g_signal_handler_disconnect(m_pButton, nDestroySignalId);
17529 GtkInstanceEditable::signal_activate();
17532 double toGtk(sal_Int64 nValue) const
17534 return static_cast<double>(nValue) / Power10(get_digits());
17537 sal_Int64 fromGtk(double fValue) const
17539 return basegfx::fround64(fValue * Power10(get_digits()));
17542 #if !GTK_CHECK_VERSION(4, 0, 0)
17543 static gboolean signalScroll(GtkWidget* pWidget, GdkEventScroll* /*pEvent*/, gpointer /*widget*/)
17545 // tdf#149823 follow WheelBehavior setting, so if we don't have focus
17546 // we don't react to the scroll-event.
17547 MouseWheelBehaviour nWheelBehavior(Application::GetSettings().GetMouseSettings().GetWheelBehavior());
17548 switch (nWheelBehavior)
17550 case MouseWheelBehaviour::ALWAYS:
17551 break;
17552 case MouseWheelBehaviour::Disable:
17553 g_signal_stop_emission_by_name(pWidget, "scroll-event");
17554 break;
17555 case MouseWheelBehaviour::FocusOnly:
17556 if (!gtk_widget_has_focus(pWidget))
17557 g_signal_stop_emission_by_name(pWidget, "scroll-event");
17558 break;
17560 return false;
17562 #endif
17564 public:
17565 GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
17566 : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
17567 , m_pButton(pButton)
17568 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
17569 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
17570 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
17571 , m_bFormatting(false)
17572 , m_bBlockOutput(false)
17573 , m_bBlank(false)
17575 #if GTK_CHECK_VERSION(4, 0, 0)
17576 gtk_text_set_activates_default(GTK_TEXT(m_pDelegate), true);
17577 #endif
17578 #if !GTK_CHECK_VERSION(4, 0, 0)
17579 g_signal_connect(pButton, "scroll-event", G_CALLBACK(signalScroll), this);
17580 #endif
17583 virtual sal_Int64 get_value() const override
17585 return fromGtk(gtk_spin_button_get_value(m_pButton));
17588 virtual void set_value(sal_Int64 value) override
17590 disable_notify_events();
17591 m_bBlank = false;
17592 gtk_spin_button_set_value(m_pButton, toGtk(value));
17593 enable_notify_events();
17596 virtual void set_text(const OUString& rText) override
17598 disable_notify_events();
17599 // tdf#122786 if we're just formatting a value, then we're done,
17600 // however if set_text has been called directly we want to update our
17601 // value from this new text, but don't want to reformat with that value
17602 if (!m_bFormatting)
17604 #if GTK_CHECK_VERSION(4, 0, 0)
17605 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
17606 #else
17607 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
17608 #endif
17610 m_bBlockOutput = true;
17611 gtk_spin_button_update(m_pButton);
17612 m_bBlank = rText.isEmpty();
17613 m_bBlockOutput = false;
17615 else
17617 bool bKeepBlank = m_bBlank && get_value() == 0;
17618 if (!bKeepBlank)
17620 #if GTK_CHECK_VERSION(4, 0, 0)
17621 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
17622 #else
17623 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
17624 #endif
17625 m_bBlank = false;
17628 enable_notify_events();
17631 virtual void set_range(sal_Int64 min, sal_Int64 max) override
17633 disable_notify_events();
17634 gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
17635 enable_notify_events();
17638 virtual void get_range(sal_Int64& min, sal_Int64& max) const override
17640 double gtkmin, gtkmax;
17641 gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
17642 min = fromGtk(gtkmin);
17643 max = fromGtk(gtkmax);
17646 virtual void set_increments(int step, int page) override
17648 disable_notify_events();
17649 gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
17650 enable_notify_events();
17653 virtual void get_increments(int& step, int& page) const override
17655 double gtkstep, gtkpage;
17656 gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
17657 step = fromGtk(gtkstep);
17658 page = fromGtk(gtkpage);
17661 virtual void set_digits(unsigned int digits) override
17663 disable_notify_events();
17664 gtk_spin_button_set_digits(m_pButton, digits);
17665 enable_notify_events();
17668 virtual unsigned int get_digits() const override
17670 return gtk_spin_button_get_digits(m_pButton);
17673 virtual void set_font(const vcl::Font& rFont) override
17675 m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
17678 virtual void disable_notify_events() override
17680 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
17681 GtkInstanceEditable::disable_notify_events();
17684 virtual void enable_notify_events() override
17686 GtkInstanceEditable::enable_notify_events();
17687 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
17690 virtual ~GtkInstanceSpinButton() override
17692 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
17693 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
17694 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
17700 namespace {
17702 class GtkInstanceFormattedSpinButton : public GtkInstanceEditable, public virtual weld::FormattedSpinButton
17704 private:
17705 GtkSpinButton* m_pButton;
17706 std::unique_ptr<weld::EntryFormatter> m_xOwnFormatter;
17707 weld::EntryFormatter* m_pFormatter;
17708 gulong m_nValueChangedSignalId;
17709 gulong m_nOutputSignalId;
17710 gulong m_nInputSignalId;
17711 bool m_bEmptyField;
17712 bool m_bSyncingValue;
17713 double m_dValueWhenEmpty;
17715 bool signal_output()
17717 double fValue = gtk_spin_button_get_value(m_pButton);
17718 m_bEmptyField &= fValue == m_dValueWhenEmpty;
17719 if (!m_bEmptyField)
17720 GetFormatter().SetValue(fValue);
17721 return true;
17724 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
17726 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
17727 SolarMutexGuard aGuard;
17728 return pThis->signal_output();
17731 gint signal_input(double* value)
17733 Formatter& rFormatter = GetFormatter();
17734 rFormatter.Modify();
17735 // if the blank-mode is enabled then if the input is empty don't parse
17736 // the input but keep the value as it is. store what the value the
17737 // blank is associated with and until the value is changed, or the text
17738 // is updated from the outside, don't output that value
17739 m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && get_text().isEmpty();
17740 if (m_bEmptyField)
17742 m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
17743 *value = m_dValueWhenEmpty;
17745 else
17746 *value = rFormatter.GetValue();
17747 return 1;
17750 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
17752 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
17753 SolarMutexGuard aGuard;
17754 return pThis->signal_input(new_value);
17757 static void signalValueChanged(GtkSpinButton*, gpointer widget)
17759 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
17760 SolarMutexGuard aGuard;
17761 pThis->signal_value_changed();
17764 public:
17765 GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
17766 : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
17767 , m_pButton(pButton)
17768 , m_pFormatter(nullptr)
17769 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
17770 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
17771 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
17772 , m_bEmptyField(false)
17773 , m_bSyncingValue(false)
17774 , m_dValueWhenEmpty(0.0)
17778 virtual void set_text(const OUString& rText) override
17780 GtkInstanceEditable::set_text(rText);
17781 Formatter& rFormatter = GetFormatter();
17782 m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && rText.isEmpty();
17783 if (m_bEmptyField)
17784 m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
17787 virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override
17789 if (!m_pFormatter) // once a formatter is set, it takes over "changed"
17791 GtkInstanceEditable::connect_changed(rLink);
17792 return;
17794 m_pFormatter->connect_changed(rLink);
17797 virtual void connect_focus_out(const Link<weld::Widget&, void>& rLink) override
17799 if (!m_pFormatter) // once a formatter is set, it takes over "focus-out"
17801 GtkInstanceEditable::connect_focus_out(rLink);
17802 return;
17804 m_pFormatter->connect_focus_out(rLink);
17807 virtual void SetFormatter(weld::EntryFormatter* pFormatter) override
17809 m_xOwnFormatter.reset();
17810 m_pFormatter = pFormatter;
17811 sync_range_from_formatter();
17812 sync_value_from_formatter();
17813 sync_increments_from_formatter();
17816 virtual weld::EntryFormatter& GetFormatter() override
17818 if (!m_pFormatter)
17820 auto aFocusOutHdl = m_aFocusOutHdl;
17821 m_aFocusOutHdl = Link<weld::Widget&, void>();
17822 auto aChangeHdl = m_aChangeHdl;
17823 m_aChangeHdl = Link<weld::Entry&, void>();
17825 double fValue = gtk_spin_button_get_value(m_pButton);
17826 double fMin, fMax;
17827 gtk_spin_button_get_range(m_pButton, &fMin, &fMax);
17828 double fStep;
17829 gtk_spin_button_get_increments(m_pButton, &fStep, nullptr);
17830 m_xOwnFormatter.reset(new weld::EntryFormatter(*this));
17831 m_xOwnFormatter->SetMinValue(fMin);
17832 m_xOwnFormatter->SetMaxValue(fMax);
17833 m_xOwnFormatter->SetSpinSize(fStep);
17834 m_xOwnFormatter->SetValue(fValue);
17836 m_xOwnFormatter->connect_focus_out(aFocusOutHdl);
17837 m_xOwnFormatter->connect_changed(aChangeHdl);
17839 m_pFormatter = m_xOwnFormatter.get();
17841 return *m_pFormatter;
17844 virtual void sync_value_from_formatter() override
17846 if (!m_pFormatter)
17847 return;
17848 // tdf#135317 avoid reenterence
17849 if (m_bSyncingValue)
17850 return;
17851 m_bSyncingValue = true;
17852 disable_notify_events();
17853 // tdf#138519 use gtk_adjustment_set_value instead of gtk_spin_button_set_value because the
17854 // latter doesn't change the value if the new value is less than an EPSILON diff of 1e-10
17855 // from the old value
17856 gtk_adjustment_set_value(gtk_spin_button_get_adjustment(m_pButton), m_pFormatter->GetValue());
17857 enable_notify_events();
17858 m_bSyncingValue = false;
17861 virtual void sync_range_from_formatter() override
17863 if (!m_pFormatter)
17864 return;
17865 disable_notify_events();
17866 double fMin = m_pFormatter->HasMinValue() ? m_pFormatter->GetMinValue() : std::numeric_limits<double>::lowest();
17867 double fMax = m_pFormatter->HasMaxValue() ? m_pFormatter->GetMaxValue() : std::numeric_limits<double>::max();
17868 gtk_spin_button_set_range(m_pButton, fMin, fMax);
17869 enable_notify_events();
17872 virtual void sync_increments_from_formatter() override
17874 if (!m_pFormatter)
17875 return;
17876 disable_notify_events();
17877 double fSpinSize = m_pFormatter->GetSpinSize();
17878 gtk_spin_button_set_increments(m_pButton, fSpinSize, fSpinSize * 10);
17879 enable_notify_events();
17882 virtual void set_font(const vcl::Font& rFont) override
17884 m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
17887 virtual void disable_notify_events() override
17889 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
17890 GtkInstanceEditable::disable_notify_events();
17893 virtual void enable_notify_events() override
17895 GtkInstanceEditable::enable_notify_events();
17896 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
17899 virtual ~GtkInstanceFormattedSpinButton() override
17901 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
17902 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
17903 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
17905 m_pFormatter = nullptr;
17906 m_xOwnFormatter.reset();
17912 namespace {
17914 class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
17916 private:
17917 GtkLabel* m_pLabel;
17919 void set_text_background_color(const Color& rColor)
17921 guint16 nRed = rColor.GetRed() << 8;
17922 guint16 nGreen = rColor.GetGreen() << 8;
17923 guint16 nBlue = rColor.GetBlue() << 8;
17925 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_BACKGROUND, PANGO_ATTR_INVALID};
17927 PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
17928 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
17929 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
17930 pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
17931 gtk_label_set_attributes(m_pLabel, pAttrs);
17932 pango_attr_list_unref(pAttrs);
17933 pango_attr_list_unref(pRemovedAttrs);
17936 void set_text_foreground_color(const Color& rColor, bool bSetBold)
17938 guint16 nRed = rColor.GetRed() << 8;
17939 guint16 nGreen = rColor.GetGreen() << 8;
17940 guint16 nBlue = rColor.GetBlue() << 8;
17942 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_WEIGHT, PANGO_ATTR_INVALID};
17944 if (!bSetBold)
17945 aFilterAttrs[1] = PANGO_ATTR_INVALID;
17947 PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
17948 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
17949 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
17950 if (rColor != COL_AUTO)
17951 pango_attr_list_insert(pAttrs, pango_attr_foreground_new(nRed, nGreen, nBlue));
17952 if (bSetBold)
17953 pango_attr_list_insert(pAttrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
17954 gtk_label_set_attributes(m_pLabel, pAttrs);
17955 pango_attr_list_unref(pAttrs);
17956 pango_attr_list_unref(pRemovedAttrs);
17959 public:
17960 GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
17961 : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
17962 , m_pLabel(pLabel)
17966 virtual void set_label(const OUString& rText) override
17968 ::set_label(m_pLabel, rText);
17971 virtual OUString get_label() const override
17973 return ::get_label(m_pLabel);
17976 virtual void set_mnemonic_widget(Widget* pTarget) override
17978 assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
17979 GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
17980 gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
17983 virtual void set_label_type(weld::LabelType eType) override
17985 switch (eType)
17987 case weld::LabelType::Normal:
17988 gtk_label_set_attributes(m_pLabel, nullptr);
17989 break;
17990 case weld::LabelType::Warning:
17991 set_text_background_color(Application::GetSettings().GetStyleSettings().GetWarningColor());
17992 set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetWarningTextColor(), false);
17993 break;
17994 case weld::LabelType::Error:
17995 set_text_background_color(Application::GetSettings().GetStyleSettings().GetErrorColor());
17996 set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetErrorTextColor(), false);
17997 break;
17998 case weld::LabelType::Title:
17999 set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetLightColor(), true);
18000 break;
18004 virtual void set_font(const vcl::Font& rFont) override
18006 ::set_font(m_pLabel, rFont);
18009 virtual void set_font_color(const Color& rColor) override
18011 set_text_foreground_color(rColor, false);
18017 std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
18019 GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
18020 if (!pLabel || !GTK_IS_LABEL(pLabel))
18021 return nullptr;
18022 return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
18025 namespace {
18027 GdkClipboard* widget_get_clipboard(GtkWidget* pWidget)
18029 #if GTK_CHECK_VERSION(4, 0, 0)
18030 return gtk_widget_get_clipboard(pWidget);
18031 #else
18032 return gtk_widget_get_clipboard(pWidget, GDK_SELECTION_CLIPBOARD);
18033 #endif
18036 class GtkInstanceTextView : public GtkInstanceWidget, public virtual weld::TextView
18038 private:
18039 GtkTextView* m_pTextView;
18040 GtkTextBuffer* m_pTextBuffer;
18041 GtkAdjustment* m_pVAdjustment;
18042 GtkCssProvider* m_pFgCssProvider;
18043 WidgetFont m_aCustomFont;
18044 int m_nMaxTextLength;
18045 gulong m_nChangedSignalId; // we don't disable/enable this one, it's to implement max-length
18046 gulong m_nInsertTextSignalId;
18047 gulong m_nCursorPosSignalId;
18048 gulong m_nHasSelectionSignalId; // we don't disable/enable this one, it's to implement
18049 // auto-scroll to cursor on losing selection
18050 gulong m_nVAdjustChangedSignalId;
18051 #if !GTK_CHECK_VERSION(4, 0, 0)
18052 gulong m_nButtonPressEvent; // we don't disable/enable this one, it's to block mouse
18053 // click down from getting to (potential) toplevel
18054 // GtkSalFrame parent, which grabs focus away
18056 static gboolean signalButtonPressEvent(GtkWidget*, GdkEventButton*, gpointer)
18058 // e.g. on clicking on the help TextView in OTableDesignHelpBar the currently displayed text shouldn't disappear
18059 return true;
18061 #endif
18063 static void signalChanged(GtkTextBuffer*, gpointer widget)
18065 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
18066 SolarMutexGuard aGuard;
18067 pThis->signal_changed();
18070 static void signalInserText(GtkTextBuffer *pBuffer, GtkTextIter *pLocation, gchar* /*pText*/, gint /*nLen*/, gpointer widget)
18072 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
18073 pThis->insert_text(pBuffer, pLocation);
18076 void insert_text(GtkTextBuffer *pBuffer, GtkTextIter *pLocation)
18078 if (m_nMaxTextLength)
18080 gint nCount = gtk_text_buffer_get_char_count(pBuffer);
18081 if (nCount > m_nMaxTextLength)
18083 GtkTextIter nStart, nEnd;
18084 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &nStart, m_nMaxTextLength);
18085 gtk_text_buffer_get_end_iter(m_pTextBuffer, &nEnd);
18086 gtk_text_buffer_delete(m_pTextBuffer, &nStart, &nEnd);
18087 gtk_text_iter_assign(pLocation, &nStart);
18092 static void signalCursorPosition(GtkTextBuffer*, GParamSpec*, gpointer widget)
18094 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
18095 pThis->signal_cursor_position();
18098 static void signalHasSelection(GtkTextBuffer*, GParamSpec*, gpointer widget)
18100 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
18101 pThis->signal_has_selection();
18104 void signal_has_selection()
18107 in the data browser (Data Sources, shift+ctrl+f4), entering a
18108 multiline cell selects all, on cursoring to the right, the selection
18109 is lost and the cursor is at the end but gtk doesn't auto-scroll to
18110 the cursor so if the text needs scrolling to see the cursor it is off
18111 screen, another cursor makes gtk auto-scroll as wanted. So on losing
18112 selection help gtk out and do the initial scroll ourselves here
18114 if (!gtk_text_buffer_get_has_selection(m_pTextBuffer))
18116 GtkTextMark* pMark = gtk_text_buffer_get_insert(m_pTextBuffer);
18117 gtk_text_view_scroll_mark_onscreen(m_pTextView, pMark);
18121 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
18123 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
18124 SolarMutexGuard aGuard;
18125 pThis->signal_vadjustment_changed();
18128 public:
18129 GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
18130 : GtkInstanceWidget(GTK_WIDGET(pTextView), pBuilder, bTakeOwnership)
18131 , m_pTextView(pTextView)
18132 , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
18133 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
18134 , m_pFgCssProvider(nullptr)
18135 , m_aCustomFont(m_pWidget)
18136 , m_nMaxTextLength(0)
18137 , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
18138 , m_nInsertTextSignalId(g_signal_connect_after(m_pTextBuffer, "insert-text", G_CALLBACK(signalInserText), this))
18139 , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
18140 , m_nHasSelectionSignalId(g_signal_connect(m_pTextBuffer, "notify::has-selection", G_CALLBACK(signalHasSelection), this))
18141 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
18142 #if !GTK_CHECK_VERSION(4, 0, 0)
18143 , m_nButtonPressEvent(g_signal_connect_after(m_pTextView, "button-press-event", G_CALLBACK(signalButtonPressEvent), this))
18144 #endif
18148 virtual void set_size_request(int nWidth, int nHeight) override
18150 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
18151 if (GTK_IS_SCROLLED_WINDOW(pParent))
18153 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
18154 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
18155 return;
18157 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
18160 virtual void set_text(const OUString& rText) override
18162 disable_notify_events();
18163 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
18164 gtk_text_buffer_set_text(m_pTextBuffer, sText.getStr(), sText.getLength());
18165 enable_notify_events();
18168 virtual OUString get_text() const override
18170 GtkTextIter start, end;
18171 gtk_text_buffer_get_bounds(m_pTextBuffer, &start, &end);
18172 char* pStr = gtk_text_buffer_get_text(m_pTextBuffer, &start, &end, true);
18173 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
18174 g_free(pStr);
18175 return sRet;
18178 virtual void replace_selection(const OUString& rText) override
18180 disable_notify_events();
18181 gtk_text_buffer_delete_selection(m_pTextBuffer, false, gtk_text_view_get_editable(m_pTextView));
18182 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
18183 gtk_text_buffer_insert_at_cursor(m_pTextBuffer, sText.getStr(), sText.getLength());
18184 enable_notify_events();
18187 virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
18189 GtkTextIter start, end;
18190 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
18191 rStartPos = gtk_text_iter_get_offset(&start);
18192 rEndPos = gtk_text_iter_get_offset(&end);
18193 return rStartPos != rEndPos;
18196 virtual void select_region(int nStartPos, int nEndPos) override
18198 disable_notify_events();
18199 GtkTextIter start, end;
18200 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &start, nStartPos);
18201 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &end, nEndPos);
18202 gtk_text_buffer_select_range(m_pTextBuffer, &start, &end);
18203 GtkTextMark* mark = gtk_text_buffer_create_mark(m_pTextBuffer, "scroll", &end, true);
18204 gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
18205 enable_notify_events();
18208 virtual void set_editable(bool bEditable) override
18210 gtk_text_view_set_editable(m_pTextView, bEditable);
18213 virtual bool get_editable() const override
18215 return gtk_text_view_get_editable(m_pTextView);
18218 virtual void set_max_length(int nChars) override
18220 m_nMaxTextLength = nChars;
18223 virtual void set_monospace(bool bMonospace) override
18225 gtk_text_view_set_monospace(m_pTextView, bMonospace);
18228 virtual void set_font_color(const Color& rColor) override
18230 const bool bRemoveColor = rColor == COL_AUTO;
18231 if (bRemoveColor && !m_pFgCssProvider)
18232 return;
18233 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pTextView));
18234 if (m_pFgCssProvider)
18236 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider));
18237 m_pFgCssProvider = nullptr;
18239 if (bRemoveColor)
18240 return;
18241 OUString sColor = rColor.AsRGBHexString();
18242 m_pFgCssProvider = gtk_css_provider_new();
18243 OUString aBuffer = "textview text { color: #" + sColor + "; }";
18244 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
18245 css_provider_load_from_data(m_pFgCssProvider, aResult.getStr(), aResult.getLength());
18246 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider),
18247 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
18250 virtual void set_font(const vcl::Font& rFont) override
18252 m_aCustomFont.use_custom_font(&rFont, u"textview");
18255 virtual vcl::Font get_font() override
18257 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
18258 return *pFont;
18259 return GtkInstanceWidget::get_font();
18262 virtual void disable_notify_events() override
18264 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
18265 g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
18266 g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
18267 GtkInstanceWidget::disable_notify_events();
18270 virtual void enable_notify_events() override
18272 GtkInstanceWidget::enable_notify_events();
18273 g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
18274 g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
18275 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
18278 // in gtk, 'up' when on the first line, will jump to the start of the line
18279 // if not there already
18280 virtual bool can_move_cursor_with_up() const override
18282 GtkTextIter start, end;
18283 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
18284 return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_start(&start);
18287 // in gtk, 'down' when on the first line, will jump to the end of the line
18288 // if not there already
18289 virtual bool can_move_cursor_with_down() const override
18291 GtkTextIter start, end;
18292 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
18293 return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_end(&end);
18296 virtual void cut_clipboard() override
18298 GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
18299 gtk_text_buffer_cut_clipboard(m_pTextBuffer, pClipboard, get_editable());
18302 virtual void copy_clipboard() override
18304 GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
18305 gtk_text_buffer_copy_clipboard(m_pTextBuffer, pClipboard);
18308 virtual void paste_clipboard() override
18310 GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
18311 gtk_text_buffer_paste_clipboard(m_pTextBuffer, pClipboard, nullptr, get_editable());
18314 virtual void set_alignment(TxtAlign eXAlign) override
18316 GtkJustification eJust = GTK_JUSTIFY_LEFT;
18317 switch (eXAlign)
18319 case TxtAlign::Left:
18320 eJust = GTK_JUSTIFY_LEFT;
18321 break;
18322 case TxtAlign::Center:
18323 eJust = GTK_JUSTIFY_CENTER;
18324 break;
18325 case TxtAlign::Right:
18326 eJust = GTK_JUSTIFY_RIGHT;
18327 break;
18329 gtk_text_view_set_justification(m_pTextView, eJust);
18332 virtual int vadjustment_get_value() const override
18334 return gtk_adjustment_get_value(m_pVAdjustment);
18337 virtual void vadjustment_set_value(int value) override
18339 disable_notify_events();
18340 gtk_adjustment_set_value(m_pVAdjustment, value);
18341 enable_notify_events();
18344 virtual int vadjustment_get_upper() const override
18346 return gtk_adjustment_get_upper(m_pVAdjustment);
18349 virtual int vadjustment_get_lower() const override
18351 return gtk_adjustment_get_lower(m_pVAdjustment);
18354 virtual int vadjustment_get_page_size() const override
18356 return gtk_adjustment_get_page_size(m_pVAdjustment);
18359 virtual void show() override
18361 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
18362 if (GTK_IS_SCROLLED_WINDOW(pParent))
18363 gtk_widget_show(pParent);
18364 gtk_widget_show(m_pWidget);
18367 virtual void hide() override
18369 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
18370 if (GTK_IS_SCROLLED_WINDOW(pParent))
18371 gtk_widget_hide(pParent);
18372 gtk_widget_hide(m_pWidget);
18375 virtual ~GtkInstanceTextView() override
18377 #if !GTK_CHECK_VERSION(4, 0, 0)
18378 g_signal_handler_disconnect(m_pTextView, m_nButtonPressEvent);
18379 #endif
18380 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
18381 g_signal_handler_disconnect(m_pTextBuffer, m_nInsertTextSignalId);
18382 g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
18383 g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
18384 g_signal_handler_disconnect(m_pTextBuffer, m_nHasSelectionSignalId);
18390 namespace {
18392 class GtkInstanceDrawingArea;
18394 // IMHandler
18395 class IMHandler
18397 private:
18398 GtkInstanceDrawingArea* m_pArea;
18399 #if GTK_CHECK_VERSION(4, 0, 0)
18400 GtkEventController* m_pFocusController;
18401 #endif
18402 GtkIMContext* m_pIMContext;
18403 OUString m_sPreeditText;
18404 gulong m_nFocusInSignalId;
18405 gulong m_nFocusOutSignalId;
18406 bool m_bExtTextInput;
18408 public:
18409 IMHandler(GtkInstanceDrawingArea* pArea);
18411 void signalFocus(bool bIn);
18413 #if GTK_CHECK_VERSION(4, 0, 0)
18414 static void signalFocusIn(GtkEventControllerFocus*, gpointer im_handler);
18415 #else
18416 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler);
18417 #endif
18419 #if GTK_CHECK_VERSION(4, 0, 0)
18420 static void signalFocusOut(GtkEventControllerFocus*, gpointer im_handler);
18421 #else
18422 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler);
18423 #endif
18425 ~IMHandler();
18427 void updateIMSpotLocation();
18429 void set_cursor_location(const tools::Rectangle& rRect);
18431 static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler);
18433 static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler);
18435 static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler);
18437 static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
18438 gpointer im_handler);
18440 void StartExtTextInput();
18442 static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler);
18444 void EndExtTextInput();
18446 static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler);
18448 #if !GTK_CHECK_VERSION(4, 0, 0)
18449 bool im_context_filter_keypress(const GdkEventKey* pEvent);
18450 #endif
18453 #if !GTK_CHECK_VERSION(4, 0, 0)
18454 AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
18455 #endif
18457 class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
18459 private:
18460 GtkDrawingArea* m_pDrawingArea;
18461 a11yref m_xAccessible;
18462 #if !GTK_CHECK_VERSION(4, 0, 0)
18463 AtkObject *m_pAccessible;
18464 #endif
18465 ScopedVclPtrInstance<VirtualDevice> m_xDevice;
18466 std::unique_ptr<IMHandler> m_xIMHandler;
18467 cairo_surface_t* m_pSurface;
18468 #if !GTK_CHECK_VERSION(4, 0, 0)
18469 gulong m_nDrawSignalId;
18470 #endif
18471 gulong m_nQueryTooltip;
18472 #if !GTK_CHECK_VERSION(4, 0, 0)
18473 gulong m_nPopupMenu;
18474 gulong m_nScrollEvent;
18475 #endif
18476 GtkGesture *m_pZoomGesture;
18478 #if GTK_CHECK_VERSION(4, 0, 0)
18479 static void signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer widget)
18480 #else
18481 static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
18482 #endif
18484 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
18485 SolarMutexGuard aGuard;
18486 pThis->signal_draw(cr);
18487 #if !GTK_CHECK_VERSION(4, 0, 0)
18488 return false;
18489 #endif
18491 void signal_draw(cairo_t* cr)
18493 if (!m_pSurface)
18494 return;
18496 GdkRectangle rect;
18497 #if GTK_CHECK_VERSION(4, 0, 0)
18498 double clip_x1, clip_x2, clip_y1, clip_y2;
18499 cairo_clip_extents(cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
18500 rect.x = clip_x1;
18501 rect.y = clip_y1;
18502 rect.width = clip_x2 - clip_x1;
18503 rect.height = clip_y2 - clip_y1;
18504 if (rect.width <= 0 || rect.height <= 0)
18505 return;
18506 #else
18507 if (!gdk_cairo_get_clip_rectangle(cr, &rect))
18508 return;
18509 #endif
18511 tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
18512 aRect = m_xDevice->PixelToLogic(aRect);
18513 m_xDevice->Erase(aRect);
18514 m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
18515 cairo_surface_mark_dirty(m_pSurface);
18517 cairo_set_source_surface(cr, m_pSurface, 0, 0);
18518 cairo_paint(cr);
18520 tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
18521 if (!aFocusRect.IsEmpty())
18523 gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
18524 aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
18527 virtual void signal_size_allocate(guint nWidth, guint nHeight) override
18529 Size aNewSize(nWidth, nHeight);
18530 if (m_pSurface && aNewSize == m_xDevice->GetOutputSizePixel())
18532 // unchanged
18533 return;
18535 m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
18536 m_pSurface = get_underlying_cairo_surface(*m_xDevice);
18537 GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
18539 static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
18540 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
18541 gpointer widget)
18543 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
18544 tools::Rectangle aHelpArea(x, y);
18545 OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
18546 if (aTooltip.isEmpty())
18547 return false;
18548 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
18549 GdkRectangle aGdkHelpArea;
18550 aGdkHelpArea.x = aHelpArea.Left();
18551 aGdkHelpArea.y = aHelpArea.Top();
18552 aGdkHelpArea.width = aHelpArea.GetWidth();
18553 aGdkHelpArea.height = aHelpArea.GetHeight();
18554 if (pThis->SwapForRTL())
18555 aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
18556 gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
18557 return true;
18559 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
18561 return signal_command(rCEvt);
18563 #if !GTK_CHECK_VERSION(4, 0, 0)
18564 bool signal_scroll(const GdkEventScroll* pEvent)
18566 SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
18568 if (SwapForRTL())
18569 aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
18571 CommandWheelMode nMode;
18572 sal_uInt16 nCode = aEvt.mnCode;
18573 bool bHorz = aEvt.mbHorz;
18574 if (nCode & KEY_MOD1)
18575 nMode = CommandWheelMode::ZOOM;
18576 else if (nCode & KEY_MOD2)
18577 nMode = CommandWheelMode::DATAZOOM;
18578 else
18580 nMode = CommandWheelMode::SCROLL;
18581 // #i85450# interpret shift-wheel as horizontal wheel action
18582 if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
18583 bHorz = true;
18586 CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
18587 nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
18588 CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
18589 return m_aCommandHdl.Call(aCEvt);
18591 static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
18593 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
18594 return pThis->signal_scroll(pEvent);
18596 #endif
18598 bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence,
18599 GestureEventZoomType eEventType)
18601 gdouble x = 0;
18602 gdouble y = 0;
18603 gtk_gesture_get_point(gesture, sequence, &x, &y);
18605 double fScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
18607 CommandGestureZoomData aGestureData(x, y, eEventType, fScaleDelta);
18608 CommandEvent aCEvt(Point(x, y), CommandEventId::GestureZoom, true, &aGestureData);
18609 return m_aCommandHdl.Call(aCEvt);
18612 static bool signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer widget)
18614 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
18615 return pThis->handleSignalZoom(gesture, sequence, GestureEventZoomType::Begin);
18618 static bool signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer widget)
18620 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
18621 return pThis->handleSignalZoom(gesture, sequence, GestureEventZoomType::Update);
18624 static bool signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer widget)
18626 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
18627 return pThis->handleSignalZoom(gesture, sequence, GestureEventZoomType::End);
18630 #if GTK_CHECK_VERSION(4, 0, 0)
18631 static void signalResize(GtkDrawingArea*, int nWidth, int nHeight, gpointer widget)
18633 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
18634 SolarMutexGuard aGuard;
18635 pThis->signal_size_allocate(nWidth, nHeight);
18637 #endif
18639 public:
18640 GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, a11yref xA11y, bool bTakeOwnership)
18641 : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
18642 , m_pDrawingArea(pDrawingArea)
18643 , m_xAccessible(std::move(xA11y))
18644 #if !GTK_CHECK_VERSION(4, 0, 0)
18645 , m_pAccessible(nullptr)
18646 #endif
18647 , m_xDevice(DeviceFormat::WITHOUT_ALPHA)
18648 , m_pSurface(nullptr)
18649 , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
18650 #if !GTK_CHECK_VERSION(4, 0, 0)
18651 , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
18652 , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
18653 #endif
18655 #if GTK_CHECK_VERSION(4, 0, 0)
18656 gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
18657 #else
18658 m_nDrawSignalId = g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this);
18659 gtk_widget_add_events(GTK_WIDGET(pDrawingArea), GDK_TOUCHPAD_GESTURE_MASK);
18660 #endif
18662 ensureMouseEventWidget();
18663 #if GTK_CHECK_VERSION(4,0,0)
18664 m_pZoomGesture = gtk_gesture_zoom_new();
18665 gtk_widget_add_controller(m_pMouseEventBox, GTK_EVENT_CONTROLLER(m_pZoomGesture));
18666 #else
18667 m_pZoomGesture = gtk_gesture_zoom_new(m_pMouseEventBox);
18668 #endif
18669 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_pZoomGesture),
18670 GTK_PHASE_TARGET);
18671 // Note that the default zoom gesture signal handler needs to run first to setup correct
18672 // scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
18673 g_signal_connect_after(m_pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
18674 g_signal_connect_after(m_pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
18675 g_signal_connect_after(m_pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
18677 gtk_widget_set_has_tooltip(m_pWidget, true);
18678 g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
18679 m_xDevice->EnableRTL(get_direction());
18682 #if !GTK_CHECK_VERSION(4, 0, 0)
18683 AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
18685 if (!m_pAccessible && m_xAccessible.is())
18687 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
18688 m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
18689 if (m_pAccessible)
18690 g_object_ref(m_pAccessible);
18692 return m_pAccessible;
18694 #endif
18696 #if GTK_CHECK_VERSION(4, 0, 0)
18697 virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
18699 m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "resize", G_CALLBACK(signalResize), this);
18700 weld::Widget::connect_size_allocate(rLink);
18702 #endif
18704 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
18706 #if !GTK_CHECK_VERSION(4, 0, 0)
18707 if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_PRESS_MASK))
18708 gtk_widget_add_events(m_pWidget, GDK_BUTTON_PRESS_MASK);
18709 #endif
18710 GtkInstanceWidget::connect_mouse_press(rLink);
18713 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
18715 #if !GTK_CHECK_VERSION(4, 0, 0)
18716 if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_RELEASE_MASK))
18717 gtk_widget_add_events(m_pWidget, GDK_BUTTON_RELEASE_MASK);
18718 #endif
18719 GtkInstanceWidget::connect_mouse_release(rLink);
18722 virtual void set_direction(bool bRTL) override
18724 GtkInstanceWidget::set_direction(bRTL);
18725 m_xDevice->EnableRTL(bRTL);
18728 virtual void set_cursor(PointerStyle ePointerStyle) override
18730 GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
18731 if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
18732 gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
18733 widget_set_cursor(GTK_WIDGET(m_pDrawingArea), pCursor);
18736 virtual Point get_pointer_position() const override
18738 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
18739 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
18740 GdkDevice* pPointer = gdk_seat_get_pointer(pSeat);
18741 double x(-1), y(-1);
18742 GdkSurface* pWin = widget_get_surface(m_pWidget);
18743 surface_get_device_position(pWin, pPointer, x, y, nullptr);
18744 return Point(x, y);
18747 virtual void set_input_context(const InputContext& rInputContext) override;
18749 virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override;
18751 int im_context_get_surrounding(OUString& rSurroundingText)
18753 return signal_im_context_get_surrounding(rSurroundingText);
18756 bool im_context_delete_surrounding(const Selection& rRange)
18758 return signal_im_context_delete_surrounding(rRange);
18761 #if !GTK_CHECK_VERSION(4, 0, 0)
18762 virtual bool do_signal_key_press(const GdkEventKey* pEvent) override;
18763 virtual bool do_signal_key_release(const GdkEventKey* pEvent) override;
18764 #endif
18766 virtual void queue_draw() override
18768 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
18771 virtual void queue_draw_area(int x, int y, int width, int height) override
18773 #if !GTK_CHECK_VERSION(4, 0, 0)
18774 tools::Rectangle aRect(Point(x, y), Size(width, height));
18775 aRect = m_xDevice->LogicToPixel(aRect);
18776 gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
18777 #else
18778 (void)x; (void)y; (void)width; (void)height;
18779 queue_draw();
18780 #endif
18783 virtual a11yref get_accessible_parent() override
18785 //get_accessible_parent should only be needed for the vcl implementation,
18786 //in the gtk impl the native AtkObject parent set via
18787 //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
18788 //should negate the need.
18789 assert(false && "get_accessible_parent should only be called on a vcl impl");
18790 return uno::Reference<css::accessibility::XAccessible>();
18793 virtual a11yrelationset get_accessible_relation_set() override
18795 //get_accessible_relation_set should only be needed for the vcl implementation,
18796 //in the gtk impl the native equivalent should negate the need.
18797 assert(false && "get_accessible_relation_set should only be called on a vcl impl");
18798 return uno::Reference<css::accessibility::XAccessibleRelationSet>();
18801 virtual AbsoluteScreenPixelPoint get_accessible_location_on_screen() override
18803 #if !GTK_CHECK_VERSION(4, 0, 0)
18804 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
18805 #endif
18806 gint x(0), y(0);
18807 #if !GTK_CHECK_VERSION(4, 0, 0)
18808 if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
18809 atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_SCREEN);
18810 #endif
18811 return AbsoluteScreenPixelPoint(x, y);
18814 virtual void set_accessible_name(const OUString& rName) override
18816 #if !GTK_CHECK_VERSION(4, 0, 0)
18817 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
18818 if (!pAtkObject)
18819 return;
18820 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
18821 #else
18822 (void)rName;
18823 #endif
18826 virtual OUString get_accessible_name() const override
18828 #if !GTK_CHECK_VERSION(4, 0, 0)
18829 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
18830 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
18831 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
18832 #else
18833 return OUString();
18834 #endif
18837 virtual OUString get_accessible_description() const override
18839 #if !GTK_CHECK_VERSION(4, 0, 0)
18840 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
18841 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
18842 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
18843 #else
18844 return OUString();
18845 #endif
18848 virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
18850 do_enable_drag_source(rHelper, eDNDConstants);
18853 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
18855 rUnsetDragIcon = false;
18856 if (m_aDragBeginHdl.Call(*this))
18857 return true;
18858 return false;
18861 virtual ~GtkInstanceDrawingArea() override
18863 #if GTK_CHECK_VERSION(4,0,0)
18864 gtk_widget_remove_controller(m_pMouseEventBox, GTK_EVENT_CONTROLLER(m_pZoomGesture));
18865 #else
18866 g_clear_object(&m_pZoomGesture);
18867 #endif
18869 g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
18870 #if !GTK_CHECK_VERSION(4, 0, 0)
18871 if (m_pAccessible)
18872 g_object_unref(m_pAccessible);
18873 #endif
18874 css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
18875 if (xComp.is())
18876 xComp->dispose();
18877 #if !GTK_CHECK_VERSION(4, 0, 0)
18878 g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
18879 #endif
18880 #if !GTK_CHECK_VERSION(4, 0, 0)
18881 g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
18882 #endif
18883 g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
18884 #if GTK_CHECK_VERSION(4, 0, 0)
18885 gtk_drawing_area_set_draw_func(m_pDrawingArea, nullptr, nullptr, nullptr);
18886 #else
18887 g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
18888 #endif
18891 virtual OutputDevice& get_ref_device() override
18893 return *m_xDevice;
18896 bool signal_command(const CommandEvent& rCEvt)
18898 return m_aCommandHdl.Call(rCEvt);
18901 virtual void click(const Point& rPos) override
18903 MouseEvent aEvent(rPos);
18904 m_aMousePressHdl.Call(aEvent);
18905 m_aMouseReleaseHdl.Call(aEvent);
18909 IMHandler::IMHandler(GtkInstanceDrawingArea* pArea)
18910 : m_pArea(pArea)
18911 , m_pIMContext(gtk_im_multicontext_new())
18912 , m_bExtTextInput(false)
18914 GtkWidget* pWidget = m_pArea->getWidget();
18916 #if GTK_CHECK_VERSION(4, 0, 0)
18917 m_pFocusController = gtk_event_controller_focus_new();
18918 gtk_widget_add_controller(pWidget, m_pFocusController);
18920 m_nFocusInSignalId = g_signal_connect(m_pFocusController, "enter", G_CALLBACK(signalFocusIn), this);
18921 m_nFocusOutSignalId = g_signal_connect(m_pFocusController, "leave", G_CALLBACK(signalFocusOut), this);
18922 #else
18923 m_nFocusInSignalId = g_signal_connect(pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
18924 m_nFocusOutSignalId = g_signal_connect(pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
18925 #endif
18927 g_signal_connect(m_pIMContext, "preedit-start", G_CALLBACK(signalIMPreeditStart), this);
18928 g_signal_connect(m_pIMContext, "preedit-end", G_CALLBACK(signalIMPreeditEnd), this);
18929 g_signal_connect(m_pIMContext, "commit", G_CALLBACK(signalIMCommit), this);
18930 g_signal_connect(m_pIMContext, "preedit-changed", G_CALLBACK(signalIMPreeditChanged), this);
18931 g_signal_connect(m_pIMContext, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding), this);
18932 g_signal_connect(m_pIMContext, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding), this);
18934 if (!gtk_widget_get_realized(pWidget))
18935 gtk_widget_realize(pWidget);
18936 im_context_set_client_widget(m_pIMContext, pWidget);
18937 if (gtk_widget_has_focus(m_pArea->getWidget()))
18938 gtk_im_context_focus_in(m_pIMContext);
18941 void IMHandler::signalFocus(bool bIn)
18943 if (bIn)
18944 gtk_im_context_focus_in(m_pIMContext);
18945 else
18946 gtk_im_context_focus_out(m_pIMContext);
18949 #if GTK_CHECK_VERSION(4, 0, 0)
18950 void IMHandler::signalFocusIn(GtkEventControllerFocus*, gpointer im_handler)
18951 #else
18952 gboolean IMHandler::signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
18953 #endif
18955 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18956 pThis->signalFocus(true);
18957 #if !GTK_CHECK_VERSION(4, 0, 0)
18958 return false;
18959 #endif
18962 #if GTK_CHECK_VERSION(4, 0, 0)
18963 void IMHandler::signalFocusOut(GtkEventControllerFocus*, gpointer im_handler)
18964 #else
18965 gboolean IMHandler::signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
18966 #endif
18968 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18969 pThis->signalFocus(false);
18970 #if !GTK_CHECK_VERSION(4, 0, 0)
18971 return false;
18972 #endif
18975 IMHandler::~IMHandler()
18977 EndExtTextInput();
18979 #if GTK_CHECK_VERSION(4, 0, 0)
18980 g_signal_handler_disconnect(m_pFocusController, m_nFocusOutSignalId);
18981 g_signal_handler_disconnect(m_pFocusController, m_nFocusInSignalId);
18982 #else
18983 g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
18984 g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
18985 #endif
18987 if (gtk_widget_has_focus(m_pArea->getWidget()))
18988 gtk_im_context_focus_out(m_pIMContext);
18990 // first give IC a chance to deinitialize
18991 im_context_set_client_widget(m_pIMContext, nullptr);
18992 // destroy old IC
18993 g_object_unref(m_pIMContext);
18996 void IMHandler::updateIMSpotLocation()
18998 CommandEvent aCEvt(Point(), CommandEventId::CursorPos);
18999 // we expect set_cursor_location to get triggered by this
19000 m_pArea->signal_command(aCEvt);
19003 void IMHandler::set_cursor_location(const tools::Rectangle& rRect)
19005 GdkRectangle aArea{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
19006 static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
19007 gtk_im_context_set_cursor_location(m_pIMContext, &aArea);
19010 void IMHandler::signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler)
19012 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
19014 SolarMutexGuard aGuard;
19016 // at least editeng expects to have seen a start before accepting a commit
19017 pThis->StartExtTextInput();
19019 OUString sText(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
19020 CommandExtTextInputData aData(sText, nullptr, sText.getLength(), 0, false);
19021 CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
19022 pThis->m_pArea->signal_command(aCEvt);
19024 pThis->updateIMSpotLocation();
19026 pThis->EndExtTextInput();
19028 pThis->m_sPreeditText.clear();
19031 void IMHandler::signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler)
19033 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
19035 SolarMutexGuard aGuard;
19037 sal_Int32 nCursorPos(0);
19038 sal_uInt8 nCursorFlags(0);
19039 std::vector<ExtTextInputAttr> aInputFlags;
19040 OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
19042 // change from nothing to nothing -> do not start preedit e.g. this
19043 // will activate input into a calc cell without user input
19044 if (sText.isEmpty() && pThis->m_sPreeditText.isEmpty())
19045 return;
19047 pThis->m_sPreeditText = sText;
19049 CommandExtTextInputData aData(sText, aInputFlags.data(), nCursorPos, nCursorFlags, false);
19050 CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
19051 pThis->m_pArea->signal_command(aCEvt);
19053 pThis->updateIMSpotLocation();
19056 gboolean IMHandler::signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler)
19058 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
19060 SolarMutexGuard aGuard;
19062 OUString sSurroundingText;
19063 int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
19065 if (nCursorIndex != -1)
19067 OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
19068 std::u16string_view sCursorText(sSurroundingText.subView(0, nCursorIndex));
19069 gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
19070 OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
19073 return true;
19076 gboolean IMHandler::signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
19077 gpointer im_handler)
19079 bool bRet = false;
19081 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
19083 SolarMutexGuard aGuard;
19085 OUString sSurroundingText;
19086 sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
19088 Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(sSurroundingText, nCursorIndex, nOffset, nChars);
19089 if (aSelection != Selection(SAL_MAX_UINT32, SAL_MAX_UINT32))
19090 bRet = pThis->m_pArea->im_context_delete_surrounding(aSelection);
19091 return bRet;
19094 void IMHandler::StartExtTextInput()
19096 if (m_bExtTextInput)
19097 return;
19098 CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput);
19099 m_pArea->signal_command(aCEvt);
19100 m_bExtTextInput = true;
19103 void IMHandler::signalIMPreeditStart(GtkIMContext*, gpointer im_handler)
19105 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
19106 SolarMutexGuard aGuard;
19107 pThis->StartExtTextInput();
19108 pThis->updateIMSpotLocation();
19111 void IMHandler::EndExtTextInput()
19113 if (!m_bExtTextInput)
19114 return;
19115 CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput);
19116 m_pArea->signal_command(aCEvt);
19117 m_bExtTextInput = false;
19120 void IMHandler::signalIMPreeditEnd(GtkIMContext*, gpointer im_handler)
19122 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
19123 SolarMutexGuard aGuard;
19124 pThis->updateIMSpotLocation();
19125 pThis->EndExtTextInput();
19128 #if !GTK_CHECK_VERSION(4, 0, 0)
19129 bool IMHandler::im_context_filter_keypress(const GdkEventKey* pEvent)
19131 return gtk_im_context_filter_keypress(m_pIMContext, const_cast<GdkEventKey*>(pEvent));
19133 #endif
19135 #if !GTK_CHECK_VERSION(4, 0, 0)
19136 bool GtkInstanceDrawingArea::do_signal_key_press(const GdkEventKey* pEvent)
19138 if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
19139 return true;
19140 return GtkInstanceWidget::do_signal_key_press(pEvent);
19143 bool GtkInstanceDrawingArea::do_signal_key_release(const GdkEventKey* pEvent)
19145 if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
19146 return true;
19147 return GtkInstanceWidget::do_signal_key_release(pEvent);
19149 #endif
19151 void GtkInstanceDrawingArea::set_input_context(const InputContext& rInputContext)
19153 bool bUseIm(rInputContext.GetOptions() & InputContextFlags::Text);
19154 if (!bUseIm)
19156 m_xIMHandler.reset();
19157 return;
19159 // create a new im context
19160 if (!m_xIMHandler)
19161 m_xIMHandler.reset(new IMHandler(this));
19164 void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int /*nExtTextInputWidth*/)
19166 if (!m_xIMHandler)
19167 return;
19168 m_xIMHandler->set_cursor_location(rCursorRect);
19173 #if !GTK_CHECK_VERSION(4, 0, 0)
19175 static void InsertSpecialChar(GtkEntry *pEntry)
19177 if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
19179 weld::Window* pDialogParent = nullptr;
19181 GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pEntry));
19182 if (GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr)
19183 pDialogParent = pFrame->GetFrameWeld();
19185 std::unique_ptr<GtkInstanceWindow> xFrameWeld;
19186 if (!pDialogParent && pTopLevel)
19188 xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel), nullptr, false));
19189 pDialogParent = xFrameWeld.get();
19192 OUString aChars = pImplFncGetSpecialChars(pDialogParent, ::get_font(GTK_WIDGET(pEntry)));
19193 if (!aChars.isEmpty())
19195 gtk_editable_delete_selection(GTK_EDITABLE(pEntry));
19196 gint position = gtk_editable_get_position(GTK_EDITABLE(pEntry));
19197 OString sText(OUStringToOString(aChars, RTL_TEXTENCODING_UTF8));
19198 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sText.getStr(), sText.getLength(),
19199 &position);
19200 gtk_editable_set_position(GTK_EDITABLE(pEntry), position);
19205 static gboolean signalEntryInsertSpecialCharKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer)
19207 if ((pEvent->keyval == GDK_KEY_S || pEvent->keyval == GDK_KEY_s) &&
19208 (pEvent->state & GDK_MODIFIER_MASK) == static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK))
19210 InsertSpecialChar(pEntry);
19211 return true;
19213 return false;
19216 static void signalActivateEntryInsertSpecialChar(GtkEntry *pEntry)
19218 InsertSpecialChar(pEntry);
19221 static void signalEntryPopulatePopup(GtkEntry* pEntry, GtkWidget* pMenu, gpointer)
19223 if (!GTK_IS_MENU(pMenu))
19224 return;
19226 if (!vcl::GetGetSpecialCharsFunction())
19227 return;
19229 GtkWidget *item = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)).getStr());
19230 gtk_widget_show(item);
19231 g_signal_connect_swapped(item, "activate", G_CALLBACK(signalActivateEntryInsertSpecialChar), pEntry);
19232 gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), item);
19235 #endif
19237 namespace {
19239 GtkBuilder* makeMenuToggleButtonBuilder()
19241 #if !GTK_CHECK_VERSION(4, 0, 0)
19242 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton3.ui");
19243 #else
19244 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton4.ui");
19245 #endif
19246 OUString aPath;
19247 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
19248 return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
19251 #if !GTK_CHECK_VERSION(4, 0, 0)
19253 GtkBuilder* makeComboBoxBuilder()
19255 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/combobox.ui");
19256 OUString aPath;
19257 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
19258 return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
19261 // pop down the toplevel combobox menu when something is activated from a custom
19262 // submenu, i.e. wysiwyg style menu
19263 class CustomRenderMenuButtonHelper : public MenuHelper
19265 private:
19266 GtkToggleButton* m_pComboBox;
19267 public:
19268 CustomRenderMenuButtonHelper(GtkMenu* pMenu, GtkToggleButton* pComboBox)
19269 : MenuHelper(pMenu, false)
19270 , m_pComboBox(pComboBox)
19273 virtual void signal_item_activate(const OUString& /*rIdent*/) override
19275 gtk_toggle_button_set_active(m_pComboBox, false);
19279 #endif
19281 gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
19282 gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
19284 const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
19285 if (aHelpData.mbBalloonHelp) // extended tips
19287 #if !GTK_CHECK_VERSION(4, 0, 0)
19288 // by default use accessible description
19289 AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
19290 const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
19291 if (pDesc && pDesc[0])
19293 gtk_tooltip_set_text(tooltip, pDesc);
19294 return true;
19296 #endif
19299 const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
19300 if (pDesc && pDesc[0])
19302 gtk_tooltip_set_text(tooltip, pDesc);
19303 return true;
19306 return false;
19309 #if GTK_CHECK_VERSION(4, 0, 0)
19311 class GtkInstanceComboBox : public GtkInstanceWidget, public vcl::ISearchableStringList, public virtual weld::ComboBox
19313 private:
19314 GtkComboBox* m_pComboBox;
19315 // GtkOverlay* m_pOverlay;
19316 // GtkTreeView* m_pTreeView;
19317 // GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
19318 GtkWidget* m_pMenuWindow;
19319 GtkTreeModel* m_pTreeModel;
19320 GtkCellRenderer* m_pButtonTextRenderer;
19321 GtkWidget* m_pEntry;
19322 GtkEditable* m_pEditable;
19323 // GtkCellView* m_pCellView;
19324 GtkEventController* m_pKeyController;
19325 GtkEventController* m_pEntryKeyController;
19326 GtkEventController* m_pMenuKeyController;
19327 GtkEventController* m_pEntryFocusController;
19328 // std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
19329 WidgetFont m_aCustomFont;
19330 std::optional<vcl::Font> m_xEntryFont;
19331 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
19332 vcl::QuickSelectionEngine m_aQuickSelectionEngine;
19333 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
19334 #if 0
19335 OUString m_sMenuButtonRow;
19336 #endif
19337 // bool m_bHoverSelection;
19338 // bool m_bMouseInOverlayButton;
19339 bool m_bPopupActive;
19340 bool m_bAutoComplete;
19341 bool m_bAutoCompleteCaseSensitive;
19342 bool m_bChangedByMenu;
19343 bool m_bCustomRenderer;
19344 bool m_bUserSelectEntry;
19345 gint m_nTextCol;
19346 gint m_nIdCol;
19347 // gulong m_nToggleFocusInSignalId;
19348 // gulong m_nToggleFocusOutSignalId;
19349 // gulong m_nRowActivatedSignalId;
19350 gulong m_nChangedSignalId;
19351 gulong m_nPopupShownSignalId;
19352 gulong m_nKeyPressEventSignalId;
19353 gulong m_nEntryInsertTextSignalId;
19354 gulong m_nEntryActivateSignalId;
19355 gulong m_nEntryFocusInSignalId;
19356 gulong m_nEntryFocusOutSignalId;
19357 gulong m_nEntryKeyPressEventSignalId;
19358 guint m_nAutoCompleteIdleId;
19359 // gint m_nNonCustomLineHeight;
19360 gint m_nPrePopupCursorPos;
19361 int m_nMRUCount;
19362 int m_nMaxMRUCount;
19364 static gboolean idleAutoComplete(gpointer widget)
19366 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19367 pThis->auto_complete();
19368 return false;
19371 void auto_complete()
19373 m_nAutoCompleteIdleId = 0;
19374 OUString aStartText = get_active_text();
19375 int nStartPos, nEndPos;
19376 get_entry_selection_bounds(nStartPos, nEndPos);
19377 int nMaxSelection = std::max(nStartPos, nEndPos);
19378 if (nMaxSelection != aStartText.getLength())
19379 return;
19381 disable_notify_events();
19382 int nActive = get_active();
19383 int nStart = nActive;
19385 if (nStart == -1)
19386 nStart = 0;
19388 int nPos = -1;
19390 int nZeroRow = 0;
19391 if (m_nMRUCount)
19392 nZeroRow += (m_nMRUCount + 1);
19394 if (!m_bAutoCompleteCaseSensitive)
19396 // Try match case insensitive from current position
19397 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
19398 if (nPos == -1 && nStart != 0)
19400 // Try match case insensitive, but from start
19401 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
19405 if (nPos == -1)
19407 // Try match case sensitive from current position
19408 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
19409 if (nPos == -1 && nStart != 0)
19411 // Try match case sensitive, but from start
19412 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
19416 if (nPos != -1)
19418 OUString aText = get_text_including_mru(nPos);
19419 if (aText != aStartText)
19421 SolarMutexGuard aGuard;
19422 set_active_including_mru(nPos, true);
19424 select_entry_region(aText.getLength(), aStartText.getLength());
19426 enable_notify_events();
19429 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
19430 gint* position, gpointer widget)
19432 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19433 SolarMutexGuard aGuard;
19434 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
19437 void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
19439 if (m_bPopupActive) // not entered by the user
19440 return;
19442 // first filter inserted text
19443 if (m_aEntryInsertTextHdl.IsSet())
19445 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
19446 const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
19447 if (bContinue && !sText.isEmpty())
19449 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
19450 g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
19451 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
19452 g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
19454 g_signal_stop_emission_by_name(pEntry, "insert-text");
19457 if (m_bAutoComplete)
19459 // now check for autocompletes
19460 if (m_nAutoCompleteIdleId)
19461 g_source_remove(m_nAutoCompleteIdleId);
19462 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
19466 static void signalChanged(GtkComboBox*, gpointer widget)
19468 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19469 SolarMutexGuard aGuard;
19470 pThis->fire_signal_changed();
19473 void fire_signal_changed()
19475 m_bUserSelectEntry = true;
19476 m_bChangedByMenu = m_bPopupActive;
19477 signal_changed();
19478 m_bChangedByMenu = false;
19481 static void signalPopupToggled(GObject*, GParamSpec*, gpointer widget)
19483 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19484 SolarMutexGuard aGuard;
19485 pThis->signal_popup_toggled();
19488 #if 0
19489 int get_popup_height(gint& rPopupWidth)
19491 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
19493 int nMaxRows = rSettings.GetListBoxMaximumLineCount();
19494 bool bAddScrollWidth = false;
19495 int nRows = get_count_including_mru();
19496 if (nMaxRows < nRows)
19498 nRows = nMaxRows;
19499 bAddScrollWidth = true;
19502 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
19503 gint nRowHeight = get_height_row(m_pTreeView, pColumns);
19504 g_list_free(pColumns);
19506 gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
19507 gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
19509 // if we're using a custom renderer, limit the height to the height nMaxRows would be
19510 // for a normal renderer, and then round down to how many custom rows fit in that
19511 // space
19512 if (m_nNonCustomLineHeight != -1 && nRowHeight)
19514 gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
19515 if (nHeight > nNormalHeight)
19517 gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
19518 gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
19519 nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
19523 if (bAddScrollWidth)
19524 rPopupWidth += rSettings.GetScrollBarSize();
19526 return nHeight;
19528 #endif
19530 bool toggle_button_get_active()
19532 GValue value = G_VALUE_INIT;
19533 g_value_init(&value, G_TYPE_BOOLEAN);
19534 g_object_get_property(G_OBJECT(m_pComboBox), "popup-shown", &value);
19535 return g_value_get_boolean(&value);
19538 void menu_toggled()
19540 if (!m_bPopupActive)
19542 #if 0
19543 if (m_bHoverSelection)
19545 // turn hover selection back off until mouse is moved again
19546 // *after* menu is shown again
19547 gtk_tree_view_set_hover_selection(m_pTreeView, false);
19548 m_bHoverSelection = false;
19550 #endif
19552 if (!m_bUserSelectEntry)
19553 set_active_including_mru(m_nPrePopupCursorPos, true);
19555 #if 0
19556 // undo show_menu tooltip blocking
19557 GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
19558 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
19559 if (pFrame)
19560 pFrame->UnblockTooltip();
19561 #endif
19563 else
19565 m_nPrePopupCursorPos = get_active();
19567 m_bUserSelectEntry = false;
19569 // if we are in mru mode always start with the cursor at the top of the menu
19570 if (m_nMaxMRUCount)
19571 set_active_including_mru(0, true);
19575 virtual void signal_popup_toggled() override
19577 m_aQuickSelectionEngine.Reset();
19579 bool bOldPopupActive = m_bPopupActive;
19580 m_bPopupActive = toggle_button_get_active();
19582 menu_toggled();
19584 if (bOldPopupActive != m_bPopupActive)
19586 ComboBox::signal_popup_toggled();
19587 // restore focus to the GtkEntry when the popup is gone, which
19588 // is what the vcl case does, to ease the transition a little,
19589 // but don't do it if the focus was moved out of togglebutton
19590 // by something else already (e.g. font combobox in toolbar
19591 // on a "direct pick" from the menu which moves focus into
19592 // the main document
19593 if (!m_bPopupActive && m_pEntry && has_child_focus())
19595 disable_notify_events();
19596 gtk_widget_grab_focus(m_pEntry);
19597 enable_notify_events();
19602 #if GTK_CHECK_VERSION(4, 0, 0)
19603 static void signalEntryFocusIn(GtkEventControllerFocus*, gpointer widget)
19605 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19606 SolarMutexGuard aGuard;
19607 pThis->signal_entry_focus_in();
19609 #else
19610 static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
19612 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19613 pThis->signal_entry_focus_in();
19614 return false;
19616 #endif
19618 void signal_entry_focus_in()
19620 signal_focus_in();
19623 #if GTK_CHECK_VERSION(4, 0, 0)
19624 static void signalEntryFocusOut(GtkEventControllerFocus*, gpointer widget)
19626 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19627 SolarMutexGuard aGuard;
19628 pThis->signal_entry_focus_out();
19630 #else
19631 static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
19633 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19634 pThis->signal_entry_focus_out();
19635 return false;
19637 #endif
19639 void signal_entry_focus_out()
19641 // if we have an untidy selection on losing focus remove the selection
19642 int nStartPos, nEndPos;
19643 if (get_entry_selection_bounds(nStartPos, nEndPos))
19645 int nMin = std::min(nStartPos, nEndPos);
19646 int nMax = std::max(nStartPos, nEndPos);
19647 if (nMin != 0 || nMax != get_active_text().getLength())
19648 select_entry_region(0, 0);
19650 signal_focus_out();
19653 static void signalEntryActivate(GtkEntry*, gpointer widget)
19655 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19656 pThis->signal_entry_activate();
19659 void signal_entry_activate()
19661 if (m_aEntryActivateHdl.IsSet())
19663 SolarMutexGuard aGuard;
19664 if (m_aEntryActivateHdl.Call(*this))
19665 g_signal_stop_emission_by_name(m_pEntry, "activate");
19667 update_mru();
19670 OUString get(int pos, int col) const
19672 OUString sRet;
19673 GtkTreeIter iter;
19674 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
19676 gchar* pStr;
19677 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
19678 sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
19679 g_free(pStr);
19681 return sRet;
19684 void set(int pos, int col, std::u16string_view rText)
19686 GtkTreeIter iter;
19687 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
19689 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
19690 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
19694 int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
19696 GtkTreeIter iter;
19697 if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
19698 return -1;
19700 int nRet = 0;
19702 if (!bSearchMRUArea && m_nMRUCount)
19704 if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
19705 return -1;
19706 nRet += (m_nMRUCount + 1);
19709 OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
19712 gchar* pStr;
19713 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
19714 const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
19715 g_free(pStr);
19716 if (bEqual)
19717 return nRet;
19718 ++nRet;
19719 } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
19721 return -1;
19724 bool separator_function(const GtkTreePath* path)
19726 return ::separator_function(path, m_aSeparatorRows);
19729 bool separator_function(int pos)
19731 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
19732 bool bRet = separator_function(path);
19733 gtk_tree_path_free(path);
19734 return bRet;
19737 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
19739 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19740 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
19741 bool bRet = pThis->separator_function(path);
19742 gtk_tree_path_free(path);
19743 return bRet;
19746 // https://gitlab.gnome.org/GNOME/gtk/issues/310
19748 // in the absence of a built-in solution
19749 // a) support typeahead for the case where there is no entry widget, typing ahead
19750 // into the button itself will select via the vcl selection engine, a matching
19751 // entry
19752 static gboolean signalKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
19754 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19755 SolarMutexGuard aGuard;
19756 return pThis->signal_key_press(CreateKeyEvent(keyval, keycode, state, 0));
19759 // tdf#131076 we want return in a ComboBox to act like return in a
19760 // GtkEntry and activate the default dialog/assistant button
19761 bool combobox_activate()
19763 GtkWidget *pComboBox = GTK_WIDGET(m_pComboBox);
19764 GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
19765 GtkWindow *pWindow = GTK_WINDOW(pToplevel);
19766 if (!pWindow)
19767 return false;
19768 if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
19769 return false;
19770 bool bDone = false;
19771 GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
19772 if (pDefaultWidget && pDefaultWidget != pComboBox && gtk_widget_get_sensitive(pDefaultWidget))
19773 bDone = gtk_widget_activate(pDefaultWidget);
19774 return bDone;
19777 static gboolean signalEntryKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
19779 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19780 LocalizeDecimalSeparator(keyval);
19781 return pThis->signal_entry_key_press(CreateKeyEvent(keyval, keycode, state, 0));
19784 bool signal_entry_key_press(const KeyEvent& rKEvt)
19786 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
19788 bool bDone = false;
19790 auto nCode = aKeyCode.GetCode();
19791 switch (nCode)
19793 case KEY_DOWN:
19795 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
19796 if (!nKeyMod)
19798 int nCount = get_count_including_mru();
19799 int nActive = get_active_including_mru() + 1;
19800 while (nActive < nCount && separator_function(nActive))
19801 ++nActive;
19802 if (nActive < nCount)
19803 set_active_including_mru(nActive, true);
19804 bDone = true;
19806 else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
19808 gtk_combo_box_popup(m_pComboBox);
19809 bDone = true;
19811 break;
19813 case KEY_UP:
19815 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
19816 if (!nKeyMod)
19818 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
19819 int nActive = get_active_including_mru() - 1;
19820 while (nActive >= nStartBound && separator_function(nActive))
19821 --nActive;
19822 if (nActive >= nStartBound)
19823 set_active_including_mru(nActive, true);
19824 bDone = true;
19826 break;
19828 case KEY_PAGEUP:
19830 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
19831 if (!nKeyMod)
19833 int nCount = get_count_including_mru();
19834 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
19835 int nActive = nStartBound;
19836 while (nActive < nCount && separator_function(nActive))
19837 ++nActive;
19838 if (nActive < nCount)
19839 set_active_including_mru(nActive, true);
19840 bDone = true;
19842 break;
19844 case KEY_PAGEDOWN:
19846 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
19847 if (!nKeyMod)
19849 int nActive = get_count_including_mru() - 1;
19850 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
19851 while (nActive >= nStartBound && separator_function(nActive))
19852 --nActive;
19853 if (nActive >= nStartBound)
19854 set_active_including_mru(nActive, true);
19855 bDone = true;
19857 break;
19859 default:
19860 break;
19863 return bDone;
19866 bool signal_key_press(const KeyEvent& rKEvt)
19868 #if 0
19869 if (m_bHoverSelection)
19871 // once a key is pressed, turn off hover selection until mouse is
19872 // moved again otherwise when the treeview scrolls it jumps to the
19873 // position under the mouse.
19874 gtk_tree_view_set_hover_selection(m_pTreeView, false);
19875 m_bHoverSelection = false;
19877 #endif
19879 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
19881 bool bDone = false;
19883 auto nCode = aKeyCode.GetCode();
19884 switch (nCode)
19886 case KEY_DOWN:
19887 case KEY_UP:
19888 case KEY_PAGEUP:
19889 case KEY_PAGEDOWN:
19890 case KEY_HOME:
19891 case KEY_END:
19892 case KEY_LEFT:
19893 case KEY_RIGHT:
19894 case KEY_RETURN:
19896 m_aQuickSelectionEngine.Reset();
19897 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
19898 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
19899 if (nCode == KEY_RETURN && !nKeyMod)
19901 if (!m_bPopupActive)
19902 bDone = combobox_activate();
19903 else
19905 // treat 'return' as if the active entry was clicked on
19906 signalChanged(m_pComboBox, this);
19907 gtk_combo_box_popdown(m_pComboBox);
19908 bDone = true;
19911 else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
19913 gtk_combo_box_popdown(m_pComboBox);
19914 bDone = true;
19916 else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
19918 gtk_combo_box_popup(m_pComboBox);
19919 bDone = true;
19921 break;
19923 case KEY_ESCAPE:
19925 m_aQuickSelectionEngine.Reset();
19926 if (m_bPopupActive)
19928 gtk_combo_box_popdown(m_pComboBox);
19929 bDone = true;
19931 break;
19933 default:
19934 // tdf#131076 let base space toggle menu popup when it's not already visible
19935 if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
19936 bDone = false;
19937 else
19938 bDone = m_aQuickSelectionEngine.HandleKeyEvent(rKEvt);
19939 break;
19942 if (!bDone)
19944 if (!m_pEntry)
19945 bDone = signal_entry_key_press(rKEvt);
19946 else
19948 // with gtk4-4.2.1 the unconsumed keystrokes don't appear to get to
19949 // the GtkEntry for up/down to move to the next entry without this extra help
19950 // (which means this extra indirection is probably effectively
19951 // the same as if calling signal_entry_key_press directly here)
19952 bDone = gtk_event_controller_key_forward(GTK_EVENT_CONTROLLER_KEY(m_pMenuKeyController), m_pEntry);
19956 return bDone;
19959 vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
19961 int nEntryCount(get_count_including_mru());
19962 if (nPos >= nEntryCount)
19963 nPos = 0;
19964 out_entryText = get_text_including_mru(nPos);
19966 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
19967 // => normalize
19968 return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
19971 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
19973 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
19974 return reinterpret_cast<sal_Int64>(entry) - 1;
19977 int tree_view_get_cursor() const
19979 int nRet = -1;
19980 #if 0
19981 GtkTreePath* path;
19982 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
19983 if (path)
19985 gint depth;
19986 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
19987 nRet = indices[depth-1];
19988 gtk_tree_path_free(path);
19990 #endif
19992 return nRet;
19995 int get_selected_entry() const
19997 if (m_bPopupActive)
19998 return tree_view_get_cursor();
19999 else
20000 return get_active_including_mru();
20003 void set_typeahead_selected_entry(int nSelect)
20005 set_active_including_mru(nSelect, true);
20008 virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
20010 int nCurrentPos = get_selected_entry();
20011 return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
20014 virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
20016 int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
20017 return typeahead_getEntry(nNextPos, out_entryText);
20020 virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
20022 int nSelect = typeahead_getEntryPos(entry);
20023 if (nSelect == get_selected_entry())
20025 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
20026 // to select the given entry by typing its starting letters. No need to act.
20027 return;
20030 // normalize
20031 int nCount = get_count_including_mru();
20032 if (nSelect >= nCount)
20033 nSelect = nCount ? nCount-1 : -1;
20035 set_typeahead_selected_entry(nSelect);
20038 #if 0
20039 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
20041 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20042 pThis->grab_broken(pEvent);
20045 void grab_broken(const GdkEventGrabBroken *event)
20047 if (event->grab_window == nullptr)
20049 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20051 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
20053 //try and regrab, so when we lose the grab to the menu of the color palette
20054 //combobox we regain it so the color palette doesn't itself disappear on next
20055 //click on the color palette combobox
20056 do_grab(GTK_WIDGET(m_pMenuWindow));
20060 static gboolean signalButtonPress(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
20062 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20063 return pThis->button_press(pWidget, pEvent);
20066 bool button_press(GtkWidget* pWidget, GdkEventButton* pEvent)
20068 //we want to pop down if the button was pressed outside our popup
20069 gdouble x = pEvent->x_root;
20070 gdouble y = pEvent->y_root;
20071 gint xoffset, yoffset;
20072 gdk_window_get_root_origin(widget_get_surface(pWidget), &xoffset, &yoffset);
20074 GtkAllocation alloc;
20075 gtk_widget_get_allocation(pWidget, &alloc);
20076 xoffset += alloc.x;
20077 yoffset += alloc.y;
20079 gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow), &alloc);
20080 gint x1 = alloc.x + xoffset;
20081 gint y1 = alloc.y + yoffset;
20082 gint x2 = x1 + alloc.width;
20083 gint y2 = y1 + alloc.height;
20085 if (x > x1 && x < x2 && y > y1 && y < y2)
20086 return false;
20088 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20090 return false;
20093 static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
20095 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20096 pThis->signal_motion();
20097 return false;
20100 void signal_motion()
20102 // if hover-selection was disabled after pressing a key, then turn it back on again
20103 if (!m_bHoverSelection && !m_bMouseInOverlayButton)
20105 gtk_tree_view_set_hover_selection(m_pTreeView, true);
20106 m_bHoverSelection = true;
20109 #endif
20111 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
20113 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20114 pThis->handle_row_activated();
20117 void handle_row_activated()
20119 m_bUserSelectEntry = true;
20120 m_bChangedByMenu = true;
20121 disable_notify_events();
20122 int nActive = get_active();
20123 if (m_pEditable)
20124 gtk_editable_set_text(m_pEditable, OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
20125 #if 0
20126 else
20127 tree_view_set_cursor(nActive);
20128 #endif
20129 // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20130 enable_notify_events();
20131 fire_signal_changed();
20132 update_mru();
20135 void do_clear()
20137 disable_notify_events();
20138 gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
20139 m_aSeparatorRows.clear();
20140 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
20141 m_nMRUCount = 0;
20142 enable_notify_events();
20145 virtual int get_max_mru_count() const override
20147 return m_nMaxMRUCount;
20150 virtual void set_max_mru_count(int nMaxMRUCount) override
20152 m_nMaxMRUCount = nMaxMRUCount;
20153 update_mru();
20156 void update_mru()
20158 int nMRUCount = m_nMRUCount;
20160 if (m_nMaxMRUCount)
20162 OUString sActiveText = get_active_text();
20163 OUString sActiveId = get_active_id();
20164 insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
20165 ++m_nMRUCount;
20167 for (int i = 1; i < m_nMRUCount - 1; ++i)
20169 if (get_text_including_mru(i) == sActiveText)
20171 remove_including_mru(i);
20172 --m_nMRUCount;
20173 break;
20178 while (m_nMRUCount > m_nMaxMRUCount)
20180 remove_including_mru(m_nMRUCount - 1);
20181 --m_nMRUCount;
20184 if (m_nMRUCount && !nMRUCount)
20185 insert_separator_including_mru(m_nMRUCount, "separator");
20186 else if (!m_nMRUCount && nMRUCount)
20187 remove_including_mru(m_nMRUCount); // remove separator
20190 int get_count_including_mru() const
20192 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
20195 int get_active_including_mru() const
20197 return gtk_combo_box_get_active(m_pComboBox);
20200 void set_active_including_mru(int pos, bool bInteractive)
20202 disable_notify_events();
20204 gtk_combo_box_set_active(m_pComboBox, pos);
20206 m_bChangedByMenu = false;
20207 enable_notify_events();
20209 if (bInteractive && !m_bPopupActive)
20210 signal_changed();
20213 int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
20215 return find(rStr, m_nTextCol, bSearchMRU);
20218 int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
20220 return find(rId, m_nIdCol, bSearchMRU);
20223 OUString get_text_including_mru(int pos) const
20225 return get(pos, m_nTextCol);
20228 OUString get_id_including_mru(int pos) const
20230 return get(pos, m_nIdCol);
20233 void set_id_including_mru(int pos, std::u16string_view rId)
20235 set(pos, m_nIdCol, rId);
20238 void remove_including_mru(int pos)
20240 disable_notify_events();
20241 GtkTreeIter iter;
20242 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
20243 if (!m_aSeparatorRows.empty())
20245 bool bFound = false;
20247 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
20249 for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
20251 GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
20252 if (seppath)
20254 if (gtk_tree_path_compare(pPath, seppath) == 0)
20255 bFound = true;
20256 gtk_tree_path_free(seppath);
20258 if (bFound)
20260 m_aSeparatorRows.erase(aIter);
20261 break;
20265 gtk_tree_path_free(pPath);
20267 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
20268 enable_notify_events();
20271 void insert_separator_including_mru(int pos, const OUString& rId)
20273 disable_notify_events();
20274 GtkTreeIter iter;
20275 if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
20276 gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
20277 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
20278 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
20279 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
20280 gtk_tree_path_free(pPath);
20281 enable_notify_events();
20284 void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
20286 disable_notify_events();
20287 GtkTreeIter iter;
20288 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
20289 enable_notify_events();
20292 #if 0
20293 static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
20295 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20296 return pThis->signal_get_child_position(pAllocation);
20299 bool signal_get_child_position(GdkRectangle* pAllocation)
20301 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
20302 return false;
20303 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
20304 return false;
20305 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
20306 if (nRow == -1)
20307 return false;
20309 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
20311 GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
20312 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
20313 tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
20314 gtk_tree_path_free(pPath);
20315 g_list_free(pColumns);
20317 pAllocation->x = aRect.Right() - pAllocation->width;
20318 pAllocation->y = aRect.Top();
20319 pAllocation->height = aRect.GetHeight();
20321 return true;
20324 static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
20326 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20327 pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
20328 return false;
20331 void signal_overlay_button_crossing(bool bEnter)
20333 m_bMouseInOverlayButton = bEnter;
20334 if (!bEnter)
20335 return;
20337 if (m_bHoverSelection)
20339 // once toggled button is pressed, turn off hover selection until
20340 // mouse leaves the overlay button
20341 gtk_tree_view_set_hover_selection(m_pTreeView, false);
20342 m_bHoverSelection = false;
20344 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
20345 assert(nRow != -1);
20346 tree_view_set_cursor(nRow); // select the buttons row
20348 #endif
20350 int include_mru(int pos)
20352 if (m_nMRUCount && pos != -1)
20353 pos += (m_nMRUCount + 1);
20354 return pos;
20357 public:
20358 GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
20359 : GtkInstanceWidget(GTK_WIDGET(pComboBox), pBuilder, bTakeOwnership)
20360 , m_pComboBox(pComboBox)
20361 // , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
20362 // , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
20363 // , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
20364 , m_pMenuWindow(nullptr)
20365 , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
20366 , m_pButtonTextRenderer(nullptr)
20367 // , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
20368 , m_pEntry(GTK_IS_ENTRY(gtk_combo_box_get_child(pComboBox)) ? gtk_combo_box_get_child(pComboBox) : nullptr)
20369 , m_pEditable(GTK_EDITABLE(m_pEntry))
20370 , m_aCustomFont(m_pWidget)
20371 // , m_pCellView(nullptr)
20372 , m_aQuickSelectionEngine(*this)
20373 // , m_bHoverSelection(false)
20374 // , m_bMouseInOverlayButton(false)
20375 , m_bPopupActive(false)
20376 , m_bAutoComplete(false)
20377 , m_bAutoCompleteCaseSensitive(false)
20378 , m_bChangedByMenu(false)
20379 , m_bCustomRenderer(false)
20380 , m_bUserSelectEntry(false)
20381 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
20382 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
20383 // , m_nToggleFocusInSignalId(0)
20384 // , m_nToggleFocusOutSignalId(0)
20385 // , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
20386 , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this))
20387 , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this))
20388 , m_nAutoCompleteIdleId(0)
20389 // , m_nNonCustomLineHeight(-1)
20390 , m_nPrePopupCursorPos(-1)
20391 , m_nMRUCount(0)
20392 , m_nMaxMRUCount(0)
20394 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pComboBox));
20395 pChild; pChild = gtk_widget_get_next_sibling(pChild))
20397 if (GTK_IS_POPOVER(pChild))
20399 m_pMenuWindow = pChild;
20400 break;
20403 SAL_WARN_IF(!m_pMenuWindow, "vcl.gtk", "GtkInstanceComboBox: couldn't find popup menu");
20405 bool bHasEntry = gtk_combo_box_get_has_entry(m_pComboBox);
20406 bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
20408 bool bFindButtonTextRenderer = !bHasEntry;
20409 GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(m_pComboBox);
20410 GList* cells = gtk_cell_layout_get_cells(pCellLayout);
20411 guint i = g_list_length(cells) - 1;;
20412 // reorder the cell renderers
20413 for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
20415 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
20416 gtk_cell_layout_reorder(pCellLayout, pCellRenderer, i--);
20417 if (bFindButtonTextRenderer)
20419 m_pButtonTextRenderer = pCellRenderer;
20420 bFindButtonTextRenderer = false;
20423 g_list_free(cells);
20425 // Seeing as GtkCellRendererPixbuf no longer takes a surface, then insert our own replacement
20426 // to render that instead here
20427 if (bPixbufUsedSurface)
20429 GtkCellRenderer* pSurfaceRenderer = surface_cell_renderer_new();
20430 gtk_cell_layout_pack_start(pCellLayout, pSurfaceRenderer, false);
20431 gtk_cell_layout_reorder(pCellLayout, pSurfaceRenderer, 0);
20432 gtk_cell_layout_set_attributes(pCellLayout, pSurfaceRenderer, "surface", 3, nullptr);
20435 if (bHasEntry)
20437 m_bAutoComplete = true;
20438 m_nEntryInsertTextSignalId = g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalEntryInsertText), this);
20439 m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
20440 m_pEntryFocusController = GTK_EVENT_CONTROLLER(gtk_event_controller_focus_new());
20441 m_nEntryFocusInSignalId = g_signal_connect(m_pEntryFocusController, "enter", G_CALLBACK(signalEntryFocusIn), this);
20442 m_nEntryFocusOutSignalId = g_signal_connect(m_pEntryFocusController, "leave", G_CALLBACK(signalEntryFocusOut), this);
20443 gtk_widget_add_controller(m_pEntry, m_pEntryFocusController);
20444 m_pEntryKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
20445 m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntryKeyController, "key-pressed", G_CALLBACK(signalEntryKeyPress), this);
20446 gtk_widget_add_controller(m_pEntry, m_pEntryKeyController);
20447 m_nKeyPressEventSignalId = 0;
20448 m_pKeyController = nullptr;
20450 else
20452 m_nEntryInsertTextSignalId = 0;
20453 m_nEntryActivateSignalId = 0;
20454 m_pEntryFocusController = nullptr;
20455 m_nEntryFocusInSignalId = 0;
20456 m_nEntryFocusOutSignalId = 0;
20457 m_pEntryKeyController = nullptr;
20458 m_nEntryKeyPressEventSignalId = 0;
20459 m_pKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
20460 m_nKeyPressEventSignalId = g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
20461 gtk_widget_add_controller(GTK_WIDGET(m_pComboBox), m_pKeyController);
20464 // g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
20465 // g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
20466 // g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
20468 // support typeahead for the menu itself, typing into the menu will
20469 // select via the vcl selection engine, a matching entry.
20470 if (m_pMenuWindow)
20472 m_pMenuKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
20473 g_signal_connect(m_pMenuKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
20474 gtk_widget_add_controller(m_pMenuWindow, m_pMenuKeyController);
20476 else
20477 m_pMenuKeyController = nullptr;
20478 #if 0
20479 g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
20480 gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
20481 g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
20482 g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
20483 #endif
20486 virtual int get_active() const override
20488 int nActive = get_active_including_mru();
20489 if (nActive == -1)
20490 return -1;
20492 if (m_nMRUCount)
20494 if (nActive < m_nMRUCount)
20495 nActive = find_text(get_text_including_mru(nActive));
20496 else
20497 nActive -= (m_nMRUCount + 1);
20500 return nActive;
20503 virtual OUString get_active_id() const override
20505 int nActive = get_active();
20506 return nActive != -1 ? get_id(nActive) : OUString();
20509 virtual void set_active_id(const OUString& rStr) override
20511 set_active(find_id(rStr));
20512 m_bChangedByMenu = false;
20515 virtual void set_size_request(int nWidth, int nHeight) override
20517 if (m_pButtonTextRenderer)
20519 // tweak the cell render to get a narrower size to stick
20520 if (nWidth != -1)
20522 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
20523 // the popup menu render them in full, in the interim ellipse both of them
20524 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
20526 // to find out how much of the width of the combobox belongs to the cell, set
20527 // the cell and widget to the min cell width and see what the difference is
20528 int min;
20529 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
20530 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
20531 gtk_widget_set_size_request(m_pWidget, min, -1);
20532 int nNonCellWidth = get_preferred_size().Width() - min;
20534 int nCellWidth = nWidth - nNonCellWidth;
20535 if (nCellWidth >= 0)
20537 // now set the cell to the max width which it can be within the
20538 // requested widget width
20539 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
20542 else
20544 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
20545 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
20549 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
20552 virtual void set_active(int pos) override
20554 set_active_including_mru(include_mru(pos), false);
20557 virtual OUString get_active_text() const override
20559 if (m_pEditable)
20561 const gchar* pText = gtk_editable_get_text(m_pEditable);
20562 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
20565 int nActive = get_active();
20566 if (nActive == -1)
20567 return OUString();
20569 return get_text(nActive);
20572 virtual OUString get_text(int pos) const override
20574 if (m_nMRUCount)
20575 pos += (m_nMRUCount + 1);
20576 return get_text_including_mru(pos);
20579 virtual OUString get_id(int pos) const override
20581 if (m_nMRUCount)
20582 pos += (m_nMRUCount + 1);
20583 return get_id_including_mru(pos);
20586 virtual void set_id(int pos, const OUString& rId) override
20588 if (m_nMRUCount)
20589 pos += (m_nMRUCount + 1);
20590 set_id_including_mru(pos, rId);
20593 virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
20595 freeze();
20597 int nInsertionPoint;
20598 if (!bKeepExisting)
20600 clear();
20601 nInsertionPoint = 0;
20603 else
20604 nInsertionPoint = get_count();
20606 GtkTreeIter iter;
20607 // tdf#125241 inserting backwards is faster
20608 for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
20610 const auto& rItem = *aI;
20611 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
20612 rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
20615 thaw();
20618 virtual void remove(int pos) override
20620 if (m_nMRUCount)
20621 pos += (m_nMRUCount + 1);
20622 remove_including_mru(pos);
20625 virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
20627 insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
20630 virtual void insert_separator(int pos, const OUString& rId) override
20632 pos = pos == -1 ? get_count() : pos;
20633 if (m_nMRUCount)
20634 pos += (m_nMRUCount + 1);
20635 insert_separator_including_mru(pos, rId);
20638 virtual int get_count() const override
20640 int nCount = get_count_including_mru();
20641 if (m_nMRUCount)
20642 nCount -= (m_nMRUCount + 1);
20643 return nCount;
20646 virtual int find_text(const OUString& rStr) const override
20648 int nPos = find_text_including_mru(rStr, false);
20649 if (nPos != -1 && m_nMRUCount)
20650 nPos -= (m_nMRUCount + 1);
20651 return nPos;
20654 virtual int find_id(const OUString& rId) const override
20656 int nPos = find_id_including_mru(rId, false);
20657 if (nPos != -1 && m_nMRUCount)
20658 nPos -= (m_nMRUCount + 1);
20659 return nPos;
20662 virtual void clear() override
20664 do_clear();
20667 virtual void make_sorted() override
20669 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
20670 ::comphelper::getProcessComponentContext(),
20671 Application::GetSettings().GetUILanguageTag().getLocale()));
20672 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
20673 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
20674 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
20677 virtual bool has_entry() const override
20679 return gtk_combo_box_get_has_entry(m_pComboBox);
20682 virtual void set_entry_message_type(weld::EntryMessageType eType) override
20684 assert(m_pEntry);
20685 ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
20688 virtual void set_entry_text(const OUString& rText) override
20690 assert(m_pEditable);
20691 disable_notify_events();
20692 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
20693 enable_notify_events();
20696 virtual void set_entry_width_chars(int nChars) override
20698 assert(m_pEditable);
20699 disable_notify_events();
20700 gtk_editable_set_width_chars(m_pEditable, nChars);
20701 gtk_editable_set_max_width_chars(m_pEditable, nChars);
20702 enable_notify_events();
20705 virtual void set_entry_max_length(int nChars) override
20707 assert(m_pEntry);
20708 disable_notify_events();
20709 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
20710 enable_notify_events();
20713 virtual void select_entry_region(int nStartPos, int nEndPos) override
20715 assert(m_pEditable);
20716 disable_notify_events();
20717 gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
20718 enable_notify_events();
20721 virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
20723 assert(m_pEditable);
20724 return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
20727 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
20729 m_bAutoComplete = bEnable;
20730 m_bAutoCompleteCaseSensitive = bCaseSensitive;
20733 virtual void set_entry_placeholder_text(const OUString& rText) override
20735 assert(m_pEntry);
20736 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
20739 virtual void set_entry_editable(bool bEditable) override
20741 assert(m_pEditable);
20742 gtk_editable_set_editable(m_pEditable, bEditable);
20745 virtual void cut_entry_clipboard() override
20747 assert(m_pEntry);
20748 gtk_widget_activate_action(m_pEntry, "cut.clipboard", nullptr);
20751 virtual void copy_entry_clipboard() override
20753 assert(m_pEntry);
20754 gtk_widget_activate_action(m_pEntry, "copy.clipboard", nullptr);
20757 virtual void paste_entry_clipboard() override
20759 assert(m_pEntry);
20760 gtk_widget_activate_action(m_pEntry, "paste.clipboard", nullptr);
20763 virtual void set_font(const vcl::Font& rFont) override
20765 m_aCustomFont.use_custom_font(&rFont, u"combobox");
20768 virtual vcl::Font get_font() override
20770 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
20771 return *pFont;
20772 return GtkInstanceWidget::get_font();
20775 virtual void set_entry_font(const vcl::Font& rFont) override
20777 m_xEntryFont = rFont;
20778 assert(m_pEntry);
20779 PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
20780 PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
20781 update_attr_list(pAttrList, rFont);
20782 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
20783 pango_attr_list_unref(pAttrList);
20786 virtual vcl::Font get_entry_font() override
20788 if (m_xEntryFont)
20789 return *m_xEntryFont;
20790 assert(m_pEntry);
20791 PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
20792 return pango_to_vcl(pango_context_get_font_description(pContext),
20793 Application::GetSettings().GetUILanguageTag().getLocale());
20796 virtual void disable_notify_events() override
20798 if (m_pEditable)
20800 g_signal_handler_block(m_pEditable, m_nEntryInsertTextSignalId);
20801 g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
20802 g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusInSignalId);
20803 g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusOutSignalId);
20804 g_signal_handler_block(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
20806 else
20807 g_signal_handler_block(m_pKeyController, m_nKeyPressEventSignalId);
20809 // if (m_nToggleFocusInSignalId)
20810 // g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
20811 // if (m_nToggleFocusOutSignalId)
20812 // g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
20813 // g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
20814 g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
20815 g_signal_handler_block(m_pComboBox, m_nChangedSignalId);
20816 GtkInstanceWidget::disable_notify_events();
20819 virtual void enable_notify_events() override
20821 GtkInstanceWidget::enable_notify_events();
20822 g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
20823 g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId);
20824 // g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
20825 // if (m_nToggleFocusInSignalId)
20826 // g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
20827 // if (m_nToggleFocusOutSignalId)
20828 // g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
20829 if (m_pEditable)
20831 g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
20832 g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusInSignalId);
20833 g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusOutSignalId);
20834 g_signal_handler_unblock(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
20835 g_signal_handler_unblock(m_pEditable, m_nEntryInsertTextSignalId);
20837 else
20838 g_signal_handler_unblock(m_pKeyController, m_nKeyPressEventSignalId);
20841 virtual void freeze() override
20843 disable_notify_events();
20844 bool bIsFirstFreeze = IsFirstFreeze();
20845 GtkInstanceWidget::freeze();
20846 if (bIsFirstFreeze)
20848 g_object_ref(m_pTreeModel);
20849 // gtk_tree_view_set_model(m_pTreeView, nullptr);
20850 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
20851 if (m_xSorter)
20853 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
20854 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
20857 enable_notify_events();
20860 virtual void thaw() override
20862 disable_notify_events();
20863 if (IsLastThaw())
20865 if (m_xSorter)
20867 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
20868 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
20870 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
20871 // gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
20872 g_object_unref(m_pTreeModel);
20874 GtkInstanceWidget::thaw();
20875 enable_notify_events();
20878 virtual bool get_popup_shown() const override
20880 return m_bPopupActive;
20883 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
20885 // if (!m_nToggleFocusInSignalId)
20886 // m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
20887 GtkInstanceWidget::connect_focus_in(rLink);
20890 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
20892 // if (!m_nToggleFocusOutSignalId)
20893 // m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
20894 GtkInstanceWidget::connect_focus_out(rLink);
20897 virtual void grab_focus() override
20899 if (has_focus())
20900 return;
20901 if (m_pEntry)
20902 gtk_widget_grab_focus(m_pEntry);
20903 else
20905 // gtk_widget_grab_focus(m_pToggleButton);
20906 gtk_widget_grab_focus(GTK_WIDGET(m_pComboBox));
20910 virtual bool has_focus() const override
20912 if (m_pEntry && gtk_widget_has_focus(m_pEntry))
20913 return true;
20915 // if (gtk_widget_has_focus(m_pToggleButton))
20916 // return true;
20918 #if 0
20919 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
20921 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
20922 return true;
20924 #endif
20926 return GtkInstanceWidget::has_focus();
20929 virtual bool changed_by_direct_pick() const override
20931 return m_bChangedByMenu;
20934 virtual void set_custom_renderer(bool bOn) override
20936 if (bOn == m_bCustomRenderer)
20937 return;
20938 #if 0
20939 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
20940 // keep the original height around for optimal popup height calculation
20941 m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
20942 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
20943 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
20944 if (bOn)
20946 GtkCellRenderer *pRenderer = custom_cell_renderer_new();
20947 GValue value = G_VALUE_INIT;
20948 g_value_init(&value, G_TYPE_POINTER);
20949 g_value_set_pointer(&value, static_cast<gpointer>(this));
20950 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
20951 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
20952 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
20953 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
20955 else
20957 GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
20958 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
20959 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
20961 g_list_free(pColumns);
20962 m_bCustomRenderer = bOn;
20963 #endif
20966 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
20968 signal_custom_render(rOutput, rRect, bSelected, rId);
20971 Size call_signal_custom_get_size(VirtualDevice& rOutput)
20973 return signal_custom_get_size(rOutput);
20976 VclPtr<VirtualDevice> create_render_virtual_device() const override
20978 return create_virtual_device();
20981 virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
20983 #if 0
20984 m_xCustomMenuButtonHelper.reset();
20985 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
20986 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
20987 gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
20988 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
20989 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
20990 if (pMenuWidget)
20991 m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
20992 m_sMenuButtonRow = rIdent;
20993 #else
20994 (void)rIdent; (void)pMenu;
20995 #endif
20998 OUString get_mru_entries() const override
21000 const sal_Unicode cSep = ';';
21002 OUStringBuffer aEntries;
21003 for (sal_Int32 n = 0; n < m_nMRUCount; n++)
21005 aEntries.append(get_text_including_mru(n));
21006 if (n < m_nMRUCount - 1)
21007 aEntries.append(cSep);
21009 return aEntries.makeStringAndClear();
21012 virtual void set_mru_entries(const OUString& rEntries) override
21014 const sal_Unicode cSep = ';';
21016 // Remove old MRU entries
21017 for (sal_Int32 n = m_nMRUCount; n;)
21018 remove_including_mru(--n);
21020 sal_Int32 nMRUCount = 0;
21021 sal_Int32 nIndex = 0;
21024 OUString aEntry = rEntries.getToken(0, cSep, nIndex);
21025 // Accept only existing entries
21026 int nPos = find_text(aEntry);
21027 if (nPos != -1)
21029 OUString sId = get_id(nPos);
21030 insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
21031 ++nMRUCount;
21034 while (nIndex >= 0);
21036 if (nMRUCount && !m_nMRUCount)
21037 insert_separator_including_mru(nMRUCount, "separator");
21038 else if (!nMRUCount && m_nMRUCount)
21039 remove_including_mru(m_nMRUCount); // remove separator
21041 m_nMRUCount = nMRUCount;
21044 int get_menu_button_width() const override
21046 #if 0
21047 bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
21048 if (!bVisible)
21049 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
21050 gint nWidth;
21051 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
21052 if (!bVisible)
21053 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
21054 return nWidth;
21055 #else
21056 return 0;
21057 #endif
21060 virtual void set_max_drop_down_rows(int) override
21062 SAL_WARN( "vcl.gtk", "set_max_drop_down_rows unimplemented");
21065 virtual ~GtkInstanceComboBox() override
21067 // m_xCustomMenuButtonHelper.reset();
21068 do_clear();
21069 if (m_nAutoCompleteIdleId)
21070 g_source_remove(m_nAutoCompleteIdleId);
21071 if (m_pEditable)
21073 g_signal_handler_disconnect(m_pEditable, m_nEntryInsertTextSignalId);
21074 g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
21075 g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusInSignalId);
21076 g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusOutSignalId);
21077 g_signal_handler_disconnect(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
21079 else
21080 g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
21081 // if (m_nToggleFocusInSignalId)
21082 // g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
21083 // if (m_nToggleFocusOutSignalId)
21084 // g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
21085 // g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
21086 g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
21087 g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId);
21089 // gtk_tree_view_set_model(m_pTreeView, nullptr);
21094 #else
21096 class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
21098 private:
21099 GtkBuilder* m_pComboBuilder;
21100 GtkComboBox* m_pComboBox;
21101 GtkOverlay* m_pOverlay;
21102 GtkTreeView* m_pTreeView;
21103 GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
21104 GtkWindow* m_pMenuWindow;
21105 GtkTreeModel* m_pTreeModel;
21106 GtkCellRenderer* m_pButtonTextRenderer;
21107 GtkCellRenderer* m_pMenuTextRenderer;
21108 GtkWidget* m_pToggleButton;
21109 GtkWidget* m_pEntry;
21110 GtkCellView* m_pCellView;
21111 WidgetFont m_aCustomFont;
21112 std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
21113 std::optional<vcl::Font> m_xEntryFont;
21114 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
21115 vcl::QuickSelectionEngine m_aQuickSelectionEngine;
21116 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
21117 OUString m_sMenuButtonRow;
21118 bool m_bHoverSelection;
21119 bool m_bMouseInOverlayButton;
21120 bool m_bPopupActive;
21121 bool m_bAutoComplete;
21122 bool m_bAutoCompleteCaseSensitive;
21123 bool m_bChangedByMenu;
21124 bool m_bCustomRenderer;
21125 bool m_bActivateCalled;
21126 gint m_nTextCol;
21127 gint m_nIdCol;
21128 gulong m_nToggleFocusInSignalId;
21129 gulong m_nToggleFocusOutSignalId;
21130 gulong m_nRowActivatedSignalId;
21131 gulong m_nChangedSignalId;
21132 gulong m_nPopupShownSignalId;
21133 gulong m_nKeyPressEventSignalId;
21134 gulong m_nEntryInsertTextSignalId;
21135 gulong m_nEntryActivateSignalId;
21136 gulong m_nEntryFocusInSignalId;
21137 gulong m_nEntryFocusOutSignalId;
21138 gulong m_nEntryKeyPressEventSignalId;
21139 gulong m_nEntryPopulatePopupMenuSignalId;
21140 guint m_nAutoCompleteIdleId;
21141 gint m_nNonCustomLineHeight;
21142 gint m_nPrePopupCursorPos;
21143 int m_nMRUCount;
21144 int m_nMaxMRUCount;
21145 int m_nMaxDropdownRows;
21147 static gboolean idleAutoComplete(gpointer widget)
21149 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21150 pThis->auto_complete();
21151 return false;
21154 void auto_complete()
21156 m_nAutoCompleteIdleId = 0;
21157 OUString aStartText = get_active_text();
21158 int nStartPos, nEndPos;
21159 get_entry_selection_bounds(nStartPos, nEndPos);
21160 int nMaxSelection = std::max(nStartPos, nEndPos);
21161 if (nMaxSelection != aStartText.getLength())
21162 return;
21164 disable_notify_events();
21165 int nActive = get_active();
21166 int nStart = nActive;
21168 if (nStart == -1)
21169 nStart = 0;
21171 int nPos = -1;
21173 int nZeroRow = 0;
21174 if (m_nMRUCount)
21175 nZeroRow += (m_nMRUCount + 1);
21177 if (!m_bAutoCompleteCaseSensitive)
21179 // Try match case insensitive from current position
21180 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
21181 if (nPos == -1 && nStart != 0)
21183 // Try match case insensitive, but from start
21184 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
21188 if (nPos == -1)
21190 // Try match case sensitive from current position
21191 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
21192 if (nPos == -1 && nStart != 0)
21194 // Try match case sensitive, but from start
21195 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
21199 if (nPos != -1)
21201 OUString aText = get_text_including_mru(nPos);
21202 if (aText != aStartText)
21204 SolarMutexGuard aGuard;
21205 set_active_including_mru(nPos, true);
21207 select_entry_region(aText.getLength(), aStartText.getLength());
21209 enable_notify_events();
21212 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
21213 gint* position, gpointer widget)
21215 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21216 SolarMutexGuard aGuard;
21217 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
21220 void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
21222 // first filter inserted text
21223 if (m_aEntryInsertTextHdl.IsSet())
21225 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
21226 const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
21227 if (bContinue && !sText.isEmpty())
21229 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
21230 g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
21231 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
21232 g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
21234 g_signal_stop_emission_by_name(pEntry, "insert-text");
21236 if (m_bAutoComplete)
21238 // now check for autocompletes
21239 if (m_nAutoCompleteIdleId)
21240 g_source_remove(m_nAutoCompleteIdleId);
21241 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
21245 static void signalChanged(GtkEntry*, gpointer widget)
21247 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21248 SolarMutexGuard aGuard;
21249 pThis->fire_signal_changed();
21252 void fire_signal_changed()
21254 signal_changed();
21255 m_bChangedByMenu = false;
21258 static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget)
21260 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21261 pThis->signal_popup_toggled();
21264 int get_popup_height(gint& rPopupWidth)
21266 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
21268 int nMaxRows = m_nMaxDropdownRows == -1 ? rSettings.GetListBoxMaximumLineCount() : m_nMaxDropdownRows;
21269 bool bAddScrollWidth = false;
21270 int nRows = get_count_including_mru();
21271 if (nMaxRows < nRows)
21273 nRows = nMaxRows;
21274 bAddScrollWidth = true;
21277 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
21278 gint nRowHeight = get_height_row(m_pTreeView, pColumns);
21279 g_list_free(pColumns);
21281 gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
21282 gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
21284 // if we're using a custom renderer, limit the height to the height nMaxRows would be
21285 // for a normal renderer, and then round down to how many custom rows fit in that
21286 // space
21287 if (m_nNonCustomLineHeight != -1 && nRowHeight)
21289 gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
21290 if (nHeight > nNormalHeight)
21292 gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
21293 gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
21294 nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
21298 if (bAddScrollWidth)
21299 rPopupWidth += rSettings.GetScrollBarSize();
21301 return nHeight;
21304 void menu_toggled()
21306 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)))
21308 if (m_bHoverSelection)
21310 // turn hover selection back off until mouse is moved again
21311 // *after* menu is shown again
21312 gtk_tree_view_set_hover_selection(m_pTreeView, false);
21313 m_bHoverSelection = false;
21316 bool bHadFocus = gtk_window_has_toplevel_focus(m_pMenuWindow);
21318 do_ungrab(GTK_WIDGET(m_pMenuWindow));
21320 gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
21322 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
21323 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
21325 // so gdk_window_move_to_rect will work again the next time
21326 gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow));
21328 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), -1, -1);
21330 if (!m_bActivateCalled)
21331 tree_view_set_cursor(m_nPrePopupCursorPos);
21333 // undo show_menu tooltip blocking
21334 GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
21335 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
21336 if (pFrame)
21337 pFrame->UnblockTooltip();
21339 if (bHadFocus)
21341 GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
21342 void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
21343 if (pParentIsPopover)
21344 do_grab(m_pToggleButton);
21345 gtk_widget_grab_focus(m_pToggleButton);
21348 else
21350 GtkWidget* pComboBox = GTK_WIDGET(getContainer());
21352 gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
21353 GtkRequisition size;
21354 gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow), nullptr, &size);
21356 gint nPopupWidth = size.width;
21357 gint nPopupHeight = get_popup_height(nPopupWidth);
21358 nPopupWidth = std::max(nPopupWidth, nComboWidth);
21360 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight);
21362 m_nPrePopupCursorPos = get_active();
21364 m_bActivateCalled = false;
21366 // if we are in mru mode always start with the cursor at the top of the menu
21367 if (m_nMaxMRUCount)
21368 tree_view_set_cursor(0);
21370 GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pComboBox), gtk_widget_get_allocated_height(pComboBox) };
21371 show_menu(pComboBox, m_pMenuWindow, aAnchor, weld::Placement::Under, true);
21372 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
21373 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
21377 virtual void signal_popup_toggled() override
21379 m_aQuickSelectionEngine.Reset();
21381 menu_toggled();
21383 bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
21384 if (m_bPopupActive == bIsShown)
21385 return;
21387 m_bPopupActive = bIsShown;
21388 ComboBox::signal_popup_toggled();
21389 if (!m_bPopupActive && m_pEntry)
21391 disable_notify_events();
21392 //restore focus to the GtkEntry when the popup is gone, which
21393 //is what the vcl case does, to ease the transition a little
21394 gtk_widget_grab_focus(m_pEntry);
21395 enable_notify_events();
21397 // tdf#160971: For some reason, the tree view in the no longer visible
21398 // popup still incorrectly assumes it has focus in addition to the now
21399 // actually focused entry.
21400 // That would cause it to send invalid active-descendant-changed a11y events when
21401 // the selected entry changes, e.g. breaking focus tracking by the Orca screen reader.
21402 // Manually unset focus to avoid that
21403 assert(!gtk_widget_is_visible(GTK_WIDGET(m_pTreeView)));
21404 const bool bTreeViewFocus = gtk_widget_has_focus(GTK_WIDGET(m_pTreeView));
21405 if (bTreeViewFocus)
21407 SAL_WARN("vcl.gtk", "No more visible tree view in combobox still incorrectly "
21408 "claims having focus - unsetting manually.");
21409 GdkWindow* pWindow = gtk_widget_get_window(GTK_WIDGET(m_pTreeView));
21410 GdkEvent* pEvent = gdk_event_new(GDK_FOCUS_CHANGE);
21411 pEvent->focus_change.type = GDK_FOCUS_CHANGE;
21412 pEvent->focus_change.window = pWindow;
21413 if (pWindow)
21414 g_object_ref(pWindow);
21415 pEvent->focus_change.in = 0;
21416 gtk_widget_send_focus_change(GTK_WIDGET(m_pTreeView), pEvent);
21417 gdk_event_free(pEvent);
21422 static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
21424 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21425 pThis->signal_entry_focus_in();
21426 return false;
21429 void signal_entry_focus_in()
21431 signal_focus_in();
21434 static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
21436 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21437 pThis->signal_entry_focus_out();
21438 return false;
21441 void signal_entry_focus_out()
21443 // if we have an untidy selection on losing focus remove the selection
21444 int nStartPos, nEndPos;
21445 if (get_entry_selection_bounds(nStartPos, nEndPos))
21447 int nMin = std::min(nStartPos, nEndPos);
21448 int nMax = std::max(nStartPos, nEndPos);
21449 if (nMin != 0 || nMax != get_active_text().getLength())
21450 select_entry_region(0, 0);
21452 signal_focus_out();
21455 static void signalEntryActivate(GtkEntry*, gpointer widget)
21457 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21458 pThis->signal_entry_activate();
21461 void signal_entry_activate()
21463 if (m_aEntryActivateHdl.IsSet())
21465 SolarMutexGuard aGuard;
21466 if (m_aEntryActivateHdl.Call(*this))
21467 g_signal_stop_emission_by_name(m_pEntry, "activate");
21469 update_mru();
21472 OUString get(int pos, int col) const
21474 OUString sRet;
21475 GtkTreeIter iter;
21476 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
21478 gchar* pStr;
21479 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
21480 sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
21481 g_free(pStr);
21483 return sRet;
21486 void set(int pos, int col, std::u16string_view rText)
21488 GtkTreeIter iter;
21489 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
21491 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
21492 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
21496 int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
21498 GtkTreeIter iter;
21499 if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
21500 return -1;
21502 int nRet = 0;
21504 if (!bSearchMRUArea && m_nMRUCount)
21506 if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
21507 return -1;
21508 nRet += (m_nMRUCount + 1);
21511 OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
21514 gchar* pStr;
21515 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
21516 const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
21517 g_free(pStr);
21518 if (bEqual)
21519 return nRet;
21520 ++nRet;
21521 } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
21523 return -1;
21526 bool separator_function(const GtkTreePath* path)
21528 return ::separator_function(path, m_aSeparatorRows);
21531 bool separator_function(int pos)
21533 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
21534 bool bRet = separator_function(path);
21535 gtk_tree_path_free(path);
21536 return bRet;
21539 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
21541 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21542 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
21543 bool bRet = pThis->separator_function(path);
21544 gtk_tree_path_free(path);
21545 return bRet;
21548 // https://gitlab.gnome.org/GNOME/gtk/issues/310
21550 // in the absence of a built-in solution
21551 // a) support typeahead for the case where there is no entry widget, typing ahead
21552 // into the button itself will select via the vcl selection engine, a matching
21553 // entry
21554 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
21556 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21557 return pThis->signal_key_press(pEvent);
21560 // tdf#131076 we want return in a ComboBox to act like return in a
21561 // GtkEntry and activate the default dialog/assistant button
21562 bool combobox_activate()
21564 GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton);
21565 GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
21566 GtkWindow *pWindow = GTK_WINDOW(pToplevel);
21567 if (!pWindow)
21568 return false;
21569 if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
21570 return false;
21571 bool bDone = false;
21572 GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
21573 if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget))
21574 bDone = gtk_widget_activate(pDefaultWidget);
21575 return bDone;
21578 static gboolean signalEntryKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer widget)
21580 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21581 LocalizeDecimalSeparator(pEvent->keyval);
21582 if (signalEntryInsertSpecialCharKeyPress(pEntry, pEvent, nullptr))
21583 return true;
21584 return pThis->signal_entry_key_press(pEvent);
21587 bool signal_entry_key_press(const GdkEventKey* pEvent)
21589 KeyEvent aKEvt(GtkToVcl(*pEvent));
21591 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
21593 bool bDone = false;
21595 auto nCode = aKeyCode.GetCode();
21596 switch (nCode)
21598 case KEY_DOWN:
21600 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
21601 if (!nKeyMod)
21603 int nCount = get_count_including_mru();
21604 int nActive = get_active_including_mru() + 1;
21605 while (nActive < nCount && separator_function(nActive))
21606 ++nActive;
21607 if (nActive < nCount)
21608 set_active_including_mru(nActive, true);
21609 bDone = true;
21611 else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
21613 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
21614 bDone = true;
21616 break;
21618 case KEY_UP:
21620 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
21621 if (!nKeyMod)
21623 int nStartBound = m_bPopupActive || !m_nMRUCount ? 0 : (m_nMRUCount + 1);
21624 int nActive = get_active_including_mru() - 1;
21625 while (nActive >= nStartBound && separator_function(nActive))
21626 --nActive;
21627 if (nActive >= nStartBound)
21628 set_active_including_mru(nActive, true);
21629 bDone = true;
21631 break;
21633 case KEY_PAGEUP:
21635 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
21636 if (!nKeyMod)
21638 int nCount = get_count_including_mru();
21639 int nStartBound = m_bPopupActive || !m_nMaxMRUCount ? 0 : (m_nMRUCount + 1);
21640 int nActive = nStartBound;
21641 while (nActive < nCount && separator_function(nActive))
21642 ++nActive;
21643 if (nActive < nCount)
21644 set_active_including_mru(nActive, true);
21645 bDone = true;
21647 break;
21649 case KEY_PAGEDOWN:
21651 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
21652 if (!nKeyMod)
21654 int nActive = get_count_including_mru() - 1;
21655 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
21656 while (nActive >= nStartBound && separator_function(nActive))
21657 --nActive;
21658 if (nActive >= nStartBound)
21659 set_active_including_mru(nActive, true);
21660 bDone = true;
21662 break;
21664 default:
21665 break;
21668 return bDone;
21671 bool signal_key_press(const GdkEventKey* pEvent)
21673 if (m_bHoverSelection)
21675 // once a key is pressed, turn off hover selection until mouse is
21676 // moved again otherwise when the treeview scrolls it jumps to the
21677 // position under the mouse.
21678 gtk_tree_view_set_hover_selection(m_pTreeView, false);
21679 m_bHoverSelection = false;
21682 KeyEvent aKEvt(GtkToVcl(*pEvent));
21684 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
21686 bool bDone = false;
21688 auto nCode = aKeyCode.GetCode();
21689 switch (nCode)
21691 case KEY_DOWN:
21692 case KEY_UP:
21693 case KEY_PAGEUP:
21694 case KEY_PAGEDOWN:
21695 case KEY_HOME:
21696 case KEY_END:
21697 case KEY_LEFT:
21698 case KEY_RIGHT:
21699 case KEY_RETURN:
21701 m_aQuickSelectionEngine.Reset();
21702 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
21703 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
21704 if (nCode == KEY_RETURN && !nKeyMod && !m_bPopupActive)
21705 bDone = combobox_activate();
21706 else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
21708 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
21709 bDone = true;
21711 else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
21713 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
21714 bDone = true;
21716 break;
21718 case KEY_ESCAPE:
21720 m_aQuickSelectionEngine.Reset();
21721 if (m_bPopupActive)
21723 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
21724 bDone = true;
21726 break;
21728 default:
21729 // tdf#131076 let base space toggle menu popup when it's not already visible
21730 if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
21731 bDone = false;
21732 else
21733 bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
21734 break;
21737 if (!bDone && !m_pEntry)
21738 bDone = signal_entry_key_press(pEvent);
21740 return bDone;
21743 vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
21745 int nEntryCount(get_count_including_mru());
21746 if (nPos >= nEntryCount)
21747 nPos = 0;
21748 out_entryText = get_text_including_mru(nPos);
21750 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
21751 // => normalize
21752 return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
21755 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
21757 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
21758 return reinterpret_cast<sal_Int64>(entry) - 1;
21761 void tree_view_set_cursor(int pos)
21763 GtkTreePath* path;
21764 if (pos == -1)
21766 path = gtk_tree_path_new_from_indices(G_MAXINT, -1);
21767 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
21768 if (m_pCellView)
21769 gtk_cell_view_set_displayed_row(m_pCellView, nullptr);
21771 else
21773 path = gtk_tree_path_new_from_indices(pos, -1);
21774 if (gtk_tree_view_get_model(m_pTreeView))
21775 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
21776 if (m_pCellView)
21777 gtk_cell_view_set_displayed_row(m_pCellView, path);
21779 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
21780 gtk_tree_path_free(path);
21783 int tree_view_get_cursor() const
21785 int nRet = -1;
21787 GtkTreePath* path;
21788 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
21789 if (path)
21791 gint depth;
21792 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
21793 nRet = indices[depth-1];
21794 gtk_tree_path_free(path);
21797 return nRet;
21800 int get_selected_entry() const
21802 if (m_bPopupActive)
21803 return tree_view_get_cursor();
21804 else
21805 return get_active_including_mru();
21808 void set_typeahead_selected_entry(int nSelect)
21810 if (m_bPopupActive)
21811 tree_view_set_cursor(nSelect);
21812 else
21813 set_active_including_mru(nSelect, true);
21816 virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
21818 int nCurrentPos = get_selected_entry();
21819 return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
21822 virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
21824 int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
21825 return typeahead_getEntry(nNextPos, out_entryText);
21828 virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
21830 int nSelect = typeahead_getEntryPos(entry);
21831 if (nSelect == get_selected_entry())
21833 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
21834 // to select the given entry by typing its starting letters. No need to act.
21835 return;
21838 // normalize
21839 int nCount = get_count_including_mru();
21840 if (nSelect >= nCount)
21841 nSelect = nCount ? nCount-1 : -1;
21843 set_typeahead_selected_entry(nSelect);
21846 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
21848 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21849 pThis->grab_broken(pEvent);
21852 void grab_broken(const GdkEventGrabBroken *event)
21854 if (event->grab_window == nullptr)
21856 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
21858 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
21860 //try and regrab, so when we lose the grab to the menu of the color palette
21861 //combobox we regain it so the color palette doesn't itself disappear on next
21862 //click on the color palette combobox
21863 do_grab(GTK_WIDGET(m_pMenuWindow));
21867 static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
21869 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21870 return pThis->button_press(pEvent);
21873 bool button_press(GdkEventButton* pEvent)
21875 //we want to pop down if the button was pressed outside our popup
21876 if (button_event_is_outside(GTK_WIDGET(m_pMenuWindow), pEvent))
21877 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
21878 return false;
21881 static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
21883 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21884 pThis->signal_motion();
21885 return false;
21888 void signal_motion()
21890 // if hover-selection was disabled after pressing a key, then turn it back on again
21891 if (!m_bHoverSelection && !m_bMouseInOverlayButton)
21893 gtk_tree_view_set_hover_selection(m_pTreeView, true);
21894 m_bHoverSelection = true;
21898 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
21900 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21901 pThis->handle_row_activated();
21904 void handle_row_activated()
21906 m_bActivateCalled = true;
21907 m_bChangedByMenu = true;
21908 disable_notify_events();
21909 int nActive = get_active();
21910 if (m_pEntry)
21911 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
21912 else
21913 tree_view_set_cursor(nActive);
21914 enable_notify_events();
21915 fire_signal_changed();
21916 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
21917 update_mru();
21920 void do_clear()
21922 disable_notify_events();
21923 gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
21924 m_aSeparatorRows.clear();
21925 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
21926 m_nMRUCount = 0;
21927 enable_notify_events();
21930 virtual int get_max_mru_count() const override
21932 return m_nMaxMRUCount;
21935 virtual void set_max_mru_count(int nMaxMRUCount) override
21937 m_nMaxMRUCount = nMaxMRUCount;
21938 update_mru();
21941 void update_mru()
21943 int nMRUCount = m_nMRUCount;
21945 if (m_nMaxMRUCount)
21947 OUString sActiveText = get_active_text();
21948 OUString sActiveId = get_active_id();
21949 insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
21950 ++m_nMRUCount;
21952 for (int i = 1; i < m_nMRUCount - 1; ++i)
21954 if (get_text_including_mru(i) == sActiveText)
21956 remove_including_mru(i);
21957 --m_nMRUCount;
21958 break;
21963 while (m_nMRUCount > m_nMaxMRUCount)
21965 remove_including_mru(m_nMRUCount - 1);
21966 --m_nMRUCount;
21969 if (m_nMRUCount && !nMRUCount)
21970 insert_separator_including_mru(m_nMRUCount, u"separator"_ustr);
21971 else if (!m_nMRUCount && nMRUCount)
21972 remove_including_mru(m_nMRUCount); // remove separator
21975 int get_count_including_mru() const
21977 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
21980 int get_active_including_mru() const
21982 return tree_view_get_cursor();
21985 void set_active_including_mru(int pos, bool bInteractive)
21987 assert(gtk_tree_view_get_model(m_pTreeView) && "don't set_active when frozen, set_active after thaw. Note selection doesn't survive a freeze");
21989 disable_notify_events();
21991 tree_view_set_cursor(pos);
21993 if (m_pEntry)
21995 if (pos != -1)
21996 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr());
21997 else
21998 gtk_entry_set_text(GTK_ENTRY(m_pEntry), "");
22001 m_bChangedByMenu = false;
22002 enable_notify_events();
22004 if (bInteractive && !m_bPopupActive)
22005 signal_changed();
22008 int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
22010 return find(rStr, m_nTextCol, bSearchMRU);
22013 int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
22015 return find(rId, m_nIdCol, bSearchMRU);
22018 OUString get_text_including_mru(int pos) const
22020 return get(pos, m_nTextCol);
22023 OUString get_id_including_mru(int pos) const
22025 return get(pos, m_nIdCol);
22028 void set_id_including_mru(int pos, std::u16string_view rId)
22030 set(pos, m_nIdCol, rId);
22033 void remove_including_mru(int pos)
22035 disable_notify_events();
22036 GtkTreeIter iter;
22037 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
22038 if (!m_aSeparatorRows.empty())
22040 bool bFound = false;
22042 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
22044 for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
22046 GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
22047 if (seppath)
22049 if (gtk_tree_path_compare(pPath, seppath) == 0)
22050 bFound = true;
22051 gtk_tree_path_free(seppath);
22053 if (bFound)
22055 m_aSeparatorRows.erase(aIter);
22056 break;
22060 gtk_tree_path_free(pPath);
22062 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
22063 enable_notify_events();
22066 void insert_separator_including_mru(int pos, const OUString& rId)
22068 disable_notify_events();
22069 GtkTreeIter iter;
22070 if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
22071 gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
22072 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
22073 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
22074 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
22075 gtk_tree_path_free(pPath);
22076 enable_notify_events();
22079 void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
22081 disable_notify_events();
22082 GtkTreeIter iter;
22083 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
22084 enable_notify_events();
22087 static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
22089 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
22090 return pThis->signal_get_child_position(pAllocation);
22093 bool signal_get_child_position(GdkRectangle* pAllocation)
22095 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
22096 return false;
22097 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
22098 return false;
22099 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
22100 if (nRow == -1)
22101 return false;
22103 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
22105 GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
22106 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
22107 tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
22108 gtk_tree_path_free(pPath);
22109 g_list_free(pColumns);
22111 pAllocation->x = aRect.Right() - pAllocation->width;
22112 pAllocation->y = aRect.Top();
22113 pAllocation->height = aRect.GetHeight();
22115 return true;
22118 static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
22120 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
22121 pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
22122 return false;
22125 void signal_overlay_button_crossing(bool bEnter)
22127 m_bMouseInOverlayButton = bEnter;
22128 if (!bEnter)
22129 return;
22131 if (m_bHoverSelection)
22133 // once toggled button is pressed, turn off hover selection until
22134 // mouse leaves the overlay button
22135 gtk_tree_view_set_hover_selection(m_pTreeView, false);
22136 m_bHoverSelection = false;
22138 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
22139 assert(nRow != -1);
22140 tree_view_set_cursor(nRow); // select the buttons row
22143 void signal_combo_mnemonic_activate()
22145 if (m_pEntry)
22146 gtk_widget_grab_focus(m_pEntry);
22147 else
22148 gtk_widget_grab_focus(m_pToggleButton);
22151 static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
22153 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
22154 pThis->signal_combo_mnemonic_activate();
22155 return true;
22158 static gboolean signalComboTooltipQuery(GtkWidget* /*pWidget*/, gint x, gint y,
22159 gboolean keyboard_mode, GtkTooltip *tooltip,
22160 gpointer widget)
22162 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
22163 return signalTooltipQuery(GTK_WIDGET(pThis->m_pComboBox), x, y, keyboard_mode, tooltip);
22166 int include_mru(int pos)
22168 if (m_nMRUCount && pos != -1)
22169 pos += (m_nMRUCount + 1);
22170 return pos;
22173 public:
22174 GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
22175 : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership)
22176 , m_pComboBuilder(pComboBuilder)
22177 , m_pComboBox(pComboBox)
22178 , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
22179 , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
22180 , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
22181 , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder, "popup")))
22182 , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
22183 , m_pButtonTextRenderer(nullptr)
22184 , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
22185 , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry")))
22186 , m_pCellView(nullptr)
22187 , m_aCustomFont(m_pWidget)
22188 , m_aQuickSelectionEngine(*this)
22189 , m_bHoverSelection(false)
22190 , m_bMouseInOverlayButton(false)
22191 , m_bPopupActive(false)
22192 , m_bAutoComplete(false)
22193 , m_bAutoCompleteCaseSensitive(false)
22194 , m_bChangedByMenu(false)
22195 , m_bCustomRenderer(false)
22196 , m_bActivateCalled(false)
22197 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
22198 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
22199 , m_nToggleFocusInSignalId(0)
22200 , m_nToggleFocusOutSignalId(0)
22201 , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
22202 , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this))
22203 , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this))
22204 , m_nAutoCompleteIdleId(0)
22205 , m_nNonCustomLineHeight(-1)
22206 , m_nPrePopupCursorPos(-1)
22207 , m_nMRUCount(0)
22208 , m_nMaxMRUCount(0)
22209 , m_nMaxDropdownRows(-1)
22211 int nActive = gtk_combo_box_get_active(m_pComboBox);
22213 if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button"))
22214 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button");
22215 if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "novertpad"))
22216 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "novertpad");
22218 if (gtk_widget_get_has_tooltip(GTK_WIDGET(m_pComboBox)))
22220 gtk_widget_set_has_tooltip(GTK_WIDGET(getContainer()), true);
22221 g_signal_connect(getContainer(), "query-tooltip", G_CALLBACK(signalComboTooltipQuery), this);
22224 // take over a11y characteristics from the stock GtkComboBox to the toggle button
22225 if (AtkObject* pComboBoxAccessible = gtk_widget_get_accessible(GTK_WIDGET(m_pComboBox)))
22227 if (AtkObject* pToggleButtonAccessible = gtk_widget_get_accessible(GTK_WIDGET(m_pToggleButton)))
22229 atk_object_set_role(pToggleButtonAccessible, atk_object_get_role(pComboBoxAccessible));
22230 if (const char* pName = atk_object_get_name(pComboBoxAccessible))
22231 atk_object_set_name(pToggleButtonAccessible, pName);
22232 if (const char* pDesc = atk_object_get_description(pComboBoxAccessible))
22233 atk_object_set_description(pToggleButtonAccessible, pDesc);
22235 if (AtkRelationSet* pComboBoxRelationSet = atk_object_ref_relation_set(pComboBoxAccessible))
22237 AtkRelationSet* pToggleButtonRelationSet = atk_object_ref_relation_set(pToggleButtonAccessible);
22238 assert(pToggleButtonRelationSet);
22239 for (int i = 0; i < atk_relation_set_get_n_relations(pComboBoxRelationSet); i++)
22241 AtkRelation* pRelation = atk_relation_set_get_relation(pComboBoxRelationSet, i);
22242 assert(pRelation);
22243 atk_relation_set_add(pToggleButtonRelationSet, pRelation);
22245 g_object_unref(pComboBoxRelationSet);
22246 g_object_unref(pToggleButtonRelationSet);
22251 insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer()));
22252 gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false);
22253 gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true);
22255 gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
22256 /* tdf#136455 gtk_combo_box_set_model with a null Model should be good
22257 enough. But in practice, while the ComboBox model is unset, GTK
22258 doesn't unset the ComboBox menus model, so that remains listening to
22259 additions to the ListStore and slowing things down massively.
22260 Using a new model does reset the menu to listen to that unused one instead */
22261 gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)));
22263 GtkTreeViewColumn* pCol = gtk_tree_view_column_new();
22264 gtk_tree_view_append_column(m_pTreeView, pCol);
22266 bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
22268 GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
22269 // move the cell renderers from the combobox to the replacement treeview
22270 m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
22271 for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
22273 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
22274 bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer;
22275 gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer);
22276 if (!bTextRenderer)
22278 if (bPixbufUsedSurface)
22279 gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr);
22280 else
22281 gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr);
22285 gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr);
22287 if (gtk_combo_box_get_has_entry(m_pComboBox))
22289 m_bAutoComplete = true;
22290 m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
22291 m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
22292 m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
22293 m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
22294 m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this);
22295 m_nEntryPopulatePopupMenuSignalId = g_signal_connect(m_pEntry, "populate-popup", G_CALLBACK(signalEntryPopulatePopup), nullptr);
22296 m_nKeyPressEventSignalId = 0;
22298 else
22300 gtk_widget_set_visible(m_pEntry, false);
22301 m_pEntry = nullptr;
22303 GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow"));
22304 gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr);
22306 auto m_pCellArea = gtk_cell_area_box_new();
22307 m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr));
22308 gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true);
22309 GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow));
22311 gint nImageSpacing(2);
22312 GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton));
22313 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
22314 gtk_box_set_spacing(pBox, nImageSpacing);
22316 gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0);
22318 gtk_cell_view_set_fit_model(m_pCellView, true);
22319 gtk_cell_view_set_model(m_pCellView, m_pTreeModel);
22321 m_pButtonTextRenderer = gtk_cell_renderer_text_new();
22322 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true);
22323 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr);
22324 if (g_list_length(cells) > 1)
22326 GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new();
22327 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false);
22328 if (bPixbufUsedSurface)
22329 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr);
22330 else
22331 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr);
22334 gtk_widget_show_all(GTK_WIDGET(m_pCellView));
22336 m_nEntryInsertTextSignalId = 0;
22337 m_nEntryActivateSignalId = 0;
22338 m_nEntryFocusInSignalId = 0;
22339 m_nEntryFocusOutSignalId = 0;
22340 m_nEntryKeyPressEventSignalId = 0;
22341 m_nEntryPopulatePopupMenuSignalId = 0;
22342 m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
22345 g_list_free(cells);
22347 if (nActive != -1)
22348 tree_view_set_cursor(nActive);
22350 g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this);
22352 g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
22353 g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
22354 g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
22355 // support typeahead for the menu itself, typing into the menu will
22356 // select via the vcl selection engine, a matching entry.
22357 g_signal_connect(m_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this);
22359 g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
22360 gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
22361 g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
22362 g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
22365 virtual int get_active() const override
22367 int nActive = get_active_including_mru();
22368 if (nActive == -1)
22369 return -1;
22371 if (m_nMRUCount)
22373 if (nActive < m_nMRUCount)
22374 nActive = find_text(get_text_including_mru(nActive));
22375 else
22376 nActive -= (m_nMRUCount + 1);
22379 return nActive;
22382 virtual OUString get_active_id() const override
22384 int nActive = get_active();
22385 return nActive != -1 ? get_id(nActive) : OUString();
22388 virtual void set_active_id(const OUString& rStr) override
22390 set_active(find_id(rStr));
22391 m_bChangedByMenu = false;
22394 virtual void set_size_request(int nWidth, int nHeight) override
22396 if (m_pButtonTextRenderer)
22398 // tweak the cell render to get a narrower size to stick
22399 if (nWidth != -1)
22401 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
22402 // the popup menu render them in full, in the interim ellipse both of them
22403 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
22405 // to find out how much of the width of the combobox belongs to the cell, set
22406 // the cell and widget to the min cell width and see what the difference is
22407 int min;
22408 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
22409 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
22410 gtk_widget_set_size_request(m_pWidget, min, -1);
22411 int nNonCellWidth = get_preferred_size().Width() - min;
22413 int nCellWidth = nWidth - nNonCellWidth;
22414 if (nCellWidth >= 0)
22416 // now set the cell to the max width which it can be within the
22417 // requested widget width
22418 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
22421 else
22423 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
22424 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
22428 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
22431 virtual void set_active(int pos) override
22433 set_active_including_mru(include_mru(pos), false);
22436 virtual OUString get_active_text() const override
22438 if (m_pEntry)
22440 const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry));
22441 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
22444 int nActive = get_active();
22445 if (nActive == -1)
22446 return OUString();
22448 return get_text(nActive);
22451 virtual OUString get_text(int pos) const override
22453 if (m_nMRUCount)
22454 pos += (m_nMRUCount + 1);
22455 return get_text_including_mru(pos);
22458 virtual OUString get_id(int pos) const override
22460 if (m_nMRUCount)
22461 pos += (m_nMRUCount + 1);
22462 return get_id_including_mru(pos);
22465 virtual void set_id(int pos, const OUString& rId) override
22467 if (m_nMRUCount)
22468 pos += (m_nMRUCount + 1);
22469 set_id_including_mru(pos, rId);
22472 virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
22474 freeze();
22476 int nInsertionPoint;
22477 if (!bKeepExisting)
22479 clear();
22480 nInsertionPoint = 0;
22482 else
22483 nInsertionPoint = get_count();
22485 GtkTreeIter iter;
22486 // tdf#125241 inserting backwards is faster
22487 for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
22489 const auto& rItem = *aI;
22490 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
22491 rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
22494 thaw();
22497 virtual void remove(int pos) override
22499 if (m_nMRUCount)
22500 pos += (m_nMRUCount + 1);
22501 remove_including_mru(pos);
22504 virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
22506 insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
22509 virtual void insert_separator(int pos, const OUString& rId) override
22511 pos = pos == -1 ? get_count() : pos;
22512 if (m_nMRUCount)
22513 pos += (m_nMRUCount + 1);
22514 insert_separator_including_mru(pos, rId);
22517 virtual int get_count() const override
22519 int nCount = get_count_including_mru();
22520 if (m_nMRUCount)
22521 nCount -= (m_nMRUCount + 1);
22522 return nCount;
22525 virtual int find_text(const OUString& rStr) const override
22527 int nPos = find_text_including_mru(rStr, false);
22528 if (nPos != -1 && m_nMRUCount)
22529 nPos -= (m_nMRUCount + 1);
22530 return nPos;
22533 virtual int find_id(const OUString& rId) const override
22535 int nPos = find_id_including_mru(rId, false);
22536 if (nPos != -1 && m_nMRUCount)
22537 nPos -= (m_nMRUCount + 1);
22538 return nPos;
22541 virtual void clear() override
22543 do_clear();
22546 virtual void make_sorted() override
22548 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
22549 ::comphelper::getProcessComponentContext(),
22550 Application::GetSettings().GetUILanguageTag().getLocale()));
22551 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
22552 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
22553 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
22556 virtual bool has_entry() const override
22558 return gtk_combo_box_get_has_entry(m_pComboBox);
22561 virtual void set_entry_message_type(weld::EntryMessageType eType) override
22563 assert(m_pEntry);
22564 ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
22567 virtual void set_entry_text(const OUString& rText) override
22569 assert(m_pEntry);
22570 disable_notify_events();
22571 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
22572 enable_notify_events();
22575 virtual void set_entry_width_chars(int nChars) override
22577 assert(m_pEntry);
22578 disable_notify_events();
22579 gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars);
22580 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars);
22581 enable_notify_events();
22584 virtual void set_entry_max_length(int nChars) override
22586 assert(m_pEntry);
22587 disable_notify_events();
22588 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
22589 enable_notify_events();
22592 virtual void select_entry_region(int nStartPos, int nEndPos) override
22594 assert(m_pEntry);
22595 disable_notify_events();
22596 gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
22597 enable_notify_events();
22600 virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
22602 assert(m_pEntry);
22603 return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
22606 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
22608 m_bAutoComplete = bEnable;
22609 m_bAutoCompleteCaseSensitive = bCaseSensitive;
22612 virtual void set_entry_placeholder_text(const OUString& rText) override
22614 assert(m_pEntry);
22615 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
22618 virtual void set_entry_editable(bool bEditable) override
22620 assert(m_pEntry);
22621 gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
22624 virtual void cut_entry_clipboard() override
22626 assert(m_pEntry);
22627 gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
22630 virtual void copy_entry_clipboard() override
22632 assert(m_pEntry);
22633 gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
22636 virtual void paste_entry_clipboard() override
22638 assert(m_pEntry);
22639 gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
22642 virtual void set_font(const vcl::Font& rFont) override
22644 m_aCustomFont.use_custom_font(&rFont, u"box#combobox");
22647 virtual vcl::Font get_font() override
22649 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
22650 return *pFont;
22651 return GtkInstanceWidget::get_font();
22654 virtual void set_entry_font(const vcl::Font& rFont) override
22656 m_xEntryFont = rFont;
22657 assert(m_pEntry);
22658 PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
22659 PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
22660 update_attr_list(pAttrList, rFont);
22661 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
22662 pango_attr_list_unref(pAttrList);
22665 virtual vcl::Font get_entry_font() override
22667 if (m_xEntryFont)
22668 return *m_xEntryFont;
22669 assert(m_pEntry);
22670 PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
22671 return pango_to_vcl(pango_context_get_font_description(pContext),
22672 Application::GetSettings().GetUILanguageTag().getLocale());
22675 virtual void disable_notify_events() override
22677 if (m_pEntry)
22679 g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId);
22680 g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
22681 g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId);
22682 g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId);
22683 g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId);
22684 g_signal_handler_block(m_pEntry, m_nChangedSignalId);
22686 else
22687 g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId);
22688 if (m_nToggleFocusInSignalId)
22689 g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
22690 if (m_nToggleFocusOutSignalId)
22691 g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
22692 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
22693 g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId);
22694 GtkInstanceContainer::disable_notify_events();
22697 virtual void enable_notify_events() override
22699 GtkInstanceContainer::enable_notify_events();
22700 g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId);
22701 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
22702 if (m_nToggleFocusInSignalId)
22703 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
22704 if (m_nToggleFocusOutSignalId)
22705 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
22706 if (m_pEntry)
22708 g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
22709 g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
22710 g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId);
22711 g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId);
22712 g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId);
22713 g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId);
22715 else
22716 g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId);
22719 virtual void freeze() override
22721 disable_notify_events();
22722 bool bIsFirstFreeze = IsFirstFreeze();
22723 GtkInstanceContainer::freeze();
22724 if (bIsFirstFreeze)
22726 g_object_ref(m_pTreeModel);
22727 gtk_tree_view_set_model(m_pTreeView, nullptr);
22728 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
22729 if (m_xSorter)
22731 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
22732 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
22735 enable_notify_events();
22738 virtual void thaw() override
22740 disable_notify_events();
22741 if (IsLastThaw())
22743 if (m_xSorter)
22745 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
22746 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
22748 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
22749 gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
22750 g_object_unref(m_pTreeModel);
22752 GtkInstanceContainer::thaw();
22753 enable_notify_events();
22756 virtual bool get_popup_shown() const override
22758 return m_bPopupActive;
22761 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
22763 if (!m_nToggleFocusInSignalId)
22764 m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
22765 GtkInstanceContainer::connect_focus_in(rLink);
22768 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
22770 if (!m_nToggleFocusOutSignalId)
22771 m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
22772 GtkInstanceContainer::connect_focus_out(rLink);
22775 virtual void grab_focus() override
22777 if (has_focus())
22778 return;
22779 if (m_pEntry)
22780 gtk_widget_grab_focus(m_pEntry);
22781 else
22782 gtk_widget_grab_focus(m_pToggleButton);
22785 virtual bool has_focus() const override
22787 if (m_pEntry && gtk_widget_has_focus(m_pEntry))
22788 return true;
22790 if (gtk_widget_has_focus(m_pToggleButton))
22791 return true;
22793 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
22795 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
22796 return true;
22799 return GtkInstanceWidget::has_focus();
22802 virtual bool changed_by_direct_pick() const override
22804 return m_bChangedByMenu;
22807 virtual void set_custom_renderer(bool bOn) override
22809 if (bOn == m_bCustomRenderer)
22810 return;
22811 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
22812 // keep the original height around for optimal popup height calculation
22813 m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
22814 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
22815 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
22816 if (bOn)
22818 GtkCellRenderer *pRenderer = custom_cell_renderer_new();
22819 GValue value = G_VALUE_INIT;
22820 g_value_init(&value, G_TYPE_POINTER);
22821 g_value_set_pointer(&value, static_cast<gpointer>(this));
22822 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
22823 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
22824 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
22825 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
22827 else
22829 GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
22830 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
22831 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
22833 g_list_free(pColumns);
22834 m_bCustomRenderer = bOn;
22837 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
22839 signal_custom_render(rOutput, rRect, bSelected, rId);
22842 Size call_signal_custom_get_size(VirtualDevice& rOutput)
22844 return signal_custom_get_size(rOutput);
22847 VclPtr<VirtualDevice> create_render_virtual_device() const override
22849 return create_virtual_device();
22852 virtual void set_item_menu(const OUString& rIdent, weld::Menu* pMenu) override
22854 m_xCustomMenuButtonHelper.reset();
22855 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
22856 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
22857 gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
22858 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
22859 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
22860 if (pMenuWidget)
22861 m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
22862 m_sMenuButtonRow = rIdent;
22865 OUString get_mru_entries() const override
22867 const sal_Unicode cSep = ';';
22869 OUStringBuffer aEntries;
22870 for (sal_Int32 n = 0; n < m_nMRUCount; n++)
22872 aEntries.append(get_text_including_mru(n));
22873 if (n < m_nMRUCount - 1)
22874 aEntries.append(cSep);
22876 return aEntries.makeStringAndClear();
22879 virtual void set_mru_entries(const OUString& rEntries) override
22881 const sal_Unicode cSep = ';';
22883 // Remove old MRU entries
22884 for (sal_Int32 n = m_nMRUCount; n;)
22885 remove_including_mru(--n);
22887 sal_Int32 nMRUCount = 0;
22888 sal_Int32 nIndex = 0;
22891 OUString aEntry = rEntries.getToken(0, cSep, nIndex);
22892 // Accept only existing entries
22893 int nPos = find_text(aEntry);
22894 if (nPos != -1)
22896 OUString sId = get_id(nPos);
22897 insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
22898 ++nMRUCount;
22901 while (nIndex >= 0);
22903 if (nMRUCount && !m_nMRUCount)
22904 insert_separator_including_mru(nMRUCount, u"separator"_ustr);
22905 else if (!nMRUCount && m_nMRUCount)
22906 remove_including_mru(m_nMRUCount); // remove separator
22908 m_nMRUCount = nMRUCount;
22911 int get_menu_button_width() const override
22913 bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
22914 if (!bVisible)
22915 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
22916 gint nWidth;
22917 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
22918 if (!bVisible)
22919 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
22920 return nWidth;
22923 virtual void set_max_drop_down_rows(int nMaxRows) override
22925 m_nMaxDropdownRows = nMaxRows;
22928 virtual ~GtkInstanceComboBox() override
22930 m_xCustomMenuButtonHelper.reset();
22931 do_clear();
22932 if (m_nAutoCompleteIdleId)
22933 g_source_remove(m_nAutoCompleteIdleId);
22934 if (m_pEntry)
22936 g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
22937 g_signal_handler_disconnect(m_pEntry, m_nEntryInsertTextSignalId);
22938 g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
22939 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
22940 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
22941 g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId);
22942 g_signal_handler_disconnect(m_pEntry, m_nEntryPopulatePopupMenuSignalId);
22944 else
22945 g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
22946 if (m_nToggleFocusInSignalId)
22947 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
22948 if (m_nToggleFocusOutSignalId)
22949 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
22950 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
22951 g_signal_handler_disconnect(m_pToggleButton, m_nPopupShownSignalId);
22953 gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
22954 gtk_tree_view_set_model(m_pTreeView, nullptr);
22956 // restore original hierarchy in dtor so a new GtkInstanceComboBox will
22957 // result in the same layout each time
22959 DisconnectMouseEvents();
22961 g_object_ref(m_pComboBox);
22963 GtkContainer* pContainer = getContainer();
22965 gtk_container_remove(pContainer, GTK_WIDGET(m_pComboBox));
22967 replaceWidget(GTK_WIDGET(pContainer), GTK_WIDGET(m_pComboBox));
22969 g_object_unref(m_pComboBox);
22972 g_object_unref(m_pComboBuilder);
22976 #endif
22980 void custom_cell_renderer_ensure_device(CustomCellRenderer *cellsurface, gpointer user_data)
22982 if (!cellsurface->device)
22984 cellsurface->device = VclPtr<VirtualDevice>::Create();
22985 cellsurface->device->SetBackground(COL_TRANSPARENT);
22986 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
22987 // expand the point size of the desired font to the equivalent pixel size
22988 weld::SetPointFont(*cellsurface->device, pWidget->get_font());
22992 Size custom_cell_renderer_get_size(VirtualDevice& rDevice, const OUString& rCellId, gpointer user_data)
22994 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
22995 if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
22996 return pTreeView->call_signal_custom_get_size(rDevice, rCellId);
22997 else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
22998 return pComboBox->call_signal_custom_get_size(rDevice);
22999 return Size();
23002 void custom_cell_renderer_render(VirtualDevice& rDevice, const tools::Rectangle& rRect, bool bSelected, const OUString& rCellId, gpointer user_data)
23004 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
23005 if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
23006 pTreeView->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
23007 else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
23008 pComboBox->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
23011 namespace {
23013 class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
23015 private:
23016 GtkInstanceEntry* m_pEntry;
23017 GtkInstanceTreeView* m_pTreeView;
23018 #if !GTK_CHECK_VERSION(4, 0, 0)
23019 gulong m_nKeyPressSignalId;
23020 #endif
23021 gulong m_nEntryInsertTextSignalId;
23022 guint m_nAutoCompleteIdleId;
23023 bool m_bAutoCompleteCaseSensitive;
23024 bool m_bTreeChange;
23026 #if !GTK_CHECK_VERSION(4, 0, 0)
23027 bool signal_key_press(GdkEventKey* pEvent)
23029 if (GtkSalFrame::GetMouseModCode(pEvent->state)) // only with no modifiers held
23030 return false;
23032 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 ||
23033 pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
23035 gboolean ret;
23036 disable_notify_events();
23037 GtkWidget* pWidget = m_pTreeView->getWidget();
23038 if (m_pTreeView->get_selected_index() == -1)
23040 m_pTreeView->set_cursor(0);
23041 m_pTreeView->select(0);
23042 m_xEntry->set_text(m_xTreeView->get_selected_text());
23044 else
23046 gtk_widget_grab_focus(pWidget);
23047 g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
23048 m_xEntry->set_text(m_xTreeView->get_selected_text());
23049 gtk_widget_grab_focus(m_pEntry->getWidget());
23051 m_xEntry->select_region(0, -1);
23052 enable_notify_events();
23053 m_bTreeChange = true;
23054 m_pEntry->fire_signal_changed();
23055 m_bTreeChange = false;
23056 return true;
23058 return false;
23061 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
23063 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
23064 return pThis->signal_key_press(pEvent);
23066 #endif
23068 static gboolean idleAutoComplete(gpointer widget)
23070 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
23071 pThis->auto_complete();
23072 return false;
23075 void auto_complete()
23077 m_nAutoCompleteIdleId = 0;
23078 OUString aStartText = get_active_text();
23079 int nStartPos, nEndPos;
23080 get_entry_selection_bounds(nStartPos, nEndPos);
23081 int nMaxSelection = std::max(nStartPos, nEndPos);
23082 if (nMaxSelection != aStartText.getLength())
23083 return;
23085 disable_notify_events();
23086 int nActive = get_active();
23087 int nStart = nActive;
23089 if (nStart == -1)
23090 nStart = 0;
23092 // Try match case sensitive from current position
23093 int nPos = m_pTreeView->starts_with(aStartText, nStart, true);
23094 if (nPos == -1 && nStart != 0)
23096 // Try match case insensitive, but from start
23097 nPos = m_pTreeView->starts_with(aStartText, 0, true);
23100 if (!m_bAutoCompleteCaseSensitive)
23102 // Try match case insensitive from current position
23103 nPos = m_pTreeView->starts_with(aStartText, nStart, false);
23104 if (nPos == -1 && nStart != 0)
23106 // Try match case insensitive, but from start
23107 nPos = m_pTreeView->starts_with(aStartText, 0, false);
23111 if (nPos == -1)
23113 // Try match case sensitive from current position
23114 nPos = m_pTreeView->starts_with(aStartText, nStart, true);
23115 if (nPos == -1 && nStart != 0)
23117 // Try match case sensitive, but from start
23118 nPos = m_pTreeView->starts_with(aStartText, 0, true);
23122 if (nPos != -1)
23124 OUString aText = get_text(nPos);
23125 if (aText != aStartText)
23126 set_active_text(aText);
23127 select_entry_region(aText.getLength(), aStartText.getLength());
23129 enable_notify_events();
23132 void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
23134 // now check for autocompletes
23135 if (m_nAutoCompleteIdleId)
23136 g_source_remove(m_nAutoCompleteIdleId);
23137 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
23140 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
23141 gint* position, gpointer widget)
23143 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
23144 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
23148 public:
23149 #if GTK_CHECK_VERSION(4, 0, 0)
23150 GtkInstanceEntryTreeView(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
23151 std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
23152 #else
23153 GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
23154 std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
23155 #endif
23156 : EntryTreeView(std::move(xEntry), std::move(xTreeView))
23157 , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
23158 , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
23159 , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
23160 , m_nAutoCompleteIdleId(0)
23161 , m_bAutoCompleteCaseSensitive(false)
23162 , m_bTreeChange(false)
23164 assert(m_pEntry);
23165 GtkWidget* pWidget = m_pEntry->getWidget();
23166 #if !GTK_CHECK_VERSION(4, 0, 0)
23167 m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
23168 #endif
23169 m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
23172 virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
23174 assert(false);
23177 virtual void make_sorted() override
23179 GtkWidget* pTreeView = m_pTreeView->getWidget();
23180 GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
23181 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
23182 gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
23185 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
23187 assert(!bEnable && "not implemented yet"); (void)bEnable;
23188 m_bAutoCompleteCaseSensitive = bCaseSensitive;
23191 virtual void set_entry_placeholder_text(const OUString& rText) override
23193 m_xEntry->set_placeholder_text(rText);
23196 virtual void set_entry_editable(bool bEditable) override
23198 m_xEntry->set_editable(bEditable);
23201 virtual void cut_entry_clipboard() override
23203 m_xEntry->cut_clipboard();
23206 virtual void copy_entry_clipboard() override
23208 m_xEntry->copy_clipboard();
23211 virtual void paste_entry_clipboard() override
23213 m_xEntry->paste_clipboard();
23216 virtual void set_font(const vcl::Font&) override
23218 assert(false && "not implemented");
23221 virtual void set_entry_font(const vcl::Font& rFont) override
23223 m_xEntry->set_font(rFont);
23226 virtual vcl::Font get_entry_font() override
23228 return m_xEntry->get_font();
23231 virtual void grab_focus() override { m_xEntry->grab_focus(); }
23233 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
23235 m_xEntry->connect_focus_in(rLink);
23238 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
23240 m_xEntry->connect_focus_out(rLink);
23243 virtual void disable_notify_events() override
23245 GtkWidget* pWidget = m_pEntry->getWidget();
23246 g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
23247 #if !GTK_CHECK_VERSION(4, 0, 0)
23248 g_signal_handler_block(pWidget, m_nKeyPressSignalId);
23249 #endif
23250 m_pTreeView->disable_notify_events();
23251 GtkInstanceContainer::disable_notify_events();
23254 virtual void enable_notify_events() override
23256 GtkWidget* pWidget = m_pEntry->getWidget();
23257 #if !GTK_CHECK_VERSION(4, 0, 0)
23258 g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
23259 #endif
23260 g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
23261 m_pTreeView->enable_notify_events();
23262 GtkInstanceContainer::enable_notify_events();
23265 virtual bool changed_by_direct_pick() const override
23267 return m_bTreeChange;
23270 virtual void set_custom_renderer(bool /*bOn*/) override
23272 assert(false && "not implemented");
23275 virtual int get_max_mru_count() const override
23277 assert(false && "not implemented");
23278 return 0;
23281 virtual void set_max_mru_count(int) override
23283 assert(false && "not implemented");
23286 virtual OUString get_mru_entries() const override
23288 assert(false && "not implemented");
23289 return OUString();
23292 virtual void set_mru_entries(const OUString&) override
23294 assert(false && "not implemented");
23297 virtual void set_item_menu(const OUString&, weld::Menu*) override
23299 assert(false && "not implemented");
23302 VclPtr<VirtualDevice> create_render_virtual_device() const override
23304 return create_virtual_device();
23307 int get_menu_button_width() const override
23309 assert(false && "not implemented");
23310 return 0;
23313 virtual void set_max_drop_down_rows(int) override { assert(false && "not implemented"); }
23315 virtual ~GtkInstanceEntryTreeView() override
23317 if (m_nAutoCompleteIdleId)
23318 g_source_remove(m_nAutoCompleteIdleId);
23319 GtkWidget* pWidget = m_pEntry->getWidget();
23320 #if !GTK_CHECK_VERSION(4, 0, 0)
23321 g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
23322 #endif
23323 g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
23329 namespace {
23331 class GtkInstanceExpander : public GtkInstanceWidget, public virtual weld::Expander
23333 private:
23334 GtkExpander* m_pExpander;
23335 gulong m_nSignalId;
23336 #if !GTK_CHECK_VERSION(4, 0, 0)
23337 gulong m_nButtonPressEventSignalId;
23338 gulong m_nMappedSignalId;
23339 #endif
23341 static void signalExpanded(GtkExpander* /*pExpander*/, GParamSpec*, gpointer widget)
23343 GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
23344 SolarMutexGuard aGuard;
23345 pThis->signal_expanded();
23348 #if !GTK_CHECK_VERSION(4, 0, 0)
23349 static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
23351 // don't let button press get to parent window, for the case of the
23352 // an expander in a sidebar where otherwise single click to expand
23353 // doesn't work
23354 return true;
23357 /* tdf#141186 if the expander is initially collapsed then when mapped all its
23358 children are mapped too. If they are mapped then the mnemonics of the
23359 children are taken into account on shortcuts and non-visible children in a
23360 collapsed expander can be triggered which is confusing.
23362 If the expander is expanded and collapsed the child is unmapped and the
23363 problem doesn't occur.
23365 So to avoid the problem of an initially collapsed expander, listen to
23366 the map event and if the expander is mapped but collapsed then unmap the
23367 child of the expander.
23369 This problem was seen in gtk3-3.24.33 and not with gtk4-4.6.4 so a gtk3
23370 fix only needed.
23372 static void signalMap(GtkWidget*, gpointer widget)
23374 GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
23375 if (!gtk_expander_get_expanded(pThis->m_pExpander))
23377 if (GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pThis->m_pExpander)))
23378 gtk_widget_unmap(pChild);
23381 #endif
23383 public:
23384 GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
23385 : GtkInstanceWidget(GTK_WIDGET(pExpander), pBuilder, bTakeOwnership)
23386 , m_pExpander(pExpander)
23387 , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
23388 #if !GTK_CHECK_VERSION(4, 0, 0)
23389 , m_nButtonPressEventSignalId(g_signal_connect_after(m_pExpander, "button-press-event", G_CALLBACK(signalButton), this))
23390 , m_nMappedSignalId(g_signal_connect_after(m_pExpander, "map", G_CALLBACK(signalMap), this))
23391 #endif
23395 virtual void set_label(const OUString& rText) override
23397 ::set_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)), rText);
23400 virtual OUString get_label() const override
23402 return ::get_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)));
23405 virtual bool get_expanded() const override
23407 return gtk_expander_get_expanded(m_pExpander);
23410 virtual void set_expanded(bool bExpand) override
23412 gtk_expander_set_expanded(m_pExpander, bExpand);
23415 virtual ~GtkInstanceExpander() override
23417 #if !GTK_CHECK_VERSION(4, 0, 0)
23418 g_signal_handler_disconnect(m_pExpander, m_nMappedSignalId);
23419 g_signal_handler_disconnect(m_pExpander, m_nButtonPressEventSignalId);
23420 #endif
23421 g_signal_handler_disconnect(m_pExpander, m_nSignalId);
23427 namespace {
23429 class GtkInstancePopover : public GtkInstanceContainer, public virtual weld::Popover
23431 private:
23432 #if !GTK_CHECK_VERSION(4, 0, 0)
23433 //popover cannot escape dialog under X so we might need to stick up own window instead
23434 GtkWindow* m_pMenuHack;
23435 bool m_bMenuPoppedUp;
23436 bool m_nButtonPressSeen;
23437 #endif
23438 GtkPopover* m_pPopover;
23439 gulong m_nSignalId;
23440 ImplSVEvent* m_pClosedEvent;
23442 static void signalClosed(GtkPopover*, gpointer widget)
23444 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23445 // call signal-closed async so the closed callback isn't called
23446 // while the GtkPopover handler is still in-execution
23447 pThis->launch_signal_closed();
23450 DECL_LINK(async_signal_closed, void*, void);
23452 void launch_signal_closed()
23454 if (m_pClosedEvent)
23455 Application::RemoveUserEvent(m_pClosedEvent);
23456 m_pClosedEvent = Application::PostUserEvent(LINK(this, GtkInstancePopover, async_signal_closed));
23459 #if !GTK_CHECK_VERSION(4, 0, 0)
23460 static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
23462 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23463 return pThis->key_press(pEvent);
23466 bool key_press(const GdkEventKey* pEvent)
23468 if (pEvent->keyval == GDK_KEY_Escape)
23470 popdown();
23471 return true;
23473 return false;
23476 static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
23478 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23479 pThis->m_nButtonPressSeen = true;
23480 return false;
23483 static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
23485 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23486 if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
23487 pThis->popdown();
23488 return false;
23491 bool forward_event_if_popup_under_mouse(GdkEvent* pEvent)
23493 GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
23494 GtkWidget* pTopLevel = widget_get_toplevel(pEventWidget);
23496 if (pTopLevel == GTK_WIDGET(m_pMenuHack))
23497 return false;
23499 GdkSurface* pSurface = widget_get_surface(pTopLevel);
23500 void* pMouseEnteredAnotherPopup = g_object_get_data(G_OBJECT(pSurface), "g-lo-InstancePopup");
23501 if (!pMouseEnteredAnotherPopup)
23502 return false;
23504 return gtk_widget_event(pEventWidget, pEvent);
23507 static gboolean signalButtonCrossing(GtkWidget*, GdkEvent* pEvent, gpointer widget)
23509 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23510 return pThis->forward_event_if_popup_under_mouse(pEvent);
23513 static gboolean signalMotion(GtkWidget*, GdkEvent* pEvent, gpointer widget)
23515 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23516 return pThis->forward_event_if_popup_under_mouse(pEvent);
23519 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
23521 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
23522 pThis->grab_broken(pEvent);
23525 void grab_broken(const GdkEventGrabBroken *event)
23527 if (event->grab_window == nullptr)
23529 popdown();
23531 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
23533 //try and regrab, so when we lose the grab to the menu of the color palette
23534 //combobox we regain it so the color palette doesn't itself disappear on next
23535 //click on the color palette combobox
23536 do_grab(GTK_WIDGET(m_pMenuHack));
23540 #endif
23542 public:
23543 GtkInstancePopover(GtkPopover* pPopover, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
23544 #if !GTK_CHECK_VERSION(4, 0, 0)
23545 : GtkInstanceContainer(GTK_CONTAINER(pPopover), pBuilder, bTakeOwnership)
23546 , m_pMenuHack(nullptr)
23547 , m_bMenuPoppedUp(false)
23548 , m_nButtonPressSeen(false)
23549 #else
23550 : GtkInstanceContainer(GTK_WIDGET(pPopover), pBuilder, bTakeOwnership)
23551 #endif
23552 , m_pPopover(pPopover)
23553 , m_nSignalId(g_signal_connect(m_pPopover, "closed", G_CALLBACK(signalClosed), this))
23554 , m_pClosedEvent(nullptr)
23556 #if !GTK_CHECK_VERSION(4, 0, 0)
23557 //under wayland a Popover will work to "escape" the parent dialog, not
23558 //so under X, so come up with this hack to use a raw GtkWindow
23559 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
23560 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
23562 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
23563 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
23564 gtk_window_set_resizable(m_pMenuHack, false);
23565 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
23566 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
23567 g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
23568 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
23569 // to emulate a modeless popover we forward the leave/enter/motion events to the widgets
23570 // they would have gone to a if we were really modeless
23571 if (!gtk_popover_get_modal(m_pPopover))
23573 g_signal_connect(m_pMenuHack, "leave-notify-event", G_CALLBACK(signalButtonCrossing), this);
23574 g_signal_connect(m_pMenuHack, "enter-notify-event", G_CALLBACK(signalButtonCrossing), this);
23575 g_signal_connect(m_pMenuHack, "motion-notify-event", G_CALLBACK(signalMotion), this);
23578 #endif
23581 virtual void popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
23583 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
23584 assert(pGtkWidget);
23586 GtkWidget* pWidget = pGtkWidget->getWidget();
23588 GdkRectangle aRect;
23589 pWidget = getPopupRect(pWidget, rRect, aRect);
23591 #if GTK_CHECK_VERSION(4, 0, 0)
23592 gtk_widget_set_parent(GTK_WIDGET(m_pPopover), pWidget);
23593 #else
23594 gtk_popover_set_relative_to(m_pPopover, pWidget);
23595 #endif
23596 gtk_popover_set_pointing_to(m_pPopover, &aRect);
23598 if (ePlace == weld::Placement::Under)
23599 gtk_popover_set_position(m_pPopover, GTK_POS_BOTTOM);
23600 else
23602 if (::SwapForRTL(pWidget))
23603 gtk_popover_set_position(m_pPopover, GTK_POS_LEFT);
23604 else
23605 gtk_popover_set_position(m_pPopover, GTK_POS_RIGHT);
23608 #if !GTK_CHECK_VERSION(4, 0, 0)
23609 //under wayland a Popover will work to "escape" the parent dialog, not
23610 //so under X, so come up with this hack to use a raw GtkWindow
23611 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
23612 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
23614 if (!m_bMenuPoppedUp)
23616 MovePopoverContentsToWindow(GTK_WIDGET(m_pPopover), m_pMenuHack, pWidget, aRect, ePlace);
23617 m_bMenuPoppedUp = true;
23619 return;
23621 #endif
23623 gtk_popover_popup(m_pPopover);
23626 #if !GTK_CHECK_VERSION(4, 0, 0)
23627 virtual bool get_visible() const override
23629 if (m_pMenuHack)
23630 return gtk_widget_get_visible(GTK_WIDGET(m_pMenuHack));
23631 return gtk_widget_get_visible(m_pWidget);
23634 virtual void ensureMouseEventWidget() override
23636 if (!m_pMouseEventBox && m_pMenuHack)
23638 m_pMouseEventBox = GTK_WIDGET(m_pMenuHack);
23639 return;
23641 GtkInstanceContainer::ensureMouseEventWidget();
23643 #endif
23645 virtual void popdown() override
23647 #if !GTK_CHECK_VERSION(4, 0, 0)
23648 //under wayland a Popover will work to "escape" the parent dialog, not
23649 //so under X, so come up with this hack to use a raw GtkWindow
23650 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
23651 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
23653 if (m_bMenuPoppedUp)
23655 m_nButtonPressSeen = false;
23656 MoveWindowContentsToPopover(m_pMenuHack, GTK_WIDGET(m_pPopover), gtk_popover_get_relative_to(m_pPopover));
23657 m_bMenuPoppedUp = false;
23658 signal_closed();
23660 return;
23662 #endif
23664 gtk_popover_popdown(m_pPopover);
23667 void PopdownAndFlushClosedSignal()
23669 if (get_visible())
23670 popdown();
23671 if (m_pClosedEvent)
23673 Application::RemoveUserEvent(m_pClosedEvent);
23674 async_signal_closed(nullptr);
23678 virtual void resize_to_request() override
23680 // resizing to request is what gtk does automatically
23683 virtual ~GtkInstancePopover() override
23685 PopdownAndFlushClosedSignal();
23686 DisconnectMouseEvents();
23687 #if !GTK_CHECK_VERSION(4, 0, 0)
23688 if (m_pMenuHack)
23689 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
23690 #endif
23691 g_signal_handler_disconnect(m_pPopover, m_nSignalId);
23695 IMPL_LINK_NOARG(GtkInstancePopover, async_signal_closed, void*, void)
23697 m_pClosedEvent = nullptr;
23698 signal_closed();
23703 #if !GTK_CHECK_VERSION(4, 0, 0)
23705 namespace
23708 AtkObject* drawing_area_get_accessible(GtkWidget *pWidget)
23710 AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
23711 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
23712 GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
23713 AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
23714 if (pAtkObj)
23715 return pAtkObj;
23716 return pDefaultAccessible;
23719 void ensure_intercept_drawing_area_accessibility()
23721 static bool bDone;
23722 if (!bDone)
23724 gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
23725 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
23726 default_drawing_area_get_accessible = pWidgetClass->get_accessible;
23727 pWidgetClass->get_accessible = drawing_area_get_accessible;
23728 g_type_class_unref(pClass);
23729 bDone = true;
23733 void ensure_disable_ctrl_page_up_down(GType eType)
23735 gpointer pClass = g_type_class_ref(eType);
23736 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
23737 GtkBindingSet* pBindingSet = gtk_binding_set_by_class(pWidgetClass);
23738 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, GDK_CONTROL_MASK);
23739 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
23740 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, GDK_CONTROL_MASK);
23741 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
23742 g_type_class_unref(pClass);
23745 // tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
23746 // keystrokes are consumed by the surrounding notebook bindings instead
23747 void ensure_disable_ctrl_page_up_down_bindings()
23749 static bool bDone;
23750 if (!bDone)
23752 ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW);
23753 ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON);
23754 bDone = true;
23759 #endif
23761 namespace {
23763 bool IsAllowedBuiltInIcon(std::u16string_view iconName)
23765 // limit the named icons to those known by VclBuilder
23766 return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
23771 namespace {
23773 #if !GTK_CHECK_VERSION(4, 0, 0)
23774 void silence_gwarning(const gchar* /*log_domain*/,
23775 GLogLevelFlags /*log_level*/,
23776 const gchar* /*message*/,
23777 gpointer /*user_data*/)
23780 #endif
23782 void load_ui_file(GtkBuilder* pBuilder, const OUString& rUri)
23784 #if GTK_CHECK_VERSION(4, 0, 0)
23785 builder_add_from_gtk3_file(pBuilder, rUri);
23786 #else
23787 guint nLogHandlerId = 0;
23788 GLogLevelFlags nFatalMask(static_cast<GLogLevelFlags>(G_LOG_FLAG_RECURSION|G_LOG_LEVEL_ERROR));
23789 if (rUri.endsWith("sfx/ui/tabbarcontents.ui"))
23791 // gtk unhelpfully has a bogus warning for the accelerator in this .ui because it assumes menus with accelerators
23792 // if attached to something are attached to a MenuShell, but it's a MenuButton in this case. Turn off warnings, and
23793 // in the case of fatal-warnings temp disable fatal warnings, for this case.
23794 nLogHandlerId = g_log_set_handler("GLib-GObject",
23795 static_cast<GLogLevelFlags>(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION),
23796 silence_gwarning, nullptr);
23797 nFatalMask = g_log_set_always_fatal(nFatalMask);
23800 OUString aPath;
23801 osl::FileBase::getSystemPathFromFileURL(rUri, aPath);
23802 GError *err = nullptr;
23803 auto rc = gtk_builder_add_from_file(pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), &err);
23805 if (nLogHandlerId)
23807 g_log_remove_handler("GLib-GObject", nLogHandlerId);
23808 g_log_set_always_fatal(nFatalMask);
23811 if (!rc)
23813 SAL_WARN( "vcl.gtk", "GtkInstanceBuilder: error when calling gtk_builder_add_from_file: " << err->message);
23814 g_error_free(err);
23816 assert(rc && "could not load UI file");
23817 #endif
23820 #if !GTK_CHECK_VERSION(4, 0, 0)
23821 void fix_expander(GtkExpander* pExpander, GParamSpec*, gpointer)
23823 if (gtk_expander_get_resize_toplevel(pExpander))
23825 GtkWidget *pToplevel = widget_get_toplevel(GTK_WIDGET(pExpander));
23827 // https://gitlab.gnome.org/GNOME/gtk/issues/70
23828 // I imagine at some point a release with a fix will be available in which
23829 // case this can be avoided depending on version number
23830 if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
23832 int nToplevelWidth, nToplevelHeight;
23833 int nChildHeight;
23835 GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
23836 gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
23837 gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
23839 if (gtk_expander_get_expanded(pExpander))
23840 nToplevelHeight += nChildHeight;
23841 else
23842 nToplevelHeight -= nChildHeight;
23844 gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
23848 #endif
23850 class GtkInstanceBuilder : public weld::Builder
23852 private:
23853 ResHookProc m_pStringReplace;
23854 OUString m_aHelpRoot;
23855 OUString m_aIconTheme;
23856 OUString m_aUILang;
23857 GtkBuilder* m_pBuilder;
23858 GSList* m_pObjectList;
23859 GtkWidget* m_pParentWidget;
23860 gulong m_nNotifySignalId;
23861 std::vector<GtkButton*> m_aMnemonicButtons;
23862 #if GTK_CHECK_VERSION(4, 0, 0)
23863 std::vector<GtkCheckButton*> m_aMnemonicCheckButtons;
23864 #endif
23865 std::vector<GtkLabel*> m_aMnemonicLabels;
23867 VclPtr<SystemChildWindow> m_xInterimGlue;
23868 bool m_bAllowCycleFocusOut;
23870 void postprocess_widget(GtkWidget* pWidget)
23872 const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
23873 officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
23875 //fixup icons
23876 //wanted: better way to do this, e.g. make gtk use gio for
23877 //loading from a filename and provide gio protocol handler
23878 //for our image in a zip urls
23880 //unpack the images and keep them as dirs and just
23881 //add the paths to the gtk icon theme dir
23882 if (GTK_IS_IMAGE(pWidget))
23884 GtkImage* pImage = GTK_IMAGE(pWidget);
23885 if (const gchar* icon_name = image_get_icon_name(pImage))
23887 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
23888 if (!IsAllowedBuiltInIcon(aIconName))
23889 image_set_from_icon_name_theme_lang(pImage, aIconName, m_aIconTheme, m_aUILang);
23892 #if GTK_CHECK_VERSION(4, 0, 0)
23893 else if (GTK_IS_PICTURE(pWidget))
23895 GtkPicture* pPicture = GTK_PICTURE(pWidget);
23896 if (GFile* icon_file = gtk_picture_get_file(pPicture))
23898 char* icon_name = g_file_get_uri(icon_file);
23899 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
23900 g_free(icon_name);
23901 assert(aIconName.startsWith("private:///graphicrepository/"));
23902 aIconName.startsWith("private:///graphicrepository/", &aIconName);
23903 picture_set_from_icon_name_theme_lang(GTK_PICTURE(pWidget), aIconName, m_aIconTheme, m_aUILang);
23906 #endif
23907 #if !GTK_CHECK_VERSION(4, 0, 0)
23908 else if (GTK_IS_TOOL_BUTTON(pWidget))
23910 GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
23911 if (const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton))
23913 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
23914 if (!IsAllowedBuiltInIcon(aIconName))
23916 if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
23918 gtk_tool_button_set_icon_widget(pToolButton, pImage);
23919 gtk_widget_show(pImage);
23924 // if no tooltip reuse the label as default tooltip
23925 if (!gtk_widget_get_tooltip_text(pWidget))
23927 if (const gchar* label = gtk_tool_button_get_label(pToolButton))
23928 gtk_widget_set_tooltip_text(pWidget, label);
23931 else if (GTK_IS_EXPANDER(pWidget))
23933 g_signal_connect(pWidget, "notify::expanded", G_CALLBACK(fix_expander), this);
23935 #else
23936 else if (GTK_IS_BUTTON(pWidget))
23938 GtkButton* pButton = GTK_BUTTON(pWidget);
23939 if (const gchar* icon_name = gtk_button_get_icon_name(pButton))
23941 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
23942 if (!IsAllowedBuiltInIcon(aIconName))
23944 if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
23946 gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
23947 gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
23948 gtk_button_set_child(pButton, pImage);
23949 gtk_widget_show(pImage);
23954 else if (GTK_IS_MENU_BUTTON(pWidget))
23956 GtkMenuButton* pButton = GTK_MENU_BUTTON(pWidget);
23957 if (const gchar* icon_name = gtk_menu_button_get_icon_name(pButton))
23959 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
23960 if (!IsAllowedBuiltInIcon(aIconName))
23962 if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
23964 gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
23965 gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
23966 // TODO after gtk 4.6 is released require that version and drop this
23967 static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
23968 if (menu_button_set_child)
23969 menu_button_set_child(pButton, pImage);
23970 gtk_widget_show(pImage);
23975 #endif
23977 //set helpids
23978 OUString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
23979 if (!sBuildableName.isEmpty())
23981 OUString sHelpId = m_aHelpRoot + sBuildableName;
23982 set_help_id(pWidget, sHelpId);
23983 //hook up for extended help
23984 const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
23985 if (aHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
23987 gtk_widget_set_has_tooltip(pWidget, true);
23988 g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
23991 if (bHideHelp && sBuildableName == "help")
23992 gtk_widget_hide(pWidget);
23995 if (m_pStringReplace)
23997 // tdf#136498 %PRODUCTNAME shown in tool tips
23998 const char* pTooltip = gtk_widget_get_tooltip_text(pWidget);
23999 if (pTooltip && pTooltip[0])
24001 OUString aTooltip(pTooltip, strlen(pTooltip), RTL_TEXTENCODING_UTF8);
24002 aTooltip = (*m_pStringReplace)(aTooltip);
24003 gtk_widget_set_tooltip_text(pWidget, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
24007 // expand placeholder and collect potentially missing mnemonics
24008 if (GTK_IS_BUTTON(pWidget))
24010 GtkButton* pButton = GTK_BUTTON(pWidget);
24011 if (m_pStringReplace)
24013 OUString aLabel(button_get_label(pButton));
24014 if (!aLabel.isEmpty())
24015 button_set_label(pButton, (*m_pStringReplace)(aLabel));
24017 if (gtk_button_get_use_underline(pButton))
24018 m_aMnemonicButtons.push_back(pButton);
24020 #if GTK_CHECK_VERSION(4, 0, 0)
24021 else if (GTK_IS_CHECK_BUTTON(pWidget))
24023 GtkCheckButton* pButton = GTK_CHECK_BUTTON(pWidget);
24024 if (m_pStringReplace)
24026 OUString aLabel(get_label(pButton));
24027 if (!aLabel.isEmpty())
24028 set_label(pButton, (*m_pStringReplace)(aLabel));
24030 if (gtk_check_button_get_use_underline(pButton))
24031 m_aMnemonicCheckButtons.push_back(pButton);
24033 #endif
24034 else if (GTK_IS_LABEL(pWidget))
24036 GtkLabel* pLabel = GTK_LABEL(pWidget);
24037 if (m_pStringReplace)
24039 OUString aLabel(get_label(pLabel));
24040 if (!aLabel.isEmpty())
24041 set_label(pLabel, (*m_pStringReplace)(aLabel));
24043 if (gtk_label_get_use_underline(pLabel))
24044 m_aMnemonicLabels.push_back(pLabel);
24046 else if (GTK_IS_TEXT_VIEW(pWidget))
24048 GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
24049 if (m_pStringReplace)
24051 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
24052 GtkTextIter start, end;
24053 gtk_text_buffer_get_bounds(pBuffer, &start, &end);
24054 char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
24055 int nTextLen = pTextStr ? strlen(pTextStr) : 0;
24056 if (nTextLen)
24058 OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
24059 OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
24060 gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
24062 g_free(pTextStr);
24065 #if !GTK_CHECK_VERSION(4, 0, 0)
24066 else if (GTK_IS_ENTRY(pWidget))
24068 g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalEntryInsertSpecialCharKeyPress), nullptr);
24069 g_signal_connect(pWidget, "populate-popup", G_CALLBACK(signalEntryPopulatePopup), nullptr);
24071 #endif
24072 else if (GTK_IS_WINDOW(pWidget))
24074 if (m_pStringReplace)
24076 GtkWindow* pWindow = GTK_WINDOW(pWidget);
24077 set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
24078 if (GTK_IS_MESSAGE_DIALOG(pWindow))
24080 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
24081 set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
24082 set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
24088 //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
24089 //In order for GtkBuilder to find the translations bindtextdomain has to be called
24090 //for the domain. So here on the first setting of "domain" we call Translate::Create
24091 //to make sure that happens. Without this, if some other part of LibreOffice has
24092 //used the translation machinery for this domain it will still work, but if it
24093 //hasn't, e.g. tdf#119929, then the translation fails
24094 void translation_domain_set()
24096 Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
24097 g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
24100 static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
24102 g_return_if_fail(pSpec != nullptr);
24103 if (strcmp(pSpec->name, "translation-domain") == 0)
24105 GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
24106 pBuilder->translation_domain_set();
24110 static void postprocess(gpointer data, gpointer user_data)
24112 GObject* pObject = static_cast<GObject*>(data);
24113 if (!GTK_IS_WIDGET(pObject))
24114 return;
24115 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
24116 pThis->postprocess_widget(GTK_WIDGET(pObject));
24119 void DisallowCycleFocusOut()
24121 assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
24123 GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
24124 assert(pTopLevel);
24125 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
24126 assert(pFrame);
24127 // unhook handler and let gtk cycle its own way through this widget's
24128 // children because it has no non-gtk siblings
24129 pFrame->DisallowCycleFocusOut();
24132 static void signalMap(GtkWidget*, gpointer user_data)
24134 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
24135 // tdf#138047 wait until map to do this because the final SalFrame may
24136 // not be the same as at ctor time
24137 pThis->DisallowCycleFocusOut();
24140 void AllowCycleFocusOut()
24142 assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
24144 GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
24145 assert(pTopLevel);
24146 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
24147 assert(pFrame);
24148 // rehook handler and let vcl cycle its own way through this widget's
24149 // children
24150 pFrame->AllowCycleFocusOut();
24152 // tdf#145567 if the focus is in this hierarchy then, now that we are tearing down,
24153 // move focus to the usual focus candidate for the frame
24154 GtkWindow* pFocusWin = get_active_window();
24155 GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
24156 bool bHasFocus = pFocus && gtk_widget_is_ancestor(pFocus, pTopLevel);
24157 if (bHasFocus)
24158 pFrame->GrabFocus();
24161 static void signalUnmap(GtkWidget*, gpointer user_data)
24163 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
24164 pThis->AllowCycleFocusOut();
24167 public:
24168 GtkInstanceBuilder(GtkWidget* pParent, std::u16string_view rUIRoot, const OUString& rUIFile,
24169 SystemChildWindow* pInterimGlue, bool bAllowCycleFocusOut)
24170 : weld::Builder()
24171 , m_pStringReplace(Translate::GetReadStringHook())
24172 , m_pParentWidget(pParent)
24173 , m_nNotifySignalId(0)
24174 , m_xInterimGlue(pInterimGlue)
24175 , m_bAllowCycleFocusOut(bAllowCycleFocusOut)
24177 OUString sHelpRoot(rUIFile);
24178 #if !GTK_CHECK_VERSION(4, 0, 0)
24179 ensure_intercept_drawing_area_accessibility();
24180 ensure_disable_ctrl_page_up_down_bindings();
24181 #endif
24183 sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
24184 if (nIdx != -1)
24185 sHelpRoot = sHelpRoot.copy(0, nIdx);
24186 sHelpRoot += "/";
24187 m_aHelpRoot = sHelpRoot;
24188 m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
24189 m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
24191 OUString aUri(rUIRoot + rUIFile);
24193 m_pBuilder = gtk_builder_new();
24194 m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
24196 load_ui_file(m_pBuilder, aUri);
24198 m_pObjectList = gtk_builder_get_objects(m_pBuilder);
24199 g_slist_foreach(m_pObjectList, postprocess, this);
24201 GenerateMissingMnemonics();
24203 if (m_xInterimGlue)
24205 assert(m_pParentWidget);
24206 g_object_set_data(G_OBJECT(m_pParentWidget), "InterimWindowGlue", m_xInterimGlue.get());
24208 if (!m_bAllowCycleFocusOut)
24210 g_signal_connect(G_OBJECT(m_pParentWidget), "map", G_CALLBACK(signalMap), this);
24211 g_signal_connect(G_OBJECT(m_pParentWidget), "unmap", G_CALLBACK(signalUnmap), this);
24216 void GenerateMissingMnemonics()
24218 MnemonicGenerator aMnemonicGenerator('_');
24219 for (const auto a : m_aMnemonicButtons)
24220 aMnemonicGenerator.RegisterMnemonic(button_get_label(a));
24221 #if GTK_CHECK_VERSION(4, 0, 0)
24222 for (const auto a : m_aMnemonicCheckButtons)
24223 aMnemonicGenerator.RegisterMnemonic(get_label(a));
24224 #endif
24225 for (const auto a : m_aMnemonicLabels)
24226 aMnemonicGenerator.RegisterMnemonic(get_label(a));
24228 for (const auto a : m_aMnemonicButtons)
24230 OUString aLabel(button_get_label(a));
24231 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
24232 if (aLabel == aNewLabel)
24233 continue;
24234 button_set_label(a, aNewLabel);
24236 #if GTK_CHECK_VERSION(4, 0, 0)
24237 for (const auto a : m_aMnemonicCheckButtons)
24239 OUString aLabel(get_label(a));
24240 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
24241 if (aLabel == aNewLabel)
24242 continue;
24243 set_label(a, aNewLabel);
24245 #endif
24246 for (const auto a : m_aMnemonicLabels)
24248 OUString aLabel(get_label(a));
24249 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
24250 if (aLabel == aNewLabel)
24251 continue;
24252 set_label(a, aNewLabel);
24255 m_aMnemonicLabels.clear();
24256 #if GTK_CHECK_VERSION(4, 0, 0)
24257 m_aMnemonicCheckButtons.clear();
24258 #endif
24259 m_aMnemonicButtons.clear();
24262 OUString get_current_page_help_id()
24264 OUString sPageHelpId;
24265 // check to see if there is a notebook called tabcontrol and get the
24266 // helpid for the current page of that
24267 std::unique_ptr<weld::Notebook> xNotebook(weld_notebook(u"tabcontrol"_ustr));
24268 if (xNotebook)
24270 if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
24272 GtkWidget* pContainer = pPage->getWidget();
24273 if (GtkWidget* pPageWidget = widget_get_first_child(pContainer))
24274 sPageHelpId = ::get_help_id(pPageWidget);
24277 return sPageHelpId;
24280 virtual ~GtkInstanceBuilder() override
24282 g_slist_free(m_pObjectList);
24283 g_object_unref(m_pBuilder);
24285 if (m_xInterimGlue && !m_bAllowCycleFocusOut)
24286 AllowCycleFocusOut();
24288 m_xInterimGlue.disposeAndClear();
24291 //ideally we would have/use weld::Container add and explicitly
24292 //call add when we want to do this, but in the vcl impl the
24293 //parent has to be set when the child is created, so for the
24294 //gtk impl emulate this by doing this implicitly at weld time
24295 void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
24297 if (GTK_IS_POPOVER(pWidget))
24298 return;
24299 if (GTK_IS_WINDOW(pWidget))
24300 return;
24301 #if GTK_CHECK_VERSION(4, 0, 0)
24302 if (!gtk_widget_get_parent(pWidget))
24303 gtk_widget_set_parent(pWidget, m_pParentWidget);
24304 #else
24305 if (widget_get_toplevel(pWidget) == pWidget)
24306 gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
24307 #endif
24310 virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OUString &id) override
24312 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24313 if (!pMessageDialog)
24314 return nullptr;
24315 gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
24316 return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, true);
24319 virtual std::unique_ptr<weld::Assistant> weld_assistant(const OUString &id) override
24321 GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24322 if (!pAssistant)
24323 return nullptr;
24324 if (m_pParentWidget)
24325 gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
24326 return std::make_unique<GtkInstanceAssistant>(pAssistant, this, true);
24329 virtual std::unique_ptr<weld::Dialog> weld_dialog(const OUString &id) override
24331 GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24332 if (!pDialog)
24333 return nullptr;
24334 if (m_pParentWidget)
24335 gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
24336 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
24339 virtual std::unique_ptr<weld::Window> create_screenshot_window() override
24341 GtkWidget* pTopLevel = nullptr;
24343 for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
24345 GObject* pObj = static_cast<GObject*>(l->data);
24347 if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
24348 continue;
24350 if (!pTopLevel)
24351 pTopLevel = GTK_WIDGET(pObj);
24352 else if (GTK_IS_WINDOW(pObj))
24353 pTopLevel = GTK_WIDGET(pObj);
24356 if (!pTopLevel)
24357 return nullptr;
24359 GtkWindow* pDialog;
24360 if (GTK_IS_WINDOW(pTopLevel))
24361 pDialog = GTK_WINDOW(pTopLevel);
24362 else
24364 pDialog = GTK_WINDOW(gtk_dialog_new());
24365 ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
24367 GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
24368 #if !GTK_CHECK_VERSION(4, 0, 0)
24369 gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
24370 gtk_widget_show_all(pTopLevel);
24371 #else
24372 gtk_box_append(GTK_BOX(pContentArea), pTopLevel);
24373 gtk_widget_show(pTopLevel);
24374 #endif
24377 if (m_pParentWidget)
24378 gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
24379 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
24382 virtual std::unique_ptr<weld::Widget> weld_widget(const OUString &id) override
24384 GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24385 if (!pWidget)
24386 return nullptr;
24387 auto_add_parentless_widgets_to_container(pWidget);
24388 return std::make_unique<GtkInstanceWidget>(pWidget, this, false);
24391 virtual std::unique_ptr<weld::Container> weld_container(const OUString &id) override
24393 #if !GTK_CHECK_VERSION(4, 0, 0)
24394 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24395 #else
24396 GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24397 #endif
24398 if (!pContainer)
24399 return nullptr;
24400 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
24401 return std::make_unique<GtkInstanceContainer>(pContainer, this, false);
24404 virtual std::unique_ptr<weld::Box> weld_box(const OUString &id) override
24406 GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24407 if (!pBox)
24408 return nullptr;
24409 auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
24410 return std::make_unique<GtkInstanceBox>(pBox, this, false);
24413 virtual std::unique_ptr<weld::Paned> weld_paned(const OUString &id) override
24415 GtkPaned* pPaned = GTK_PANED(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24416 if (!pPaned)
24417 return nullptr;
24418 auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned));
24419 return std::make_unique<GtkInstancePaned>(pPaned, this, false);
24422 virtual std::unique_ptr<weld::Frame> weld_frame(const OUString &id) override
24424 GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24425 if (!pFrame)
24426 return nullptr;
24427 auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
24428 return std::make_unique<GtkInstanceFrame>(pFrame, this, false);
24431 virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OUString &id, bool bUserManagedScrolling = false) override
24433 GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24434 if (!pScrolledWindow)
24435 return nullptr;
24436 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
24437 return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, false, bUserManagedScrolling);
24440 virtual std::unique_ptr<weld::Notebook> weld_notebook(const OUString &id) override
24442 GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24443 if (!pNotebook)
24444 return nullptr;
24445 auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
24446 return std::make_unique<GtkInstanceNotebook>(pNotebook, this, false);
24449 virtual std::unique_ptr<weld::Button> weld_button(const OUString &id) override
24451 GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24452 if (!pButton)
24453 return nullptr;
24454 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
24455 return std::make_unique<GtkInstanceButton>(pButton, this, false);
24458 virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OUString &id) override
24460 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24461 if (!pButton)
24462 return nullptr;
24463 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
24464 return std::make_unique<GtkInstanceMenuButton>(pButton, nullptr, this, false);
24467 virtual std::unique_ptr<weld::MenuToggleButton> weld_menu_toggle_button(const OUString &id) override
24469 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24470 if (!pButton)
24471 return nullptr;
24472 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
24473 // gtk doesn't come with exactly the same concept
24474 GtkBuilder* pMenuToggleButton = makeMenuToggleButtonBuilder();
24475 return std::make_unique<GtkInstanceMenuToggleButton>(pMenuToggleButton, pButton, this, false);
24478 virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OUString &id) override
24480 GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24481 if (!pButton)
24482 return nullptr;
24483 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
24484 return std::make_unique<GtkInstanceLinkButton>(pButton, this, false);
24487 virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OUString &id) override
24489 GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24490 if (!pToggleButton)
24491 return nullptr;
24492 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
24493 return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, false);
24496 virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OUString &id) override
24498 #if GTK_CHECK_VERSION(4, 0, 0)
24499 GtkCheckButton* pRadioButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24500 #else
24501 GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24502 #endif
24503 if (!pRadioButton)
24504 return nullptr;
24505 auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
24506 return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, false);
24509 virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OUString &id) override
24511 GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24512 if (!pCheckButton)
24513 return nullptr;
24514 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
24515 return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, false);
24518 virtual std::unique_ptr<weld::Scale> weld_scale(const OUString &id) override
24520 GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24521 if (!pScale)
24522 return nullptr;
24523 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
24524 return std::make_unique<GtkInstanceScale>(pScale, this, false);
24527 virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OUString &id) override
24529 GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24530 if (!pProgressBar)
24531 return nullptr;
24532 auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
24533 return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, false);
24536 virtual std::unique_ptr<weld::LevelBar> weld_level_bar(const OUString& id) override
24538 GtkLevelBar* pLevelBar = GTK_LEVEL_BAR(gtk_builder_get_object(
24539 m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24540 if (!pLevelBar)
24541 return nullptr;
24542 auto_add_parentless_widgets_to_container(GTK_WIDGET(pLevelBar));
24543 return std::make_unique<GtkInstanceLevelBar>(pLevelBar, this, false);
24546 virtual std::unique_ptr<weld::Spinner> weld_spinner(const OUString &id) override
24548 GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24549 if (!pSpinner)
24550 return nullptr;
24551 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
24552 return std::make_unique<GtkInstanceSpinner>(pSpinner, this, false);
24555 virtual std::unique_ptr<weld::Image> weld_image(const OUString &id) override
24557 GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24558 if (!pWidget)
24559 return nullptr;
24560 if (GTK_IS_IMAGE(pWidget))
24562 auto_add_parentless_widgets_to_container(pWidget);
24563 return std::make_unique<GtkInstanceImage>(GTK_IMAGE(pWidget), this, false);
24565 #if GTK_CHECK_VERSION(4, 0, 0)
24566 if (GTK_IS_PICTURE(pWidget))
24568 auto_add_parentless_widgets_to_container(pWidget);
24569 return std::make_unique<GtkInstancePicture>(GTK_PICTURE(pWidget), this, false);
24571 #endif
24572 return nullptr;
24575 virtual std::unique_ptr<weld::Calendar> weld_calendar(const OUString &id) override
24577 GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24578 if (!pCalendar)
24579 return nullptr;
24580 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
24581 return std::make_unique<GtkInstanceCalendar>(pCalendar, this, false);
24584 virtual std::unique_ptr<weld::Entry> weld_entry(const OUString &id) override
24586 GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24587 if (!pEntry)
24588 return nullptr;
24589 auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
24590 return std::make_unique<GtkInstanceEntry>(pEntry, this, false);
24593 virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OUString &id) override
24595 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24596 if (!pSpinButton)
24597 return nullptr;
24598 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
24599 return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, false);
24602 virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OUString& id, FieldUnit eUnit) override
24604 return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id), eUnit);
24607 virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OUString &id) override
24609 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24610 if (!pSpinButton)
24611 return nullptr;
24612 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
24613 return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, false);
24616 virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OUString &id) override
24618 GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24619 if (!pComboBox)
24620 return nullptr;
24621 auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
24623 #if GTK_CHECK_VERSION(4, 0, 0)
24624 return std::make_unique<GtkInstanceComboBox>(pComboBox, this, false);
24625 #else
24626 /* we replace GtkComboBox because of difficulties with too tall menus
24628 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
24629 has_entry long menus take forever to appear (tdf#125388)
24631 on measuring each row, the GtkComboBox GtkTreeMenu will call
24632 its area_apply_attributes_cb function on the row, but that calls
24633 gtk_tree_menu_get_path_item which then loops through each child of the
24634 menu looking for the widget of the row, so performance drops to useless.
24636 All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
24637 with fragile hackery which assumes that the unwanted callback is the only one with a
24639 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
24640 when a super tall combobox menu is activated, and the selected
24641 entry is sufficiently far down the list, then the menu doesn't
24642 appear under wayland
24644 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
24645 no typeahead support
24647 4) we want to be able to control the width of the button, but have a drop down menu which
24648 is not limited to the width of the button
24650 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
24651 super tall menu doesn't appear under X sometimes
24653 GtkBuilder* pComboBuilder = makeComboBoxBuilder();
24654 return std::make_unique<GtkInstanceComboBox>(pComboBuilder, pComboBox, this, false);
24655 #endif
24658 virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OUString &id) override
24660 GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24661 if (!pTreeView)
24662 return nullptr;
24663 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
24664 return std::make_unique<GtkInstanceTreeView>(pTreeView, this, false);
24667 virtual std::unique_ptr<weld::IconView> weld_icon_view(const OUString &id) override
24669 GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24670 if (!pIconView)
24671 return nullptr;
24672 auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
24673 return std::make_unique<GtkInstanceIconView>(pIconView, this, false);
24676 virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OUString& containerid, const OUString& entryid, const OUString& treeviewid) override
24678 #if GTK_CHECK_VERSION(4, 0, 0)
24679 GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, OUStringToOString(containerid, RTL_TEXTENCODING_UTF8).getStr()));
24680 #else
24681 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, OUStringToOString(containerid, RTL_TEXTENCODING_UTF8).getStr()));
24682 #endif
24683 if (!pContainer)
24684 return nullptr;
24685 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
24686 return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, false,
24687 weld_entry(entryid),
24688 weld_tree_view(treeviewid));
24691 virtual std::unique_ptr<weld::Label> weld_label(const OUString &id) override
24693 GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24694 if (!pLabel)
24695 return nullptr;
24696 auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
24697 return std::make_unique<GtkInstanceLabel>(pLabel, this, false);
24700 virtual std::unique_ptr<weld::TextView> weld_text_view(const OUString &id) override
24702 GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24703 if (!pTextView)
24704 return nullptr;
24705 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
24706 return std::make_unique<GtkInstanceTextView>(pTextView, this, false);
24709 virtual std::unique_ptr<weld::Expander> weld_expander(const OUString &id) override
24711 GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24712 if (!pExpander)
24713 return nullptr;
24714 auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
24715 return std::make_unique<GtkInstanceExpander>(pExpander, this, false);
24718 virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OUString &id, const a11yref& rA11y,
24719 FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/) override
24721 GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24722 if (!pDrawingArea)
24723 return nullptr;
24724 auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
24725 return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, false);
24728 virtual std::unique_ptr<weld::Menu> weld_menu(const OUString &id) override
24730 #if GTK_CHECK_VERSION(4, 0, 0)
24731 GtkPopoverMenu* pMenu = GTK_POPOVER_MENU(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24732 #else
24733 GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24734 #endif
24735 if (!pMenu)
24736 return nullptr;
24737 return std::make_unique<GtkInstanceMenu>(pMenu, true);
24740 virtual std::unique_ptr<weld::Popover> weld_popover(const OUString &id) override
24742 GtkPopover* pPopover = GTK_POPOVER(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24743 if (!pPopover)
24744 return nullptr;
24745 #if GTK_CHECK_VERSION(4, 0, 0)
24746 return std::make_unique<GtkInstancePopover>(pPopover, this, false);
24747 #else
24748 return std::make_unique<GtkInstancePopover>(pPopover, this, true);
24749 #endif
24752 virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OUString &id) override
24754 #if GTK_CHECK_VERSION(4, 0, 0)
24755 GtkBox* pToolbar = GTK_BOX(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24756 #else
24757 GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24758 #endif
24759 if (!pToolbar)
24760 return nullptr;
24761 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
24762 return std::make_unique<GtkInstanceToolbar>(pToolbar, this, false);
24765 virtual std::unique_ptr<weld::Scrollbar> weld_scrollbar(const OUString &id) override
24767 GtkScrollbar* pScrollbar = GTK_SCROLLBAR(gtk_builder_get_object(m_pBuilder, OUStringToOString(id, RTL_TEXTENCODING_UTF8).getStr()));
24768 if (!pScrollbar)
24769 return nullptr;
24770 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrollbar));
24771 return std::make_unique<GtkInstanceScrollbar>(pScrollbar, this, false);
24774 virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
24776 return std::make_unique<GtkInstanceSizeGroup>();
24782 void GtkInstanceWindow::help()
24784 //show help for widget with keyboard focus
24785 GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
24786 if (!pWidget)
24787 pWidget = GTK_WIDGET(m_pWindow);
24788 OUString sHelpId = ::get_help_id(pWidget);
24789 while (sHelpId.isEmpty())
24791 pWidget = gtk_widget_get_parent(pWidget);
24792 if (!pWidget)
24793 break;
24794 sHelpId = ::get_help_id(pWidget);
24796 std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
24797 weld::Widget* pSource = xTemp ? xTemp.get() : this;
24798 bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
24799 Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
24800 if (!pHelp)
24801 return;
24803 #if !GTK_CHECK_VERSION(4, 0, 0)
24804 // tdf#126007, there's a nice fallback route for offline help where
24805 // the current page of a notebook will get checked when the help
24806 // button is pressed and there was no help for the dialog found.
24808 // But for online help that route doesn't get taken, so bodge this here
24809 // by using the page help id if available and if the help button itself
24810 // was the original id
24811 if (m_pBuilder && sHelpId.endsWith("/help"))
24813 OUString sPageId = m_pBuilder->get_current_page_help_id();
24814 if (!sPageId.isEmpty())
24815 sHelpId = sPageId;
24816 else
24818 // tdf#129068 likewise the help for the wrapping dialog is less
24819 // helpful than the help for the content area could be
24820 GtkContainer* pContainer = nullptr;
24821 if (GTK_IS_DIALOG(m_pWindow))
24822 pContainer = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow)));
24823 else if (GTK_IS_ASSISTANT(m_pWindow))
24825 GtkAssistant* pAssistant = GTK_ASSISTANT(m_pWindow);
24826 pContainer = GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant, gtk_assistant_get_current_page(pAssistant)));
24828 if (pContainer)
24830 GtkWidget* pContentWidget = widget_get_first_child(GTK_WIDGET(pContainer));
24831 if (pContentWidget)
24832 sHelpId = ::get_help_id(pContentWidget);
24836 #endif
24837 pHelp->Start(sHelpId, pSource);
24840 //iterate upwards through the hierarchy from this widgets through its parents
24841 //calling func with their helpid until func returns true or we run out of parents
24842 void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OUString&)>& func)
24844 GtkWidget* pParent = m_pWidget;
24845 while ((pParent = gtk_widget_get_parent(pParent)))
24847 if (func(::get_help_id(pParent)))
24848 return;
24852 std::unique_ptr<weld::Builder> GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
24854 GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
24855 GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
24856 return std::make_unique<GtkInstanceBuilder>(pBuilderParent, rUIRoot, rUIFile, nullptr, true);
24859 #if !GTK_CHECK_VERSION(4, 0, 0)
24860 // tdf#135965 for the case of native widgets inside a GtkSalFrame and F1 pressed, run help
24861 // on gtk widget help ids until we hit a vcl parent and then use vcl window help ids
24862 gboolean GtkSalFrame::NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer pFrame)
24864 Help* pHelp = Application::GetHelp();
24865 if (!pHelp)
24866 return true;
24868 GtkWindow* pWindow = static_cast<GtkWindow*>(pFrame);
24870 vcl::Window* pChildWindow = nullptr;
24872 //show help for widget with keyboard focus
24873 GtkWidget* pWidget = gtk_window_get_focus(pWindow);
24874 if (!pWidget)
24875 pWidget = GTK_WIDGET(pWindow);
24876 OUString sHelpId = ::get_help_id(pWidget);
24877 while (sHelpId.isEmpty())
24879 pWidget = gtk_widget_get_parent(pWidget);
24880 if (!pWidget)
24881 break;
24882 pChildWindow = static_cast<vcl::Window*>(g_object_get_data(G_OBJECT(pWidget), "InterimWindowGlue"));
24883 if (pChildWindow)
24885 sHelpId = pChildWindow->GetHelpId();
24886 break;
24888 sHelpId = ::get_help_id(pWidget);
24891 if (pChildWindow)
24893 while (sHelpId.isEmpty())
24895 pChildWindow = pChildWindow->GetParent();
24896 if (!pChildWindow)
24897 break;
24898 sHelpId = pChildWindow->GetHelpId();
24900 if (!pChildWindow)
24901 return true;
24902 pHelp->Start(sHelpId, pChildWindow);
24903 return true;
24906 if (!pWidget)
24907 return true;
24908 std::unique_ptr<weld::Widget> xTemp(new GtkInstanceWidget(pWidget, nullptr, false));
24909 pHelp->Start(sHelpId, xTemp.get());
24910 return true;
24912 #endif
24914 std::unique_ptr<weld::Builder> GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
24915 bool bAllowCycleFocusOut, sal_uInt64)
24917 // Create a foreign window which we know is a GtkGrid and make the native widgets a child of that, so we can
24918 // support GtkWidgets within a vcl::Window
24919 SystemWindowData winData = {};
24920 winData.bClipUsingNativeWidget = true;
24921 auto xEmbedWindow = VclPtr<SystemChildWindow>::Create(pParent, 0, &winData, false);
24922 xEmbedWindow->Show(true, ShowFlags::NoActivate);
24923 xEmbedWindow->set_expand(true);
24925 const SystemEnvData* pEnvData = xEmbedWindow->GetSystemData();
24926 if (!pEnvData)
24927 return nullptr;
24929 GtkWidget *pWindow = static_cast<GtkWidget*>(pEnvData->pWidget);
24930 #if !GTK_CHECK_VERSION(4, 0, 0)
24931 gtk_widget_show_all(pWindow);
24932 #else
24933 gtk_widget_show(pWindow);
24934 #endif
24936 // build the widget tree as a child of the GtkEventBox GtkGrid parent
24937 return std::make_unique<GtkInstanceBuilder>(pWindow, rUIRoot, rUIFile, xEmbedWindow.get(), bAllowCycleFocusOut);
24940 weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
24942 GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
24943 GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
24944 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
24945 VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
24946 OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
24947 return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
24950 weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
24952 if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
24953 return pGtkXWindow->getFrameWeld();
24954 return SalInstance::GetFrameWeld(rWindow);
24957 weld::Window* GtkSalFrame::GetFrameWeld() const
24959 if (!m_xFrameWeld)
24960 m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(widget_get_toplevel(getWindow())), nullptr, false));
24961 return m_xFrameWeld.get();
24964 void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
24966 #if ENABLE_GSTREAMER_1_0
24967 auto aSymbol = gstElementFactoryNameSymbol();
24968 if (!aSymbol)
24969 return nullptr;
24971 const SystemEnvData* pEnvData = pWindow->GetSystemData();
24972 if (!pEnvData)
24973 return nullptr;
24975 GstElement* pVideosink = aSymbol("gtksink", "gtksink");
24976 if (!pVideosink)
24977 return nullptr;
24979 GtkWidget *pGstWidget;
24980 g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
24981 gtk_widget_set_vexpand(pGstWidget, true);
24982 gtk_widget_set_hexpand(pGstWidget, true);
24984 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
24985 #if !GTK_CHECK_VERSION(4, 0, 0)
24986 gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
24987 #endif
24988 g_object_unref(pGstWidget);
24989 #if !GTK_CHECK_VERSION(4, 0, 0)
24990 gtk_widget_show_all(pParent);
24991 #else
24992 gtk_widget_show(pParent);
24993 #endif
24995 return pVideosink;
24996 #else
24997 (void)pWindow;
24998 return nullptr;
24999 #endif
25002 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */