Introduce weld::IconView::insert_separator
[LibreOffice.git] / vcl / unx / gtk3 / gtkinst.cxx
blob6536a0b3ffea0fc1de846d06779e181840ea5a60
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 <stack>
14 #include <string.h>
15 #include <string_view>
17 #include <dndhelper.hxx>
18 #include <osl/process.h>
19 #include <unx/gtk/gtkdata.hxx>
20 #include <unx/gtk/gtkinst.hxx>
21 #include <unx/genprn.h>
22 #include <unx/salobj.h>
23 #include <unx/gtk/gtkgdi.hxx>
24 #include <unx/gtk/gtkframe.hxx>
25 #include <unx/gtk/gtkobject.hxx>
26 #include <unx/gtk/atkbridge.hxx>
27 #include <unx/gtk/gtksalmenu.hxx>
28 #include <headless/svpvd.hxx>
29 #include <headless/svpbmp.hxx>
30 #include <vcl/builder.hxx>
31 #include <vcl/inputtypes.hxx>
32 #include <vcl/specialchars.hxx>
33 #include <vcl/sysdata.hxx>
34 #include <vcl/transfer.hxx>
35 #include <vcl/toolkit/floatwin.hxx>
36 #include <unx/genpspgraphics.h>
37 #include <rtl/strbuf.hxx>
38 #include <sal/log.hxx>
39 #include <rtl/uri.hxx>
41 #include <vcl/settings.hxx>
43 #include <dlfcn.h>
44 #include <fcntl.h>
45 #include <unistd.h>
47 #if !GTK_CHECK_VERSION(4, 0, 0)
48 #include "a11y/atkwrapper.hxx"
49 #endif
50 #include <com/sun/star/datatransfer/XTransferable.hpp>
51 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
52 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
53 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
54 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
55 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
56 #include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
57 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
58 #include <com/sun/star/lang/IllegalArgumentException.hpp>
59 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
60 #include <com/sun/star/lang/XServiceInfo.hpp>
61 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
62 #include <com/sun/star/lang/XInitialization.hpp>
63 #include <comphelper/lok.hxx>
64 #include <comphelper/processfactory.hxx>
65 #include <comphelper/propertyvalue.hxx>
66 #include <comphelper/sequence.hxx>
67 #include <comphelper/string.hxx>
68 #include <cppuhelper/compbase.hxx>
69 #include <cppuhelper/implbase.hxx>
70 #include <cppuhelper/supportsservice.hxx>
71 #include <officecfg/Office/Common.hxx>
72 #include <rtl/bootstrap.hxx>
73 #include <o3tl/unreachable.hxx>
74 #include <o3tl/string_view.hxx>
75 #include <svl/zforlist.hxx>
76 #include <svl/zformat.hxx>
77 #include <tools/helpers.hxx>
78 #include <tools/fract.hxx>
79 #include <tools/stream.hxx>
80 #include <unotools/resmgr.hxx>
81 #include <unx/gstsink.hxx>
82 #include <vcl/ImageTree.hxx>
83 #include <vcl/abstdlg.hxx>
84 #include <vcl/event.hxx>
85 #include <vcl/i18nhelp.hxx>
86 #include <vcl/quickselectionengine.hxx>
87 #include <vcl/mnemonic.hxx>
88 #include <vcl/pngwrite.hxx>
89 #include <vcl/stdtext.hxx>
90 #include <vcl/syswin.hxx>
91 #include <vcl/virdev.hxx>
92 #include <vcl/weld.hxx>
93 #include <vcl/wrkwin.hxx>
94 #include "customcellrenderer.hxx"
95 #include <strings.hrc>
96 #include <window.h>
97 #include <numeric>
98 #include <boost/property_tree/ptree.hpp>
99 #include <opengl/zone.hxx>
101 using namespace com::sun::star;
102 using namespace com::sun::star::uno;
103 using namespace com::sun::star::lang;
105 extern "C"
107 #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalInstance()->GetYieldMutex())
108 #if !GTK_CHECK_VERSION(4, 0, 0)
109 static void GdkThreadsEnter()
111 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
112 pYieldMutex->ThreadsEnter();
114 static void GdkThreadsLeave()
116 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
117 pYieldMutex->ThreadsLeave();
119 #endif
121 VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
123 SAL_INFO(
124 "vcl.gtk",
125 "create vcl plugin instance with gtk version " << gtk_get_major_version()
126 << " " << gtk_get_minor_version() << " " << gtk_get_micro_version());
128 if (gtk_get_major_version() == 3 && gtk_get_minor_version() < 18)
130 g_warning("require gtk >= 3.18 for theme expectations");
131 return nullptr;
134 // for gtk2 it is always built with X support, so this is always called
135 // for gtk3 it is normally built with X and Wayland support, if
136 // X is supported GDK_WINDOWING_X11 is defined and this is always
137 // called, regardless of if we're running under X or Wayland.
138 // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
139 // X, because we need to do it earlier than we have a display
140 #if defined(GDK_WINDOWING_X11)
141 /* #i92121# workaround deadlocks in the X11 implementation
143 static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
144 /* #i90094#
145 from now on we know that an X connection will be
146 established, so protect X against itself
148 if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
149 XInitThreads();
150 #endif
152 #if !GTK_CHECK_VERSION(4, 0, 0)
153 // init gdk thread protection
154 bool const sup = g_thread_supported();
155 // extracted from the 'if' to avoid Clang -Wunreachable-code
156 if ( !sup )
157 g_thread_init( nullptr );
159 gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
160 SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
161 #endif
163 auto pYieldMutex = std::make_unique<GtkYieldMutex>();
165 #if !GTK_CHECK_VERSION(4, 0, 0)
166 gdk_threads_init();
167 #endif
169 GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
170 SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
172 // Create SalData, this does not leak
173 new GtkSalData();
175 return pInstance;
179 #if !GTK_CHECK_VERSION(4, 0, 0)
180 static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
182 VclInputFlags nType = VclInputFlags::NONE;
183 switch (gdk_event_get_event_type(pEvent))
185 case GDK_MOTION_NOTIFY:
186 case GDK_BUTTON_PRESS:
187 #if !GTK_CHECK_VERSION(4, 0, 0)
188 case GDK_2BUTTON_PRESS:
189 case GDK_3BUTTON_PRESS:
190 #endif
191 case GDK_BUTTON_RELEASE:
192 case GDK_ENTER_NOTIFY:
193 case GDK_LEAVE_NOTIFY:
194 case GDK_SCROLL:
195 nType = VclInputFlags::MOUSE;
196 break;
197 case GDK_KEY_PRESS:
198 // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
199 nType = VclInputFlags::KEYBOARD;
200 break;
201 #if !GTK_CHECK_VERSION(4, 0, 0)
202 case GDK_EXPOSE:
203 nType = VclInputFlags::PAINT;
204 break;
205 #endif
206 default:
207 nType = VclInputFlags::OTHER;
208 break;
210 return nType;
212 #endif
214 GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
215 : SvpSalInstance( std::move(pMutex) )
216 , m_pTimer(nullptr)
217 , bNeedsInit(true)
218 , m_pLastCairoFontOptions(nullptr)
220 m_bSupportsOpenGL = true;
223 //We want to defer initializing gtk until we are after uno has been
224 //bootstrapped so we can ask the config what the UI language is so that we can
225 //force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
226 //UI in a LTR locale
227 void GtkInstance::AfterAppInit()
229 EnsureInit();
232 void GtkInstance::EnsureInit()
234 if (!bNeedsInit)
235 return;
236 // initialize SalData
237 GtkSalData *pSalData = GetGtkSalData();
238 pSalData->Init();
239 GtkSalData::initNWF();
241 #if !GTK_CHECK_VERSION(4, 0, 0)
242 InitAtkBridge();
243 #endif
245 ImplSVData* pSVData = ImplGetSVData();
246 #ifdef GTK_TOOLKIT_NAME
247 pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
248 #else
249 pSVData->maAppData.mxToolkitName = OUString("gtk3");
250 #endif
252 bNeedsInit = false;
255 GtkInstance::~GtkInstance()
257 assert( nullptr == m_pTimer );
258 #if !GTK_CHECK_VERSION(4, 0, 0)
259 DeInitAtkBridge();
260 #endif
261 ResetLastSeenCairoFontOptions(nullptr);
264 SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
266 EnsureInit();
267 return new GtkSalFrame( pParent, nStyle );
270 SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
272 EnsureInit();
273 return new GtkSalFrame( pParentData );
276 SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
278 EnsureInit();
279 //FIXME: Missing CreateObject functionality ...
280 if (pWindowData && pWindowData->bClipUsingNativeWidget)
281 return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame*>(pParent), bShow);
282 return new GtkSalObject(static_cast<GtkSalFrame*>(pParent), bShow);
285 extern "C"
287 typedef void*(* getDefaultFnc)();
288 typedef void(* addItemFnc)(void *, const char *);
291 void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
293 EnsureInit();
294 OString sGtkURL;
295 rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
296 if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
297 sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
298 else
300 //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
301 //Decode %XX components
302 OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
303 //Convert back to system locale encoding
304 OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
305 //Encode to an escaped ASCII-encoded URI
306 gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
307 sGtkURL = OString(g_uri);
308 g_free(g_uri);
310 GtkRecentManager *manager = gtk_recent_manager_get_default ();
311 gtk_recent_manager_add_item (manager, sGtkURL.getStr());
314 SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
315 ImplJobSetup* pSetupData )
317 EnsureInit();
318 mbPrinterInit = true;
319 // create and initialize SalInfoPrinter
320 PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
321 configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
322 return pPrinter;
325 std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
327 EnsureInit();
328 mbPrinterInit = true;
329 return std::unique_ptr<SalPrinter>(new PspSalPrinter(pInfoPrinter));
333 * These methods always occur in pairs
334 * A ThreadsEnter is followed by a ThreadsLeave
335 * We need to queue up the recursive lock count
336 * for each pair, so we can accurately restore
337 * it later.
339 thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
341 void GtkYieldMutex::ThreadsEnter()
343 acquire();
344 if (yieldCounts.empty())
345 return;
346 auto n = yieldCounts.top();
347 yieldCounts.pop();
349 const bool bUndoingLeaveWithoutEnter = n == 0;
350 // if the ThreadsLeave bLeaveWithoutEnter of true condition occurred to
351 // create this entry then return early undoing the initial acquire of the
352 // function
353 if G_UNLIKELY(bUndoingLeaveWithoutEnter)
355 release();
356 return;
359 assert(n > 0);
360 n--;
361 if (n > 0)
362 acquire(n);
365 void GtkYieldMutex::ThreadsLeave()
367 const bool bLeaveWithoutEnter = m_nCount == 0;
368 SAL_WARN_IF(bLeaveWithoutEnter, "vcl.gtk", "gdk_threads_leave without matching gdk_threads_enter");
369 yieldCounts.push(m_nCount);
370 if G_UNLIKELY(bLeaveWithoutEnter) // this ideally shouldn't happen, but can due to the gtk3 file dialog
371 return;
372 release(true);
375 std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
376 tools::Long &nDX, tools::Long &nDY,
377 DeviceFormat /*eFormat*/,
378 const SystemGraphicsData* pGd )
380 EnsureInit();
381 SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
382 assert(pSvpSalGraphics);
383 // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
384 cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
385 std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
386 pNew->SetSize( nDX, nDY );
387 return pNew;
390 std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
392 EnsureInit();
393 return SvpSalInstance::CreateSalBitmap();
396 std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
398 EnsureInit();
399 GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
400 pSalMenu->SetMenu( pVCLMenu );
401 return std::unique_ptr<SalMenu>(pSalMenu);
404 std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
406 EnsureInit();
407 return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
410 SalTimer* GtkInstance::CreateSalTimer()
412 EnsureInit();
413 assert( nullptr == m_pTimer );
414 if ( nullptr == m_pTimer )
415 m_pTimer = new GtkSalTimer();
416 return m_pTimer;
419 void GtkInstance::RemoveTimer ()
421 EnsureInit();
422 m_pTimer = nullptr;
425 bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
427 EnsureInit();
428 return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
431 bool GtkInstance::IsTimerExpired()
433 EnsureInit();
434 return (m_pTimer && m_pTimer->Expired());
437 namespace
439 bool DisplayHasAnyInput()
441 GdkDisplay* pDisplay = gdk_display_get_default();
442 #if defined(GDK_WINDOWING_WAYLAND)
443 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
445 bool bRet = false;
446 wl_display* pWLDisplay = gdk_wayland_display_get_wl_display(pDisplay);
447 static auto wayland_display_get_fd = reinterpret_cast<int (*) (wl_display*)>(dlsym(nullptr, "wl_display_get_fd"));
448 if (wayland_display_get_fd)
450 GPollFD aPollFD;
451 aPollFD.fd = wayland_display_get_fd(pWLDisplay);
452 aPollFD.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
453 bRet = g_poll(&aPollFD, 1, 0) > 0;
455 return bRet;
457 #endif
458 #if defined(GDK_WINDOWING_X11)
459 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
461 GPollFD aPollFD;
462 aPollFD.fd = ConnectionNumber(gdk_x11_display_get_xdisplay(pDisplay));
463 aPollFD.events = G_IO_IN;
464 return g_poll(&aPollFD, 1, 0) > 0;
466 #endif
467 return false;
471 bool GtkInstance::AnyInput( VclInputFlags nType )
473 EnsureInit();
474 if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
475 return true;
477 // strip timer bits now
478 nType = nType & ~VclInputFlags::TIMER;
480 static constexpr VclInputFlags ANY_INPUT_EXCLUDING_TIMER = VCL_INPUT_ANY & ~VclInputFlags::TIMER;
482 const bool bCheckForAnyInput = nType == ANY_INPUT_EXCLUDING_TIMER;
484 bool bRet = false;
486 if (bCheckForAnyInput)
487 bRet = DisplayHasAnyInput();
489 #if !GTK_CHECK_VERSION(4, 0, 0)
490 GdkDisplay* pDisplay = gdk_display_get_default();
491 if (!gdk_display_has_pending(pDisplay))
492 return bRet;
494 if (bCheckForAnyInput)
495 return true;
497 std::deque<GdkEvent*> aEvents;
498 GdkEvent *pEvent = nullptr;
499 while ((pEvent = gdk_display_get_event(pDisplay)))
501 aEvents.push_back(pEvent);
502 VclInputFlags nEventType = categorizeEvent(pEvent);
503 if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
505 bRet = true;
509 while (!aEvents.empty())
511 pEvent = aEvents.front();
512 gdk_display_put_event(pDisplay, pEvent);
513 gdk_event_free(pEvent);
514 aEvents.pop_front();
516 #endif
518 return bRet;
521 std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
523 EnsureInit();
524 return std::make_unique<GenPspGraphics>();
527 const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
529 #if !GTK_CHECK_VERSION(4, 0, 0)
530 const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
531 #else
532 auto pDefaultWin = ImplGetDefaultWindow();
533 assert(pDefaultWin);
534 SalFrame* pDefaultFrame = pDefaultWin->ImplGetFrame();
535 GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pDefaultFrame);
536 assert(pGtkFrame);
537 const cairo_font_options_t* pCairoFontOptions = pGtkFrame->get_font_options();
538 #endif
539 if (!m_pLastCairoFontOptions && pCairoFontOptions)
540 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
541 return pCairoFontOptions;
544 const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
546 return m_pLastCairoFontOptions;
549 void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
551 if (m_pLastCairoFontOptions)
552 cairo_font_options_destroy(m_pLastCairoFontOptions);
553 if (pCairoFontOptions)
554 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
555 else
556 m_pLastCairoFontOptions = nullptr;
560 namespace
562 struct TypeEntry
564 const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
565 const char* pType; // Mime encoding on our side
568 const TypeEntry aConversionTab[] =
570 { "ISO10646-1", "text/plain;charset=utf-16" },
571 { "UTF8_STRING", "text/plain;charset=utf-8" },
572 { "UTF-8", "text/plain;charset=utf-8" },
573 { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
574 // ISO encodings
575 { "ISO8859-2", "text/plain;charset=iso8859-2" },
576 { "ISO8859-3", "text/plain;charset=iso8859-3" },
577 { "ISO8859-4", "text/plain;charset=iso8859-4" },
578 { "ISO8859-5", "text/plain;charset=iso8859-5" },
579 { "ISO8859-6", "text/plain;charset=iso8859-6" },
580 { "ISO8859-7", "text/plain;charset=iso8859-7" },
581 { "ISO8859-8", "text/plain;charset=iso8859-8" },
582 { "ISO8859-9", "text/plain;charset=iso8859-9" },
583 { "ISO8859-10", "text/plain;charset=iso8859-10" },
584 { "ISO8859-13", "text/plain;charset=iso8859-13" },
585 { "ISO8859-14", "text/plain;charset=iso8859-14" },
586 { "ISO8859-15", "text/plain;charset=iso8859-15" },
587 // asian encodings
588 { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
589 { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
590 { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
591 { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
592 { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
593 { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
594 // eastern european encodings
595 { "KOI8-R", "text/plain;charset=koi8-r" },
596 { "KOI8-U", "text/plain;charset=koi8-u" },
597 // String (== iso8859-1)
598 { "STRING", "text/plain;charset=iso8859-1" },
599 // special for compound text
600 { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
602 // PIXMAP
603 { "PIXMAP", "image/bmp" }
606 class DataFlavorEq
608 private:
609 const css::datatransfer::DataFlavor& m_rData;
610 public:
611 explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
612 bool operator() (const css::datatransfer::DataFlavor& rData) const
614 return rData.MimeType == m_rData.MimeType &&
615 rData.DataType == m_rData.DataType;
620 #if GTK_CHECK_VERSION(4, 0, 0)
621 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(const char * const *targets, gint n_targets)
622 #else
623 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
624 #endif
626 std::vector<css::datatransfer::DataFlavor> aVector;
628 bool bHaveText = false, bHaveUTF16 = false;
630 for (gint i = 0; i < n_targets; ++i)
632 #if GTK_CHECK_VERSION(4, 0, 0)
633 const gchar* pName = targets[i];
634 #else
635 gchar* pName = gdk_atom_name(targets[i]);
636 #endif
637 const char* pFinalName = pName;
638 css::datatransfer::DataFlavor aFlavor;
640 // omit text/plain;charset=unicode since it is not well defined
641 if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
643 #if !GTK_CHECK_VERSION(4, 0, 0)
644 g_free(pName);
645 #endif
646 continue;
649 for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
651 if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
653 pFinalName = aConversionTab[j].pType;
654 break;
658 // There are more non-MIME-types reported that are not translated by
659 // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
660 // them out for now before they confuse this code's clients:
661 if (rtl_str_indexOfChar(pFinalName, '/') == -1)
663 #if !GTK_CHECK_VERSION(4, 0, 0)
664 g_free(pName);
665 #endif
666 continue;
669 aFlavor.MimeType = OUString(pFinalName,
670 strlen(pFinalName),
671 RTL_TEXTENCODING_UTF8);
673 m_aMimeTypeToGtkType[aFlavor.MimeType] = targets[i];
675 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
677 sal_Int32 nIndex(0);
678 if (o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
680 bHaveText = true;
681 std::u16string_view aToken(o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex));
682 if (aToken == u"charset=utf-16")
684 bHaveUTF16 = true;
685 aFlavor.DataType = cppu::UnoType<OUString>::get();
688 aVector.push_back(aFlavor);
689 #if !GTK_CHECK_VERSION(4, 0, 0)
690 g_free(pName);
691 #endif
694 //If we have text, but no UTF-16 format which is basically the only
695 //text-format LibreOffice supports for cnp then claim we do and we
696 //will convert on demand
697 if (bHaveText && !bHaveUTF16)
699 css::datatransfer::DataFlavor aFlavor;
700 aFlavor.MimeType = "text/plain;charset=utf-16";
701 aFlavor.DataType = cppu::UnoType<OUString>::get();
702 aVector.push_back(aFlavor);
705 return aVector;
708 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
710 return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
713 sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
715 const std::vector<css::datatransfer::DataFlavor> aAll =
716 getTransferDataFlavorsAsVector();
718 return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
721 #if GTK_CHECK_VERSION(4, 0, 0)
722 void read_transfer_result::read_block_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
724 GInputStream* stream = G_INPUT_STREAM(source);
725 read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
727 gsize bytes_read = g_input_stream_read_finish(stream, res, nullptr);
729 bool bFinished = bytes_read == 0;
731 if (bFinished)
733 g_object_unref(stream);
734 pRes->aVector.resize(pRes->nRead);
735 pRes->bDone = true;
736 g_main_context_wakeup(nullptr);
737 return;
740 pRes->nRead += bytes_read;
742 pRes->aVector.resize(pRes->nRead + read_transfer_result::BlockSize);
744 g_input_stream_read_async(stream,
745 pRes->aVector.data() + pRes->nRead,
746 read_transfer_result::BlockSize,
747 G_PRIORITY_DEFAULT,
748 nullptr,
749 read_block_async_completed,
750 user_data);
753 OUString read_transfer_result::get_as_string() const
755 const char* pStr = reinterpret_cast<const char*>(aVector.data());
756 return OUString(pStr, aVector.size(), RTL_TEXTENCODING_UTF8).replaceAll("\r\n", "\n");
759 css::uno::Sequence<sal_Int8> read_transfer_result::get_as_sequence() const
761 return css::uno::Sequence<sal_Int8>(aVector.data(), aVector.size());
763 #endif
765 namespace {
767 GdkClipboard* clipboard_get(SelectionType eSelection)
769 #if GTK_CHECK_VERSION(4, 0, 0)
770 if (eSelection == SELECTION_CLIPBOARD)
771 return gdk_display_get_clipboard(gdk_display_get_default());
772 return gdk_display_get_primary_clipboard(gdk_display_get_default());
773 #else
774 return gtk_clipboard_get(eSelection == SELECTION_CLIPBOARD ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY);
775 #endif
778 #if GTK_CHECK_VERSION(4, 0, 0)
780 void read_clipboard_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
782 GdkClipboard* clipboard = GDK_CLIPBOARD(source);
783 read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
785 GInputStream* pResult = gdk_clipboard_read_finish(clipboard, res, nullptr, nullptr);
787 if (!pResult)
789 pRes->bDone = true;
790 g_main_context_wakeup(nullptr);
791 return;
794 pRes->aVector.resize(read_transfer_result::BlockSize);
796 g_input_stream_read_async(pResult,
797 pRes->aVector.data(),
798 pRes->aVector.size(),
799 G_PRIORITY_DEFAULT,
800 nullptr,
801 read_transfer_result::read_block_async_completed,
802 user_data);
805 #endif
807 class GtkClipboardTransferable : public GtkTransferable
809 private:
810 SelectionType m_eSelection;
812 public:
814 explicit GtkClipboardTransferable(SelectionType eSelection)
815 : m_eSelection(eSelection)
820 * XTransferable
823 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
825 css::datatransfer::DataFlavor aFlavor(rFlavor);
826 if (aFlavor.MimeType == "text/plain;charset=utf-16")
827 aFlavor.MimeType = "text/plain;charset=utf-8";
829 auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
830 if (it == m_aMimeTypeToGtkType.end())
831 return css::uno::Any();
833 css::uno::Any aRet;
835 GdkClipboard* clipboard = clipboard_get(m_eSelection);
837 #if !GTK_CHECK_VERSION(4, 0, 0)
838 if (aFlavor.MimeType == "text/plain;charset=utf-8")
840 gchar *pText = gtk_clipboard_wait_for_text(clipboard);
841 OUString aStr(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
842 g_free(pText);
843 aRet <<= aStr.replaceAll("\r\n", "\n");
844 return aRet;
846 else
848 GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
849 it->second);
850 if (!data)
852 return css::uno::Any();
854 gint length;
855 const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
856 &length);
857 Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
858 gtk_selection_data_free(data);
859 aRet <<= aSeq;
861 #else
862 SalInstance* pInstance = GetSalInstance();
863 read_transfer_result aRes;
864 const char *mime_types[] = { it->second.getStr(), nullptr };
866 gdk_clipboard_read_async(clipboard,
867 mime_types,
868 G_PRIORITY_DEFAULT,
869 nullptr,
870 read_clipboard_async_completed,
871 &aRes);
873 while (!aRes.bDone)
874 pInstance->DoYield(true, false);
876 if (aFlavor.MimeType == "text/plain;charset=utf-8")
877 aRet <<= aRes.get_as_string();
878 else
879 aRet <<= aRes.get_as_sequence();
880 #endif
881 return aRet;
884 std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
885 override
887 std::vector<css::datatransfer::DataFlavor> aVector;
889 GdkClipboard* clipboard = clipboard_get(m_eSelection);
891 #if GTK_CHECK_VERSION(4, 0, 0)
892 GdkContentFormats* pFormats = gdk_clipboard_get_formats(clipboard);
893 gsize n_targets;
894 const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
895 aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
896 #else
897 GdkAtom *targets;
898 gint n_targets;
899 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
901 aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
902 g_free(targets);
904 #endif
906 return aVector;
910 class VclGtkClipboard :
911 public cppu::WeakComponentImplHelper<
912 datatransfer::clipboard::XSystemClipboard,
913 datatransfer::clipboard::XFlushableClipboard,
914 XServiceInfo>
916 SelectionType m_eSelection;
917 osl::Mutex m_aMutex;
918 gulong m_nOwnerChangedSignalId;
919 ImplSVEvent* m_pSetClipboardEvent;
920 Reference<css::datatransfer::XTransferable> m_aContents;
921 Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
922 std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
923 #if GTK_CHECK_VERSION(4, 0, 0)
924 std::vector<OString> m_aGtkTargets;
925 TransferableContent* m_pClipboardContent;
926 #else
927 std::vector<GtkTargetEntry> m_aGtkTargets;
928 #endif
929 VclToGtkHelper m_aConversionHelper;
931 DECL_LINK(AsyncSetGtkClipboard, void*, void);
933 #if GTK_CHECK_VERSION(4, 0, 0)
934 DECL_LINK(DetachClipboard, void*, void);
935 #endif
937 public:
939 explicit VclGtkClipboard(SelectionType eSelection);
940 virtual ~VclGtkClipboard() override;
943 * XServiceInfo
946 virtual OUString SAL_CALL getImplementationName() override;
947 virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
948 virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
951 * XClipboard
954 virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
956 virtual void SAL_CALL setContents(
957 const Reference< css::datatransfer::XTransferable >& xTrans,
958 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
960 virtual OUString SAL_CALL getName() override;
963 * XClipboardEx
966 virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
969 * XFlushableClipboard
971 virtual void SAL_CALL flushClipboard() override;
974 * XClipboardNotifier
976 virtual void SAL_CALL addClipboardListener(
977 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
979 virtual void SAL_CALL removeClipboardListener(
980 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
982 #if !GTK_CHECK_VERSION(4, 0, 0)
983 void ClipboardGet(GtkSelectionData *selection_data, guint info);
984 #endif
985 void OwnerPossiblyChanged(GdkClipboard *clipboard);
986 void ClipboardClear();
987 void SetGtkClipboard();
988 void SyncGtkClipboard();
993 OUString VclGtkClipboard::getImplementationName()
995 return "com.sun.star.datatransfer.VclGtkClipboard";
998 Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
1000 Sequence<OUString> aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
1001 return aRet;
1004 sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
1006 return cppu::supportsService(this, ServiceName);
1009 Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
1011 if (!m_aContents.is())
1013 //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
1014 //the owner of the clipboard and have not already fetched it.
1015 m_aContents = new GtkClipboardTransferable(m_eSelection);
1016 #if GTK_CHECK_VERSION(4, 0, 0)
1017 if (m_pClipboardContent)
1018 transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
1019 #endif
1021 return m_aContents;
1024 #if !GTK_CHECK_VERSION(4, 0, 0)
1025 void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
1027 if (!m_aContents.is())
1028 return;
1029 // tdf#129809 take a reference in case m_aContents is replaced during this
1030 // call
1031 Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
1032 m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
1035 namespace
1037 const OString& getPID()
1039 static OString sPID;
1040 if (!sPID.getLength())
1042 oslProcessIdentifier aProcessId = 0;
1043 oslProcessInfo info;
1044 info.Size = sizeof (oslProcessInfo);
1045 if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
1046 aProcessId = info.Ident;
1047 sPID = OString::number(aProcessId);
1049 return sPID;
1052 void ClipboardGetFunc(GdkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
1053 guint info,
1054 gpointer user_data_or_owner)
1056 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
1057 pThis->ClipboardGet(selection_data, info);
1060 void ClipboardClearFunc(GdkClipboard* /*clipboard*/, gpointer user_data_or_owner)
1062 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
1063 pThis->ClipboardClear();
1066 #endif
1068 namespace
1070 #if GTK_CHECK_VERSION(4, 0, 0)
1071 void handle_owner_change(GdkClipboard *clipboard, gpointer user_data)
1073 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
1074 pThis->OwnerPossiblyChanged(clipboard);
1076 #else
1077 void handle_owner_change(GdkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
1079 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
1080 pThis->OwnerPossiblyChanged(clipboard);
1082 #endif
1085 void VclGtkClipboard::OwnerPossiblyChanged(GdkClipboard* clipboard)
1087 SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
1088 if (!m_aContents.is())
1089 return;
1091 #if GTK_CHECK_VERSION(4, 0, 0)
1092 bool bSelf = gdk_clipboard_is_local(clipboard);
1093 #else
1094 //if gdk_display_supports_selection_notification is not supported, e.g. like
1095 //right now under wayland, then you only get owner-changed notifications at
1096 //opportune times when the selection might have changed. So here
1097 //we see if the selection supports a dummy selection type identifying
1098 //our pid, in which case it's us.
1099 bool bSelf = false;
1101 //disconnect and reconnect after gtk_clipboard_wait_for_targets to
1102 //avoid possible recursion
1103 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
1105 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1106 GdkAtom *targets;
1107 gint n_targets;
1108 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
1110 for (gint i = 0; i < n_targets && !bSelf; ++i)
1112 gchar* pName = gdk_atom_name(targets[i]);
1113 if (strcmp(pName, sTunnel.getStr()) == 0)
1115 bSelf = true;
1117 g_free(pName);
1120 g_free(targets);
1123 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
1124 G_CALLBACK(handle_owner_change), this);
1125 #endif
1127 if (!bSelf)
1129 //null out m_aContents to return control to the system-one which
1130 //will be retrieved if getContents is called again
1131 setContents(Reference<css::datatransfer::XTransferable>(),
1132 Reference<css::datatransfer::clipboard::XClipboardOwner>());
1136 void VclGtkClipboard::ClipboardClear()
1138 if (m_pSetClipboardEvent)
1140 Application::RemoveUserEvent(m_pSetClipboardEvent);
1141 m_pSetClipboardEvent = nullptr;
1143 #if !GTK_CHECK_VERSION(4, 0, 0)
1144 for (auto &a : m_aGtkTargets)
1145 g_free(a.target);
1146 #endif
1147 m_aGtkTargets.clear();
1150 #if GTK_CHECK_VERSION(4, 0, 0)
1151 IMPL_LINK_NOARG(VclGtkClipboard, DetachClipboard, void*, void)
1153 ClipboardClear();
1156 OString VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
1158 OString aEntry = OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8);
1159 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
1160 DataFlavorEq(rFlavor));
1161 if (it == aInfoToFlavor.end())
1162 aInfoToFlavor.push_back(rFlavor);
1163 return aEntry;
1165 #else
1166 GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
1168 GtkTargetEntry aEntry;
1169 aEntry.target =
1170 g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
1171 aEntry.flags = 0;
1172 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
1173 DataFlavorEq(rFlavor));
1174 if (it != aInfoToFlavor.end())
1175 aEntry.info = std::distance(aInfoToFlavor.begin(), it);
1176 else
1178 aEntry.info = aInfoToFlavor.size();
1179 aInfoToFlavor.push_back(rFlavor);
1181 return aEntry;
1183 #endif
1185 #if GTK_CHECK_VERSION(4, 0, 0)
1187 namespace
1189 void write_mime_type_done(GObject* pStream, GAsyncResult* pResult, gpointer pTaskPtr)
1191 GTask* pTask = static_cast<GTask*>(pTaskPtr);
1193 GError* pError = nullptr;
1194 if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(pStream),
1195 pResult, nullptr, &pError))
1197 g_task_return_error(pTask, pError);
1199 else
1201 g_task_return_boolean(pTask, true);
1204 g_object_unref(pTask);
1207 class MimeTypeEq
1209 private:
1210 const OUString& m_rMimeType;
1211 public:
1212 explicit MimeTypeEq(const OUString& rMimeType) : m_rMimeType(rMimeType) {}
1213 bool operator() (const css::datatransfer::DataFlavor& rData) const
1215 return rData.MimeType == m_rMimeType;
1220 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
1221 GdkContentProvider* provider,
1222 const char* mime_type,
1223 GOutputStream* stream,
1224 int io_priority,
1225 GCancellable* cancellable,
1226 GAsyncReadyCallback callback,
1227 gpointer user_data)
1229 GTask *task = g_task_new(provider, cancellable, callback, user_data);
1230 g_task_set_priority(task, io_priority);
1232 OUString sMimeType(mime_type, strlen(mime_type), RTL_TEXTENCODING_UTF8);
1234 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
1235 MimeTypeEq(sMimeType));
1236 if (it == aInfoToFlavor.end())
1238 SAL_WARN( "vcl.gtk", "unknown mime-type request from clipboard");
1239 g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1240 "unknown mime-type “%s” request from clipboard", mime_type);
1241 g_object_unref(task);
1242 return;
1245 css::datatransfer::DataFlavor aFlavor(*it);
1246 if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
1247 aFlavor.MimeType = "text/plain;charset=utf-8";
1249 Sequence<sal_Int8> aData;
1250 Any aValue;
1254 aValue = rTrans->getTransferData(aFlavor);
1256 catch (...)
1260 if (aValue.getValueTypeClass() == TypeClass_STRING)
1262 OUString aString;
1263 aValue >>= aString;
1264 aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
1266 else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
1268 aValue >>= aData;
1270 else if (aFlavor.MimeType == "text/plain;charset=utf-8")
1272 //didn't have utf-8, try utf-16 and convert
1273 aFlavor.MimeType = "text/plain;charset=utf-16";
1274 aFlavor.DataType = cppu::UnoType<OUString>::get();
1277 aValue = rTrans->getTransferData(aFlavor);
1279 catch (...)
1282 OUString aString;
1283 aValue >>= aString;
1284 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
1286 g_output_stream_write_all_async(stream, aUTF8String.getStr(), aUTF8String.getLength(),
1287 io_priority, cancellable, write_mime_type_done, task);
1288 return;
1291 g_output_stream_write_all_async(stream, aData.getArray(), aData.getLength(),
1292 io_priority, cancellable, write_mime_type_done, task);
1294 #else
1295 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
1296 GtkSelectionData *selection_data, guint info)
1298 GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
1299 RTL_TEXTENCODING_UTF8).getStr(),
1300 false));
1302 css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
1303 if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
1304 aFlavor.MimeType = "text/plain;charset=utf-8";
1306 Sequence<sal_Int8> aData;
1307 Any aValue;
1311 aValue = rTrans->getTransferData(aFlavor);
1313 catch (...)
1317 if (aValue.getValueTypeClass() == TypeClass_STRING)
1319 OUString aString;
1320 aValue >>= aString;
1321 aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
1323 else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
1325 aValue >>= aData;
1327 else if (aFlavor.MimeType == "text/plain;charset=utf-8")
1329 //didn't have utf-8, try utf-16 and convert
1330 aFlavor.MimeType = "text/plain;charset=utf-16";
1331 aFlavor.DataType = cppu::UnoType<OUString>::get();
1334 aValue = rTrans->getTransferData(aFlavor);
1336 catch (...)
1339 OUString aString;
1340 aValue >>= aString;
1341 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
1342 gtk_selection_data_set(selection_data, type, 8,
1343 reinterpret_cast<const guchar *>(aUTF8String.getStr()),
1344 aUTF8String.getLength());
1345 return;
1348 gtk_selection_data_set(selection_data, type, 8,
1349 reinterpret_cast<const guchar *>(aData.getArray()),
1350 aData.getLength());
1352 #endif
1354 VclGtkClipboard::VclGtkClipboard(SelectionType eSelection)
1355 : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
1356 datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
1357 (m_aMutex)
1358 , m_eSelection(eSelection)
1359 , m_pSetClipboardEvent(nullptr)
1360 #if GTK_CHECK_VERSION(4, 0, 0)
1361 , m_pClipboardContent(nullptr)
1362 #endif
1364 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1365 #if GTK_CHECK_VERSION(4, 0, 0)
1366 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "changed",
1367 G_CALLBACK(handle_owner_change), this);
1368 #else
1369 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
1370 G_CALLBACK(handle_owner_change), this);
1371 #endif
1374 void VclGtkClipboard::flushClipboard()
1376 #if !GTK_CHECK_VERSION(4, 0, 0)
1377 SolarMutexGuard aGuard;
1379 if (m_eSelection != SELECTION_CLIPBOARD)
1380 return;
1382 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1383 gtk_clipboard_store(clipboard);
1384 #endif
1387 VclGtkClipboard::~VclGtkClipboard()
1389 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1390 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
1391 if (!m_aGtkTargets.empty())
1393 #if GTK_CHECK_VERSION(4, 0, 0)
1394 gdk_clipboard_set_content(clipboard, nullptr);
1395 m_pClipboardContent = nullptr;
1396 #else
1397 gtk_clipboard_clear(clipboard);
1398 #endif
1399 ClipboardClear();
1401 assert(!m_pSetClipboardEvent);
1402 assert(m_aGtkTargets.empty());
1405 #if GTK_CHECK_VERSION(4, 0, 0)
1406 std::vector<OString> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
1407 #else
1408 std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
1409 #endif
1411 #if GTK_CHECK_VERSION(4, 0, 0)
1412 std::vector<OString> aGtkTargets;
1413 #else
1414 std::vector<GtkTargetEntry> aGtkTargets;
1415 #endif
1417 bool bHaveText(false), bHaveUTF8(false);
1418 for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
1420 sal_Int32 nIndex(0);
1421 if (o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
1423 bHaveText = true;
1424 std::u16string_view aToken(o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex));
1425 if (aToken == u"charset=utf-8")
1427 bHaveUTF8 = true;
1430 aGtkTargets.push_back(makeGtkTargetEntry(rFlavor));
1433 if (bHaveText)
1435 css::datatransfer::DataFlavor aFlavor;
1436 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
1437 if (!bHaveUTF8)
1439 aFlavor.MimeType = "text/plain;charset=utf-8";
1440 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1442 aFlavor.MimeType = "UTF8_STRING";
1443 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1444 aFlavor.MimeType = "STRING";
1445 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1448 return aGtkTargets;
1451 IMPL_LINK_NOARG(VclGtkClipboard, AsyncSetGtkClipboard, void*, void)
1453 osl::ClearableMutexGuard aGuard( m_aMutex );
1454 m_pSetClipboardEvent = nullptr;
1455 SetGtkClipboard();
1458 void VclGtkClipboard::SyncGtkClipboard()
1460 osl::ClearableMutexGuard aGuard(m_aMutex);
1461 if (m_pSetClipboardEvent)
1463 Application::RemoveUserEvent(m_pSetClipboardEvent);
1464 m_pSetClipboardEvent = nullptr;
1465 SetGtkClipboard();
1469 void VclGtkClipboard::SetGtkClipboard()
1471 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1472 #if GTK_CHECK_VERSION(4, 0, 0)
1473 m_pClipboardContent = TRANSFERABLE_CONTENT(transerable_content_new(&m_aConversionHelper, m_aContents.get()));
1474 transerable_content_set_detach_clipboard_link(m_pClipboardContent, LINK(this, VclGtkClipboard, DetachClipboard));
1475 gdk_clipboard_set_content(clipboard, GDK_CONTENT_PROVIDER(m_pClipboardContent));
1476 #else
1477 gtk_clipboard_set_with_data(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size(),
1478 ClipboardGetFunc, ClipboardClearFunc, this);
1479 gtk_clipboard_set_can_store(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size());
1480 #endif
1483 void VclGtkClipboard::setContents(
1484 const Reference< css::datatransfer::XTransferable >& xTrans,
1485 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
1487 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
1488 if (xTrans.is())
1490 aFormats = xTrans->getTransferDataFlavors();
1493 osl::ClearableMutexGuard aGuard( m_aMutex );
1494 Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
1495 Reference< datatransfer::XTransferable > xOldContents( m_aContents );
1496 m_aContents = xTrans;
1497 #if GTK_CHECK_VERSION(4, 0, 0)
1498 if (m_pClipboardContent)
1499 transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
1500 #endif
1501 m_aOwner = xClipboardOwner;
1503 std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
1504 datatransfer::clipboard::ClipboardEvent aEv;
1506 GdkClipboard* clipboard = clipboard_get(m_eSelection);
1507 if (!m_aGtkTargets.empty())
1509 #if GTK_CHECK_VERSION(4, 0, 0)
1510 gdk_clipboard_set_content(clipboard, nullptr);
1511 m_pClipboardContent = nullptr;
1512 #else
1513 gtk_clipboard_clear(clipboard);
1514 #endif
1515 ClipboardClear();
1517 assert(m_aGtkTargets.empty());
1518 if (m_aContents.is())
1520 #if GTK_CHECK_VERSION(4, 0, 0)
1521 std::vector<OString> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1522 #else
1523 std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1524 #endif
1525 if (!aGtkTargets.empty())
1527 #if !GTK_CHECK_VERSION(4, 0, 0)
1528 GtkTargetEntry aEntry;
1529 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1530 aEntry.target = g_strdup(sTunnel.getStr());
1531 aEntry.flags = 0;
1532 aEntry.info = 0;
1533 aGtkTargets.push_back(aEntry);
1534 #endif
1535 m_aGtkTargets = aGtkTargets;
1537 if (!m_pSetClipboardEvent)
1538 m_pSetClipboardEvent = Application::PostUserEvent(LINK(this, VclGtkClipboard, AsyncSetGtkClipboard));
1542 aEv.Contents = getContents();
1544 aGuard.clear();
1546 if (xOldOwner.is() && xOldOwner != xClipboardOwner)
1547 xOldOwner->lostOwnership( this, xOldContents );
1548 for (auto const& listener : aListeners)
1550 listener->changedContents( aEv );
1554 OUString VclGtkClipboard::getName()
1556 return (m_eSelection == SELECTION_CLIPBOARD) ? OUString("CLIPBOARD") : OUString("PRIMARY");
1559 sal_Int8 VclGtkClipboard::getRenderingCapabilities()
1561 return 0;
1564 void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1566 osl::ClearableMutexGuard aGuard( m_aMutex );
1568 m_aListeners.push_back( listener );
1571 void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1573 osl::ClearableMutexGuard aGuard( m_aMutex );
1575 m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
1578 Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
1580 if ( IsRunningUnitTest() )
1581 return SalInstance::CreateClipboard( arguments );
1583 OUString sel;
1584 if (!arguments.hasElements()) {
1585 sel = "CLIPBOARD";
1586 } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
1587 throw css::lang::IllegalArgumentException(
1588 "bad GtkInstance::CreateClipboard arguments",
1589 css::uno::Reference<css::uno::XInterface>(), -1);
1592 SelectionType eSelection = (sel == "CLIPBOARD") ? SELECTION_CLIPBOARD : SELECTION_PRIMARY;
1594 if (m_aClipboards[eSelection].is())
1595 return m_aClipboards[eSelection];
1597 Reference<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(new VclGtkClipboard(eSelection)));
1598 m_aClipboards[eSelection] = xClipboard;
1599 return xClipboard;
1602 GtkInstDropTarget::GtkInstDropTarget()
1603 : WeakComponentImplHelper(m_aMutex)
1604 , m_pFrame(nullptr)
1605 , m_pFormatConversionRequest(nullptr)
1606 , m_bActive(false)
1607 #if !GTK_CHECK_VERSION(4, 0, 0)
1608 , m_bInDrag(false)
1609 #endif
1610 , m_nDefaultActions(0)
1614 OUString SAL_CALL GtkInstDropTarget::getImplementationName()
1616 return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
1619 sal_Bool SAL_CALL GtkInstDropTarget::supportsService(OUString const & ServiceName)
1621 return cppu::supportsService(this, ServiceName);
1624 css::uno::Sequence<OUString> SAL_CALL GtkInstDropTarget::getSupportedServiceNames()
1626 Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
1627 return aRet;
1630 GtkInstDropTarget::~GtkInstDropTarget()
1632 if (m_pFrame)
1633 m_pFrame->deregisterDropTarget(this);
1636 void GtkInstDropTarget::deinitialize()
1638 m_pFrame = nullptr;
1639 m_bActive = false;
1642 void GtkInstDropTarget::initialize(const Sequence<Any>& rArguments)
1644 if (rArguments.getLength() < 2)
1646 throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
1647 static_cast<OWeakObject*>(this));
1650 sal_IntPtr nFrame = 0;
1651 rArguments.getConstArray()[1] >>= nFrame;
1653 if (!nFrame)
1655 throw RuntimeException("DropTarget::initialize: missing SalFrame",
1656 static_cast<OWeakObject*>(this));
1659 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1660 m_pFrame->registerDropTarget(this);
1661 m_bActive = true;
1664 void GtkInstDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1666 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1668 m_aListeners.push_back( xListener );
1671 void GtkInstDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1673 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1675 m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end());
1678 void GtkInstDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
1680 osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
1681 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1682 aGuard.clear();
1684 for (auto const& listener : aListeners)
1686 listener->drop( dtde );
1690 void GtkInstDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
1692 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1693 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1694 aGuard.clear();
1696 for (auto const& listener : aListeners)
1698 listener->dragEnter( dtde );
1702 void GtkInstDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
1704 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1705 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1706 aGuard.clear();
1708 for (auto const& listener : aListeners)
1710 listener->dragOver( dtde );
1714 void GtkInstDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
1716 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1717 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1718 aGuard.clear();
1720 for (auto const& listener : aListeners)
1722 listener->dragExit( dte );
1726 sal_Bool GtkInstDropTarget::isActive()
1728 return m_bActive;
1731 void GtkInstDropTarget::setActive(sal_Bool bActive)
1733 m_bActive = bActive;
1736 sal_Int8 GtkInstDropTarget::getDefaultActions()
1738 return m_nDefaultActions;
1741 void GtkInstDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
1743 m_nDefaultActions = nDefaultActions;
1746 Reference<XInterface> GtkInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
1748 return vcl::X11DnDHelper(new GtkInstDropTarget(), pSysEnv->aShellWindow);
1751 GtkInstDragSource::~GtkInstDragSource()
1753 if (m_pFrame)
1754 m_pFrame->deregisterDragSource(this);
1756 if (GtkInstDragSource::g_ActiveDragSource == this)
1758 SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkInstDragSource before dtor");
1759 GtkInstDragSource::g_ActiveDragSource = nullptr;
1763 void GtkInstDragSource::deinitialize()
1765 m_pFrame = nullptr;
1768 sal_Bool GtkInstDragSource::isDragImageSupported()
1770 return true;
1773 sal_Int32 GtkInstDragSource::getDefaultCursor( sal_Int8 )
1775 return 0;
1778 void GtkInstDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
1780 if (rArguments.getLength() < 2)
1782 throw RuntimeException("DragSource::initialize: Cannot install window event handler",
1783 static_cast<OWeakObject*>(this));
1786 sal_IntPtr nFrame = 0;
1787 rArguments.getConstArray()[1] >>= nFrame;
1789 if (!nFrame)
1791 throw RuntimeException("DragSource::initialize: missing SalFrame",
1792 static_cast<OWeakObject*>(this));
1795 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1796 m_pFrame->registerDragSource(this);
1799 OUString SAL_CALL GtkInstDragSource::getImplementationName()
1801 return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
1804 sal_Bool SAL_CALL GtkInstDragSource::supportsService(OUString const & ServiceName)
1806 return cppu::supportsService(this, ServiceName);
1809 css::uno::Sequence<OUString> SAL_CALL GtkInstDragSource::getSupportedServiceNames()
1811 Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
1812 return aRet;
1815 Reference<XInterface> GtkInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
1817 return vcl::X11DnDHelper(new GtkInstDragSource(), pSysEnv->aShellWindow);
1820 namespace {
1822 class GtkOpenGLContext : public OpenGLContext
1824 GLWindow m_aGLWin;
1825 GtkWidget *m_pGLArea;
1826 GdkGLContext *m_pContext;
1827 gulong m_nDestroySignalId;
1828 gulong m_nRenderSignalId;
1829 guint m_nAreaFrameBuffer;
1830 guint m_nFrameBuffer;
1831 guint m_nRenderBuffer;
1832 guint m_nDepthBuffer;
1833 guint m_nFrameScratchBuffer;
1834 guint m_nRenderScratchBuffer;
1835 guint m_nDepthScratchBuffer;
1837 public:
1838 GtkOpenGLContext()
1839 : m_pGLArea(nullptr)
1840 , m_pContext(nullptr)
1841 , m_nDestroySignalId(0)
1842 , m_nRenderSignalId(0)
1843 , m_nAreaFrameBuffer(0)
1844 , m_nFrameBuffer(0)
1845 , m_nRenderBuffer(0)
1846 , m_nDepthBuffer(0)
1847 , m_nFrameScratchBuffer(0)
1848 , m_nRenderScratchBuffer(0)
1849 , m_nDepthScratchBuffer(0)
1853 virtual void initWindow() override
1855 if( !m_pChildWindow )
1857 SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
1858 m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
1861 if (m_pChildWindow)
1863 InitChildWindow(m_pChildWindow.get());
1867 private:
1868 virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
1869 virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
1871 static void signalDestroy(GtkWidget*, gpointer context)
1873 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
1874 pThis->m_pGLArea = nullptr;
1875 pThis->m_nDestroySignalId = 0;
1876 pThis->m_nRenderSignalId = 0;
1879 static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
1881 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
1883 int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
1884 int width = pThis->m_aGLWin.Width * scale;
1885 int height = pThis->m_aGLWin.Height * scale;
1887 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1889 glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
1890 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1892 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1893 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1895 gdk_gl_context_make_current(pThis->m_pContext);
1896 return true;
1899 virtual void adjustToNewSize() override
1901 if (!m_pGLArea)
1902 return;
1904 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1905 int width = m_aGLWin.Width * scale;
1906 int height = m_aGLWin.Height * scale;
1908 // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
1909 int allocwidth = std::max(width, 1);
1910 int allocheight = std::max(height, 1);
1912 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1913 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1915 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1916 return;
1919 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1920 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1921 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1922 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1923 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
1925 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1926 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1927 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1928 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1930 gdk_gl_context_make_current(m_pContext);
1931 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1932 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1933 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
1935 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1936 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1937 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1938 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1939 glViewport(0, 0, width, height);
1941 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1942 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1943 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1944 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1945 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1947 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1948 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1949 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1950 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1952 glViewport(0, 0, width, height);
1955 // Use a throw away toplevel to determine the OpenGL version because once
1956 // an GdkGLContext is created for a window then it seems that
1957 // glGenVertexArrays will always be called when the window gets rendered.
1958 static int GetOpenGLVersion()
1960 int nMajorGLVersion(0);
1962 GtkWidget* pWindow;
1963 #if !GTK_CHECK_VERSION(4,0,0)
1964 pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1965 #else
1966 pWindow = gtk_window_new();
1967 #endif
1969 gtk_widget_realize(pWindow);
1971 if (GdkSurface* pSurface = widget_get_surface(pWindow))
1973 if (GdkGLContext* pContext = surface_create_gl_context(pSurface))
1975 if (gdk_gl_context_realize(pContext, nullptr))
1977 OpenGLZone aZone;
1978 gdk_gl_context_make_current(pContext);
1979 gdk_gl_context_get_version(pContext, &nMajorGLVersion, nullptr);
1980 gdk_gl_context_clear_current();
1982 g_object_unref(pContext);
1986 #if !GTK_CHECK_VERSION(4,0,0)
1987 gtk_widget_destroy(pWindow);
1988 #else
1989 gtk_window_destroy(GTK_WINDOW(pWindow));
1990 #endif
1991 return nMajorGLVersion;
1994 virtual bool ImplInit() override
1996 static int nOpenGLVersion = GetOpenGLVersion();
1997 if (nOpenGLVersion < 3)
1999 SAL_WARN("vcl.gtk", "gtk GL requires glGenVertexArrays which is OpenGL 3, while system provides: " << nOpenGLVersion);
2000 return false;
2003 const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
2004 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
2005 m_pGLArea = gtk_gl_area_new();
2006 m_nDestroySignalId = g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
2007 m_nRenderSignalId = g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
2008 gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
2009 gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
2010 gtk_widget_set_hexpand(m_pGLArea, true);
2011 gtk_widget_set_vexpand(m_pGLArea, true);
2012 #if !GTK_CHECK_VERSION(4, 0, 0)
2013 gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
2014 gtk_widget_show_all(pParent);
2015 #else
2016 gtk_grid_attach(GTK_GRID(pParent), m_pGLArea, 0, 0, 1, 1);
2017 gtk_widget_show(pParent);
2018 gtk_widget_show(m_pGLArea);
2019 #endif
2021 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
2022 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
2024 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
2025 return false;
2028 gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
2029 glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
2031 GdkSurface* pWindow = widget_get_surface(pParent);
2032 m_pContext = surface_create_gl_context(pWindow);
2033 if (!m_pContext)
2034 return false;
2036 if (!gdk_gl_context_realize(m_pContext, nullptr))
2037 return false;
2039 gdk_gl_context_make_current(m_pContext);
2040 glGenFramebuffersEXT(1, &m_nFrameBuffer);
2041 glGenRenderbuffersEXT(1, &m_nRenderBuffer);
2042 glGenRenderbuffersEXT(1, &m_nDepthBuffer);
2043 glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
2044 glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
2045 glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
2047 bool bRet = InitGL();
2048 InitGLDebugging();
2049 return bRet;
2052 virtual void restoreDefaultFramebuffer() override
2054 OpenGLContext::restoreDefaultFramebuffer();
2055 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
2056 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
2057 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
2060 virtual void makeCurrent() override
2062 if (isCurrent())
2063 return;
2065 clearCurrent();
2067 if (m_pGLArea)
2069 int scale = gtk_widget_get_scale_factor(m_pGLArea);
2070 int width = m_aGLWin.Width * scale;
2071 int height = m_aGLWin.Height * scale;
2073 gdk_gl_context_make_current(m_pContext);
2075 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
2076 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
2077 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
2078 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
2079 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
2080 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
2081 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
2082 glViewport(0, 0, width, height);
2085 registerAsCurrent();
2088 virtual void destroyCurrentContext() override
2090 gdk_gl_context_clear_current();
2093 virtual bool isCurrent() override
2095 return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
2098 virtual void sync() override
2102 virtual void resetCurrent() override
2104 clearCurrent();
2105 gdk_gl_context_clear_current();
2108 virtual void swapBuffers() override
2110 int scale = gtk_widget_get_scale_factor(m_pGLArea);
2111 int width = m_aGLWin.Width * scale;
2112 int height = m_aGLWin.Height * scale;
2114 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
2115 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
2117 glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
2118 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
2120 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
2121 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
2123 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
2124 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
2126 gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
2127 BuffersSwapped();
2130 virtual ~GtkOpenGLContext() override
2132 if (m_nDestroySignalId)
2133 g_signal_handler_disconnect(m_pGLArea, m_nDestroySignalId);
2134 if (m_nRenderSignalId)
2135 g_signal_handler_disconnect(m_pGLArea, m_nRenderSignalId);
2136 if (m_pContext)
2137 g_clear_object(&m_pContext);
2143 OpenGLContext* GtkInstance::CreateOpenGLContext()
2145 return new GtkOpenGLContext;
2148 // tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
2149 bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
2151 static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
2152 if (!get_type)
2153 return false;
2154 static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
2155 return bResult;
2158 bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
2160 static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
2161 if (!get_type)
2162 return false;
2163 static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
2164 return bResult;
2167 namespace
2170 class GtkInstanceBuilder;
2172 void set_help_id(const GtkWidget *pWidget, const OString& rHelpId)
2174 gchar *helpid = g_strdup(rHelpId.getStr());
2175 g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
2178 OString get_help_id(const GtkWidget *pWidget)
2180 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
2181 const gchar* pStr = static_cast<const gchar*>(pData);
2182 return OString(pStr, pStr ? strlen(pStr) : 0);
2185 KeyEvent CreateKeyEvent(guint keyval, guint16 hardware_keycode, guint state, guint8 group)
2187 sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(keyval);
2188 #if !GTK_CHECK_VERSION(4, 0, 0)
2189 if (nKeyCode == 0)
2191 guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), hardware_keycode, group);
2192 nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
2194 #else
2195 (void)hardware_keycode;
2196 (void)group;
2197 #endif
2198 nKeyCode |= GtkSalFrame::GetKeyModCode(state);
2199 return KeyEvent(gdk_keyval_to_unicode(keyval), nKeyCode, 0);
2202 #if !GTK_CHECK_VERSION(4, 0, 0)
2203 KeyEvent GtkToVcl(const GdkEventKey& rEvent)
2205 return CreateKeyEvent(rEvent.keyval, rEvent.hardware_keycode, rEvent.state, rEvent.group);
2207 #endif
2210 static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
2212 MouseEventModifiers nMode = MouseEventModifiers::NONE;
2213 if ( nButton == MOUSE_LEFT )
2214 nMode |= MouseEventModifiers::SIMPLECLICK;
2215 if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
2216 nMode |= MouseEventModifiers::SELECT;
2217 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
2218 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
2219 nMode |= MouseEventModifiers::MULTISELECT;
2220 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
2221 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
2222 nMode |= MouseEventModifiers::RANGESELECT;
2223 return nMode;
2226 static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
2228 MouseEventModifiers nMode = MouseEventModifiers::NONE;
2229 if ( !nCode )
2230 nMode |= MouseEventModifiers::SIMPLEMOVE;
2231 if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
2232 nMode |= MouseEventModifiers::DRAGMOVE;
2233 if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
2234 nMode |= MouseEventModifiers::DRAGCOPY;
2235 return nMode;
2238 namespace
2240 bool SwapForRTL(GtkWidget* pWidget)
2242 GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
2243 if (eDir == GTK_TEXT_DIR_RTL)
2244 return true;
2245 if (eDir == GTK_TEXT_DIR_LTR)
2246 return false;
2247 return AllSettings::GetLayoutRTL();
2250 GtkWidget* getPopupRect(GtkWidget* pWidget, const tools::Rectangle& rInRect, GdkRectangle& rOutRect)
2252 if (GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pWidget))
2254 // this is the relatively unusual case where pParent is the toplevel GtkSalFrame and not a stock GtkWidget
2255 // so use the same style of logic as GtkSalMenu::ShowNativePopupMenu to get the right position
2256 tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pFrame->GetWindow(), rInRect);
2257 aFloatRect.Move(-pFrame->maGeometry.nX, -pFrame->maGeometry.nY);
2259 rOutRect = GdkRectangle{static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
2260 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
2262 pWidget = pFrame->getMouseEventWidget();
2264 else
2266 rOutRect = GdkRectangle{static_cast<int>(rInRect.Left()), static_cast<int>(rInRect.Top()),
2267 static_cast<int>(rInRect.GetWidth()), static_cast<int>(rInRect.GetHeight())};
2268 if (SwapForRTL(pWidget))
2269 rOutRect.x = gtk_widget_get_allocated_width(pWidget) - rOutRect.width - 1 - rOutRect.x;
2271 return pWidget;
2274 void replaceWidget(GtkWidget* pWidget, GtkWidget* pReplacement)
2276 // remove the widget and replace it with pReplacement
2277 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
2279 // if pWidget was un-parented then don't bother
2280 if (!pParent)
2281 return;
2283 g_object_ref(pWidget);
2285 gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
2286 if (GTK_IS_GRID(pParent))
2288 #if !GTK_CHECK_VERSION(4, 0, 0)
2289 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
2290 "left-attach", &nLeftAttach,
2291 "top-attach", &nTopAttach,
2292 "width", &nWidth,
2293 "height", &nHeight,
2294 nullptr);
2295 #else
2296 gtk_grid_query_child(GTK_GRID(pParent), pWidget,
2297 &nLeftAttach, &nTopAttach,
2298 &nWidth, &nHeight);
2299 #endif
2302 #if !GTK_CHECK_VERSION(4, 0, 0)
2303 gboolean bExpand(false), bFill(false);
2304 GtkPackType ePackType(GTK_PACK_START);
2305 guint nPadding(0);
2306 gint nPosition(0);
2307 if (GTK_IS_BOX(pParent))
2309 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
2310 "expand", &bExpand,
2311 "fill", &bFill,
2312 "pack-type", &ePackType,
2313 "padding", &nPadding,
2314 "position", &nPosition,
2315 nullptr);
2317 #endif
2319 #if !GTK_CHECK_VERSION(4, 0, 0)
2320 // for gtk3 remove before replacement inserted, or there are warnings
2321 // from GTK_BIN about having two children
2322 container_remove(pParent, pWidget);
2323 #endif
2325 gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
2326 #if !GTK_CHECK_VERSION(4, 0, 0)
2327 gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
2328 #endif
2330 int nReqWidth, nReqHeight;
2331 gtk_widget_get_size_request(pWidget, &nReqWidth, &nReqHeight);
2332 gtk_widget_set_size_request(pReplacement, nReqWidth, nReqHeight);
2334 static GQuark quark_size_groups = g_quark_from_static_string("gtk-widget-size-groups");
2335 GSList* pSizeGroups = static_cast<GSList*>(g_object_get_qdata(G_OBJECT(pWidget), quark_size_groups));
2336 while (pSizeGroups)
2338 GtkSizeGroup *pSizeGroup = static_cast<GtkSizeGroup*>(pSizeGroups->data);
2339 pSizeGroups = pSizeGroups->next;
2340 gtk_size_group_remove_widget(pSizeGroup, pWidget);
2341 gtk_size_group_add_widget(pSizeGroup, pReplacement);
2344 // tdf#135368 change the mnemonic to point to our replacement
2345 GList* pLabels = gtk_widget_list_mnemonic_labels(pWidget);
2346 for (GList* pLabel = g_list_first(pLabels); pLabel; pLabel = g_list_next(pLabel))
2348 GtkWidget* pLabelWidget = static_cast<GtkWidget*>(pLabel->data);
2349 if (!GTK_IS_LABEL(pLabelWidget))
2350 continue;
2351 gtk_label_set_mnemonic_widget(GTK_LABEL(pLabelWidget), pReplacement);
2353 g_list_free(pLabels);
2356 if (GTK_IS_GRID(pParent))
2358 gtk_grid_attach(GTK_GRID(pParent), pReplacement, nLeftAttach, nTopAttach, nWidth, nHeight);
2360 else if (GTK_IS_BOX(pParent))
2362 #if !GTK_CHECK_VERSION(4, 0, 0)
2363 gtk_box_pack_start(GTK_BOX(pParent), pReplacement, bExpand, bFill, nPadding);
2364 gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
2365 "pack-type", ePackType,
2366 "position", nPosition,
2367 nullptr);
2368 #else
2369 gtk_box_insert_child_after(GTK_BOX(pParent), pReplacement, pWidget);
2370 #endif
2372 #if !GTK_CHECK_VERSION(4, 0, 0)
2373 else
2374 gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
2375 #endif
2377 if (gtk_widget_get_hexpand_set(pWidget))
2378 gtk_widget_set_hexpand(pReplacement, gtk_widget_get_hexpand(pWidget));
2380 if (gtk_widget_get_vexpand_set(pWidget))
2381 gtk_widget_set_vexpand(pReplacement, gtk_widget_get_vexpand(pWidget));
2383 gtk_widget_set_halign(pReplacement, gtk_widget_get_halign(pWidget));
2384 gtk_widget_set_valign(pReplacement, gtk_widget_get_valign(pWidget));
2386 #if GTK_CHECK_VERSION(4, 0, 0)
2387 // for gtk4 remove after replacement inserted so we could use gtk_box_insert_child_after
2388 container_remove(pParent, pWidget);
2389 #endif
2391 // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
2392 g_object_unref(pWidget);
2395 void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
2397 g_object_ref(pWidget);
2399 replaceWidget(pWidget, pReplacement);
2401 // coverity[pass_freed_arg : FALSE] - pWidget is not freed here due to initial g_object_ref
2402 container_add(pReplacement, pWidget);
2404 // coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
2405 g_object_unref(pWidget);
2408 GtkWidget* ensureEventWidget(GtkWidget* pWidget)
2410 #if GTK_CHECK_VERSION(4, 0, 0)
2411 return pWidget;
2412 #else
2414 if (!pWidget)
2415 return nullptr;
2417 GtkWidget* pMouseEventBox;
2418 // not every widget has a GdkWindow and can get any event, so if we
2419 // want an event it doesn't have, insert a GtkEventBox so we can get
2420 // those
2421 if (gtk_widget_get_has_window(pWidget))
2422 pMouseEventBox = pWidget;
2423 else
2425 // remove the widget and replace it with an eventbox and put the old
2426 // widget into it
2427 pMouseEventBox = gtk_event_box_new();
2428 gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
2429 gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
2430 insertAsParent(pWidget, pMouseEventBox);
2433 return pMouseEventBox;
2434 #endif
2438 namespace {
2440 #if !GTK_CHECK_VERSION(4, 0, 0)
2441 GdkDragAction VclToGdk(sal_Int8 dragOperation)
2443 GdkDragAction eRet(static_cast<GdkDragAction>(0));
2444 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
2445 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
2446 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
2447 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
2448 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
2449 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
2450 return eRet;
2452 #endif
2454 GtkWindow* get_active_window()
2456 GtkWindow* pFocus = nullptr;
2458 GList* pList = gtk_window_list_toplevels();
2460 for (GList* pEntry = pList; pEntry; pEntry = pEntry->next)
2462 #if GTK_CHECK_VERSION(4, 0, 0)
2463 if (gtk_window_is_active(GTK_WINDOW(pEntry->data)))
2464 #else
2465 if (gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry->data)))
2466 #endif
2468 pFocus = GTK_WINDOW(pEntry->data);
2469 break;
2473 g_list_free(pList);
2475 return pFocus;
2478 void LocalizeDecimalSeparator(guint& keyval)
2480 // #i1820# use locale specific decimal separator
2481 if (keyval == GDK_KEY_KP_Decimal && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2483 GtkWindow* pFocusWin = get_active_window();
2484 GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
2485 // tdf#138932 except if the target is a GtkEntry used for passwords
2486 // GTK4: TODO is it a GtkEntry or a child GtkText that has the focus in this situation?
2487 if (!pFocus || !GTK_IS_ENTRY(pFocus) || gtk_entry_get_visibility(GTK_ENTRY(pFocus)))
2489 OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
2490 keyval = aSep[0];
2495 void set_cursor(GtkWidget* pWidget, const char *pName)
2497 #if !GTK_CHECK_VERSION(4, 0, 0)
2498 if (!gtk_widget_get_realized(pWidget))
2499 gtk_widget_realize(pWidget);
2500 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
2501 GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
2502 widget_set_cursor(pWidget, pCursor);
2503 gdk_display_flush(pDisplay);
2504 if (pCursor)
2505 g_object_unref(pCursor);
2506 #else
2507 (void)pWidget;
2508 (void)pName;
2509 #endif
2512 vcl::Font get_font(GtkWidget* pWidget)
2514 PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
2515 return pango_to_vcl(pango_context_get_font_description(pContext),
2516 Application::GetSettings().GetUILanguageTag().getLocale());
2521 OString get_buildable_id(GtkBuildable* pWidget)
2523 #if GTK_CHECK_VERSION(4, 0, 0)
2524 const gchar* pStr = gtk_buildable_get_buildable_id(pWidget);
2525 #else
2526 const gchar* pStr = gtk_buildable_get_name(pWidget);
2527 #endif
2528 return OString(pStr, pStr ? strlen(pStr) : 0);
2531 void set_buildable_id(GtkBuildable* pWidget, const OString& rId)
2533 #if GTK_CHECK_VERSION(4, 0, 0)
2534 GtkBuildableIface *iface = GTK_BUILDABLE_GET_IFACE(pWidget);
2535 (*iface->set_id)(pWidget, rId.getStr());
2536 #else
2537 gtk_buildable_set_name(pWidget, rId.getStr());
2538 #endif
2541 namespace {
2543 class GtkInstanceWidget : public virtual weld::Widget
2545 protected:
2546 GtkWidget* m_pWidget;
2547 GtkWidget* m_pMouseEventBox;
2548 GtkInstanceBuilder* m_pBuilder;
2550 #if !GTK_CHECK_VERSION(4, 0, 0)
2551 DECL_LINK(async_drag_cancel, void*, void);
2552 #endif
2554 bool IsFirstFreeze() const { return m_nFreezeCount == 0; }
2555 bool IsLastThaw() const { return m_nFreezeCount == 1; }
2557 #if GTK_CHECK_VERSION(4, 0, 0)
2558 static void signalFocusIn(GtkEventControllerFocus*, gpointer widget)
2560 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2561 SolarMutexGuard aGuard;
2562 pThis->signal_focus_in();
2564 #else
2565 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
2567 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2568 SolarMutexGuard aGuard;
2569 pThis->signal_focus_in();
2570 return false;
2572 #endif
2574 void signal_focus_in()
2576 GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
2577 // see commentary in GtkSalObjectWidgetClip::Show
2578 if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
2579 return;
2581 m_aFocusInHdl.Call(*this);
2584 static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
2586 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2587 SolarMutexGuard aGuard;
2588 return pThis->signal_mnemonic_activate();
2591 bool signal_mnemonic_activate()
2593 return m_aMnemonicActivateHdl.Call(*this);
2596 #if GTK_CHECK_VERSION(4, 0, 0)
2597 static void signalFocusOut(GtkEventControllerFocus*, gpointer widget)
2599 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2600 SolarMutexGuard aGuard;
2601 pThis->signal_focus_in();
2603 #else
2604 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
2606 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2607 SolarMutexGuard aGuard;
2608 pThis->signal_focus_out();
2609 return false;
2611 #endif
2613 #if !GTK_CHECK_VERSION(4, 0, 0)
2614 void launch_drag_cancel(GdkDragContext* context)
2616 // post our drag cancel to happen at the next available event cycle
2617 if (m_pDragCancelEvent)
2618 return;
2619 g_object_ref(context);
2620 m_pDragCancelEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_drag_cancel), context);
2622 #endif
2624 void signal_focus_out()
2626 GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
2627 // see commentary in GtkSalObjectWidgetClip::Show
2628 if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
2629 return;
2631 m_aFocusOutHdl.Call(*this);
2634 virtual void ensureMouseEventWidget()
2636 if (!m_pMouseEventBox)
2637 m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
2640 void ensureButtonPressSignal()
2642 if (!m_nButtonPressSignalId)
2644 #if GTK_CHECK_VERSION(4, 0, 0)
2645 GtkEventController* pClickController = get_click_controller();
2646 m_nButtonPressSignalId = g_signal_connect(pClickController, "pressed", G_CALLBACK(signalButtonPress), this);
2647 #else
2648 ensureMouseEventWidget();
2649 m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButtonPress), this);
2650 #endif
2654 void ensureButtonReleaseSignal()
2656 if (!m_nButtonReleaseSignalId)
2658 #if GTK_CHECK_VERSION(4, 0, 0)
2659 GtkEventController* pClickController = get_click_controller();
2660 m_nButtonReleaseSignalId = g_signal_connect(pClickController, "released", G_CALLBACK(signalButtonRelease), this);
2661 #else
2662 ensureMouseEventWidget();
2663 m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButtonRelease), this);
2664 #endif
2668 #if !GTK_CHECK_VERSION(4, 0, 0)
2669 static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
2671 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2672 SolarMutexGuard aGuard;
2673 //center it when we don't know where else to use
2674 Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
2675 gtk_widget_get_allocated_height(pWidget) / 2);
2676 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
2677 return pThis->signal_popup_menu(aCEvt);
2679 #endif
2681 bool SwapForRTL() const
2683 return ::SwapForRTL(m_pWidget);
2686 void do_enable_drag_source(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
2688 ensure_drag_source();
2690 #if !GTK_CHECK_VERSION(4, 0, 0)
2691 auto aFormats = rHelper->getTransferDataFlavors();
2692 std::vector<GtkTargetEntry> aGtkTargets(m_xDragSource->FormatsToGtk(aFormats));
2694 m_eDragAction = VclToGdk(eDNDConstants);
2695 drag_source_set(aGtkTargets, m_eDragAction);
2697 for (auto &a : aGtkTargets)
2698 g_free(a.target);
2700 m_xDragSource->set_datatransfer(rHelper, rHelper);
2701 #else
2702 (void)rHelper;
2703 (void)eDNDConstants;
2704 #endif
2707 void localizeDecimalSeparator()
2709 // tdf#128867 if localize decimal separator is active we will always
2710 // need to be able to change the output of the decimal key press
2711 if (!m_nKeyPressSignalId && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2713 #if GTK_CHECK_VERSION(4, 0, 0)
2714 m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
2715 #else
2716 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
2717 #endif
2721 void ensure_drag_begin_end()
2723 if (!m_nDragBeginSignalId)
2725 // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
2726 #if GTK_CHECK_VERSION(4, 0, 0)
2727 m_nDragBeginSignalId = g_signal_connect_after(get_drag_controller(), "drag-begin", G_CALLBACK(signalDragBegin), this);
2728 #else
2729 m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
2730 #endif
2732 if (!m_nDragEndSignalId)
2734 #if GTK_CHECK_VERSION(4, 0, 0)
2735 m_nDragEndSignalId = g_signal_connect(get_drag_controller(), "drag-end", G_CALLBACK(signalDragEnd), this);
2736 #else
2737 m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
2738 #endif
2742 void DisconnectMouseEvents()
2744 if (m_nButtonPressSignalId)
2746 #if GTK_CHECK_VERSION(4, 0, 0)
2747 g_signal_handler_disconnect(get_click_controller(), m_nButtonPressSignalId);
2748 #else
2749 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
2750 #endif
2751 m_nButtonPressSignalId = 0;
2753 if (m_nMotionSignalId)
2755 #if GTK_CHECK_VERSION(4, 0, 0)
2756 g_signal_handler_disconnect(get_motion_controller(), m_nMotionSignalId);
2757 #else
2758 g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
2759 #endif
2760 m_nMotionSignalId = 0;
2762 if (m_nLeaveSignalId)
2764 #if GTK_CHECK_VERSION(4, 0, 0)
2765 g_signal_handler_disconnect(get_motion_controller(), m_nLeaveSignalId);
2766 #else
2767 g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
2768 #endif
2769 m_nLeaveSignalId = 0;
2771 if (m_nEnterSignalId)
2773 #if GTK_CHECK_VERSION(4, 0, 0)
2774 g_signal_handler_disconnect(get_motion_controller(), m_nEnterSignalId);
2775 #else
2776 g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
2777 #endif
2778 m_nEnterSignalId = 0;
2780 if (m_nButtonReleaseSignalId)
2782 #if GTK_CHECK_VERSION(4, 0, 0)
2783 g_signal_handler_disconnect(get_click_controller(), m_nButtonReleaseSignalId);
2784 #else
2785 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
2786 #endif
2787 m_nButtonReleaseSignalId = 0;
2790 #if !GTK_CHECK_VERSION(4, 0, 0)
2791 if (!m_pMouseEventBox || m_pMouseEventBox == m_pWidget)
2792 return;
2794 // GtkWindow replacement for GtkPopover case
2795 if (!GTK_IS_EVENT_BOX(m_pMouseEventBox))
2797 m_pMouseEventBox = nullptr;
2798 return;
2801 // put things back they way we found them
2802 GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
2804 g_object_ref(m_pWidget);
2805 gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
2807 gtk_widget_destroy(m_pMouseEventBox);
2809 gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
2810 // coverity[freed_arg : FALSE] - this does not free m_pWidget, it is reffed by pParent
2811 g_object_unref(m_pWidget);
2813 m_pMouseEventBox = m_pWidget;
2814 #endif
2817 private:
2818 bool m_bTakeOwnership;
2819 #if !GTK_CHECK_VERSION(4, 0, 0)
2820 bool m_bDraggedOver;
2821 #endif
2822 int m_nWaitCount;
2823 int m_nFreezeCount;
2824 sal_uInt16 m_nLastMouseButton;
2825 #if !GTK_CHECK_VERSION(4, 0, 0)
2826 sal_uInt16 m_nLastMouseClicks;
2827 #endif
2828 int m_nPressedButton;
2829 #if !GTK_CHECK_VERSION(4, 0, 0)
2830 int m_nPressStartX;
2831 int m_nPressStartY;
2832 #endif
2833 ImplSVEvent* m_pDragCancelEvent;
2834 GtkCssProvider* m_pBgCssProvider;
2835 #if !GTK_CHECK_VERSION(4, 0, 0)
2836 GdkDragAction m_eDragAction;
2837 #endif
2838 gulong m_nFocusInSignalId;
2839 gulong m_nMnemonicActivateSignalId;
2840 gulong m_nFocusOutSignalId;
2841 gulong m_nKeyPressSignalId;
2842 gulong m_nKeyReleaseSignalId;
2843 protected:
2844 gulong m_nSizeAllocateSignalId;
2845 private:
2846 gulong m_nButtonPressSignalId;
2847 gulong m_nMotionSignalId;
2848 gulong m_nLeaveSignalId;
2849 gulong m_nEnterSignalId;
2850 gulong m_nButtonReleaseSignalId;
2851 gulong m_nDragMotionSignalId;
2852 gulong m_nDragDropSignalId;
2853 gulong m_nDragDropReceivedSignalId;
2854 gulong m_nDragLeaveSignalId;
2855 gulong m_nDragBeginSignalId;
2856 gulong m_nDragEndSignalId;
2857 gulong m_nDragFailedSignalId;
2858 gulong m_nDragDataDeleteignalId;
2859 gulong m_nDragGetSignalId;
2861 #if GTK_CHECK_VERSION(4, 0, 0)
2862 int m_nGrabCount;
2863 GtkEventController* m_pFocusController;
2864 GtkEventController* m_pClickController;
2865 GtkEventController* m_pMotionController;
2866 GtkEventController* m_pDragController;
2867 GtkEventController* m_pKeyController;
2868 #endif
2870 rtl::Reference<GtkInstDropTarget> m_xDropTarget;
2871 rtl::Reference<GtkInstDragSource> m_xDragSource;
2873 static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
2875 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2876 SolarMutexGuard aGuard;
2877 pThis->signal_size_allocate(allocation->width, allocation->height);
2880 #if GTK_CHECK_VERSION(4, 0, 0)
2881 static gboolean signalKeyPressed(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
2883 LocalizeDecimalSeparator(keyval);
2884 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2885 return pThis->signal_key_press(keyval, keycode, state);
2888 static gboolean signalKeyReleased(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
2890 LocalizeDecimalSeparator(keyval);
2891 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2892 return pThis->signal_key_release(keyval, keycode, state);
2894 #else
2895 static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
2897 LocalizeDecimalSeparator(pEvent->keyval);
2898 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2899 if (pEvent->type == GDK_KEY_PRESS)
2900 return pThis->signal_key_press(pEvent);
2901 return pThis->signal_key_release(pEvent);
2903 #endif
2905 virtual bool signal_popup_menu(const CommandEvent&)
2907 return false;
2910 #if GTK_CHECK_VERSION(4, 0, 0)
2911 static void signalButtonPress(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
2913 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2914 SolarMutexGuard aGuard;
2915 pThis->signal_button(pGesture, SalEvent::MouseButtonDown, n_press, x, y);
2918 static void signalButtonRelease(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
2920 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2921 SolarMutexGuard aGuard;
2922 pThis->signal_button(pGesture, SalEvent::MouseButtonUp, n_press, x, y);
2925 void signal_button(GtkGestureClick* pGesture, SalEvent nEventType, int n_press, gdouble x, gdouble y)
2927 m_nPressedButton = -1;
2929 Point aPos(x, y);
2930 if (SwapForRTL())
2931 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2933 if (n_press == 1)
2935 GdkEventSequence* pSequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(pGesture));
2936 GdkEvent* pEvent = gtk_gesture_get_last_event(GTK_GESTURE(pGesture), pSequence);
2937 if (gdk_event_triggers_context_menu(pEvent))
2939 //if handled for context menu, stop processing
2940 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
2941 if (signal_popup_menu(aCEvt))
2943 gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
2944 return;
2949 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
2950 int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
2952 switch (nButton)
2954 case 1:
2955 m_nLastMouseButton = MOUSE_LEFT;
2956 break;
2957 case 2:
2958 m_nLastMouseButton = MOUSE_MIDDLE;
2959 break;
2960 case 3:
2961 m_nLastMouseButton = MOUSE_RIGHT;
2962 break;
2963 default:
2964 return;
2967 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(eType);
2968 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
2969 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2970 MouseEvent aMEvt(aPos, n_press, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
2972 if (nEventType == SalEvent::MouseButtonDown && m_aMousePressHdl.Call(aMEvt))
2973 gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
2975 if (nEventType == SalEvent::MouseButtonUp && m_aMouseReleaseHdl.Call(aMEvt))
2976 gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
2979 #else
2981 static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
2983 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2984 SolarMutexGuard aGuard;
2985 return pThis->signal_button(pEvent);
2988 static gboolean signalButtonRelease(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
2990 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2991 SolarMutexGuard aGuard;
2992 return pThis->signal_button(pEvent);
2995 bool signal_button(GdkEventButton* pEvent)
2997 m_nPressedButton = -1;
2999 Point aPos(pEvent->x, pEvent->y);
3000 if (SwapForRTL())
3001 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
3003 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
3005 //if handled for context menu, stop processing
3006 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
3007 if (signal_popup_menu(aCEvt))
3008 return true;
3011 if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
3012 return false;
3014 SalEvent nEventType = SalEvent::NONE;
3015 switch (pEvent->type)
3017 case GDK_BUTTON_PRESS:
3018 if (GdkEvent* pPeekEvent = gdk_event_peek())
3020 bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
3021 pPeekEvent->type == GDK_3BUTTON_PRESS;
3022 gdk_event_free(pPeekEvent);
3023 if (bSkip)
3025 return false;
3028 nEventType = SalEvent::MouseButtonDown;
3029 m_nLastMouseClicks = 1;
3030 break;
3031 case GDK_2BUTTON_PRESS:
3032 m_nLastMouseClicks = 2;
3033 nEventType = SalEvent::MouseButtonDown;
3034 break;
3035 case GDK_3BUTTON_PRESS:
3036 m_nLastMouseClicks = 3;
3037 nEventType = SalEvent::MouseButtonDown;
3038 break;
3039 case GDK_BUTTON_RELEASE:
3040 nEventType = SalEvent::MouseButtonUp;
3041 break;
3042 default:
3043 return false;
3046 switch (pEvent->button)
3048 case 1:
3049 m_nLastMouseButton = MOUSE_LEFT;
3050 break;
3051 case 2:
3052 m_nLastMouseButton = MOUSE_MIDDLE;
3053 break;
3054 case 3:
3055 m_nLastMouseButton = MOUSE_RIGHT;
3056 break;
3057 default:
3058 return false;
3061 /* Save press to possibly begin a drag */
3062 if (pEvent->type != GDK_BUTTON_RELEASE)
3064 m_nPressedButton = pEvent->button;
3065 m_nPressStartX = pEvent->x;
3066 m_nPressStartY = pEvent->y;
3069 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
3070 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
3071 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
3072 MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
3074 if (nEventType == SalEvent::MouseButtonDown)
3076 if (!m_aMousePressHdl.IsSet())
3077 return false;
3078 return m_aMousePressHdl.Call(aMEvt);
3081 if (!m_aMouseReleaseHdl.IsSet())
3082 return false;
3083 return m_aMouseReleaseHdl.Call(aMEvt);
3085 #endif
3087 bool simple_signal_motion(double x, double y, guint nState)
3089 if (!m_aMouseMotionHdl.IsSet())
3090 return false;
3092 Point aPos(x, y);
3093 if (SwapForRTL())
3094 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
3095 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
3096 MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
3098 return m_aMouseMotionHdl.Call(aMEvt);
3101 #if GTK_CHECK_VERSION(4, 0, 0)
3102 static void signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
3104 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3105 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3107 SolarMutexGuard aGuard;
3108 pThis->simple_signal_motion(x, y, eType);
3111 #else
3112 static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
3114 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3115 SolarMutexGuard aGuard;
3116 return pThis->signal_motion(pEvent);
3119 bool signal_motion(const GdkEventMotion* pEvent)
3121 GtkTargetList* pDragData = (m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is()) ? gtk_drag_source_get_target_list(m_pWidget) : nullptr;
3122 bool bUnsetDragIcon(false);
3123 if (pDragData && gtk_drag_check_threshold(m_pWidget, m_nPressStartX, m_nPressStartY, pEvent->x, pEvent->y) && !do_signal_drag_begin(bUnsetDragIcon))
3125 GdkDragContext* pContext = gtk_drag_begin_with_coordinates(m_pWidget,
3126 pDragData,
3127 m_eDragAction,
3128 m_nPressedButton,
3129 const_cast<GdkEvent*>(reinterpret_cast<const GdkEvent*>(pEvent)),
3130 m_nPressStartX, m_nPressStartY);
3132 if (pContext && bUnsetDragIcon)
3134 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
3135 gtk_drag_set_icon_surface(pContext, surface);
3138 m_nPressedButton = -1;
3139 return false;
3142 return simple_signal_motion(pEvent->x, pEvent->y, pEvent->state);
3144 #endif
3146 bool signal_crossing(double x, double y, guint nState, MouseEventModifiers eMouseEventModifiers)
3148 if (!m_aMouseMotionHdl.IsSet())
3149 return false;
3151 Point aPos(x, y);
3152 if (SwapForRTL())
3153 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
3154 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
3155 MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
3156 eModifiers = eModifiers | eMouseEventModifiers;
3157 MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
3159 m_aMouseMotionHdl.Call(aMEvt);
3160 return false;
3163 #if GTK_CHECK_VERSION(4, 0, 0)
3164 static void signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
3166 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3167 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3168 SolarMutexGuard aGuard;
3169 pThis->signal_crossing(x, y, eType, MouseEventModifiers::ENTERWINDOW);
3172 static void signalLeave(GtkEventControllerMotion *pController, gpointer widget)
3174 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3175 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3176 SolarMutexGuard aGuard;
3177 pThis->signal_crossing(-1, -1, eType, MouseEventModifiers::LEAVEWINDOW);
3179 #else
3180 static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
3182 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3183 MouseEventModifiers eMouseEventModifiers = pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW;
3184 SolarMutexGuard aGuard;
3185 return pThis->signal_crossing(pEvent->x, pEvent->y, pEvent->state, eMouseEventModifiers);
3187 #endif
3189 virtual void drag_started()
3193 #if !GTK_CHECK_VERSION(4, 0, 0)
3194 static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
3196 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3197 if (!pThis->m_bDraggedOver)
3199 pThis->m_bDraggedOver = true;
3200 pThis->drag_started();
3202 return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
3205 static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
3207 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3208 return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
3211 static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
3213 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3214 pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
3216 #endif
3218 virtual void drag_ended()
3222 #if !GTK_CHECK_VERSION(4, 0, 0)
3223 static void signalDragLeave(GtkWidget* pWidget, GdkDragContext*, guint /*time*/, gpointer widget)
3225 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3226 pThis->m_xDropTarget->signalDragLeave(pWidget);
3227 if (pThis->m_bDraggedOver)
3229 pThis->m_bDraggedOver = false;
3230 pThis->drag_ended();
3233 #endif
3235 #if GTK_CHECK_VERSION(4, 0, 0)
3236 static void signalDragBegin(GtkDragSource* context, GdkDrag*, gpointer widget)
3237 #else
3238 static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
3239 #endif
3241 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3242 pThis->signal_drag_begin(context);
3245 void ensure_drag_source()
3247 if (!m_xDragSource)
3249 m_xDragSource.set(new GtkInstDragSource);
3251 #if !GTK_CHECK_VERSION(4, 0, 0)
3252 m_nDragFailedSignalId = g_signal_connect(m_pWidget, "drag-failed", G_CALLBACK(signalDragFailed), this);
3253 m_nDragDataDeleteignalId = g_signal_connect(m_pWidget, "drag-data-delete", G_CALLBACK(signalDragDelete), this);
3254 m_nDragGetSignalId = g_signal_connect(m_pWidget, "drag-data-get", G_CALLBACK(signalDragDataGet), this);
3255 #endif
3257 ensure_drag_begin_end();
3261 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
3263 rUnsetDragIcon = false;
3264 return false;
3267 #if GTK_CHECK_VERSION(4, 0, 0)
3268 void signal_drag_begin(GtkDragSource* context)
3269 #else
3270 void signal_drag_begin(GdkDragContext* context)
3271 #endif
3273 bool bUnsetDragIcon(false);
3274 if (do_signal_drag_begin(bUnsetDragIcon))
3276 #if !GTK_CHECK_VERSION(4, 0, 0)
3277 launch_drag_cancel(context);
3278 #else
3279 (void)context;
3280 #endif
3281 return;
3283 #if !GTK_CHECK_VERSION(4, 0, 0)
3284 if (bUnsetDragIcon)
3286 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
3287 gtk_drag_set_icon_surface(context, surface);
3289 #endif
3290 if (!m_xDragSource)
3291 return;
3292 m_xDragSource->setActiveDragSource();
3295 virtual void do_signal_drag_end()
3299 #if GTK_CHECK_VERSION(4, 0, 0)
3300 static void signalDragEnd(GtkGestureDrag* /*gesture*/, double /*offset_x*/, double /*offset_y*/, gpointer widget)
3301 #else
3302 static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
3303 #endif
3305 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3306 pThis->do_signal_drag_end();
3307 #if !GTK_CHECK_VERSION(4, 0, 0)
3308 if (pThis->m_xDragSource.is())
3309 pThis->m_xDragSource->dragEnd(context);
3310 #endif
3313 #if !GTK_CHECK_VERSION(4, 0, 0)
3314 static gboolean signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer widget)
3316 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3317 pThis->m_xDragSource->dragFailed();
3318 return false;
3321 static void signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer widget)
3323 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3324 pThis->m_xDragSource->dragDelete();
3327 static void signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
3328 guint /*time*/, gpointer widget)
3330 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
3331 pThis->m_xDragSource->dragDataGet(data, info);
3333 #endif
3335 #if !GTK_CHECK_VERSION(4, 0, 0)
3336 virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
3338 if (rGtkTargets.empty() && !eDragAction)
3339 gtk_drag_source_unset(m_pWidget);
3340 else
3341 gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
3343 #endif
3345 void do_set_background(const Color& rColor)
3347 const bool bRemoveColor = rColor == COL_AUTO;
3348 if (bRemoveColor && !m_pBgCssProvider)
3349 return;
3350 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
3351 if (m_pBgCssProvider)
3353 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
3354 m_pBgCssProvider = nullptr;
3356 if (bRemoveColor)
3357 return;
3358 OUString sColor = rColor.AsRGBHexString();
3359 m_pBgCssProvider = gtk_css_provider_new();
3360 OUString aBuffer = "* { background-color: #" + sColor + "; }";
3361 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
3362 css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength());
3363 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
3364 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3367 #if !GTK_CHECK_VERSION(4, 0, 0)
3368 static void update_style(GtkWidget* pWidget, gpointer pData)
3370 if (GTK_IS_CONTAINER(pWidget))
3371 gtk_container_foreach(GTK_CONTAINER(pWidget), update_style, pData);
3372 GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(pWidget);
3373 pWidgetClass->style_updated(pWidget);
3375 #endif
3377 public:
3378 GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3379 : m_pWidget(pWidget)
3380 , m_pMouseEventBox(nullptr)
3381 , m_pBuilder(pBuilder)
3382 , m_bTakeOwnership(bTakeOwnership)
3383 #if !GTK_CHECK_VERSION(4, 0, 0)
3384 , m_bDraggedOver(false)
3385 #endif
3386 , m_nWaitCount(0)
3387 , m_nFreezeCount(0)
3388 , m_nLastMouseButton(0)
3389 #if !GTK_CHECK_VERSION(4, 0, 0)
3390 , m_nLastMouseClicks(0)
3391 #endif
3392 , m_nPressedButton(-1)
3393 #if !GTK_CHECK_VERSION(4, 0, 0)
3394 , m_nPressStartX(-1)
3395 , m_nPressStartY(-1)
3396 #endif
3397 , m_pDragCancelEvent(nullptr)
3398 , m_pBgCssProvider(nullptr)
3399 #if !GTK_CHECK_VERSION(4, 0, 0)
3400 , m_eDragAction(GdkDragAction(0))
3401 #endif
3402 , m_nFocusInSignalId(0)
3403 , m_nMnemonicActivateSignalId(0)
3404 , m_nFocusOutSignalId(0)
3405 , m_nKeyPressSignalId(0)
3406 , m_nKeyReleaseSignalId(0)
3407 , m_nSizeAllocateSignalId(0)
3408 , m_nButtonPressSignalId(0)
3409 , m_nMotionSignalId(0)
3410 , m_nLeaveSignalId(0)
3411 , m_nEnterSignalId(0)
3412 , m_nButtonReleaseSignalId(0)
3413 , m_nDragMotionSignalId(0)
3414 , m_nDragDropSignalId(0)
3415 , m_nDragDropReceivedSignalId(0)
3416 , m_nDragLeaveSignalId(0)
3417 , m_nDragBeginSignalId(0)
3418 , m_nDragEndSignalId(0)
3419 , m_nDragFailedSignalId(0)
3420 , m_nDragDataDeleteignalId(0)
3421 , m_nDragGetSignalId(0)
3422 #if GTK_CHECK_VERSION(4, 0, 0)
3423 , m_nGrabCount(0)
3424 , m_pFocusController(nullptr)
3425 , m_pClickController(nullptr)
3426 , m_pMotionController(nullptr)
3427 , m_pDragController(nullptr)
3428 , m_pKeyController(nullptr)
3429 #endif
3431 if (!bTakeOwnership)
3432 g_object_ref(m_pWidget);
3434 localizeDecimalSeparator();
3437 virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
3439 if (!m_nKeyPressSignalId)
3441 #if GTK_CHECK_VERSION(4, 0, 0)
3442 m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
3443 #else
3444 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
3445 #endif
3447 weld::Widget::connect_key_press(rLink);
3450 virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
3452 if (!m_nKeyReleaseSignalId)
3454 #if GTK_CHECK_VERSION(4, 0, 0)
3455 m_nKeyReleaseSignalId = g_signal_connect(get_key_controller(), "key-released", G_CALLBACK(signalKeyReleased), this);
3456 #else
3457 m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
3458 #endif
3460 weld::Widget::connect_key_release(rLink);
3463 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
3465 ensureButtonPressSignal();
3466 weld::Widget::connect_mouse_press(rLink);
3469 virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
3471 #if GTK_CHECK_VERSION(4, 0, 0)
3472 GtkEventController* pMotionController = get_motion_controller();
3473 if (!m_nMotionSignalId)
3474 m_nMotionSignalId = g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
3475 if (!m_nLeaveSignalId)
3476 m_nLeaveSignalId = g_signal_connect(pMotionController, "leave", G_CALLBACK(signalEnter), this);
3477 if (!m_nEnterSignalId)
3478 m_nEnterSignalId = g_signal_connect(pMotionController, "enter", G_CALLBACK(signalLeave), this);
3479 #else
3480 ensureMouseEventWidget();
3481 if (!m_nMotionSignalId)
3482 m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
3483 if (!m_nLeaveSignalId)
3484 m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
3485 if (!m_nEnterSignalId)
3486 m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
3487 #endif
3488 weld::Widget::connect_mouse_move(rLink);
3491 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
3493 ensureButtonReleaseSignal();
3494 weld::Widget::connect_mouse_release(rLink);
3497 virtual void set_sensitive(bool sensitive) override
3499 gtk_widget_set_sensitive(m_pWidget, sensitive);
3502 virtual bool get_sensitive() const override
3504 return gtk_widget_get_sensitive(m_pWidget);
3507 virtual bool get_visible() const override
3509 return gtk_widget_get_visible(m_pWidget);
3512 virtual bool is_visible() const override
3514 return gtk_widget_is_visible(m_pWidget);
3517 virtual void set_can_focus(bool bCanFocus) override
3519 gtk_widget_set_can_focus(m_pWidget, bCanFocus);
3522 virtual void grab_focus() override
3524 if (has_focus())
3525 return;
3526 gtk_widget_grab_focus(m_pWidget);
3529 virtual bool has_focus() const override
3531 return gtk_widget_has_focus(m_pWidget);
3534 virtual bool is_active() const override
3536 GtkWindow* pTopLevel = GTK_WINDOW(widget_get_toplevel(m_pWidget));
3537 return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
3540 // is the focus in a child of this widget, where a transient popup attached
3541 // to a widget is considered a child of that widget
3542 virtual bool has_child_focus() const override
3544 GtkWindow* pFocusWin = get_active_window();
3545 if (!pFocusWin)
3546 return false;
3547 GtkWidget* pFocus = gtk_window_get_focus(pFocusWin);
3548 if (pFocus && gtk_widget_is_ancestor(pFocus, m_pWidget))
3549 return true;
3550 #if !GTK_CHECK_VERSION(4, 0, 0)
3551 GtkWidget* pAttachedTo = gtk_window_get_attached_to(pFocusWin);
3552 if (!pAttachedTo)
3553 return false;
3554 if (pAttachedTo == m_pWidget || gtk_widget_is_ancestor(pAttachedTo, m_pWidget))
3555 return true;
3556 #endif
3557 return false;
3560 virtual void show() override
3562 gtk_widget_show(m_pWidget);
3565 virtual void hide() override
3567 gtk_widget_hide(m_pWidget);
3570 virtual void set_size_request(int nWidth, int nHeight) override
3572 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3573 if (GTK_IS_VIEWPORT(pParent))
3574 pParent = gtk_widget_get_parent(pParent);
3575 if (GTK_IS_SCROLLED_WINDOW(pParent))
3577 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
3578 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
3580 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
3583 virtual Size get_size_request() const override
3585 int nWidth, nHeight;
3586 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
3587 return Size(nWidth, nHeight);
3590 virtual Size get_preferred_size() const override
3592 GtkRequisition size;
3593 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
3594 return Size(size.width, size.height);
3597 virtual float get_approximate_digit_width() const override
3599 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
3600 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
3601 pango_context_get_font_description(pContext),
3602 pango_context_get_language(pContext));
3603 float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
3604 pango_font_metrics_unref(pMetrics);
3606 return nDigitWidth / PANGO_SCALE;
3609 virtual int get_text_height() const override
3611 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
3612 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
3613 pango_context_get_font_description(pContext),
3614 pango_context_get_language(pContext));
3615 int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
3616 pango_font_metrics_unref(pMetrics);
3617 return nLineHeight / PANGO_SCALE;
3620 virtual Size get_pixel_size(const OUString& rText) const override
3622 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
3623 PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
3624 gint nWidth, nHeight;
3625 pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
3626 g_object_unref(pLayout);
3627 return Size(nWidth, nHeight);
3630 virtual vcl::Font get_font() override
3632 return ::get_font(m_pWidget);
3635 virtual void set_grid_left_attach(int nAttach) override
3637 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3638 #if GTK_CHECK_VERSION(4, 0, 0)
3639 int row, width, height;
3640 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &row, &width, &height);
3641 g_object_ref(m_pWidget);
3642 gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
3643 gtk_grid_attach(GTK_GRID(pParent), m_pWidget, nAttach, row, width, height);
3644 g_object_unref(m_pWidget);
3645 #else
3646 gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "left-attach", nAttach, nullptr);
3647 #endif
3650 virtual int get_grid_left_attach() const override
3652 gint nAttach(0);
3653 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3654 #if GTK_CHECK_VERSION(4, 0, 0)
3655 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &nAttach, nullptr, nullptr, nullptr);
3656 #else
3657 gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "left-attach", &nAttach, nullptr);
3658 #endif
3659 return nAttach;
3662 virtual void set_grid_width(int nCols) override
3664 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3665 #if GTK_CHECK_VERSION(4, 0, 0)
3666 int col, row, height;
3667 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, &row, nullptr, &height);
3668 g_object_ref(m_pWidget);
3669 gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
3670 gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, row, nCols, height);
3671 g_object_unref(m_pWidget);
3672 #else
3673 gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "width", nCols, nullptr);
3674 #endif
3677 virtual void set_grid_top_attach(int nAttach) override
3679 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3680 #if GTK_CHECK_VERSION(4, 0, 0)
3681 int col, width, height;
3682 gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, nullptr, &width, &height);
3683 g_object_ref(m_pWidget);
3684 gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
3685 gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, nAttach, width, height);
3686 g_object_unref(m_pWidget);
3687 #else
3688 gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "top-attach", nAttach, nullptr);
3689 #endif
3692 virtual int get_grid_top_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, nullptr, &nAttach, nullptr, nullptr);
3698 #else
3699 gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "top-attach", &nAttach, nullptr);
3700 #endif
3701 return nAttach;
3704 virtual void set_hexpand(bool bExpand) override
3706 gtk_widget_set_hexpand(m_pWidget, bExpand);
3709 virtual bool get_hexpand() const override
3711 return gtk_widget_get_hexpand(m_pWidget);
3714 virtual void set_vexpand(bool bExpand) override
3716 gtk_widget_set_vexpand(m_pWidget, bExpand);
3719 virtual bool get_vexpand() const override
3721 return gtk_widget_get_vexpand(m_pWidget);
3724 virtual void set_margin_top(int nMargin) override
3726 gtk_widget_set_margin_top(m_pWidget, nMargin);
3729 virtual void set_margin_bottom(int nMargin) override
3731 gtk_widget_set_margin_bottom(m_pWidget, nMargin);
3734 virtual void set_margin_start(int nMargin) override
3736 gtk_widget_set_margin_start(m_pWidget, nMargin);
3739 virtual void set_margin_end(int nMargin) override
3741 gtk_widget_set_margin_end(m_pWidget, nMargin);
3744 virtual int get_margin_top() const override
3746 return gtk_widget_get_margin_top(m_pWidget);
3749 virtual int get_margin_bottom() const override
3751 return gtk_widget_get_margin_bottom(m_pWidget);
3754 virtual int get_margin_start() const override
3756 return gtk_widget_get_margin_start(m_pWidget);
3759 virtual int get_margin_end() const override
3761 return gtk_widget_get_margin_end(m_pWidget);
3764 virtual void set_accessible_name(const OUString& rName) override
3766 #if GTK_CHECK_VERSION(4, 0, 0)
3767 gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL,
3768 OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr(), -1);
3769 #else
3770 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3771 if (!pAtkObject)
3772 return;
3773 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
3774 #endif
3777 virtual void set_accessible_description(const OUString& rDescription) override
3779 #if GTK_CHECK_VERSION(4, 0, 0)
3780 gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
3781 OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr(), -1);
3782 #else
3783 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3784 if (!pAtkObject)
3785 return;
3786 atk_object_set_description(pAtkObject, OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr());
3787 #endif
3790 virtual OUString get_accessible_name() const override
3792 #if !GTK_CHECK_VERSION(4, 0, 0)
3793 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3794 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
3795 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3796 #else
3797 char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL, nullptr);
3798 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3799 g_free(pStr);
3800 return sRet;
3801 #endif
3804 virtual OUString get_accessible_description() const override
3806 #if !GTK_CHECK_VERSION(4, 0, 0)
3807 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3808 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
3809 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3810 #else
3811 char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, nullptr);
3812 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3813 g_free(pStr);
3814 return sRet;
3815 #endif
3818 virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
3820 GtkWidget* pGtkLabel = pLabel ? dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget() : nullptr;
3821 #if GTK_CHECK_VERSION(4, 0, 0)
3822 gtk_accessible_update_relation(GTK_ACCESSIBLE(m_pWidget),
3823 GTK_ACCESSIBLE_RELATION_LABELLED_BY,
3824 pGtkLabel, nullptr,
3825 -1);
3826 #else
3827 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
3828 if (!pAtkObject)
3829 return;
3830 AtkObject *pAtkLabel = pGtkLabel ? gtk_widget_get_accessible(pGtkLabel) : nullptr;
3831 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
3832 AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
3833 if (pRelation)
3835 // clear ATK_RELATION_LABEL_FOR from old label
3836 GPtrArray* pOldLabelTarget = atk_relation_get_target(pRelation);
3837 guint nElements = pOldLabelTarget ? pOldLabelTarget->len : 0;
3838 for (guint i = 0; i < nElements; ++i)
3840 gpointer pOldLabelObject = g_ptr_array_index(pOldLabelTarget, i);
3841 AtkRelationSet *pOldLabelRelationSet = atk_object_ref_relation_set(ATK_OBJECT(pOldLabelObject));
3842 if (AtkRelation *pOldLabelRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR))
3843 atk_relation_set_remove(pOldLabelRelationSet, pOldLabelRelation);
3844 g_object_unref(pOldLabelRelationSet);
3846 atk_relation_set_remove(pRelationSet, pRelation);
3849 if (pAtkLabel)
3851 AtkObject *obj_array_labelled_by[1];
3852 obj_array_labelled_by[0] = pAtkLabel;
3853 pRelation = atk_relation_new(obj_array_labelled_by, 1, ATK_RELATION_LABELLED_BY);
3854 atk_relation_set_add(pRelationSet, pRelation);
3856 // add ATK_RELATION_LABEL_FOR to new label to match
3857 AtkRelationSet *pNewLabelRelationSet = atk_object_ref_relation_set(pAtkLabel);
3858 AtkRelation *pNewLabelRelation = atk_relation_set_get_relation_by_type(pNewLabelRelationSet, ATK_RELATION_LABEL_FOR);
3859 if (pNewLabelRelation)
3860 atk_relation_set_remove(pNewLabelRelationSet, pRelation);
3861 AtkObject *obj_array_label_for[1];
3862 obj_array_label_for[0] = pAtkObject;
3863 pNewLabelRelation = atk_relation_new(obj_array_label_for, 1, ATK_RELATION_LABEL_FOR);
3864 atk_relation_set_add(pNewLabelRelationSet, pNewLabelRelation);
3865 g_object_unref(pNewLabelRelationSet);
3868 g_object_unref(pRelationSet);
3869 #endif
3872 virtual bool get_extents_relative_to(const weld::Widget& rRelative, int& x, int &y, int& width, int &height) const override
3874 //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
3875 //the document underneath to auto-scroll to place content in a visible location
3876 gtk_coord fX(0.0), fY(0.0);
3877 bool ret = gtk_widget_translate_coordinates(m_pWidget,
3878 dynamic_cast<const GtkInstanceWidget&>(rRelative).getWidget(),
3879 0, 0, &fX, &fY);
3880 x = fX;
3881 y = fY;
3882 width = gtk_widget_get_allocated_width(m_pWidget);
3883 height = gtk_widget_get_allocated_height(m_pWidget);
3884 return ret;
3887 virtual void set_tooltip_text(const OUString& rTip) override
3889 gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
3892 virtual OUString get_tooltip_text() const override
3894 const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
3895 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3898 virtual std::unique_ptr<weld::Container> weld_parent() const override;
3900 virtual OString get_buildable_name() const override
3902 return ::get_buildable_id(GTK_BUILDABLE(m_pWidget));
3905 virtual void set_buildable_name(const OString& rId) override
3907 ::set_buildable_id(GTK_BUILDABLE(m_pWidget), rId);
3910 virtual void set_help_id(const OString& rHelpId) override
3912 ::set_help_id(m_pWidget, rHelpId);
3915 virtual OString get_help_id() const override
3917 OString sRet = ::get_help_id(m_pWidget);
3918 if (sRet.isEmpty())
3919 sRet = OString("null");
3920 return sRet;
3923 GtkWidget* getWidget() const
3925 return m_pWidget;
3928 GtkWindow* getWindow() const
3930 return GTK_WINDOW(widget_get_toplevel(m_pWidget));
3933 #if GTK_CHECK_VERSION(4, 0, 0)
3934 GtkEventController* get_focus_controller()
3936 if (!m_pFocusController)
3938 gtk_widget_set_focusable(m_pWidget, true);
3939 m_pFocusController = gtk_event_controller_focus_new();
3940 gtk_widget_add_controller(m_pWidget, m_pFocusController);
3942 return m_pFocusController;
3945 #if GTK_CHECK_VERSION(4, 0, 0)
3946 GtkEventController* get_click_controller()
3948 if (!m_pClickController)
3950 GtkGesture *pClick = gtk_gesture_click_new();
3951 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
3952 m_pClickController = GTK_EVENT_CONTROLLER(pClick);
3953 gtk_widget_add_controller(m_pWidget, m_pClickController);
3955 return m_pClickController;
3958 GtkEventController* get_motion_controller()
3960 if (!m_pMotionController)
3962 m_pMotionController = gtk_event_controller_motion_new();
3963 gtk_widget_add_controller(m_pWidget, m_pMotionController);
3965 return m_pMotionController;
3968 GtkEventController* get_drag_controller()
3970 if (!m_pDragController)
3972 GtkDragSource* pDrag = gtk_drag_source_new();
3973 m_pDragController = GTK_EVENT_CONTROLLER(pDrag);
3974 gtk_widget_add_controller(m_pWidget, m_pDragController);
3976 return m_pDragController;
3979 GtkEventController* get_key_controller()
3981 if (!m_pKeyController)
3983 m_pKeyController = gtk_event_controller_key_new();
3984 gtk_widget_add_controller(m_pWidget, m_pKeyController);
3986 return m_pKeyController;
3989 #endif
3992 #endif
3994 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
3996 if (!m_nFocusInSignalId)
3998 #if GTK_CHECK_VERSION(4, 0, 0)
3999 m_nFocusInSignalId = g_signal_connect(get_focus_controller(), "enter", G_CALLBACK(signalFocusIn), this);
4000 #else
4001 m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
4002 #endif
4005 weld::Widget::connect_focus_in(rLink);
4008 virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
4010 if (!m_nMnemonicActivateSignalId)
4011 m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
4012 weld::Widget::connect_mnemonic_activate(rLink);
4015 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
4017 if (!m_nFocusOutSignalId)
4019 #if GTK_CHECK_VERSION(4, 0, 0)
4020 m_nFocusOutSignalId = g_signal_connect(get_focus_controller(), "leave", G_CALLBACK(signalFocusOut), this);
4021 #else
4022 m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
4023 #endif
4025 weld::Widget::connect_focus_out(rLink);
4028 virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
4030 m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
4031 weld::Widget::connect_size_allocate(rLink);
4034 virtual void signal_size_allocate(guint nWidth, guint nHeight)
4036 m_aSizeAllocateHdl.Call(Size(nWidth, nHeight));
4039 #if GTK_CHECK_VERSION(4, 0, 0)
4040 bool signal_key_press(guint keyval, guint keycode, GdkModifierType state)
4042 if (m_aKeyPressHdl.IsSet())
4044 SolarMutexGuard aGuard;
4045 return m_aKeyPressHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
4047 return false;
4050 bool signal_key_release(guint keyval, guint keycode, GdkModifierType state)
4052 if (m_aKeyReleaseHdl.IsSet())
4054 SolarMutexGuard aGuard;
4055 return m_aKeyReleaseHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
4057 return false;
4059 #else
4061 virtual bool do_signal_key_press(const GdkEventKey* pEvent)
4063 if (m_aKeyPressHdl.IsSet())
4065 SolarMutexGuard aGuard;
4066 return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
4068 return false;
4071 virtual bool do_signal_key_release(const GdkEventKey* pEvent)
4073 if (m_aKeyReleaseHdl.IsSet())
4075 SolarMutexGuard aGuard;
4076 return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
4078 return false;
4081 bool signal_key_press(const GdkEventKey* pEvent)
4083 return do_signal_key_press(pEvent);
4086 bool signal_key_release(const GdkEventKey* pEvent)
4088 return do_signal_key_release(pEvent);
4090 #endif
4092 virtual void grab_add() override
4094 #if GTK_CHECK_VERSION(4, 0, 0)
4095 ++m_nGrabCount;
4096 #else
4097 gtk_grab_add(m_pWidget);
4098 #endif
4101 virtual bool has_grab() const override
4103 #if GTK_CHECK_VERSION(4, 0, 0)
4104 return m_nGrabCount != 0;
4105 #else
4106 return gtk_widget_has_grab(m_pWidget);
4107 #endif
4110 virtual void grab_remove() override
4112 #if GTK_CHECK_VERSION(4, 0, 0)
4113 --m_nGrabCount;
4114 #else
4115 gtk_grab_remove(m_pWidget);
4116 #endif
4119 virtual bool get_direction() const override
4121 return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
4124 virtual void set_direction(bool bRTL) override
4126 gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
4129 virtual void freeze() override
4131 ++m_nFreezeCount;
4132 #if !GTK_CHECK_VERSION(4, 0, 0)
4133 gtk_widget_freeze_child_notify(m_pWidget);
4134 #endif
4135 g_object_freeze_notify(G_OBJECT(m_pWidget));
4138 virtual void thaw() override
4140 --m_nFreezeCount;
4141 g_object_thaw_notify(G_OBJECT(m_pWidget));
4142 #if !GTK_CHECK_VERSION(4, 0, 0)
4143 gtk_widget_thaw_child_notify(m_pWidget);
4144 #endif
4147 virtual void set_busy_cursor(bool bBusy) override
4149 if (bBusy)
4150 ++m_nWaitCount;
4151 else
4152 --m_nWaitCount;
4153 if (m_nWaitCount == 1)
4154 set_cursor(m_pWidget, "progress");
4155 else if (m_nWaitCount == 0)
4156 set_cursor(m_pWidget, nullptr);
4157 assert (m_nWaitCount >= 0);
4160 virtual void queue_resize() override
4162 gtk_widget_queue_resize(m_pWidget);
4165 virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
4167 if (!m_xDropTarget)
4169 m_xDropTarget.set(new GtkInstDropTarget);
4170 #if !GTK_CHECK_VERSION(4, 0, 0)
4171 if (!gtk_drag_dest_get_track_motion(m_pWidget))
4173 gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
4174 gtk_drag_dest_set_track_motion(m_pWidget, true);
4176 m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
4177 m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
4178 m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
4179 m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
4180 #endif
4182 return m_xDropTarget;
4185 virtual css::uno::Reference<css::datatransfer::clipboard::XClipboard> get_clipboard() const override
4187 // the gen backend can have per-frame clipboards which is (presumably) useful for LibreOffice Online
4188 // but normal usage is the shared system clipboard
4189 return GetSystemClipboard();
4192 virtual void connect_get_property_tree(const Link<tools::JsonWriter&, void>& /*rLink*/) override
4194 //not implemented for the gtk variant
4197 virtual void get_property_tree(tools::JsonWriter& /*rJsonWriter*/) override
4199 //not implemented for the gtk variant
4202 virtual void call_attention_to() override
4204 // Change the class name to restart the animation under
4205 // its other name: https://css-tricks.com/restart-css-animation/
4206 #if GTK_CHECK_VERSION(4, 0, 0)
4207 if (gtk_widget_has_css_class(m_pWidget, "call_attention_1"))
4209 gtk_widget_remove_css_class(m_pWidget, "call_attention_1");
4210 gtk_widget_add_css_class(m_pWidget, "call_attention_2");
4212 else
4214 gtk_widget_remove_css_class(m_pWidget, "call_attention_2");
4215 gtk_widget_add_css_class(m_pWidget, "call_attention_1");
4217 #else
4218 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
4219 if (gtk_style_context_has_class(pWidgetContext, "call_attention_1"))
4221 gtk_style_context_remove_class(pWidgetContext, "call_attention_1");
4222 gtk_style_context_add_class(pWidgetContext, "call_attention_2");
4224 else
4226 gtk_style_context_remove_class(pWidgetContext, "call_attention_2");
4227 gtk_style_context_add_class(pWidgetContext, "call_attention_1");
4229 #endif
4232 virtual void set_stack_background() override
4234 do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());
4237 virtual void set_title_background() override
4239 do_set_background(Application::GetSettings().GetStyleSettings().GetShadowColor());
4242 virtual void set_highlight_background() override
4244 do_set_background(Application::GetSettings().GetStyleSettings().GetHighlightColor());
4247 virtual void set_background(const Color& rColor) override
4249 do_set_background(rColor);
4252 virtual void set_toolbar_background() override
4254 // no-op
4257 virtual ~GtkInstanceWidget() override
4259 if (m_pDragCancelEvent)
4260 Application::RemoveUserEvent(m_pDragCancelEvent);
4261 if (m_nDragMotionSignalId)
4262 g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
4263 if (m_nDragDropSignalId)
4264 g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
4265 if (m_nDragDropReceivedSignalId)
4266 g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
4267 if (m_nDragLeaveSignalId)
4268 g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
4269 if (m_nDragEndSignalId)
4271 #if GTK_CHECK_VERSION(4, 0, 0)
4272 g_signal_handler_disconnect(get_drag_controller(), m_nDragEndSignalId);
4273 #else
4274 g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
4275 #endif
4277 if (m_nDragBeginSignalId)
4279 #if GTK_CHECK_VERSION(4, 0, 0)
4280 g_signal_handler_disconnect(get_drag_controller(), m_nDragBeginSignalId);
4281 #else
4282 g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
4283 #endif
4285 if (m_nDragFailedSignalId)
4286 g_signal_handler_disconnect(m_pWidget, m_nDragFailedSignalId);
4287 if (m_nDragDataDeleteignalId)
4288 g_signal_handler_disconnect(m_pWidget, m_nDragDataDeleteignalId);
4289 if (m_nDragGetSignalId)
4290 g_signal_handler_disconnect(m_pWidget, m_nDragGetSignalId);
4291 if (m_nKeyPressSignalId)
4293 #if GTK_CHECK_VERSION(4, 0, 0)
4294 g_signal_handler_disconnect(get_key_controller(), m_nKeyPressSignalId);
4295 #else
4296 g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
4297 #endif
4299 if (m_nKeyReleaseSignalId)
4301 #if GTK_CHECK_VERSION(4, 0, 0)
4302 g_signal_handler_disconnect(get_key_controller(), m_nKeyReleaseSignalId);
4303 #else
4304 g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
4305 #endif
4308 if (m_nFocusInSignalId)
4310 #if GTK_CHECK_VERSION(4, 0, 0)
4311 g_signal_handler_disconnect(get_focus_controller(), m_nFocusInSignalId);
4312 #else
4313 g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
4314 #endif
4316 if (m_nMnemonicActivateSignalId)
4317 g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
4318 if (m_nFocusOutSignalId)
4320 #if GTK_CHECK_VERSION(4, 0, 0)
4321 g_signal_handler_disconnect(get_focus_controller(), m_nFocusOutSignalId);
4322 #else
4323 g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
4324 #endif
4326 if (m_nSizeAllocateSignalId)
4327 g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
4329 do_set_background(COL_AUTO);
4331 DisconnectMouseEvents();
4333 if (m_bTakeOwnership)
4335 #if !GTK_CHECK_VERSION(4, 0, 0)
4336 gtk_widget_destroy(m_pWidget);
4337 #else
4338 gtk_window_destroy(GTK_WINDOW(m_pWidget));
4339 #endif
4341 else
4342 g_object_unref(m_pWidget);
4345 virtual void disable_notify_events()
4347 if (m_nFocusInSignalId)
4349 #if GTK_CHECK_VERSION(4, 0, 0)
4350 g_signal_handler_block(get_focus_controller(), m_nFocusInSignalId);
4351 #else
4352 g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
4353 #endif
4355 if (m_nMnemonicActivateSignalId)
4356 g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
4357 if (m_nFocusOutSignalId)
4359 #if GTK_CHECK_VERSION(4, 0, 0)
4360 g_signal_handler_block(get_focus_controller(), m_nFocusOutSignalId);
4361 #else
4362 g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
4363 #endif
4365 if (m_nSizeAllocateSignalId)
4366 g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
4369 virtual void enable_notify_events()
4371 if (m_nSizeAllocateSignalId)
4372 g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
4373 if (m_nFocusOutSignalId)
4375 #if GTK_CHECK_VERSION(4, 0, 0)
4376 g_signal_handler_unblock(get_focus_controller(), m_nFocusOutSignalId);
4377 #else
4378 g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
4379 #endif
4381 if (m_nMnemonicActivateSignalId)
4382 g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
4384 if (m_nFocusInSignalId)
4386 #if GTK_CHECK_VERSION(4, 0, 0)
4387 g_signal_handler_unblock(get_focus_controller(), m_nFocusInSignalId);
4388 #else
4389 g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
4390 #endif
4394 virtual void help_hierarchy_foreach(const std::function<bool(const OString&)>& func) override;
4396 virtual OUString strip_mnemonic(const OUString &rLabel) const override
4398 return rLabel.replaceFirst("_", "");
4401 virtual VclPtr<VirtualDevice> create_virtual_device() const override
4403 // create with no separate alpha layer like everything sane does
4404 auto xRet = VclPtr<VirtualDevice>::Create();
4405 xRet->SetBackground(COL_TRANSPARENT);
4406 return xRet;
4409 virtual void draw(OutputDevice& rOutput, const Point& rPos, const Size& rPixelSize) override
4411 // detect if we have to manually setup its size
4412 bool bAlreadyRealized = gtk_widget_get_realized(m_pWidget);
4413 // has to be visible for draw to work
4414 bool bAlreadyVisible = gtk_widget_get_visible(m_pWidget);
4415 // has to be mapped for draw to work
4416 bool bAlreadyMapped = gtk_widget_get_mapped(m_pWidget);
4418 if (!bAlreadyRealized)
4420 #if !GTK_CHECK_VERSION(4, 0, 0)
4422 tdf#141633 The "sample db" example (Mockup.odb) has multiline
4423 entries used in its "Journal Entry" column. Those are painted by
4424 taking snapshots of a never-really-shown textview widget.
4425 Without this style_updated then the textview is always drawn
4426 using its original default font size and changing the page zoom
4427 has no effect on the size of text in the "Journal Entry" column.
4429 update_style(m_pWidget, nullptr);
4430 #endif
4431 gtk_widget_realize(m_pWidget);
4433 if (!bAlreadyVisible)
4434 gtk_widget_show(m_pWidget);
4435 if (!bAlreadyMapped)
4436 gtk_widget_map(m_pWidget);
4438 assert(gtk_widget_is_drawable(m_pWidget)); // all that should result in this holding
4440 // turn off animations, otherwise we get a frame of an animation sequence
4441 gboolean bAnimations;
4442 GtkSettings* pSettings = gtk_widget_get_settings(m_pWidget);
4443 g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
4444 if (bAnimations)
4445 g_object_set(pSettings, "gtk-enable-animations", false, nullptr);
4447 Size aSize(rPixelSize);
4449 GtkAllocation aOrigAllocation;
4450 gtk_widget_get_allocation(m_pWidget, &aOrigAllocation);
4452 GtkAllocation aNewAllocation {aOrigAllocation.x,
4453 aOrigAllocation.y,
4454 static_cast<int>(aSize.Width()),
4455 static_cast<int>(aSize.Height()) };
4456 #if !GTK_CHECK_VERSION(4, 0, 0)
4457 gtk_widget_size_allocate(m_pWidget, &aNewAllocation);
4458 #else
4459 gtk_widget_size_allocate(m_pWidget, &aNewAllocation, 0);
4460 #endif
4462 #if !GTK_CHECK_VERSION(4, 0, 0)
4463 if (GTK_IS_CONTAINER(m_pWidget))
4464 gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
4465 #endif
4467 VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
4468 xOutput->SetOutputSizePixel(aSize);
4470 switch (rOutput.GetOutDevType())
4472 case OUTDEV_WINDOW:
4473 case OUTDEV_VIRDEV:
4474 xOutput->DrawOutDev(Point(), aSize, rPos, aSize, rOutput);
4475 break;
4476 case OUTDEV_PRINTER:
4477 case OUTDEV_PDF:
4478 xOutput->SetBackground(rOutput.GetBackground());
4479 xOutput->Erase();
4480 break;
4483 cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
4484 cairo_t* cr = cairo_create(pSurface);
4486 #if !GTK_CHECK_VERSION(4, 0, 0)
4487 gtk_widget_draw(m_pWidget, cr);
4488 #else
4489 GtkSnapshot* pSnapshot = gtk_snapshot_new();
4490 GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(m_pWidget);
4491 pWidgetClass->snapshot(m_pWidget, pSnapshot);
4492 GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
4493 gsk_render_node_draw(pNode, cr);
4494 gsk_render_node_unref(pNode);
4495 #endif
4497 cairo_destroy(cr);
4499 #if !GTK_CHECK_VERSION(4, 0, 0)
4500 gtk_widget_set_allocation(m_pWidget, &aOrigAllocation);
4501 gtk_widget_size_allocate(m_pWidget, &aOrigAllocation);
4502 #else
4503 gtk_widget_size_allocate(m_pWidget, &aOrigAllocation, 0);
4504 #endif
4506 switch (rOutput.GetOutDevType())
4508 case OUTDEV_WINDOW:
4509 case OUTDEV_VIRDEV:
4510 rOutput.DrawOutDev(rPos, aSize, Point(), aSize, *xOutput);
4511 break;
4512 case OUTDEV_PRINTER:
4513 case OUTDEV_PDF:
4514 rOutput.DrawBitmapEx(rPos, xOutput->GetBitmapEx(Point(), aSize));
4515 break;
4518 if (bAnimations)
4519 g_object_set(pSettings, "gtk-enable-animations", true, nullptr);
4521 if (!bAlreadyMapped)
4522 gtk_widget_unmap(m_pWidget);
4523 if (!bAlreadyVisible)
4524 gtk_widget_hide(m_pWidget);
4525 if (!bAlreadyRealized)
4526 gtk_widget_unrealize(m_pWidget);
4532 #if !GTK_CHECK_VERSION(4, 0, 0)
4533 IMPL_LINK(GtkInstanceWidget, async_drag_cancel, void*, arg, void)
4535 m_pDragCancelEvent = nullptr;
4536 GdkDragContext* context = static_cast<GdkDragContext*>(arg);
4538 // tdf#132477 simply calling gtk_drag_cancel on the treeview dnd under X
4539 // doesn't seem to work as hoped for (though under wayland all is well).
4540 // Under X the next (allowed) drag effort doesn't work to drop anything,
4541 // but a then repeated attempt does.
4542 // emitting cancel to get gtk to cancel the drag for us does work as hoped for.
4543 g_signal_emit_by_name(context, "cancel", 0, GDK_DRAG_CANCEL_USER_CANCELLED);
4545 g_object_unref(context);
4547 #endif
4549 namespace
4551 OString MapToGtkAccelerator(const OUString &rStr)
4553 return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
4556 OUString get_label(GtkLabel* pLabel)
4558 const gchar* pStr = gtk_label_get_label(pLabel);
4559 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4562 void set_label(GtkLabel* pLabel, const OUString& rText)
4564 gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
4567 #if GTK_CHECK_VERSION(4, 0, 0)
4568 GtkWidget* find_label_widget(GtkWidget* pContainer)
4570 GtkWidget* pLabel = nullptr;
4571 for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
4572 pChild; pChild = gtk_widget_get_next_sibling(pChild))
4574 if (GTK_IS_LABEL(pChild))
4576 pLabel = pChild;
4577 break;
4579 else
4581 pLabel = find_label_widget(pChild);
4582 if (pLabel)
4583 break;
4586 return pLabel;
4589 GtkWidget* find_image_widget(GtkWidget* pContainer)
4591 GtkWidget* pImage = nullptr;
4592 for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
4593 pChild; pChild = gtk_widget_get_next_sibling(pChild))
4595 if (GTK_IS_IMAGE(pChild))
4597 pImage = pChild;
4598 break;
4600 else
4602 pImage = find_image_widget(pChild);
4603 if (pImage)
4604 break;
4607 return pImage;
4609 #else
4610 GtkWidget* find_label_widget(GtkContainer* pContainer)
4612 GList* pChildren = gtk_container_get_children(pContainer);
4614 GtkWidget* pChild = nullptr;
4615 for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
4617 if (GTK_IS_LABEL(pCandidate->data))
4619 pChild = GTK_WIDGET(pCandidate->data);
4620 break;
4622 else if (GTK_IS_CONTAINER(pCandidate->data))
4624 pChild = find_label_widget(GTK_CONTAINER(pCandidate->data));
4625 if (pChild)
4626 break;
4629 g_list_free(pChildren);
4631 return pChild;
4634 GtkWidget* find_image_widget(GtkContainer* pContainer)
4636 GList* pChildren = gtk_container_get_children(pContainer);
4638 GtkWidget* pChild = nullptr;
4639 for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
4641 if (GTK_IS_IMAGE(pCandidate->data))
4643 pChild = GTK_WIDGET(pCandidate->data);
4644 break;
4646 else if (GTK_IS_CONTAINER(pCandidate->data))
4648 pChild = find_image_widget(GTK_CONTAINER(pCandidate->data));
4649 if (pChild)
4650 break;
4653 g_list_free(pChildren);
4655 return pChild;
4657 #endif
4659 GtkLabel* get_label_widget(GtkWidget* pButton)
4661 #if !GTK_CHECK_VERSION(4, 0, 0)
4662 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
4664 if (GTK_IS_CONTAINER(pChild))
4665 pChild = find_label_widget(GTK_CONTAINER(pChild));
4666 else if (!GTK_IS_LABEL(pChild))
4667 pChild = nullptr;
4669 return GTK_LABEL(pChild);
4670 #else
4671 return GTK_LABEL(find_label_widget(pButton));
4672 #endif
4675 GtkImage* get_image_widget(GtkWidget *pButton)
4677 #if !GTK_CHECK_VERSION(4, 0, 0)
4678 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
4680 if (GTK_IS_CONTAINER(pChild))
4681 pChild = find_image_widget(GTK_CONTAINER(pChild));
4682 else if (!GTK_IS_IMAGE(pChild))
4683 pChild = nullptr;
4685 return GTK_IMAGE(pChild);
4686 #else
4687 return GTK_IMAGE(find_image_widget(pButton));
4688 #endif
4691 OUString button_get_label(GtkButton* pButton)
4693 if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
4694 return ::get_label(pLabel);
4695 const gchar* pStr = gtk_button_get_label(pButton);
4696 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4699 void button_set_label(GtkButton* pButton, const OUString& rText)
4701 if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
4703 ::set_label(pLabel, rText);
4704 gtk_widget_set_visible(GTK_WIDGET(pLabel), true);
4705 return;
4707 gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
4710 #if GTK_CHECK_VERSION(4, 0, 0)
4711 OUString get_label(GtkCheckButton* pButton)
4713 const gchar* pStr = gtk_check_button_get_label(pButton);
4714 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4717 void set_label(GtkCheckButton* pButton, const OUString& rText)
4719 gtk_check_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
4721 #endif
4723 OUString get_title(GtkWindow* pWindow)
4725 const gchar* pStr = gtk_window_get_title(pWindow);
4726 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4729 void set_title(GtkWindow* pWindow, std::u16string_view rTitle)
4731 gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
4734 OUString get_primary_text(GtkMessageDialog* pMessageDialog)
4736 gchar* pText = nullptr;
4737 g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
4738 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
4741 void set_primary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
4743 g_object_set(G_OBJECT(pMessageDialog), "text",
4744 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
4745 nullptr);
4748 void set_secondary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
4750 g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
4751 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
4752 nullptr);
4755 OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
4757 gchar* pText = nullptr;
4758 g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
4759 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
4763 namespace
4765 GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
4767 auto nLength = rStream.TellEnd();
4768 if (!nLength)
4769 return nullptr;
4770 const guchar* pData = static_cast<const guchar*>(rStream.GetData());
4771 assert((*pData == 137 || *pData == '<') && "if we want to support more than png or svg this function must change");
4772 // if we know the image type, it's a little faster to hand the type over and skip the type detection.
4773 GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new_with_type(*pData == 137 ? "png" : "svg", nullptr);
4774 gdk_pixbuf_loader_write(pixbuf_loader, pData, nLength, nullptr);
4775 gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
4776 GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
4777 if (pixbuf)
4778 g_object_ref(pixbuf);
4779 g_object_unref(pixbuf_loader);
4780 return pixbuf;
4783 GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
4785 auto xMemStm = ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
4786 if (!xMemStm)
4787 return nullptr;
4788 return load_icon_from_stream(*xMemStm);
4792 GdkPixbuf* load_icon_by_name(const OUString& rIconName)
4794 OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
4795 OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
4796 return load_icon_by_name_theme_lang(rIconName, sIconTheme, sUILang);
4799 namespace
4801 GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
4803 Image aImage(rImage);
4805 OUString sStock(aImage.GetStock());
4806 if (!sStock.isEmpty())
4807 return load_icon_by_name(sStock);
4809 SvMemoryStream aMemStm;
4811 // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
4812 css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
4813 "Compression", sal_Int32(1)) };
4815 vcl::PNGWriter aWriter(aImage.GetBitmapEx(), &aFilterData);
4816 aWriter.Write(aMemStm);
4818 return load_icon_from_stream(aMemStm);
4821 GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
4823 Size aSize(rDevice.GetOutputSizePixel());
4824 cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
4825 double m_fXScale, m_fYScale;
4826 dl_cairo_surface_get_device_scale(orig_surface, &m_fXScale, &m_fYScale);
4828 cairo_surface_t* surface;
4829 if (m_fXScale != 1.0 || m_fYScale != -1)
4831 surface = cairo_surface_create_similar_image(orig_surface,
4832 CAIRO_FORMAT_ARGB32,
4833 aSize.Width(),
4834 aSize.Height());
4835 cairo_t* cr = cairo_create(surface);
4836 cairo_set_source_surface(cr, orig_surface, 0, 0);
4837 cairo_paint(cr);
4838 cairo_destroy(cr);
4840 else
4841 surface = orig_surface;
4843 GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
4845 if (surface != orig_surface)
4846 cairo_surface_destroy(surface);
4848 return pRet;
4851 #if GTK_CHECK_VERSION(4, 0, 0)
4852 cairo_surface_t* render_paintable_to_surface(GdkPaintable *paintable, int nWidth, int nHeight)
4854 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
4856 GtkSnapshot* snapshot = gtk_snapshot_new();
4857 gdk_paintable_snapshot(paintable, snapshot, nWidth, nHeight);
4858 GskRenderNode* node = gtk_snapshot_free_to_node(snapshot);
4860 cairo_t* cr = cairo_create(surface);
4861 gsk_render_node_draw(node, cr);
4862 cairo_destroy(cr);
4864 gsk_render_node_unref(node);
4866 return surface;
4868 #endif
4870 GdkPixbuf* getPixbuf(const OUString& rIconName)
4872 if (rIconName.isEmpty())
4873 return nullptr;
4875 GdkPixbuf* pixbuf = nullptr;
4876 if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
4878 assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
4879 "unknown stock image");
4881 #if GTK_CHECK_VERSION(4, 0, 0)
4882 GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
4883 GtkIconPaintable *icon = gtk_icon_theme_lookup_icon(icon_theme,
4884 OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
4885 nullptr,
4888 AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR,
4889 static_cast<GtkIconLookupFlags>(0));
4890 GdkPaintable* paintable = GDK_PAINTABLE(icon);
4891 int nWidth = gdk_paintable_get_intrinsic_width(paintable);
4892 int nHeight = gdk_paintable_get_intrinsic_height(paintable);
4893 cairo_surface_t* surface = render_paintable_to_surface(paintable, nWidth, nHeight);
4894 pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, nWidth, nHeight);
4895 cairo_surface_destroy(surface);
4896 #else
4897 GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
4898 GError *error = nullptr;
4899 pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
4900 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
4901 #endif
4903 else
4905 const AllSettings& rSettings = Application::GetSettings();
4906 pixbuf = load_icon_by_name_theme_lang(rIconName,
4907 rSettings.GetStyleSettings().DetermineIconTheme(),
4908 rSettings.GetUILanguageTag().getBcp47());
4910 return pixbuf;
4914 namespace
4916 #if GTK_CHECK_VERSION(4, 0, 0)
4917 SurfacePaintable* paintable_new_from_virtual_device(const VirtualDevice& rImageSurface)
4919 cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
4921 Size aSize(rImageSurface.GetOutputSizePixel());
4922 cairo_surface_t* target = cairo_surface_create_similar(surface,
4923 cairo_surface_get_content(surface),
4924 aSize.Width(),
4925 aSize.Height());
4926 cairo_t* cr = cairo_create(target);
4927 cairo_set_source_surface(cr, surface, 0, 0);
4928 cairo_paint(cr);
4929 cairo_destroy(cr);
4931 SurfacePaintable* pPaintable = SURFACE_PAINTABLE(g_object_new(surface_paintable_get_type(), nullptr));
4932 surface_paintable_set_source(pPaintable, target, aSize.Width(), aSize.Height());
4933 return pPaintable;
4936 GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
4938 SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
4939 return gtk_image_new_from_paintable(GDK_PAINTABLE(paintable));
4942 GtkWidget* picture_new_from_virtual_device(const VirtualDevice& rImageSurface)
4944 SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
4945 return gtk_picture_new_for_paintable(GDK_PAINTABLE(paintable));
4948 #else
4949 GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
4951 GtkWidget* pImage = nullptr;
4952 cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
4954 Size aSize(rImageSurface.GetOutputSizePixel());
4955 cairo_surface_t* target = cairo_surface_create_similar(surface,
4956 cairo_surface_get_content(surface),
4957 aSize.Width(),
4958 aSize.Height());
4959 cairo_t* cr = cairo_create(target);
4960 cairo_set_source_surface(cr, surface, 0, 0);
4961 cairo_paint(cr);
4962 cairo_destroy(cr);
4964 pImage = gtk_image_new_from_surface(target);
4965 cairo_surface_destroy(target);
4966 return pImage;
4968 #endif
4970 void image_set_from_icon_name(GtkImage* pImage, const OUString& rIconName)
4972 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
4973 gtk_image_set_from_pixbuf(pImage, pixbuf);
4974 if (!pixbuf)
4975 return;
4976 g_object_unref(pixbuf);
4979 void image_set_from_virtual_device(GtkImage* pImage, const VirtualDevice* pDevice)
4981 #if GTK_CHECK_VERSION(4, 0, 0)
4982 gtk_image_set_from_paintable(pImage, pDevice ? GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)) : nullptr);
4983 #else
4984 gtk_image_set_from_surface(pImage, pDevice ? get_underlying_cairo_surface(*pDevice) : nullptr);
4985 #endif
4988 void image_set_from_xgraphic(GtkImage* pImage, const css::uno::Reference<css::graphic::XGraphic>& rImage)
4990 GdkPixbuf* pixbuf = getPixbuf(rImage);
4991 gtk_image_set_from_pixbuf(pImage, pixbuf);
4992 if (pixbuf)
4993 g_object_unref(pixbuf);
4996 #if GTK_CHECK_VERSION(4, 0, 0)
4997 void picture_set_from_icon_name(GtkPicture* pPicture, const OUString& rIconName)
4999 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
5000 gtk_picture_set_pixbuf(pPicture, pixbuf);
5001 if (pixbuf)
5002 g_object_unref(pixbuf);
5005 void picture_set_from_virtual_device(GtkPicture* pPicture, const VirtualDevice* pDevice)
5007 if (!pDevice)
5008 gtk_picture_set_paintable(pPicture, nullptr);
5009 else
5010 gtk_picture_set_paintable(pPicture, GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)));
5013 void picture_set_from_xgraphic(GtkPicture* pPicture, const css::uno::Reference<css::graphic::XGraphic>& rPicture)
5015 GdkPixbuf* pixbuf = getPixbuf(rPicture);
5016 gtk_picture_set_pixbuf(pPicture, pixbuf);
5017 if (pixbuf)
5018 g_object_unref(pixbuf);
5020 #endif
5022 void button_set_from_icon_name(GtkButton* pButton, const OUString& rIconName)
5024 if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
5026 ::image_set_from_icon_name(pImage, rIconName);
5027 gtk_widget_set_visible(GTK_WIDGET(pImage), true);
5028 return;
5031 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
5032 GtkWidget* pImage;
5033 if (!pixbuf)
5034 pImage = nullptr;
5035 else
5037 pImage = gtk_image_new_from_pixbuf(pixbuf);
5038 g_object_unref(pixbuf);
5040 #if GTK_CHECK_VERSION(4, 0, 0)
5041 gtk_button_set_child(pButton, pImage);
5042 #else
5043 gtk_button_set_image(pButton, pImage);
5044 #endif
5047 void button_set_image(GtkButton* pButton, const VirtualDevice* pDevice)
5049 #if !GTK_CHECK_VERSION(4, 0, 0)
5050 gtk_button_set_always_show_image(pButton, true);
5051 gtk_button_set_image_position(pButton, GTK_POS_LEFT);
5052 #endif
5053 GtkWidget* pImage = pDevice ? image_new_from_virtual_device(*pDevice) : nullptr;
5054 #if GTK_CHECK_VERSION(4, 0, 0)
5055 gtk_button_set_child(pButton, pImage);
5056 #else
5057 gtk_button_set_image(pButton, pImage);
5058 #endif
5061 void button_set_image(GtkButton* pButton, const css::uno::Reference<css::graphic::XGraphic>& rImage)
5063 if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
5065 ::image_set_from_xgraphic(pImage, rImage);
5066 gtk_widget_set_visible(GTK_WIDGET(pImage), true);
5067 return;
5070 GdkPixbuf* pixbuf = getPixbuf(rImage);
5071 GtkWidget* pImage;
5072 if (!pixbuf)
5073 pImage = nullptr;
5074 else
5076 pImage = gtk_image_new_from_pixbuf(pixbuf);
5077 g_object_unref(pixbuf);
5079 #if GTK_CHECK_VERSION(4, 0, 0)
5080 gtk_button_set_child(pButton, pImage);
5081 #else
5082 gtk_button_set_image(pButton, pImage);
5083 #endif
5087 class MenuHelper
5089 protected:
5090 #if !GTK_CHECK_VERSION(4, 0, 0)
5091 GtkMenu* m_pMenu;
5093 std::map<OString, GtkMenuItem*> m_aMap;
5094 #else
5095 GtkPopoverMenu* m_pMenu;
5097 o3tl::sorted_vector<OString> m_aInsertedActions; // must outlive m_aActionEntries
5098 std::map<OString, OString> m_aIdToAction;
5099 std::set<OString> m_aHiddenIds;
5100 std::vector<GActionEntry> m_aActionEntries;
5101 GActionGroup* m_pActionGroup;
5102 // move 'invisible' entries to m_pHiddenActionGroup
5103 GActionGroup* m_pHiddenActionGroup;
5104 #endif
5105 bool m_bTakeOwnership;
5106 private:
5108 virtual void signal_item_activate(const OString& rIdent) = 0;
5110 #if !GTK_CHECK_VERSION(4, 0, 0)
5111 static void collect(GtkWidget* pItem, gpointer widget)
5113 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
5114 if (GtkWidget* pSubMenu = gtk_menu_item_get_submenu(pMenuItem))
5115 gtk_container_foreach(GTK_CONTAINER(pSubMenu), collect, widget);
5116 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
5117 pThis->add_to_map(pMenuItem);
5120 static void signalActivate(GtkMenuItem* pItem, gpointer widget)
5122 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
5123 SolarMutexGuard aGuard;
5124 pThis->signal_item_activate(::get_buildable_id(GTK_BUILDABLE(pItem)));
5126 #else
5127 static std::pair<GMenuModel*, int> find_id(GMenuModel* pMenuModel, const OString& rId)
5129 for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
5131 OString sTarget;
5132 char *id;
5133 if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
5135 sTarget = OString(id);
5136 g_free(id);
5139 if (sTarget == rId)
5140 return std::make_pair(pMenuModel, i);
5142 if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
5144 std::pair<GMenuModel*, int> aRet = find_id(pSectionModel, rId);
5145 if (aRet.first)
5146 return aRet;
5148 if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
5150 std::pair<GMenuModel*, int> aRet = find_id(pSubMenuModel, rId);
5151 if (aRet.first)
5152 return aRet;
5156 return std::make_pair(nullptr, -1);
5159 void clear_actions()
5161 for (const auto& rAction : m_aActionEntries)
5163 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), rAction.name);
5164 g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), rAction.name);
5166 m_aActionEntries.clear();
5167 m_aInsertedActions.clear();
5168 m_aIdToAction.clear();
5171 static void action_activated(GSimpleAction*, GVariant* pParameter, gpointer widget)
5173 gsize nLength(0);
5174 const gchar* pStr = g_variant_get_string(pParameter, &nLength);
5175 OString aStr(pStr, nLength);
5176 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
5177 SolarMutexGuard aGuard;
5178 pThis->signal_item_activate(aStr);
5180 #endif
5182 public:
5183 #if !GTK_CHECK_VERSION(4, 0, 0)
5184 MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
5185 #else
5186 MenuHelper(GtkPopoverMenu* pMenu, bool bTakeOwnership)
5187 #endif
5188 : m_pMenu(pMenu)
5189 , m_bTakeOwnership(bTakeOwnership)
5191 #if !GTK_CHECK_VERSION(4, 0, 0)
5192 if (!m_pMenu)
5193 return;
5194 gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
5195 #else
5196 m_pActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
5197 m_pHiddenActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
5198 #endif
5201 #if !GTK_CHECK_VERSION(4, 0, 0)
5202 void add_to_map(GtkMenuItem* pMenuItem)
5204 OString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
5205 m_aMap[id] = pMenuItem;
5206 g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
5209 void remove_from_map(GtkMenuItem* pMenuItem)
5211 OString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
5212 auto iter = m_aMap.find(id);
5213 g_signal_handlers_disconnect_by_data(pMenuItem, this);
5214 m_aMap.erase(iter);
5217 void disable_item_notify_events()
5219 for (auto& a : m_aMap)
5220 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
5223 void enable_item_notify_events()
5225 for (auto& a : m_aMap)
5226 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
5228 #endif
5230 #if GTK_CHECK_VERSION(4, 0, 0)
5231 /* LibreOffice likes to think of separators between menu entries, while gtk likes
5232 to think of sections of menus with separators drawn between sections. We always
5233 arrange to have a section in a menu so toplevel menumodels comprise of
5234 sections and we move entries between sections on pretending to insert separators */
5235 static std::pair<GMenuModel*, int> get_section_and_pos_for(GMenuModel* pMenuModel, int pos)
5237 int nSectionCount = g_menu_model_get_n_items(pMenuModel);
5238 assert(nSectionCount);
5240 GMenuModel* pSectionModel = nullptr;
5241 int nIndexWithinSection = 0;
5243 int nExternalPos = 0;
5244 for (int nSection = 0; nSection < nSectionCount; ++nSection)
5246 pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
5247 assert(pSectionModel);
5248 int nCount = g_menu_model_get_n_items(pSectionModel);
5249 for (nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
5251 if (pos == nExternalPos)
5252 break;
5253 ++nExternalPos;
5255 ++nExternalPos;
5258 return std::make_pair(pSectionModel, nIndexWithinSection);
5261 static int count_immediate_children(GMenuModel* pMenuModel)
5263 int nSectionCount = g_menu_model_get_n_items(pMenuModel);
5264 assert(nSectionCount);
5266 int nExternalPos = 0;
5267 for (int nSection = 0; nSection < nSectionCount; ++nSection)
5269 GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
5270 assert(pSectionModel);
5271 int nCount = g_menu_model_get_n_items(pSectionModel);
5272 for (int nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
5274 ++nExternalPos;
5276 ++nExternalPos;
5279 return nExternalPos - 1;
5281 #endif
5283 #if GTK_CHECK_VERSION(4, 0, 0)
5284 void process_menu_model(GMenuModel* pMenuModel)
5286 for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
5288 OString sAction, sTarget;
5289 char *id;
5290 if (g_menu_model_get_item_attribute(pMenuModel, i, "action", "s", &id))
5292 assert(o3tl::starts_with(id, "menu."));
5294 sAction = OString(id + 5);
5296 auto res = m_aInsertedActions.insert(sAction);
5297 if (res.second)
5299 // the const char* arg isn't copied by anything so it must continue to exist for the life time of
5300 // the action group
5301 if (sAction.startsWith("radio."))
5302 m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", "'none'", nullptr, {}});
5303 else
5304 m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", nullptr, nullptr, {}});
5307 g_free(id);
5310 if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
5312 sTarget = OString(id);
5313 g_free(id);
5316 m_aIdToAction[sTarget] = sAction;
5318 if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
5319 process_menu_model(pSectionModel);
5320 if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
5321 process_menu_model(pSubMenuModel);
5325 // build an action group for the menu, "action" is the normal menu entry case
5326 // the others are radiogroups
5327 void update_action_group_from_popover_model()
5329 clear_actions();
5331 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5333 process_menu_model(pMenuModel);
5336 // move hidden entries to m_pHiddenActionGroup
5337 g_action_map_add_action_entries(G_ACTION_MAP(m_pActionGroup), m_aActionEntries.data(), m_aActionEntries.size(), this);
5338 for (const auto& id : m_aHiddenIds)
5340 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
5341 g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
5342 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
5345 #endif
5347 void insert_item(int pos, const OUString& rId, const OUString& rStr,
5348 const OUString* pIconName, const VirtualDevice* pImageSurface,
5349 TriState eCheckRadioFalse)
5351 #if !GTK_CHECK_VERSION(4, 0, 0)
5352 GtkWidget* pImage = nullptr;
5353 if (pIconName && !pIconName->isEmpty())
5355 GdkPixbuf* pixbuf = load_icon_by_name(*pIconName);
5356 if (!pixbuf)
5358 pImage = gtk_image_new_from_pixbuf(pixbuf);
5359 g_object_unref(pixbuf);
5362 else if (pImageSurface)
5363 pImage = image_new_from_virtual_device(*pImageSurface);
5365 GtkWidget *pItem;
5366 if (pImage)
5368 GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
5369 GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
5370 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
5371 gtk_box_pack_start(pBox, pImage, true, true, 0);
5372 gtk_box_pack_start(pBox, pLabel, true, true, 0);
5373 gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
5374 gtk_widget_show_all(pItem);
5376 else
5378 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
5379 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
5382 if (eCheckRadioFalse == TRISTATE_FALSE)
5383 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
5385 ::set_buildable_id(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8));
5386 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
5387 gtk_widget_show(pItem);
5388 add_to_map(GTK_MENU_ITEM(pItem));
5389 if (pos != -1)
5390 gtk_menu_reorder_child(m_pMenu, pItem, pos);
5391 #else
5392 (void)pIconName; (void)pImageSurface;
5394 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5396 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
5397 GMenu* pMenu = G_MENU(aSectionAndPos.first);
5398 // action with a target value ... the action name and target value are separated by a double
5399 // colon ... For example: "app.action::target"
5400 OUString sActionAndTarget;
5401 if (eCheckRadioFalse == TRISTATE_INDET)
5402 sActionAndTarget = "menu.normal." + rId + "::" + rId;
5403 else
5404 sActionAndTarget = "menu.radio." + rId + "::" + rId;
5405 g_menu_insert(pMenu, aSectionAndPos.second, MapToGtkAccelerator(rStr).getStr(), sActionAndTarget.toUtf8().getStr());
5407 assert(eCheckRadioFalse == TRISTATE_INDET); // come back to this later
5409 // TODO not redo entire group
5410 update_action_group_from_popover_model();
5412 #endif
5415 void insert_separator(int pos, const OUString& rId)
5417 #if !GTK_CHECK_VERSION(4, 0, 0)
5418 GtkWidget* pItem = gtk_separator_menu_item_new();
5419 ::set_buildable_id(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8));
5420 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
5421 gtk_widget_show(pItem);
5422 add_to_map(GTK_MENU_ITEM(pItem));
5423 if (pos != -1)
5424 gtk_menu_reorder_child(m_pMenu, pItem, pos);
5425 #else
5426 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5428 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
5430 for (int nSection = 0, nSectionCount = g_menu_model_get_n_items(pMenuModel); nSection < nSectionCount; ++nSection)
5432 GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
5433 assert(pSectionModel);
5434 if (aSectionAndPos.first == pSectionModel)
5436 GMenu* pNewSection = g_menu_new();
5437 GMenuItem* pSectionItem = g_menu_item_new_section(nullptr, G_MENU_MODEL(pNewSection));
5438 OUString sActionAndTarget = "menu.separator." + rId + "::" + rId;
5439 g_menu_item_set_detailed_action(pSectionItem, sActionAndTarget.toUtf8().getStr());
5440 g_menu_insert_item(G_MENU(pMenuModel), nSection + 1, pSectionItem);
5441 int nOldSectionCount = g_menu_model_get_n_items(pSectionModel);
5442 for (int i = nOldSectionCount - 1; i >= aSectionAndPos.second; --i)
5444 GMenuItem* pMenuItem = g_menu_item_new_from_model(pSectionModel, i);
5445 g_menu_prepend_item(pNewSection, pMenuItem);
5446 g_menu_remove(G_MENU(pSectionModel), i);
5447 g_object_unref(pMenuItem);
5449 g_object_unref(pSectionItem);
5450 g_object_unref(pNewSection);
5455 #endif
5458 void remove_item(const OString& rIdent)
5460 #if !GTK_CHECK_VERSION(4, 0, 0)
5461 GtkMenuItem* pMenuItem = m_aMap[rIdent];
5462 remove_from_map(pMenuItem);
5463 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
5464 #else
5465 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5467 std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
5468 if (!aRes.first)
5469 return;
5470 g_menu_remove(G_MENU(aRes.first), aRes.second);
5472 #endif
5475 void set_item_sensitive(const OString& rIdent, bool bSensitive)
5477 #if GTK_CHECK_VERSION(4, 0, 0)
5478 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5479 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction[rIdent].getStr());
5480 g_simple_action_set_enabled(G_SIMPLE_ACTION(pAction), bSensitive);
5481 #else
5482 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
5483 #endif
5486 bool get_item_sensitive(const OString& rIdent) const
5488 #if GTK_CHECK_VERSION(4, 0, 0)
5489 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5490 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction.find(rIdent)->second.getStr());
5491 return g_action_get_enabled(pAction);
5492 #else
5493 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
5494 #endif
5497 void set_item_active(const OString& rIdent, bool bActive)
5499 #if !GTK_CHECK_VERSION(4, 0, 0)
5500 disable_item_notify_events();
5501 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
5502 enable_item_notify_events();
5503 #else
5504 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5505 g_action_group_change_action_state(pActionGroup, m_aIdToAction[rIdent].getStr(),
5506 g_variant_new_string(bActive ? rIdent.getStr() : "'none'"));
5507 #endif
5510 bool get_item_active(const OString& rIdent) const
5512 #if !GTK_CHECK_VERSION(4, 0, 0)
5513 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
5514 #else
5515 GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
5516 GVariant* pState = g_action_group_get_action_state(pActionGroup, m_aIdToAction.find(rIdent)->second.getStr());
5517 if (!pState)
5518 return false;
5519 const char *pStateString = g_variant_get_string(pState, nullptr);
5520 bool bInactive = g_strcmp0(pStateString, "'none'") == 0;
5521 g_variant_unref(pState);
5522 return bInactive;
5523 #endif
5526 void set_item_label(const OString& rIdent, const OUString& rText)
5528 #if !GTK_CHECK_VERSION(4, 0, 0)
5529 gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
5530 #else
5531 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5533 std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
5534 if (!aRes.first)
5535 return;
5536 // clone the original item, remove the original, insert the replacement at
5537 // the original location
5538 GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
5539 g_menu_remove(G_MENU(aRes.first), aRes.second);
5540 g_menu_item_set_label(pMenuItem, MapToGtkAccelerator(rText).getStr());
5541 g_menu_insert_item(G_MENU(aRes.first), aRes.second, pMenuItem);
5542 g_object_unref(pMenuItem);
5544 #endif
5547 OUString get_item_label(const OString& rIdent) const
5549 #if !GTK_CHECK_VERSION(4, 0, 0)
5550 const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
5551 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
5552 #else
5553 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5555 std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
5556 if (!aRes.first)
5557 return OUString();
5559 // clone the original item to query its label
5560 GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
5561 char *pLabel = nullptr;
5562 g_menu_item_get_attribute(pMenuItem, G_MENU_ATTRIBUTE_LABEL, "&s", &pLabel);
5563 OUString aRet(pLabel, pLabel ? strlen(pLabel) : 0, RTL_TEXTENCODING_UTF8);
5564 g_free(pLabel);
5565 g_object_unref(pMenuItem);
5566 return aRet;
5568 return OUString();
5569 #endif
5572 void set_item_visible(const OString& rIdent, bool bShow)
5574 #if !GTK_CHECK_VERSION(4, 0, 0)
5575 GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
5576 if (bShow)
5577 gtk_widget_show(pWidget);
5578 else
5579 gtk_widget_hide(pWidget);
5580 #else
5581 bool bOldVisible = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end();
5582 if (bShow == bOldVisible)
5583 return;
5585 if (!bShow)
5587 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
5588 g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
5589 g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
5590 m_aHiddenIds.insert(rIdent);
5592 else
5594 GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
5595 g_action_map_add_action(G_ACTION_MAP(m_pActionGroup), pAction);
5596 g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
5597 m_aHiddenIds.erase(rIdent);
5599 #endif
5602 OString get_item_id(int pos) const
5604 #if !GTK_CHECK_VERSION(4, 0, 0)
5605 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
5606 gpointer pMenuItem = g_list_nth_data(pChildren, pos);
5607 OString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
5608 g_list_free(pChildren);
5609 return id;
5610 #else
5611 OString sTarget;
5612 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5614 auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
5615 char *id;
5616 if (g_menu_model_get_item_attribute(aSectionAndPos.first, aSectionAndPos.second, "target", "s", &id))
5618 sTarget = OString(id);
5619 g_free(id);
5622 return sTarget;
5623 #endif
5626 int get_n_children() const
5628 #if !GTK_CHECK_VERSION(4, 0, 0)
5629 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
5630 int nLen = g_list_length(pChildren);
5631 g_list_free(pChildren);
5632 return nLen;
5633 #else
5634 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5635 return count_immediate_children(pMenuModel);
5636 return 0;
5637 #endif
5640 void clear_items()
5642 #if !GTK_CHECK_VERSION(4, 0, 0)
5643 for (const auto& a : m_aMap)
5645 GtkMenuItem* pMenuItem = a.second;
5646 g_signal_handlers_disconnect_by_data(pMenuItem, this);
5647 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
5649 m_aMap.clear();
5650 #else
5651 if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
5653 GMenu* pMenu = G_MENU(pMenuModel);
5654 g_menu_remove_all(pMenu);
5655 g_menu_insert_section(pMenu, 0, nullptr, G_MENU_MODEL(g_menu_new()));
5656 m_aHiddenIds.clear();
5657 update_action_group_from_popover_model();
5659 #endif
5662 #if !GTK_CHECK_VERSION(4, 0, 0)
5663 GtkMenu* getMenu() const
5664 #else
5665 GtkPopoverMenu* getMenu() const
5666 #endif
5668 return m_pMenu;
5671 virtual ~MenuHelper()
5673 #if !GTK_CHECK_VERSION(4, 0, 0)
5674 for (auto& a : m_aMap)
5675 g_signal_handlers_disconnect_by_data(a.second, this);
5676 if (m_bTakeOwnership)
5677 gtk_widget_destroy(GTK_WIDGET(m_pMenu));
5678 #else
5679 g_object_unref(m_pActionGroup);
5680 g_object_unref(m_pHiddenActionGroup);
5681 #endif
5685 class GtkInstanceSizeGroup : public weld::SizeGroup
5687 private:
5688 GtkSizeGroup* m_pGroup;
5689 public:
5690 GtkInstanceSizeGroup()
5691 : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
5694 virtual void add_widget(weld::Widget* pWidget) override
5696 GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
5697 assert(pVclWidget);
5698 gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
5700 virtual void set_mode(VclSizeGroupMode eVclMode) override
5702 GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
5703 switch (eVclMode)
5705 case VclSizeGroupMode::NONE:
5706 eGtkMode = GTK_SIZE_GROUP_NONE;
5707 break;
5708 case VclSizeGroupMode::Horizontal:
5709 eGtkMode = GTK_SIZE_GROUP_HORIZONTAL;
5710 break;
5711 case VclSizeGroupMode::Vertical:
5712 eGtkMode = GTK_SIZE_GROUP_VERTICAL;
5713 break;
5714 case VclSizeGroupMode::Both:
5715 eGtkMode = GTK_SIZE_GROUP_BOTH;
5716 break;
5718 gtk_size_group_set_mode(m_pGroup, eGtkMode);
5720 virtual ~GtkInstanceSizeGroup() override
5722 g_object_unref(m_pGroup);
5726 class ChildFrame : public WorkWindow
5728 private:
5729 Idle maLayoutIdle;
5731 DECL_LINK(ImplHandleLayoutTimerHdl, Timer*, void);
5732 public:
5733 ChildFrame(vcl::Window* pParent, WinBits nStyle)
5734 : WorkWindow(pParent, nStyle)
5735 , maLayoutIdle( "ChildFrame maLayoutIdle" )
5737 maLayoutIdle.SetPriority(TaskPriority::RESIZE);
5738 maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
5741 virtual void dispose() override
5743 maLayoutIdle.Stop();
5744 WorkWindow::dispose();
5747 virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override
5749 WorkWindow::queue_resize(eReason);
5750 if (maLayoutIdle.IsActive())
5751 return;
5752 maLayoutIdle.Start();
5755 void Layout()
5757 if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
5758 pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
5761 virtual void Resize() override
5763 maLayoutIdle.Stop();
5764 Layout();
5765 WorkWindow::Resize();
5769 IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
5771 Layout();
5774 class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
5776 private:
5777 #if !GTK_CHECK_VERSION(4, 0, 0)
5778 GtkContainer* m_pContainer;
5779 #else
5780 GtkWidget* m_pContainer;
5781 #endif
5782 gulong m_nSetFocusChildSignalId;
5783 bool m_bChildHasFocus;
5785 void signal_set_focus_child(bool bChildHasFocus)
5787 if (m_bChildHasFocus != bChildHasFocus)
5789 m_bChildHasFocus = bChildHasFocus;
5790 signal_container_focus_changed();
5794 #if !GTK_CHECK_VERSION(4, 0, 0)
5795 static void signalSetFocusChild(GtkContainer*, GtkWidget* pChild, gpointer widget)
5797 GtkInstanceContainer* pThis = static_cast<GtkInstanceContainer*>(widget);
5798 pThis->signal_set_focus_child(pChild != nullptr);
5800 #endif
5802 public:
5803 #if !GTK_CHECK_VERSION(4, 0, 0)
5804 GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5805 : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
5806 #else
5807 GtkInstanceContainer(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5808 : GtkInstanceWidget(pContainer, pBuilder, bTakeOwnership)
5809 #endif
5810 , m_pContainer(pContainer)
5811 , m_nSetFocusChildSignalId(0)
5812 , m_bChildHasFocus(false)
5816 virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
5818 #if !GTK_CHECK_VERSION(4, 0, 0)
5819 if (!m_nSetFocusChildSignalId)
5820 m_nSetFocusChildSignalId = g_signal_connect(G_OBJECT(m_pContainer), "set-focus-child", G_CALLBACK(signalSetFocusChild), this);
5821 #endif
5822 weld::Container::connect_container_focus_changed(rLink);
5825 #if GTK_CHECK_VERSION(4, 0, 0)
5826 GtkWidget* getContainer() { return m_pContainer; }
5827 #else
5828 GtkContainer* getContainer() { return m_pContainer; }
5829 #endif
5831 virtual void child_grab_focus() override
5833 gtk_widget_grab_focus(m_pWidget);
5834 #if GTK_CHECK_VERSION(4, 0, 0)
5835 bool bHasFocusChild = gtk_widget_get_focus_child(GTK_WIDGET(m_pContainer));
5836 #else
5837 bool bHasFocusChild = gtk_container_get_focus_child(m_pContainer);
5838 #endif
5839 if (!bHasFocusChild)
5841 #if GTK_CHECK_VERSION(4, 0, 0)
5842 if (GtkWidget* pChild = gtk_widget_get_first_child(m_pContainer))
5844 gtk_widget_set_focus_child(m_pContainer, pChild);
5845 bHasFocusChild = true;
5847 #else
5848 GList* pChildren = gtk_container_get_children(m_pContainer);
5849 if (GList* pChild = g_list_first(pChildren))
5851 gtk_container_set_focus_child(m_pContainer, static_cast<GtkWidget*>(pChild->data));
5852 bHasFocusChild = true;
5854 g_list_free(pChildren);
5855 #endif
5858 if (bHasFocusChild)
5860 #if GTK_CHECK_VERSION(4, 0, 0)
5861 gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
5862 #else
5863 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
5864 #endif
5869 virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
5871 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
5872 assert(pGtkWidget);
5873 GtkWidget* pChild = pGtkWidget->getWidget();
5874 g_object_ref(pChild);
5875 auto pOldContainer = getContainer();
5876 container_remove(GTK_WIDGET(pOldContainer), pChild);
5878 GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
5879 assert(!pNewParent || pNewGtkParent);
5880 if (pNewGtkParent)
5882 auto pNewContainer = pNewGtkParent->getContainer();
5883 container_add(GTK_WIDGET(pNewContainer), pChild);
5885 g_object_unref(pChild);
5888 virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
5890 // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
5891 // will create a toplevel GtkEventBox window
5892 auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
5893 SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
5894 GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
5895 assert(pGtkFrame);
5897 // relocate that toplevel GtkEventBox into this widget
5898 GtkWidget* pWindow = pGtkFrame->getWindow();
5900 GtkWidget* pParent = gtk_widget_get_parent(pWindow);
5902 g_object_ref(pWindow);
5903 container_remove(pParent, pWindow);
5904 container_add(GTK_WIDGET(m_pContainer), pWindow);
5905 #if !GTK_CHECK_VERSION(4, 0, 0)
5906 gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
5907 #endif
5908 gtk_widget_set_hexpand(pWindow, true);
5909 gtk_widget_set_vexpand(pWindow, true);
5910 gtk_widget_realize(pWindow);
5911 gtk_widget_set_can_focus(pWindow, true);
5912 g_object_unref(pWindow);
5914 // NoActivate otherwise Show grab focus to this widget
5915 xEmbedWindow->Show(true, ShowFlags::NoActivate);
5916 css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
5917 return xWindow;
5920 virtual ~GtkInstanceContainer() override
5922 if (m_nSetFocusChildSignalId)
5923 g_signal_handler_disconnect(m_pContainer, m_nSetFocusChildSignalId);
5929 std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
5931 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
5932 if (!pParent)
5933 return nullptr;
5934 #if !GTK_CHECK_VERSION(4, 0, 0)
5935 return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
5936 #else
5937 return std::make_unique<GtkInstanceContainer>(pParent, m_pBuilder, false);
5938 #endif
5941 namespace {
5943 bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
5945 //order within groups according to platform rules
5946 return getButtonPriority(get_buildable_id(GTK_BUILDABLE(pA))) <
5947 getButtonPriority(get_buildable_id(GTK_BUILDABLE(pB)));
5950 void sort_native_button_order(GtkBox* pContainer)
5952 std::vector<GtkWidget*> aChildren;
5953 #if GTK_CHECK_VERSION(4, 0, 0)
5954 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pContainer));
5955 pChild; pChild = gtk_widget_get_next_sibling(pChild))
5957 aChildren.push_back(pChild);
5959 #else
5960 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
5961 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
5962 aChildren.push_back(static_cast<GtkWidget*>(pChild->data));
5963 g_list_free(pChildren);
5964 #endif
5966 //sort child order within parent so that we match the platform button order
5967 std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
5969 #if GTK_CHECK_VERSION(4, 0, 0)
5970 for (size_t pos = 0; pos < aChildren.size(); ++pos)
5971 gtk_box_reorder_child_after(pContainer, aChildren[pos], pos ? aChildren[pos - 1] : nullptr);
5972 #else
5973 for (size_t pos = 0; pos < aChildren.size(); ++pos)
5974 gtk_box_reorder_child(pContainer, aChildren[pos], pos);
5975 #endif
5978 class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
5980 private:
5981 GtkBox* m_pBox;
5983 public:
5984 GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5985 #if !GTK_CHECK_VERSION(4, 0, 0)
5986 : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
5987 #else
5988 : GtkInstanceContainer(GTK_WIDGET(pBox), pBuilder, bTakeOwnership)
5989 #endif
5990 , m_pBox(pBox)
5994 virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
5996 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
5997 assert(pGtkWidget);
5998 GtkWidget* pChild = pGtkWidget->getWidget();
6000 #if !GTK_CHECK_VERSION(4, 0, 0)
6001 gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
6002 #else
6003 if (nNewPosition == 0)
6004 gtk_box_reorder_child_after(m_pBox, pChild, nullptr);
6005 else
6007 int nNewSiblingPos = nNewPosition - 1;
6008 int nChildPosition = 0;
6009 for (GtkWidget* pNewSibling = gtk_widget_get_first_child(GTK_WIDGET(m_pBox));
6010 pNewSibling; pNewSibling = gtk_widget_get_next_sibling(pNewSibling))
6012 if (nChildPosition == nNewSiblingPos)
6014 gtk_box_reorder_child_after(m_pBox, pChild, pNewSibling);
6015 break;
6017 ++nChildPosition;
6020 #endif
6023 virtual void sort_native_button_order() override
6025 ::sort_native_button_order(m_pBox);
6031 namespace
6033 Point get_csd_offset(GtkWidget* pTopLevel)
6035 // try and omit drawing CSD under wayland
6036 GtkWidget* pChild = widget_get_first_child(pTopLevel);
6038 gtk_coord x, y;
6039 gtk_widget_translate_coordinates(pChild, pTopLevel, 0, 0, &x, &y);
6041 #if !GTK_CHECK_VERSION(4, 0, 0)
6042 int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild));
6043 int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
6044 int totalborder = outerborder + innerborder;
6045 x -= totalborder;
6046 y -= totalborder;
6047 #endif
6049 return Point(x, y);
6052 void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
6054 GtkWidget* pTopLevel = widget_get_toplevel(pItem);
6056 gtk_coord x, y;
6057 gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
6059 Point aOffset = get_csd_offset(pTopLevel);
6061 GtkAllocation alloc;
6062 gtk_widget_get_allocation(pItem, &alloc);
6064 const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
6065 const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
6067 if (!aCurrentRange.isEmpty())
6069 weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
6070 pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
6073 #if GTK_CHECK_VERSION(4, 0, 0)
6074 for (GtkWidget* pChild = gtk_widget_get_first_child(pItem);
6075 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6077 do_collect_screenshot_data(pChild, data);
6079 #else
6080 if (GTK_IS_CONTAINER(pItem))
6081 gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
6082 #endif
6085 tools::Rectangle get_monitor_workarea(GtkWidget* pWindow)
6087 GdkRectangle aRect;
6088 #if !GTK_CHECK_VERSION(4, 0, 0)
6089 GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
6090 gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, widget_get_surface(pWindow));
6091 gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
6092 #else
6093 GdkDisplay* pDisplay = gtk_widget_get_display(pWindow);
6094 GdkSurface* gdkWindow = widget_get_surface(pWindow);
6095 GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
6096 gdk_monitor_get_geometry(pMonitor, &aRect);
6097 #endif
6098 return tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height);
6102 class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
6104 private:
6105 GtkWindow* m_pWindow;
6106 rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
6107 std::optional<Point> m_aPosWhileInvis; //tdf#146648 store last known position when visible to return as pos if hidden
6108 gulong m_nToplevelFocusChangedSignalId;
6110 #if !GTK_CHECK_VERSION(4, 0, 0)
6111 static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
6113 if (GTK_IS_BUTTON(pWidget))
6114 g_object_set(G_OBJECT(pWidget), "has-default", false, nullptr);
6115 if (GTK_IS_CONTAINER(pWidget))
6116 gtk_container_forall(GTK_CONTAINER(pWidget), implResetDefault, user_data);
6119 void recursively_unset_default_buttons()
6121 implResetDefault(GTK_WIDGET(m_pWindow), nullptr);
6123 #endif
6125 #if !GTK_CHECK_VERSION(4, 0, 0)
6126 static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
6128 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
6129 pThis->help();
6130 return true;
6132 #endif
6134 static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
6136 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
6137 pThis->signal_container_focus_changed();
6140 bool isPositioningAllowed() const
6142 // no X/Y positioning under Wayland
6143 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
6144 return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
6147 protected:
6148 void help();
6149 public:
6150 GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6151 #if !GTK_CHECK_VERSION(4, 0, 0)
6152 : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
6153 #else
6154 : GtkInstanceContainer(GTK_WIDGET(pWindow), pBuilder, bTakeOwnership)
6155 #endif
6156 , m_pWindow(pWindow)
6157 , m_nToplevelFocusChangedSignalId(0)
6159 #if !GTK_CHECK_VERSION(4, 0, 0)
6160 const bool bIsFrameWeld = pBuilder == nullptr;
6161 if (!bIsFrameWeld)
6163 //hook up F1 to show help
6164 GtkAccelGroup *pGroup = gtk_accel_group_new();
6165 GClosure* closure = g_cclosure_new(G_CALLBACK(help_pressed), this, nullptr);
6166 gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
6167 gtk_window_add_accel_group(pWindow, pGroup);
6169 #endif
6172 virtual void set_title(const OUString& rTitle) override
6174 ::set_title(m_pWindow, rTitle);
6177 virtual OUString get_title() const override
6179 return ::get_title(m_pWindow);
6182 virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
6184 if (!m_xWindow.is())
6185 m_xWindow.set(new SalGtkXWindow(this, m_pWidget));
6186 return m_xWindow;
6189 virtual void set_modal(bool bModal) override
6191 gtk_window_set_modal(m_pWindow, bModal);
6194 virtual bool get_modal() const override
6196 return gtk_window_get_modal(m_pWindow);
6199 virtual void resize_to_request() override
6201 #if GTK_CHECK_VERSION(4, 0, 0)
6202 gtk_window_set_default_size(m_pWindow, 1, 1);
6203 #else
6204 gtk_window_resize(m_pWindow, 1, 1);
6205 #endif
6208 virtual void window_move(int x, int y) override
6210 #if !GTK_CHECK_VERSION(4, 0, 0)
6211 gtk_window_move(m_pWindow, x, y);
6212 #else
6213 (void)x;
6214 (void)y;
6215 #endif
6218 virtual SystemEnvData get_system_data() const override
6220 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(GTK_WIDGET(m_pWindow));
6221 assert(pFrame && "nothing should call this impl, yet anyway, if ever, except on result of GetFrameWeld()");
6222 const SystemEnvData* pEnvData = pFrame->GetSystemData();
6223 assert(pEnvData);
6224 return *pEnvData;
6227 virtual Size get_size() const override
6229 int current_width, current_height;
6230 #if !GTK_CHECK_VERSION(4, 0, 0)
6231 gtk_window_get_size(m_pWindow, &current_width, &current_height);
6232 #else
6233 gtk_window_get_default_size(m_pWindow, &current_width, &current_height);
6234 #endif
6235 return Size(current_width, current_height);
6238 virtual Point get_position() const override
6240 if (m_aPosWhileInvis)
6242 assert(!get_visible());
6243 return *m_aPosWhileInvis;
6246 int current_x(0), current_y(0);
6247 #if !GTK_CHECK_VERSION(4, 0, 0)
6248 gtk_window_get_position(m_pWindow, &current_x, &current_y);
6249 #endif
6250 return Point(current_x, current_y);
6253 virtual void show() override
6255 m_aPosWhileInvis.reset();
6256 GtkInstanceContainer::show();
6259 virtual void hide() override
6261 if (is_visible())
6262 m_aPosWhileInvis = get_position();
6263 GtkInstanceContainer::hide();
6266 virtual tools::Rectangle get_monitor_workarea() const override
6268 return ::get_monitor_workarea(GTK_WIDGET(m_pWindow));
6271 virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
6273 #if !GTK_CHECK_VERSION(4, 0, 0)
6274 if (bTrackGeometryRequests)
6275 gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ALWAYS);
6276 else
6277 gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ON_PARENT);
6278 #else
6279 (void)bTrackGeometryRequests;
6280 #endif
6283 virtual bool get_resizable() const override
6285 return gtk_window_get_resizable(m_pWindow);
6288 virtual bool has_toplevel_focus() const override
6290 #if GTK_CHECK_VERSION(4, 0, 0)
6291 return gtk_window_is_active(m_pWindow);
6292 #else
6293 return gtk_window_has_toplevel_focus(m_pWindow);
6294 #endif
6297 virtual void present() override
6299 gtk_window_present(m_pWindow);
6302 virtual void change_default_widget(weld::Widget* pOld, weld::Widget* pNew) override
6304 GtkInstanceWidget* pGtkNew = dynamic_cast<GtkInstanceWidget*>(pNew);
6305 GtkWidget* pWidgetNew = pGtkNew ? pGtkNew->getWidget() : nullptr;
6306 #if GTK_CHECK_VERSION(4, 0, 0)
6307 gtk_window_set_default_widget(m_pWindow, pWidgetNew);
6308 (void)pOld;
6309 #else
6310 GtkInstanceWidget* pGtkOld = dynamic_cast<GtkInstanceWidget*>(pOld);
6311 GtkWidget* pWidgetOld = pGtkOld ? pGtkOld->getWidget() : nullptr;
6312 if (pWidgetOld)
6313 g_object_set(G_OBJECT(pWidgetOld), "has-default", false, nullptr);
6314 else
6315 recursively_unset_default_buttons();
6316 if (pWidgetNew)
6317 g_object_set(G_OBJECT(pWidgetNew), "has-default", true, nullptr);
6318 #endif
6321 virtual bool is_default_widget(const weld::Widget* pCandidate) const override
6323 const GtkInstanceWidget* pGtkCandidate = dynamic_cast<const GtkInstanceWidget*>(pCandidate);
6324 GtkWidget* pWidget = pGtkCandidate ? pGtkCandidate->getWidget() : nullptr;
6325 #if GTK_CHECK_VERSION(4, 0, 0)
6326 return pWidget && gtk_window_get_default_widget(m_pWindow) == pWidget;
6327 #else
6328 gboolean has_default(false);
6329 if (pWidget)
6330 g_object_get(G_OBJECT(pWidget), "has-default", &has_default, nullptr);
6331 return has_default;
6332 #endif
6335 virtual void set_window_state(const OString& rStr) override
6337 WindowStateData aData;
6338 ImplWindowStateFromStr( aData, rStr );
6340 auto nMask = aData.GetMask();
6341 auto nState = aData.GetState() & WindowStateState::SystemMask;
6343 if (nMask & WindowStateMask::Width && nMask & WindowStateMask::Height)
6345 gtk_window_set_default_size(m_pWindow, aData.GetWidth(), aData.GetHeight());
6347 if (nMask & WindowStateMask::State)
6349 if (nState & WindowStateState::Maximized)
6350 gtk_window_maximize(m_pWindow);
6351 else
6352 gtk_window_unmaximize(m_pWindow);
6355 #if !GTK_CHECK_VERSION(4, 0, 0)
6356 if (isPositioningAllowed() && (nMask & WindowStateMask::X && nMask & WindowStateMask::Y))
6358 gtk_window_move(m_pWindow, aData.GetX(), aData.GetY());
6360 #endif
6363 virtual OString get_window_state(WindowStateMask nMask) const override
6365 bool bPositioningAllowed = isPositioningAllowed();
6367 WindowStateData aData;
6368 WindowStateMask nAvailable = WindowStateMask::State |
6369 WindowStateMask::Width | WindowStateMask::Height;
6370 if (bPositioningAllowed)
6371 nAvailable |= WindowStateMask::X | WindowStateMask::Y;
6372 aData.SetMask(nMask & nAvailable);
6374 if (nMask & WindowStateMask::State)
6376 WindowStateState nState = WindowStateState::Normal;
6377 if (gtk_window_is_maximized(m_pWindow))
6378 nState |= WindowStateState::Maximized;
6379 aData.SetState(nState);
6382 if (bPositioningAllowed && (nMask & (WindowStateMask::X | WindowStateMask::Y)))
6384 auto aPos = get_position();
6385 aData.SetX(aPos.X());
6386 aData.SetY(aPos.Y());
6389 if (nMask & (WindowStateMask::Width | WindowStateMask::Height))
6391 auto aSize = get_size();
6392 aData.SetWidth(aSize.Width());
6393 aData.SetHeight(aSize.Height());
6396 return aData.ToStr();
6399 virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
6401 if (!m_nToplevelFocusChangedSignalId)
6402 m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
6403 GtkInstanceContainer::connect_container_focus_changed(rLink);
6406 virtual void disable_notify_events() override
6408 if (m_nToplevelFocusChangedSignalId)
6409 g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
6410 GtkInstanceContainer::disable_notify_events();
6413 virtual void enable_notify_events() override
6415 GtkInstanceContainer::enable_notify_events();
6416 if (m_nToplevelFocusChangedSignalId)
6417 g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
6420 virtual VclPtr<VirtualDevice> screenshot() override
6422 // detect if we have to manually setup its size
6423 bool bAlreadyRealized = gtk_widget_get_realized(GTK_WIDGET(m_pWindow));
6424 // has to be visible for draw to work
6425 bool bAlreadyVisible = gtk_widget_get_visible(GTK_WIDGET(m_pWindow));
6426 #if !GTK_CHECK_VERSION(4, 0, 0)
6427 if (!bAlreadyVisible)
6429 if (GTK_IS_DIALOG(m_pWindow))
6430 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow))));
6431 gtk_widget_show(GTK_WIDGET(m_pWindow));
6433 #endif
6435 if (!bAlreadyRealized)
6437 GtkAllocation allocation;
6438 gtk_widget_realize(GTK_WIDGET(m_pWindow));
6439 gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
6440 #if !GTK_CHECK_VERSION(4, 0, 0)
6441 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
6442 #else
6443 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation, 0);
6444 #endif
6447 VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
6448 xOutput->SetOutputSizePixel(get_size());
6449 cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
6450 cairo_t* cr = cairo_create(pSurface);
6452 Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
6454 cairo_translate(cr, -aOffset.X(), -aOffset.Y());
6456 #if !GTK_CHECK_VERSION(4, 0, 0)
6457 gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
6458 #else
6459 GtkSnapshot* pSnapshot = gtk_snapshot_new();
6460 GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(GTK_WIDGET(m_pWindow));
6461 pWidgetClass->snapshot(GTK_WIDGET(m_pWindow), pSnapshot);
6462 GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
6463 gsk_render_node_draw(pNode, cr);
6464 gsk_render_node_unref(pNode);
6465 #endif
6467 cairo_destroy(cr);
6469 if (!bAlreadyVisible)
6470 gtk_widget_hide(GTK_WIDGET(m_pWindow));
6471 if (!bAlreadyRealized)
6472 gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
6474 return xOutput;
6477 virtual weld::ScreenShotCollection collect_screenshot_data() override
6479 weld::ScreenShotCollection aRet;
6481 #if GTK_CHECK_VERSION(4, 0, 0)
6482 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pWindow));
6483 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6485 do_collect_screenshot_data(pChild, &aRet);
6487 #else
6488 gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
6489 #endif
6491 return aRet;
6494 virtual ~GtkInstanceWindow() override
6496 if (m_nToplevelFocusChangedSignalId)
6497 g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
6498 if (m_xWindow.is())
6499 m_xWindow->clear();
6503 class GtkInstanceDialog;
6505 struct DialogRunner
6507 GtkWindow* m_pDialog;
6508 GtkInstanceDialog *m_pInstance;
6509 gint m_nResponseId;
6510 GMainLoop *m_pLoop;
6511 VclPtr<vcl::Window> m_xFrameWindow;
6512 int m_nModalDepth;
6514 DialogRunner(GtkWindow* pDialog, GtkInstanceDialog* pInstance)
6515 : m_pDialog(pDialog)
6516 , m_pInstance(pInstance)
6517 , m_nResponseId(GTK_RESPONSE_NONE)
6518 , m_pLoop(nullptr)
6519 , m_nModalDepth(0)
6521 GtkWindow* pParent = gtk_window_get_transient_for(m_pDialog);
6522 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
6523 m_xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
6526 bool loop_is_running() const
6528 return m_pLoop && g_main_loop_is_running(m_pLoop);
6531 void loop_quit()
6533 if (g_main_loop_is_running(m_pLoop))
6534 g_main_loop_quit(m_pLoop);
6537 static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
6538 static void signal_cancel(GtkAssistant*, gpointer data);
6540 #if !GTK_CHECK_VERSION(4, 0, 0)
6541 static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
6543 DialogRunner* pThis = static_cast<DialogRunner*>(data);
6544 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
6546 // An assistant isn't a dialog, but we want to treat it like one
6547 signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
6549 else
6550 pThis->loop_quit();
6551 return true; /* Do not destroy */
6553 #endif
6555 static void signal_destroy(GtkDialog*, gpointer data)
6557 DialogRunner* pThis = static_cast<DialogRunner*>(data);
6558 pThis->loop_quit();
6561 void inc_modal_count()
6563 if (m_xFrameWindow)
6565 m_xFrameWindow->IncModalCount();
6566 if (m_nModalDepth == 0)
6567 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
6568 ++m_nModalDepth;
6572 void dec_modal_count()
6574 if (m_xFrameWindow)
6576 m_xFrameWindow->DecModalCount();
6577 --m_nModalDepth;
6578 if (m_nModalDepth == 0)
6579 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
6583 // same as gtk_dialog_run except that unmap doesn't auto-respond
6584 // so we can hide the dialog and restore it without a response getting
6585 // triggered
6586 gint run()
6588 g_object_ref(m_pDialog);
6590 inc_modal_count();
6592 bool bWasModal = gtk_window_get_modal(m_pDialog);
6593 if (!bWasModal)
6594 gtk_window_set_modal(m_pDialog, true);
6596 if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
6597 gtk_widget_show(GTK_WIDGET(m_pDialog));
6599 gulong nSignalResponseId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signal_response), this) : 0;
6600 gulong nSignalCancelId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signal_cancel), this) : 0;
6601 #if !GTK_CHECK_VERSION(4, 0, 0)
6602 gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
6603 #endif
6604 gulong nSignalDestroyId = g_signal_connect(m_pDialog, "destroy", G_CALLBACK(signal_destroy), this);
6606 m_pLoop = g_main_loop_new(nullptr, false);
6607 m_nResponseId = GTK_RESPONSE_NONE;
6609 main_loop_run(m_pLoop);
6611 g_main_loop_unref(m_pLoop);
6613 m_pLoop = nullptr;
6615 if (!bWasModal)
6616 gtk_window_set_modal(m_pDialog, false);
6618 if (nSignalResponseId)
6619 g_signal_handler_disconnect(m_pDialog, nSignalResponseId);
6620 if (nSignalCancelId)
6621 g_signal_handler_disconnect(m_pDialog, nSignalCancelId);
6622 #if !GTK_CHECK_VERSION(4, 0, 0)
6623 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
6624 #endif
6625 g_signal_handler_disconnect(m_pDialog, nSignalDestroyId);
6627 dec_modal_count();
6629 g_object_unref(m_pDialog);
6631 return m_nResponseId;
6634 ~DialogRunner()
6636 if (m_xFrameWindow && m_nModalDepth)
6638 // if, like the calc validation dialog does, the modality was
6639 // toggled off during execution ensure that on cleanup the parent
6640 // is left in the state it was found
6641 while (m_nModalDepth++ < 0)
6642 m_xFrameWindow->IncModalCount();
6649 typedef std::set<GtkWidget*> winset;
6651 namespace
6653 #if GTK_CHECK_VERSION(4, 0, 0)
6654 void collectVisibleChildren(GtkWidget* pTop, winset& rVisibleWidgets)
6656 for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
6657 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6659 if (!gtk_widget_get_visible(pChild))
6660 continue;
6661 rVisibleWidgets.insert(pChild);
6662 collectVisibleChildren(pChild, rVisibleWidgets);
6665 #endif
6667 void hideUnless(GtkWidget* pTop, const winset& rVisibleWidgets,
6668 std::vector<GtkWidget*> &rWasVisibleWidgets)
6670 #if GTK_CHECK_VERSION(4, 0, 0)
6671 for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
6672 pChild; pChild = gtk_widget_get_next_sibling(pChild))
6674 if (!gtk_widget_get_visible(pChild))
6675 continue;
6676 if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
6678 g_object_ref(pChild);
6679 rWasVisibleWidgets.emplace_back(pChild);
6680 gtk_widget_hide(pChild);
6682 else
6684 hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
6687 #else
6688 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTop));
6689 for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
6691 GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
6692 if (!gtk_widget_get_visible(pChild))
6693 continue;
6694 if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
6696 g_object_ref(pChild);
6697 rWasVisibleWidgets.emplace_back(pChild);
6698 gtk_widget_hide(pChild);
6700 else if (GTK_IS_CONTAINER(pChild))
6702 hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
6705 g_list_free(pChildren);
6706 #endif
6709 class GtkInstanceButton;
6711 class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
6713 private:
6714 GtkWindow* m_pDialog;
6715 DialogRunner m_aDialogRun;
6716 std::shared_ptr<weld::DialogController> m_xDialogController;
6717 // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
6718 std::shared_ptr<weld::Dialog> m_xRunAsyncSelf;
6719 std::function<void(sal_Int32)> m_aFunc;
6720 gulong m_nCloseSignalId;
6721 gulong m_nResponseSignalId;
6722 gulong m_nCancelSignalId;
6723 gulong m_nSignalDeleteId;
6725 // for calc ref dialog that shrink to range selection widgets and resize back
6726 GtkWidget* m_pRefEdit;
6727 std::vector<GtkWidget*> m_aHiddenWidgets; // vector of hidden Controls
6728 int m_nOldEditWidth; // Original width of the input field
6729 int m_nOldEditWidthReq; // Original width request of the input field
6730 #if !GTK_CHECK_VERSION(4, 0, 0)
6731 int m_nOldBorderWidth; // border width for expanded dialog
6732 #endif
6734 void signal_close()
6736 close(true);
6739 static void signalClose(GtkWidget*, gpointer widget)
6741 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6742 pThis->signal_close();
6745 static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
6747 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6748 pThis->asyncresponse(ret);
6751 static void signalAsyncCancel(GtkAssistant*, gpointer widget)
6753 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6754 // make esc in an assistant act as if cancel button was pressed
6755 pThis->close(false);
6758 #if !GTK_CHECK_VERSION(4, 0, 0)
6759 static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
6761 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6762 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
6764 // An assistant isn't a dialog, but we want to treat it like one
6765 signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
6767 return true; /* Do not destroy */
6769 #endif
6771 static int GtkToVcl(int ret)
6773 if (ret == GTK_RESPONSE_OK)
6774 ret = RET_OK;
6775 else if (ret == GTK_RESPONSE_CANCEL)
6776 ret = RET_CANCEL;
6777 else if (ret == GTK_RESPONSE_DELETE_EVENT)
6778 ret = RET_CANCEL;
6779 else if (ret == GTK_RESPONSE_CLOSE)
6780 ret = RET_CLOSE;
6781 else if (ret == GTK_RESPONSE_YES)
6782 ret = RET_YES;
6783 else if (ret == GTK_RESPONSE_NO)
6784 ret = RET_NO;
6785 else if (ret == GTK_RESPONSE_HELP)
6786 ret = RET_HELP;
6787 return ret;
6790 static int VclToGtk(int nResponse)
6792 if (nResponse == RET_OK)
6793 return GTK_RESPONSE_OK;
6794 else if (nResponse == RET_CANCEL)
6795 return GTK_RESPONSE_CANCEL;
6796 else if (nResponse == RET_CLOSE)
6797 return GTK_RESPONSE_CLOSE;
6798 else if (nResponse == RET_YES)
6799 return GTK_RESPONSE_YES;
6800 else if (nResponse == RET_NO)
6801 return GTK_RESPONSE_NO;
6802 else if (nResponse == RET_HELP)
6803 return GTK_RESPONSE_HELP;
6804 return nResponse;
6807 void asyncresponse(gint ret);
6809 #if !GTK_CHECK_VERSION(4, 0, 0)
6810 static void signalActivate(GtkMenuItem*, gpointer data)
6812 bool* pActivate = static_cast<bool*>(data);
6813 *pActivate = true;
6815 #endif
6817 #if !GTK_CHECK_VERSION(4, 0, 0)
6818 bool signal_screenshot_popup_menu(const GdkEventButton* pEvent)
6820 GtkWidget *pMenu = gtk_menu_new();
6822 GtkWidget* pMenuItem = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT)).getStr());
6823 gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
6824 bool bActivate(false);
6825 g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), &bActivate);
6826 gtk_widget_show(pMenuItem);
6828 int button, event_time;
6829 if (pEvent)
6831 button = pEvent->button;
6832 event_time = pEvent->time;
6834 else
6836 button = 0;
6837 event_time = gtk_get_current_event_time();
6840 gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
6842 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
6843 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
6845 gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
6847 if (g_main_loop_is_running(pLoop))
6848 main_loop_run(pLoop);
6850 g_main_loop_unref(pLoop);
6851 g_signal_handler_disconnect(pMenu, nSignalId);
6852 gtk_menu_detach(GTK_MENU(pMenu));
6854 if (bActivate)
6856 // open screenshot annotation dialog
6857 VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
6858 VclPtr<AbstractScreenshotAnnotationDlg> xTmp = pFact->CreateScreenshotAnnotationDlg(*this);
6859 ScopedVclPtr<AbstractScreenshotAnnotationDlg> xDialog(xTmp);
6860 xDialog->Execute();
6863 return false;
6865 #endif
6867 static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
6869 #if !GTK_CHECK_VERSION(4, 0, 0)
6870 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6871 return pThis->signal_screenshot_popup_menu(nullptr);
6872 #else
6873 (void)widget;
6874 return false;
6875 #endif
6878 #if !GTK_CHECK_VERSION(4, 0, 0)
6879 static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
6881 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
6882 SolarMutexGuard aGuard;
6883 return pThis->signal_screenshot_button(pEvent);
6886 bool signal_screenshot_button(GdkEventButton* pEvent)
6888 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
6890 //if handled for context menu, stop processing
6891 return signal_screenshot_popup_menu(pEvent);
6893 return false;
6895 #endif
6897 public:
6898 GtkInstanceDialog(GtkWindow* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6899 : GtkInstanceWindow(pDialog, pBuilder, bTakeOwnership)
6900 , m_pDialog(pDialog)
6901 , m_aDialogRun(pDialog, this)
6902 , m_nResponseSignalId(0)
6903 , m_nCancelSignalId(0)
6904 , m_nSignalDeleteId(0)
6905 , m_pRefEdit(nullptr)
6906 , m_nOldEditWidth(0)
6907 , m_nOldEditWidthReq(0)
6908 #if !GTK_CHECK_VERSION(4, 0, 0)
6909 , m_nOldBorderWidth(0)
6910 #endif
6912 if (GTK_IS_DIALOG(m_pDialog) || GTK_IS_ASSISTANT(m_pDialog))
6913 m_nCloseSignalId = g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this);
6914 else
6915 m_nCloseSignalId = 0;
6916 const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
6917 if (bScreenshotMode)
6919 g_signal_connect(m_pDialog, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu), this);
6920 #if !GTK_CHECK_VERSION(4, 0, 0)
6921 g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
6922 #endif
6926 virtual bool runAsync(std::shared_ptr<weld::DialogController> rDialogController, const std::function<void(sal_Int32)>& func) override
6928 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
6930 m_xDialogController = rDialogController;
6931 m_aFunc = func;
6933 if (get_modal())
6934 m_aDialogRun.inc_modal_count();
6935 show();
6937 m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
6938 m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
6939 #if !GTK_CHECK_VERSION(4, 0, 0)
6940 m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
6941 #endif
6943 return true;
6946 virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
6948 assert( rxSelf.get() == this );
6949 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
6951 // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
6952 // which is that rxSelf enforces.
6953 m_xRunAsyncSelf = rxSelf;
6954 m_aFunc = func;
6956 if (get_modal())
6957 m_aDialogRun.inc_modal_count();
6958 show();
6960 m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
6961 m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
6962 #if !GTK_CHECK_VERSION(4, 0, 0)
6963 m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
6964 #endif
6966 return true;
6969 GtkInstanceButton* has_click_handler(int nResponse);
6971 virtual int run() override;
6973 virtual void show() override
6975 if (gtk_widget_get_visible(m_pWidget))
6976 return;
6977 #if !GTK_CHECK_VERSION(4, 0, 0)
6978 if (GTK_IS_DIALOG(m_pDialog))
6979 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
6980 #endif
6981 GtkInstanceWindow::show();
6984 virtual void set_modal(bool bModal) override
6986 if (get_modal() == bModal)
6987 return;
6988 GtkInstanceWindow::set_modal(bModal);
6989 /* if change the dialog modality while its running, then also change the parent LibreOffice window
6990 modal count, we typically expect the dialog modality to be restored to its original state
6992 This change modality while running case is for...
6994 a) the calc/chart dialogs which put up an extra range chooser
6995 dialog, hides the original, the user can select a range of cells and
6996 on completion the original dialog is restored
6998 b) the validity dialog in calc
7000 // tdf#135567 we know we are running in the sync case if loop_is_running is true
7001 // but for the async case we instead check for m_xDialogController which is set in
7002 // runAsync and cleared in asyncresponse
7003 if (m_aDialogRun.loop_is_running() || m_xDialogController)
7005 if (bModal)
7006 m_aDialogRun.inc_modal_count();
7007 else
7008 m_aDialogRun.dec_modal_count();
7012 virtual void response(int nResponse) override;
7014 virtual void add_button(const OUString& rText, int nResponse, const OString& rHelpId) override
7016 GtkWidget* pWidget = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), MapToGtkAccelerator(rText).getStr(), VclToGtk(nResponse));
7017 if (!rHelpId.isEmpty())
7018 ::set_help_id(pWidget, rHelpId);
7021 virtual void set_default_response(int nResponse) override
7023 gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
7026 virtual GtkButton* get_widget_for_response(int nGtkResponse)
7028 return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
7031 virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
7033 virtual Container* weld_content_area() override
7035 #if !GTK_CHECK_VERSION(4, 0, 0)
7036 return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
7037 #else
7038 return new GtkInstanceContainer(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog)), m_pBuilder, false);
7039 #endif
7042 virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
7044 GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
7045 assert(pVclEdit);
7046 GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
7048 GtkWidget* pRefEdit = pVclEdit->getWidget();
7049 GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
7051 m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
7053 gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
7055 //We want just pRefBtn and pRefEdit to be shown
7056 //mark widgets we want to be visible, starting with pRefEdit
7057 //and all its direct parents.
7058 winset aVisibleWidgets;
7059 GtkWidget *pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog));
7060 for (GtkWidget *pCandidate = pRefEdit;
7061 pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
7062 pCandidate = gtk_widget_get_parent(pCandidate))
7064 aVisibleWidgets.insert(pCandidate);
7066 #if GTK_CHECK_VERSION(4, 0, 0)
7067 collectVisibleChildren(pRefEdit, aVisibleWidgets);
7068 #endif
7069 if (pRefBtn)
7071 #if GTK_CHECK_VERSION(4, 0, 0)
7072 collectVisibleChildren(pRefBtn, aVisibleWidgets);
7073 #endif
7074 //same again with pRefBtn, except stop if there's a
7075 //shared parent in the existing widgets
7076 for (GtkWidget *pCandidate = pRefBtn;
7077 pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
7078 pCandidate = gtk_widget_get_parent(pCandidate))
7080 if (aVisibleWidgets.insert(pCandidate).second)
7081 break;
7085 //hide everything except the aVisibleWidgets
7086 hideUnless(pContentArea, aVisibleWidgets, m_aHiddenWidgets);
7087 gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
7088 #if !GTK_CHECK_VERSION(4, 0, 0)
7089 m_nOldBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(m_pDialog));
7090 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), 0);
7091 if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
7092 gtk_widget_hide(pActionArea);
7093 gtk_widget_show_all(pRefEdit);
7094 if (pRefBtn)
7095 gtk_widget_show_all(pRefBtn);
7096 #else
7097 if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
7098 gtk_widget_hide(pActionArea);
7099 #endif
7101 // calc's insert->function is springing back to its original size if the ref-button
7102 // is used to shrink the dialog down and then the user clicks in the calc area to do
7103 // the selection
7104 bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
7105 if (bWorkaroundSizeSpringingBack)
7106 gtk_widget_unmap(GTK_WIDGET(m_pDialog));
7108 resize_to_request();
7110 if (bWorkaroundSizeSpringingBack)
7111 gtk_widget_map(GTK_WIDGET(m_pDialog));
7113 m_pRefEdit = pRefEdit;
7116 virtual void undo_collapse() override
7118 // All others: Show();
7119 for (GtkWidget* pWindow : m_aHiddenWidgets)
7121 gtk_widget_show(pWindow);
7122 g_object_unref(pWindow);
7124 m_aHiddenWidgets.clear();
7126 gtk_widget_set_size_request(m_pRefEdit, m_nOldEditWidthReq, -1);
7127 m_pRefEdit = nullptr;
7128 #if !GTK_CHECK_VERSION(4, 0, 0)
7129 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), m_nOldBorderWidth);
7130 if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
7131 gtk_widget_show(pActionArea);
7132 #else
7133 if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
7134 gtk_widget_show(pActionArea);
7135 #endif
7136 resize_to_request();
7137 present();
7140 void close(bool bCloseSignal);
7142 virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
7144 //not implemented for the gtk variant
7147 virtual ~GtkInstanceDialog() override
7149 if (!m_aHiddenWidgets.empty())
7151 for (GtkWidget* pWindow : m_aHiddenWidgets)
7152 g_object_unref(pWindow);
7153 m_aHiddenWidgets.clear();
7156 if (m_nCloseSignalId)
7157 g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
7158 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
7164 void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
7166 DialogRunner* pThis = static_cast<DialogRunner*>(data);
7168 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
7169 if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
7171 pThis->m_pInstance->close(false);
7172 return;
7175 pThis->m_nResponseId = nResponseId;
7176 pThis->loop_quit();
7179 void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
7181 DialogRunner* pThis = static_cast<DialogRunner*>(data);
7183 // make esc in an assistant act as if cancel button was pressed
7184 pThis->m_pInstance->close(false);
7187 namespace {
7189 class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
7191 private:
7192 GtkMessageDialog* m_pMessageDialog;
7193 public:
7194 GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7195 : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
7196 , m_pMessageDialog(pMessageDialog)
7200 virtual void set_primary_text(const OUString& rText) override
7202 ::set_primary_text(m_pMessageDialog, rText);
7205 virtual OUString get_primary_text() const override
7207 return ::get_primary_text(m_pMessageDialog);
7210 virtual void set_secondary_text(const OUString& rText) override
7212 ::set_secondary_text(m_pMessageDialog, rText);
7215 virtual OUString get_secondary_text() const override
7217 return ::get_secondary_text(m_pMessageDialog);
7220 virtual Container* weld_message_area() override
7222 #if !GTK_CHECK_VERSION(4, 0, 0)
7223 return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
7224 #else
7225 return new GtkInstanceContainer(gtk_message_dialog_get_message_area(m_pMessageDialog), m_pBuilder, false);
7226 #endif
7230 void set_label_wrap(GtkLabel* pLabel, bool bWrap)
7232 #if GTK_CHECK_VERSION(4, 0, 0)
7233 gtk_label_set_wrap(pLabel, bWrap);
7234 #else
7235 gtk_label_set_line_wrap(pLabel, bWrap);
7236 #endif
7239 class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
7241 private:
7242 GtkAssistant* m_pAssistant;
7243 GtkWidget* m_pSidebar;
7244 GtkWidget* m_pSidebarEventBox;
7245 #if !GTK_CHECK_VERSION(4, 0, 0)
7246 GtkButtonBox* m_pButtonBox;
7247 #else
7248 GtkBox* m_pButtonBox;
7249 GtkEventController* m_pSidebarClickController;
7250 #endif
7251 GtkButton* m_pHelp;
7252 GtkButton* m_pBack;
7253 GtkButton* m_pNext;
7254 GtkButton* m_pFinish;
7255 GtkButton* m_pCancel;
7256 gulong m_nButtonPressSignalId;
7257 std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
7258 std::map<OString, bool> m_aNotClickable;
7260 int find_page(std::string_view ident) const
7262 int nPages = gtk_assistant_get_n_pages(m_pAssistant);
7263 for (int i = 0; i < nPages; ++i)
7265 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, i);
7266 OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pPage));
7267 if (sBuildableName == ident)
7268 return i;
7270 return -1;
7273 static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
7275 if (GTK_IS_LABEL(pWidget))
7277 ::set_label_wrap(GTK_LABEL(pWidget), true);
7278 gtk_label_set_width_chars(GTK_LABEL(pWidget), 22);
7279 gtk_label_set_max_width_chars(GTK_LABEL(pWidget), 22);
7283 static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
7285 OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
7286 if (sBuildableName == "sidebar")
7288 GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
7289 *ppSidebar = pWidget;
7291 #if !GTK_CHECK_VERSION(4, 0, 0)
7292 if (GTK_IS_CONTAINER(pWidget))
7293 gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
7294 #endif
7297 static void signalHelpClicked(GtkButton*, gpointer widget)
7299 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
7300 pThis->signal_help_clicked();
7303 void signal_help_clicked()
7305 help();
7308 #if GTK_CHECK_VERSION(4, 0, 0)
7309 static void signalButton(GtkGestureClick* /*pGesture*/, int /*n_press*/, gdouble x, gdouble y, gpointer widget)
7311 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
7312 SolarMutexGuard aGuard;
7313 pThis->signal_button(x, y);
7315 #else
7316 static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
7318 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
7319 SolarMutexGuard aGuard;
7320 return pThis->signal_button(pEvent->x, pEvent->y);
7322 #endif
7324 bool signal_button(gtk_coord event_x, gtk_coord event_y)
7326 int nNewCurrentPage = -1;
7328 GtkAllocation allocation;
7330 int nPageIndex = 0;
7332 #if GTK_CHECK_VERSION(4, 0, 0)
7333 for (GtkWidget* pWidget = gtk_widget_get_first_child(m_pSidebar);
7334 pWidget; pWidget = gtk_widget_get_next_sibling(pWidget))
7336 #else
7337 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pSidebar));
7338 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
7340 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
7341 #endif
7342 if (!gtk_widget_get_visible(pWidget))
7343 continue;
7345 gtk_widget_get_allocation(pWidget, &allocation);
7347 gtk_coord dest_x1, dest_y1;
7348 gtk_widget_translate_coordinates(pWidget,
7349 m_pSidebarEventBox,
7352 &dest_x1,
7353 &dest_y1);
7355 gtk_coord dest_x2, dest_y2;
7356 gtk_widget_translate_coordinates(pWidget,
7357 m_pSidebarEventBox,
7358 allocation.width,
7359 allocation.height,
7360 &dest_x2,
7361 &dest_y2);
7364 if (event_x >= dest_x1 && event_x <= dest_x2 && event_y >= dest_y1 && event_y <= dest_y2)
7366 nNewCurrentPage = nPageIndex;
7367 break;
7370 ++nPageIndex;
7372 #if !GTK_CHECK_VERSION(4, 0, 0)
7373 g_list_free(pChildren);
7374 #endif
7376 if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
7378 OString sIdent = get_page_ident(nNewCurrentPage);
7379 if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
7380 set_current_page(nNewCurrentPage);
7383 return false;
7386 public:
7387 GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7388 : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
7389 , m_pAssistant(pAssistant)
7390 , m_pSidebar(nullptr)
7391 #if GTK_CHECK_VERSION(4, 0, 0)
7392 , m_pSidebarClickController(nullptr)
7393 #endif
7394 , m_nButtonPressSignalId(0)
7396 #if !GTK_CHECK_VERSION(4, 0, 0)
7397 m_pButtonBox = GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
7398 gtk_button_box_set_layout(m_pButtonBox, GTK_BUTTONBOX_END);
7399 gtk_box_set_spacing(GTK_BOX(m_pButtonBox), 6);
7400 #else
7401 m_pButtonBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
7402 #endif
7404 m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
7405 #if !GTK_CHECK_VERSION(4, 0, 0)
7406 gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
7407 #endif
7408 ::set_buildable_id(GTK_BUILDABLE(m_pBack), "previous");
7409 #if GTK_CHECK_VERSION(4, 0, 0)
7410 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack));
7411 #else
7412 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
7413 #endif
7415 m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
7416 #if !GTK_CHECK_VERSION(4, 0, 0)
7417 gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
7418 #endif
7419 ::set_buildable_id(GTK_BUILDABLE(m_pNext), "next");
7420 #if GTK_CHECK_VERSION(4, 0, 0)
7421 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext));
7422 #else
7423 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
7424 #endif
7426 m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
7427 #if !GTK_CHECK_VERSION(4, 0, 0)
7428 gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
7429 #endif
7430 #if GTK_CHECK_VERSION(4, 0, 0)
7431 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel));
7432 #else
7433 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
7434 #endif
7436 m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
7437 #if !GTK_CHECK_VERSION(4, 0, 0)
7438 gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
7439 #endif
7440 ::set_buildable_id(GTK_BUILDABLE(m_pFinish), "finish");
7441 #if GTK_CHECK_VERSION(4, 0, 0)
7442 gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish));
7443 #else
7444 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
7445 #endif
7447 #if GTK_CHECK_VERSION(4, 0, 0)
7448 m_pHelp = GTK_BUTTON(gtk_button_new_from_icon_name("help-browser-symbolic"));
7449 #else
7450 m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
7451 #endif
7452 #if !GTK_CHECK_VERSION(4, 0, 0)
7453 gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
7454 #endif
7455 g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
7456 #if GTK_CHECK_VERSION(4, 0, 0)
7457 gtk_box_prepend(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp));
7458 gtk_widget_set_hexpand(GTK_WIDGET(m_pHelp), true);
7459 gtk_widget_set_halign(GTK_WIDGET(m_pHelp), GTK_ALIGN_START);
7460 #else
7461 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
7462 #endif
7464 gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
7465 #if !GTK_CHECK_VERSION(4, 0, 0)
7466 gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
7467 #endif
7468 gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
7470 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
7471 #if !GTK_CHECK_VERSION(4, 0, 0)
7472 gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
7473 #endif
7474 gtk_widget_set_halign(pParent, GTK_ALIGN_FILL);
7476 // Hide the built-in ones early so we get a nice optimal size for the width without
7477 // including the unused contents
7478 #if GTK_CHECK_VERSION(4, 0, 0)
7479 for (GtkWidget* pChild = gtk_widget_get_first_child(pParent);
7480 pChild; pChild = gtk_widget_get_next_sibling(pChild))
7482 gtk_widget_hide(pChild);
7484 #else
7485 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pParent));
7486 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
7488 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
7489 gtk_widget_hide(pWidget);
7491 g_list_free(pChildren);
7492 #endif
7494 #if !GTK_CHECK_VERSION(4, 0, 0)
7495 gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
7496 #else
7497 gtk_widget_show(GTK_WIDGET(m_pButtonBox));
7498 #endif
7500 find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
7502 m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
7503 if (m_pSidebarEventBox)
7505 #if GTK_CHECK_VERSION(4, 0, 0)
7506 GtkGesture *pClick = gtk_gesture_click_new();
7507 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
7508 m_pSidebarClickController = GTK_EVENT_CONTROLLER(pClick);
7509 gtk_widget_add_controller(m_pSidebarEventBox, m_pSidebarClickController);
7510 m_nButtonPressSignalId = g_signal_connect(m_pSidebarClickController, "pressed", G_CALLBACK(signalButton), this);
7511 #else
7512 m_nButtonPressSignalId = g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this);
7513 #endif
7517 virtual int get_current_page() const override
7519 return gtk_assistant_get_current_page(m_pAssistant);
7522 virtual int get_n_pages() const override
7524 return gtk_assistant_get_n_pages(m_pAssistant);
7527 virtual OString get_page_ident(int nPage) const override
7529 const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
7530 return ::get_buildable_id(GTK_BUILDABLE(pWidget));
7533 virtual OString get_current_page_ident() const override
7535 return get_page_ident(get_current_page());
7538 virtual void set_current_page(int nPage) override
7540 OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
7542 gtk_assistant_set_current_page(m_pAssistant, nPage);
7544 // if the page doesn't have a title, then the dialog will now have no
7545 // title, so restore the original title as a fallback
7546 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nPage);
7547 if (!gtk_assistant_get_page_title(m_pAssistant, pPage))
7548 gtk_window_set_title(GTK_WINDOW(m_pAssistant), sDialogTitle.getStr());
7551 virtual void set_current_page(const OString& rIdent) override
7553 int nPage = find_page(rIdent);
7554 if (nPage == -1)
7555 return;
7556 set_current_page(nPage);
7559 virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
7561 int nIndex = find_page(rIdent);
7562 if (nIndex == -1)
7563 return;
7564 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
7565 gtk_assistant_set_page_title(m_pAssistant, pPage,
7566 OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
7567 #if !GTK_CHECK_VERSION(4, 0, 0)
7568 gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
7569 #endif
7572 virtual OUString get_page_title(const OString& rIdent) const override
7574 int nIndex = find_page(rIdent);
7575 if (nIndex == -1)
7576 return OUString();
7577 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
7578 const gchar* pStr = gtk_assistant_get_page_title(m_pAssistant, pPage);
7579 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7582 virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
7584 m_aNotClickable[rIdent] = !bSensitive;
7587 virtual void set_page_index(const OString& rIdent, int nNewIndex) override
7589 int nOldIndex = find_page(rIdent);
7590 if (nOldIndex == -1)
7591 return;
7593 if (nOldIndex == nNewIndex)
7594 return;
7596 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
7598 g_object_ref(pPage);
7599 OString sTitle(gtk_assistant_get_page_title(m_pAssistant, pPage));
7600 gtk_assistant_remove_page(m_pAssistant, nOldIndex);
7601 gtk_assistant_insert_page(m_pAssistant, pPage, nNewIndex);
7602 gtk_assistant_set_page_type(m_pAssistant, pPage, GTK_ASSISTANT_PAGE_CUSTOM);
7603 gtk_assistant_set_page_title(m_pAssistant, pPage, sTitle.getStr());
7604 #if !GTK_CHECK_VERSION(4, 0, 0)
7605 gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
7606 #endif
7607 g_object_unref(pPage);
7610 virtual weld::Container* append_page(const OString& rIdent) override
7612 disable_notify_events();
7614 GtkWidget *pChild = gtk_grid_new();
7615 ::set_buildable_id(GTK_BUILDABLE(pChild), rIdent);
7616 gtk_assistant_append_page(m_pAssistant, pChild);
7617 gtk_assistant_set_page_type(m_pAssistant, pChild, GTK_ASSISTANT_PAGE_CUSTOM);
7618 gtk_widget_show(pChild);
7620 enable_notify_events();
7622 #if !GTK_CHECK_VERSION(4, 0, 0)
7623 m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
7624 #else
7625 m_aPages.emplace_back(new GtkInstanceContainer(pChild, m_pBuilder, false));
7626 #endif
7628 return m_aPages.back().get();
7631 virtual void set_page_side_help_id(const OString& rHelpId) override
7633 if (!m_pSidebar)
7634 return;
7635 ::set_help_id(m_pSidebar, rHelpId);
7638 virtual GtkButton* get_widget_for_response(int nGtkResponse) override
7640 GtkButton* pButton = nullptr;
7641 if (nGtkResponse == GTK_RESPONSE_YES)
7642 pButton = m_pNext;
7643 else if (nGtkResponse == GTK_RESPONSE_NO)
7644 pButton = m_pBack;
7645 else if (nGtkResponse == GTK_RESPONSE_OK)
7646 pButton = m_pFinish;
7647 else if (nGtkResponse == GTK_RESPONSE_CANCEL)
7648 pButton = m_pCancel;
7649 else if (nGtkResponse == GTK_RESPONSE_HELP)
7650 pButton = m_pHelp;
7651 return pButton;
7654 virtual ~GtkInstanceAssistant() override
7656 if (m_nButtonPressSignalId)
7658 #if GTK_CHECK_VERSION(4, 0, 0)
7659 g_signal_handler_disconnect(m_pSidebarClickController, m_nButtonPressSignalId);
7660 #else
7661 g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
7662 #endif
7667 class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
7669 private:
7670 GtkFrame* m_pFrame;
7671 public:
7672 GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7673 #if !GTK_CHECK_VERSION(4, 0, 0)
7674 : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
7675 #else
7676 : GtkInstanceContainer(GTK_WIDGET(pFrame), pBuilder, bTakeOwnership)
7677 #endif
7678 , m_pFrame(pFrame)
7682 virtual void set_label(const OUString& rText) override
7684 gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
7687 virtual OUString get_label() const override
7689 const gchar* pStr = gtk_frame_get_label(m_pFrame);
7690 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7693 virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
7696 class GtkInstancePaned : public GtkInstanceContainer, public virtual weld::Paned
7698 private:
7699 GtkPaned* m_pPaned;
7700 public:
7701 GtkInstancePaned(GtkPaned* pPaned, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7702 #if !GTK_CHECK_VERSION(4, 0, 0)
7703 : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
7704 #else
7705 : GtkInstanceContainer(GTK_WIDGET(pPaned), pBuilder, bTakeOwnership)
7706 #endif
7707 , m_pPaned(pPaned)
7711 virtual void set_position(int nPos) override
7713 gtk_paned_set_position(m_pPaned, nPos);
7716 virtual int get_position() const override
7718 return gtk_paned_get_position(m_pPaned);
7724 static GType immobilized_viewport_get_type();
7725 static gpointer immobilized_viewport_parent_class;
7727 #ifndef NDEBUG
7728 # define IMMOBILIZED_TYPE_VIEWPORT (immobilized_viewport_get_type())
7729 # define IMMOBILIZED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), IMMOBILIZED_TYPE_VIEWPORT))
7730 #endif
7732 namespace {
7734 struct ImmobilizedViewportPrivate
7736 GtkAdjustment *hadjustment;
7737 GtkAdjustment *vadjustment;
7742 #define IMMOBILIZED_VIEWPORT_PRIVATE_DATA "ImmobilizedViewportPrivateData"
7744 enum
7746 PROP_0,
7747 PROP_HADJUSTMENT,
7748 PROP_VADJUSTMENT,
7749 PROP_HSCROLL_POLICY,
7750 PROP_VSCROLL_POLICY,
7751 PROP_SHADOW_TYPE
7754 static void viewport_set_adjustment(GtkViewport *viewport,
7755 GtkOrientation orientation,
7756 GtkAdjustment *adjustment)
7758 ImmobilizedViewportPrivate* priv =
7759 static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(G_OBJECT(viewport),
7760 IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
7762 if (!adjustment)
7763 adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
7765 if (orientation == GTK_ORIENTATION_HORIZONTAL)
7767 if (priv->hadjustment)
7768 g_object_unref(priv->hadjustment);
7769 priv->hadjustment = adjustment;
7771 else
7773 if (priv->vadjustment)
7774 g_object_unref(priv->vadjustment);
7775 priv->vadjustment = adjustment;
7778 g_object_ref_sink(adjustment);
7781 static void
7782 immobilized_viewport_set_property(GObject* object,
7783 guint prop_id,
7784 const GValue* value,
7785 GParamSpec* /*pspec*/)
7787 GtkViewport *viewport = GTK_VIEWPORT(object);
7789 switch (prop_id)
7791 case PROP_HADJUSTMENT:
7792 viewport_set_adjustment(viewport, GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(g_value_get_object(value)));
7793 break;
7794 case PROP_VADJUSTMENT:
7795 viewport_set_adjustment(viewport, GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(g_value_get_object(value)));
7796 break;
7797 case PROP_HSCROLL_POLICY:
7798 case PROP_VSCROLL_POLICY:
7799 break;
7800 default:
7801 SAL_WARN( "vcl.gtk", "unknown property\n");
7802 break;
7806 static void
7807 immobilized_viewport_get_property(GObject* object,
7808 guint prop_id,
7809 GValue* value,
7810 GParamSpec* /*pspec*/)
7812 ImmobilizedViewportPrivate* priv =
7813 static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(object,
7814 IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
7816 switch (prop_id)
7818 case PROP_HADJUSTMENT:
7819 g_value_set_object(value, priv->hadjustment);
7820 break;
7821 case PROP_VADJUSTMENT:
7822 g_value_set_object(value, priv->vadjustment);
7823 break;
7824 case PROP_HSCROLL_POLICY:
7825 g_value_set_enum(value, GTK_SCROLL_MINIMUM);
7826 break;
7827 case PROP_VSCROLL_POLICY:
7828 g_value_set_enum(value, GTK_SCROLL_MINIMUM);
7829 break;
7830 default:
7831 SAL_WARN( "vcl.gtk", "unknown property\n");
7832 break;
7836 static ImmobilizedViewportPrivate*
7837 immobilized_viewport_new_private_data()
7839 ImmobilizedViewportPrivate* priv = g_slice_new0(ImmobilizedViewportPrivate);
7840 priv->hadjustment = nullptr;
7841 priv->vadjustment = nullptr;
7842 return priv;
7845 static void
7846 immobilized_viewport_instance_init(GTypeInstance *instance, gpointer /*klass*/)
7848 GObject* object = G_OBJECT(instance);
7849 g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA,
7850 immobilized_viewport_new_private_data());
7853 static void
7854 immobilized_viewport_finalize(GObject* object)
7856 void* priv = g_object_get_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA);
7857 if (priv)
7859 g_slice_free(ImmobilizedViewportPrivate, priv);
7860 g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA, nullptr);
7862 G_OBJECT_CLASS(immobilized_viewport_parent_class)->finalize(object);
7865 static void immobilized_viewport_class_init(GtkWidgetClass* klass)
7867 immobilized_viewport_parent_class = g_type_class_peek_parent(klass);
7869 GObjectClass* o_class = G_OBJECT_CLASS(klass);
7871 /* GObject signals */
7872 o_class->finalize = immobilized_viewport_finalize;
7873 o_class->set_property = immobilized_viewport_set_property;
7874 o_class->get_property = immobilized_viewport_get_property;
7876 /* Properties */
7877 g_object_class_override_property(o_class, PROP_HADJUSTMENT, "hadjustment");
7878 g_object_class_override_property(o_class, PROP_VADJUSTMENT, "vadjustment");
7879 g_object_class_override_property(o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
7880 g_object_class_override_property(o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
7883 GType immobilized_viewport_get_type()
7885 static GType type = 0;
7887 if (!type)
7889 GTypeQuery query;
7890 g_type_query(gtk_viewport_get_type(), &query);
7892 static const GTypeInfo tinfo =
7894 static_cast<guint16>(query.class_size),
7895 nullptr, /* base init */
7896 nullptr, /* base finalize */
7897 reinterpret_cast<GClassInitFunc>(immobilized_viewport_class_init), /* class init */
7898 nullptr, /* class finalize */
7899 nullptr, /* class data */
7900 static_cast<guint16>(query.instance_size), /* instance size */
7901 0, /* nb preallocs */
7902 immobilized_viewport_instance_init, /* instance init */
7903 nullptr /* value table */
7906 type = g_type_register_static(GTK_TYPE_VIEWPORT, "ImmobilizedViewport",
7907 &tinfo, GTypeFlags(0));
7910 return type;
7913 static VclPolicyType GtkToVcl(GtkPolicyType eType)
7915 VclPolicyType eRet(VclPolicyType::NEVER);
7916 switch (eType)
7918 case GTK_POLICY_ALWAYS:
7919 eRet = VclPolicyType::ALWAYS;
7920 break;
7921 case GTK_POLICY_AUTOMATIC:
7922 eRet = VclPolicyType::AUTOMATIC;
7923 break;
7924 case GTK_POLICY_EXTERNAL:
7925 case GTK_POLICY_NEVER:
7926 eRet = VclPolicyType::NEVER;
7927 break;
7929 return eRet;
7932 static GtkPolicyType VclToGtk(VclPolicyType eType)
7934 GtkPolicyType eRet(GTK_POLICY_ALWAYS);
7935 switch (eType)
7937 case VclPolicyType::ALWAYS:
7938 eRet = GTK_POLICY_ALWAYS;
7939 break;
7940 case VclPolicyType::AUTOMATIC:
7941 eRet = GTK_POLICY_AUTOMATIC;
7942 break;
7943 case VclPolicyType::NEVER:
7944 eRet = GTK_POLICY_NEVER;
7945 break;
7947 return eRet;
7950 static GtkMessageType VclToGtk(VclMessageType eType)
7952 GtkMessageType eRet(GTK_MESSAGE_INFO);
7953 switch (eType)
7955 case VclMessageType::Info:
7956 eRet = GTK_MESSAGE_INFO;
7957 break;
7958 case VclMessageType::Warning:
7959 eRet = GTK_MESSAGE_WARNING;
7960 break;
7961 case VclMessageType::Question:
7962 eRet = GTK_MESSAGE_QUESTION;
7963 break;
7964 case VclMessageType::Error:
7965 eRet = GTK_MESSAGE_ERROR;
7966 break;
7967 case VclMessageType::Other:
7968 eRet = GTK_MESSAGE_OTHER;
7969 break;
7971 return eRet;
7974 static GtkButtonsType VclToGtk(VclButtonsType eType)
7976 GtkButtonsType eRet(GTK_BUTTONS_NONE);
7977 switch (eType)
7979 case VclButtonsType::NONE:
7980 eRet = GTK_BUTTONS_NONE;
7981 break;
7982 case VclButtonsType::Ok:
7983 eRet = GTK_BUTTONS_OK;
7984 break;
7985 case VclButtonsType::Close:
7986 eRet = GTK_BUTTONS_CLOSE;
7987 break;
7988 case VclButtonsType::Cancel:
7989 eRet = GTK_BUTTONS_CANCEL;
7990 break;
7991 case VclButtonsType::YesNo:
7992 eRet = GTK_BUTTONS_YES_NO;
7993 break;
7994 case VclButtonsType::OkCancel:
7995 eRet = GTK_BUTTONS_OK_CANCEL;
7996 break;
7998 return eRet;
8001 static GtkSelectionMode VclToGtk(SelectionMode eType)
8003 GtkSelectionMode eRet(GTK_SELECTION_NONE);
8004 switch (eType)
8006 case SelectionMode::NONE:
8007 eRet = GTK_SELECTION_NONE;
8008 break;
8009 case SelectionMode::Single:
8010 eRet = GTK_SELECTION_SINGLE;
8011 break;
8012 case SelectionMode::Range:
8013 eRet = GTK_SELECTION_BROWSE;
8014 break;
8015 case SelectionMode::Multiple:
8016 eRet = GTK_SELECTION_MULTIPLE;
8017 break;
8019 return eRet;
8022 namespace {
8024 class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
8026 private:
8027 GtkScrolledWindow* m_pScrolledWindow;
8028 GtkWidget *m_pOrigViewport;
8029 GtkCssProvider* m_pScrollBarCssProvider;
8030 GtkAdjustment* m_pVAdjustment;
8031 GtkAdjustment* m_pHAdjustment;
8032 gulong m_nVAdjustChangedSignalId;
8033 gulong m_nHAdjustChangedSignalId;
8035 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
8037 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
8038 SolarMutexGuard aGuard;
8039 pThis->signal_vadjustment_changed();
8042 static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
8044 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
8045 SolarMutexGuard aGuard;
8046 pThis->signal_hadjustment_changed();
8049 public:
8050 GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership, bool bUserManagedScrolling)
8051 #if !GTK_CHECK_VERSION(4, 0, 0)
8052 : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
8053 #else
8054 : GtkInstanceContainer(GTK_WIDGET(pScrolledWindow), pBuilder, bTakeOwnership)
8055 #endif
8056 , m_pScrolledWindow(pScrolledWindow)
8057 , m_pOrigViewport(nullptr)
8058 , m_pScrollBarCssProvider(nullptr)
8059 , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
8060 , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
8061 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
8062 , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
8064 if (bUserManagedScrolling)
8065 set_user_managed_scrolling();
8068 void set_user_managed_scrolling()
8070 disable_notify_events();
8071 //remove the original viewport and replace it with our bodged one which
8072 //doesn't do any scrolling and expects its child to figure it out somehow
8073 assert(!m_pOrigViewport);
8074 #if GTK_CHECK_VERSION(4, 0, 0)
8075 GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
8076 #else
8077 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
8078 #endif
8079 assert(GTK_IS_VIEWPORT(pViewport));
8080 #if GTK_CHECK_VERSION(4, 0, 0)
8081 GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
8082 #else
8083 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
8084 #endif
8085 g_object_ref(pChild);
8086 #if GTK_CHECK_VERSION(4, 0, 0)
8087 gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
8088 #else
8089 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
8090 #endif
8091 g_object_ref(pViewport);
8092 #if GTK_CHECK_VERSION(4, 0, 0)
8093 gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
8094 #else
8095 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
8096 #endif
8097 GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(immobilized_viewport_get_type(), nullptr));
8098 gtk_widget_show(pNewViewport);
8099 #if GTK_CHECK_VERSION(4, 0, 0)
8100 gtk_scrolled_window_set_child(m_pScrolledWindow, pNewViewport);
8101 gtk_viewport_set_child(GTK_VIEWPORT(pNewViewport), pChild);
8102 #else
8103 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
8104 gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
8105 #endif
8106 g_object_unref(pChild);
8107 m_pOrigViewport = pViewport;
8108 enable_notify_events();
8111 virtual void hadjustment_configure(int value, int lower, int upper,
8112 int step_increment, int page_increment,
8113 int page_size) override
8115 disable_notify_events();
8116 if (SwapForRTL())
8117 value = upper - (value - lower + page_size);
8118 gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
8119 enable_notify_events();
8122 virtual int hadjustment_get_value() const override
8124 int value = gtk_adjustment_get_value(m_pHAdjustment);
8126 if (SwapForRTL())
8128 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
8129 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
8130 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
8131 value = lower + (upper - value - page_size);
8134 return value;
8137 virtual void hadjustment_set_value(int value) override
8139 disable_notify_events();
8141 if (SwapForRTL())
8143 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
8144 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
8145 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
8146 value = upper - (value - lower + page_size);
8149 gtk_adjustment_set_value(m_pHAdjustment, value);
8150 enable_notify_events();
8153 virtual int hadjustment_get_upper() const override
8155 return gtk_adjustment_get_upper(m_pHAdjustment);
8158 virtual void hadjustment_set_upper(int upper) override
8160 disable_notify_events();
8161 gtk_adjustment_set_upper(m_pHAdjustment, upper);
8162 enable_notify_events();
8165 virtual int hadjustment_get_page_size() const override
8167 return gtk_adjustment_get_page_size(m_pHAdjustment);
8170 virtual void hadjustment_set_page_size(int size) override
8172 gtk_adjustment_set_page_size(m_pHAdjustment, size);
8175 virtual void hadjustment_set_page_increment(int size) override
8177 gtk_adjustment_set_page_increment(m_pHAdjustment, size);
8180 virtual void hadjustment_set_step_increment(int size) override
8182 gtk_adjustment_set_step_increment(m_pHAdjustment, size);
8185 virtual void set_hpolicy(VclPolicyType eHPolicy) override
8187 GtkPolicyType eGtkVPolicy;
8188 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
8189 gtk_scrolled_window_set_policy(m_pScrolledWindow, VclToGtk(eHPolicy), eGtkVPolicy);
8192 virtual VclPolicyType get_hpolicy() const override
8194 GtkPolicyType eGtkHPolicy;
8195 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
8196 return GtkToVcl(eGtkHPolicy);
8199 virtual void vadjustment_configure(int value, int lower, int upper,
8200 int step_increment, int page_increment,
8201 int page_size) override
8203 disable_notify_events();
8204 gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
8205 enable_notify_events();
8208 virtual int vadjustment_get_value() const override
8210 return gtk_adjustment_get_value(m_pVAdjustment);
8213 virtual void vadjustment_set_value(int value) override
8215 disable_notify_events();
8216 gtk_adjustment_set_value(m_pVAdjustment, value);
8217 enable_notify_events();
8220 virtual int vadjustment_get_upper() const override
8222 return gtk_adjustment_get_upper(m_pVAdjustment);
8225 virtual void vadjustment_set_upper(int upper) override
8227 disable_notify_events();
8228 gtk_adjustment_set_upper(m_pVAdjustment, upper);
8229 enable_notify_events();
8232 virtual int vadjustment_get_lower() const override
8234 return gtk_adjustment_get_lower(m_pVAdjustment);
8237 virtual void vadjustment_set_lower(int lower) override
8239 disable_notify_events();
8240 gtk_adjustment_set_lower(m_pVAdjustment, lower);
8241 enable_notify_events();
8244 virtual int vadjustment_get_page_size() const override
8246 return gtk_adjustment_get_page_size(m_pVAdjustment);
8249 virtual void vadjustment_set_page_size(int size) override
8251 gtk_adjustment_set_page_size(m_pVAdjustment, size);
8254 virtual void vadjustment_set_page_increment(int size) override
8256 gtk_adjustment_set_page_increment(m_pVAdjustment, size);
8259 virtual void vadjustment_set_step_increment(int size) override
8261 gtk_adjustment_set_step_increment(m_pVAdjustment, size);
8264 virtual void set_vpolicy(VclPolicyType eVPolicy) override
8266 GtkPolicyType eGtkHPolicy;
8267 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
8268 gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
8271 virtual VclPolicyType get_vpolicy() const override
8273 GtkPolicyType eGtkVPolicy;
8274 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
8275 return GtkToVcl(eGtkVPolicy);
8278 virtual int get_scroll_thickness() const override
8280 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
8281 return 0;
8282 return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
8285 virtual void set_scroll_thickness(int nThickness) override
8287 GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
8288 GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
8289 gtk_widget_set_size_request(pHorzBar, -1, nThickness);
8290 gtk_widget_set_size_request(pVertBar, nThickness, -1);
8293 virtual void disable_notify_events() override
8295 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
8296 g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
8297 GtkInstanceContainer::disable_notify_events();
8300 virtual void enable_notify_events() override
8302 GtkInstanceContainer::enable_notify_events();
8303 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
8304 g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
8307 virtual void customize_scrollbars(const Color& rBackgroundColor,
8308 const Color& rShadowColor,
8309 const Color& rFaceColor) override
8311 GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
8312 GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
8313 GtkStyleContext *pHorzContext = gtk_widget_get_style_context(pHorzBar);
8314 GtkStyleContext *pVertContext = gtk_widget_get_style_context(pVertBar);
8315 if (m_pScrollBarCssProvider)
8317 gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8318 gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8321 m_pScrollBarCssProvider = gtk_css_provider_new();
8322 // intentionally 'trough' a long, narrow open container.
8323 OUString aBuffer = "scrollbar contents trough { background-color: #" + rBackgroundColor.AsRGBHexString() + "; } "
8324 "scrollbar contents trough slider { background-color: #" + rShadowColor.AsRGBHexString() + "; } "
8325 "scrollbar contents button { background-color: #" + rFaceColor.AsRGBHexString() + "; } "
8326 "scrollbar contents button { color: #000000; } "
8327 "scrollbar contents button:disabled { color: #7f7f7f; }";
8328 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
8329 css_provider_load_from_data(m_pScrollBarCssProvider, aResult.getStr(), aResult.getLength());
8331 gtk_style_context_add_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
8332 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8333 gtk_style_context_add_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
8334 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8337 virtual ~GtkInstanceScrolledWindow() override
8339 // we use GtkInstanceContainer::[disable|enable]_notify_events later on
8340 // to avoid touching these removed handlers
8341 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
8342 g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
8344 if (m_pScrollBarCssProvider)
8346 GtkStyleContext *pHorzContext = gtk_widget_get_style_context(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
8347 GtkStyleContext *pVertContext = gtk_widget_get_style_context(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
8348 gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8349 gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
8350 m_pScrollBarCssProvider = nullptr;
8353 //put it back the way it was
8354 if (!m_pOrigViewport)
8355 return;
8357 GtkInstanceContainer::disable_notify_events();
8359 // force in new adjustment to drop the built-in handlers on value-changed
8360 // which are getting called eventually by the gtk_container_add call
8361 // and which access the scrolled window indicators which, in the case
8362 // of user-managed scrolling windows in toolbar popups during popdown
8363 // are nullptr causing crashes when the scrolling windows is not at its
8364 // initial 0,0 position
8365 GtkAdjustment *pVAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
8366 gtk_scrolled_window_set_vadjustment(m_pScrolledWindow, pVAdjustment);
8367 GtkAdjustment *pHAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
8368 gtk_scrolled_window_set_hadjustment(m_pScrolledWindow, pHAdjustment);
8370 #if GTK_CHECK_VERSION(4, 0, 0)
8371 GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
8372 #else
8373 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
8374 #endif
8375 assert(IMMOBILIZED_IS_VIEWPORT(pViewport));
8376 #if GTK_CHECK_VERSION(4, 0, 0)
8377 GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
8378 #else
8379 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
8380 #endif
8381 g_object_ref(pChild);
8382 #if GTK_CHECK_VERSION(4, 0, 0)
8383 gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
8384 #else
8385 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
8386 #endif
8387 g_object_ref(pViewport);
8388 #if GTK_CHECK_VERSION(4, 0, 0)
8389 gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
8390 #else
8391 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
8392 #endif
8394 #if GTK_CHECK_VERSION(4, 0, 0)
8395 gtk_scrolled_window_set_child(m_pScrolledWindow, m_pOrigViewport);
8396 #else
8397 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
8398 #endif
8399 // coverity[freed_arg : FALSE] - this does not free m_pOrigViewport, it is reffed by m_pScrolledWindow
8400 g_object_unref(m_pOrigViewport);
8401 #if GTK_CHECK_VERSION(4, 0, 0)
8402 gtk_viewport_set_child(GTK_VIEWPORT(m_pOrigViewport), pChild);
8403 #else
8404 gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
8405 #endif
8406 g_object_unref(pChild);
8407 #if !GTK_CHECK_VERSION(4, 0, 0)
8408 gtk_widget_destroy(pViewport);
8409 #endif
8410 g_object_unref(pViewport);
8411 m_pOrigViewport = nullptr;
8412 GtkInstanceContainer::enable_notify_events();
8418 namespace {
8420 class GtkInstanceNotebook : public GtkInstanceWidget, public virtual weld::Notebook
8422 private:
8423 GtkNotebook* m_pNotebook;
8424 GtkBox* m_pOverFlowBox;
8425 GtkNotebook* m_pOverFlowNotebook;
8426 gulong m_nSwitchPageSignalId;
8427 gulong m_nOverFlowSwitchPageSignalId;
8428 #if GTK_CHECK_VERSION(4, 0, 0)
8429 NotifyingLayout* m_pLayout;
8430 #else
8431 gulong m_nNotebookSizeAllocateSignalId;
8432 gulong m_nFocusSignalId;
8433 #endif
8434 gulong m_nChangeCurrentPageId;
8435 guint m_nLaunchSplitTimeoutId;
8436 bool m_bOverFlowBoxActive;
8437 bool m_bOverFlowBoxIsStart;
8438 bool m_bInternalPageChange;
8439 int m_nStartTabCount;
8440 int m_nEndTabCount;
8441 mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
8443 static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
8445 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
8446 SolarMutexGuard aGuard;
8447 pThis->signal_switch_page(nNewPage);
8450 static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
8452 SolarMutexGuard aGuard;
8453 pThis->signal_overflow_switch_page();
8454 return false;
8457 static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
8459 g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
8462 void signal_switch_page(int nNewPage)
8464 if (m_bOverFlowBoxIsStart)
8466 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
8467 // add count of overflow pages, minus the extra tab
8468 nNewPage += nOverFlowLen;
8471 bool bAllow = m_bInternalPageChange || !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
8472 if (!bAllow)
8474 g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
8475 return;
8477 if (m_bOverFlowBoxActive)
8478 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
8479 OString sNewIdent(get_page_ident(nNewPage));
8480 if (!m_bInternalPageChange)
8481 m_aEnterPageHdl.Call(sNewIdent);
8484 void unsplit_notebooks()
8486 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
8487 int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
8488 int nPageIndex = 0;
8489 if (!m_bOverFlowBoxIsStart)
8490 nPageIndex += nMainPages;
8492 // take the overflow pages, and put them back at the end of the normal one
8493 int i = nMainPages;
8494 while (nOverFlowPages)
8496 OString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
8497 OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
8498 remove_page(m_pOverFlowNotebook, sIdent);
8500 GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
8501 insert_page(m_pNotebook, sIdent, sLabel, pPage, -1);
8503 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
8504 gtk_notebook_get_nth_page(m_pNotebook, i));
8505 gtk_widget_set_hexpand(pTabWidget, true);
8506 --nOverFlowPages;
8507 ++i;
8508 ++nPageIndex;
8511 // remove the dangling placeholder tab page
8512 remove_page(m_pOverFlowNotebook, "useless");
8515 // a tab has been selected on the overflow notebook
8516 void signal_overflow_switch_page()
8518 int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
8519 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
8520 if (nNewPage == nOverFlowPages)
8522 // the useless tab which is there because there has to be an active tab
8523 return;
8526 // check if we are allowed leave before attempting to resplit the notebooks
8527 bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
8528 if (!bAllow)
8529 return;
8531 disable_notify_events();
8533 // take the overflow pages, and put them back at the end of the normal one
8534 unsplit_notebooks();
8536 // now redo the split, the pages will be split the other way around this time
8537 std::swap(m_nStartTabCount, m_nEndTabCount);
8538 split_notebooks();
8540 // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
8541 gtk_notebook_set_current_page(m_pNotebook, nNewPage);
8543 enable_notify_events();
8545 // trigger main notebook switch-page callback
8546 OString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
8547 m_aEnterPageHdl.Call(sNewIdent);
8550 static OString get_page_ident(GtkNotebook *pNotebook, guint nPage)
8552 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
8553 return ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
8556 static gint get_page_number(GtkNotebook *pNotebook, std::string_view ident)
8558 gint nPages = gtk_notebook_get_n_pages(pNotebook);
8559 for (gint i = 0; i < nPages; ++i)
8561 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
8562 OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
8563 if (sBuildableName == ident)
8564 return i;
8566 return -1;
8569 int remove_page(GtkNotebook *pNotebook, std::string_view ident)
8571 disable_notify_events();
8572 int nPageNumber = get_page_number(pNotebook, ident);
8573 gtk_notebook_remove_page(pNotebook, nPageNumber);
8574 enable_notify_events();
8575 return nPageNumber;
8578 static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
8580 const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
8581 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
8584 static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
8586 OString sUtf8(rText.toUtf8());
8588 GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
8590 // tdf#128241 if there's already a label here, reuse it so the buildable
8591 // name remains the same, gtk_notebook_set_tab_label_text will replace
8592 // the label widget with a new one
8593 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
8594 if (pTabWidget && GTK_IS_LABEL(pTabWidget))
8596 gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
8597 return;
8600 gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
8603 void append_useless_page(GtkNotebook *pNotebook)
8605 disable_notify_events();
8607 GtkWidget *pTabWidget = gtk_fixed_new();
8608 ::set_buildable_id(GTK_BUILDABLE(pTabWidget), "useless");
8610 GtkWidget *pChild = gtk_grid_new();
8611 gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
8612 gtk_widget_show(pChild);
8613 gtk_widget_show(pTabWidget);
8615 enable_notify_events();
8618 void insert_page(GtkNotebook *pNotebook, const OString& rIdent, const OUString& rLabel, GtkWidget *pChild, int nPos)
8620 disable_notify_events();
8622 GtkWidget *pTabWidget = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
8623 ::set_buildable_id(GTK_BUILDABLE(pTabWidget), rIdent);
8624 gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
8625 gtk_widget_show(pChild);
8626 gtk_widget_show(pTabWidget);
8628 if (nPos != -1)
8630 unsigned int nPageIndex = static_cast<unsigned int>(nPos);
8631 if (nPageIndex < m_aPages.size())
8632 m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
8635 enable_notify_events();
8638 void make_overflow_boxes()
8640 m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
8641 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
8642 container_add(pParent, GTK_WIDGET(m_pOverFlowBox));
8643 #if GTK_CHECK_VERSION(4, 0, 0)
8644 gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook));
8645 #else
8646 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
8647 #endif
8648 g_object_ref(m_pNotebook);
8649 container_remove(pParent, GTK_WIDGET(m_pNotebook));
8650 #if GTK_CHECK_VERSION(4, 0, 0)
8651 gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pNotebook));
8652 #else
8653 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
8654 #endif
8655 // coverity[freed_arg : FALSE] - this does not free m_pNotebook , it is reffed by pParent
8656 g_object_unref(m_pNotebook);
8657 gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
8660 void split_notebooks()
8662 // get the original preferred size for the notebook, the sane width
8663 // expected here depends on the notebooks all initially having
8664 // scrollable tabs enabled
8665 GtkAllocation alloc;
8666 gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
8668 // toggle the direction of the split since the last time
8669 m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
8670 if (!m_pOverFlowBox)
8671 make_overflow_boxes();
8673 // don't scroll the tabs anymore
8674 // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
8675 gtk_notebook_set_scrollable(m_pNotebook, false);
8677 #if !GTK_CHECK_VERSION(4, 0, 0)
8678 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
8679 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
8680 #else
8681 g_object_freeze_notify(G_OBJECT(m_pNotebook));
8682 g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
8683 #endif
8685 gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
8687 gint nPages;
8689 GtkRequisition size1, size2;
8691 if (!m_nStartTabCount && !m_nEndTabCount)
8693 nPages = gtk_notebook_get_n_pages(m_pNotebook);
8695 std::vector<int> aLabelWidths;
8696 //move tabs to the overflow notebook
8697 for (int i = 0; i < nPages; ++i)
8699 OUString sLabel(get_tab_label_text(m_pNotebook, i));
8700 aLabelWidths.push_back(get_pixel_size(sLabel).Width());
8702 int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
8703 int count = 0;
8704 for (int i = 0; i < nPages; ++i)
8706 count += aLabelWidths[i];
8707 if (count >= row_width)
8709 m_nStartTabCount = i;
8710 break;
8714 m_nEndTabCount = nPages - m_nStartTabCount;
8717 //move the tabs to the overflow notebook
8718 int i = 0;
8719 int nOverFlowPages = m_nStartTabCount;
8720 while (nOverFlowPages)
8722 OString sIdent(get_page_ident(m_pNotebook, 0));
8723 OUString sLabel(get_tab_label_text(m_pNotebook, 0));
8724 remove_page(m_pNotebook, sIdent);
8725 insert_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new(), -1);
8726 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
8727 gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
8728 gtk_widget_set_hexpand(pTabWidget, true);
8730 --nOverFlowPages;
8731 ++i;
8734 for (i = 0; i < m_nEndTabCount; ++i)
8736 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
8737 gtk_notebook_get_nth_page(m_pNotebook, i));
8738 gtk_widget_set_hexpand(pTabWidget, true);
8741 // have to have some tab as the active tab of the overflow notebook
8742 append_useless_page(m_pOverFlowNotebook);
8743 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
8744 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
8745 gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
8747 // add this temporarily to the normal notebook to measure how wide
8748 // the row would be if switched to the other notebook
8749 append_useless_page(m_pNotebook);
8751 gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
8752 gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
8754 auto nWidth = std::max(size1.width, size2.width);
8755 gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
8756 gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
8758 // remove it once we've measured it
8759 remove_page(m_pNotebook, "useless");
8761 #if !GTK_CHECK_VERSION(4, 0, 0)
8762 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
8763 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
8764 #else
8765 g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
8766 g_object_thaw_notify(G_OBJECT(m_pNotebook));
8767 #endif
8769 m_bOverFlowBoxActive = true;
8772 static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
8774 int nCurrentPage = pThis->get_current_page();
8775 pThis->split_notebooks();
8776 pThis->set_current_page(nCurrentPage);
8777 pThis->m_nLaunchSplitTimeoutId = 0;
8778 return false;
8781 // tdf#120371
8782 // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
8783 // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
8784 // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
8785 // tabs in a single row when they would fit
8786 void signal_notebook_size_allocate()
8788 if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
8789 return;
8790 disable_notify_events();
8791 gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
8792 if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
8794 for (gint i = 0; i < nPages; ++i)
8796 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
8797 #if GTK_CHECK_VERSION(4, 0, 0)
8798 bool bTabVisible = gtk_widget_get_child_visible(gtk_widget_get_parent(pTabWidget));
8799 #else
8800 bool bTabVisible = gtk_widget_get_child_visible(pTabWidget);
8801 #endif
8802 if (!bTabVisible)
8804 m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
8805 break;
8809 enable_notify_events();
8812 #if GTK_CHECK_VERSION(4, 0, 0)
8813 DECL_LINK(SizeAllocateHdl, void*, void);
8814 #else
8815 static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
8817 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
8818 pThis->signal_notebook_size_allocate();
8820 #endif
8822 bool signal_focus(GtkDirectionType direction)
8824 if (!m_bOverFlowBoxActive)
8825 return false;
8827 int nPage = gtk_notebook_get_current_page(m_pNotebook);
8828 if (direction == GTK_DIR_LEFT && nPage == 0)
8830 auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
8831 gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
8832 return true;
8834 else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
8836 gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
8837 return true;
8840 return false;
8843 #if !GTK_CHECK_VERSION(4, 0, 0)
8844 static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
8846 // if the notebook widget itself has focus
8847 if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
8849 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
8850 return pThis->signal_focus(direction);
8852 return false;
8854 #endif
8856 // ctrl + page_up/ page_down
8857 bool signal_change_current_page(gint arg1)
8859 bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
8860 if (bHandled)
8861 g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
8862 return false;
8865 static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
8867 if (arg1 == 0)
8868 return true;
8869 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
8870 return pThis->signal_change_current_page(arg1);
8873 public:
8874 GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8875 : GtkInstanceWidget(GTK_WIDGET(pNotebook), pBuilder, bTakeOwnership)
8876 , m_pNotebook(pNotebook)
8877 , m_pOverFlowBox(nullptr)
8878 , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
8879 , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
8880 , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
8881 #if GTK_CHECK_VERSION(4, 0, 0)
8882 , m_pLayout(nullptr)
8883 #else
8884 , m_nNotebookSizeAllocateSignalId(0)
8885 , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
8886 #endif
8887 , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
8888 , m_nLaunchSplitTimeoutId(0)
8889 , m_bOverFlowBoxActive(false)
8890 , m_bOverFlowBoxIsStart(false)
8891 , m_bInternalPageChange(false)
8892 , m_nStartTabCount(0)
8893 , m_nEndTabCount(0)
8895 #if !GTK_CHECK_VERSION(4, 0, 0)
8896 gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
8897 #endif
8898 gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
8899 if (nPages > 6)
8901 #if !GTK_CHECK_VERSION(4, 0, 0)
8902 m_nNotebookSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
8903 #else
8904 m_pLayout = NOTIFYING_LAYOUT(g_object_new(notifying_layout_get_type(), nullptr));
8905 notifying_layout_start_watch(m_pLayout, GTK_WIDGET(pNotebook), LINK(this, GtkInstanceNotebook, SizeAllocateHdl));
8906 #endif
8908 gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
8910 // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
8911 // the unwanted tab into invisibility via the 'overflow' class themed by global CreateStyleProvider
8912 GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
8913 gtk_style_context_add_class(pNotebookContext, "overflow");
8916 virtual int get_current_page() const override
8918 int nPage = gtk_notebook_get_current_page(m_pNotebook);
8919 if (nPage == -1)
8920 return nPage;
8921 if (m_bOverFlowBoxIsStart)
8923 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
8924 // add count of overflow pages, minus the extra tab
8925 nPage += nOverFlowLen;
8927 return nPage;
8930 virtual OString get_page_ident(int nPage) const override
8932 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
8933 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
8934 if (m_bOverFlowBoxIsStart)
8936 if (nPage < nOverFlowLen)
8937 return get_page_ident(m_pOverFlowNotebook, nPage);
8938 nPage -= nOverFlowLen;
8939 return get_page_ident(m_pNotebook, nPage);
8941 else
8943 if (nPage < nMainLen)
8944 return get_page_ident(m_pNotebook, nPage);
8945 nPage -= nMainLen;
8946 return get_page_ident(m_pOverFlowNotebook, nPage);
8950 virtual OString get_current_page_ident() const override
8952 const int nPage = get_current_page();
8953 return nPage != -1 ? get_page_ident(nPage) : OString();
8956 virtual int get_page_index(const OString& rIdent) const override
8958 auto nMainIndex = get_page_number(m_pNotebook, rIdent);
8959 auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
8961 if (nMainIndex == -1 && nOverFlowIndex == -1)
8962 return -1;
8964 if (m_bOverFlowBoxIsStart)
8966 if (nOverFlowIndex != -1)
8967 return nOverFlowIndex;
8968 else
8970 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
8971 return nMainIndex + nOverFlowLen;
8974 else
8976 if (nMainIndex != -1)
8977 return nMainIndex;
8978 else
8980 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
8981 return nOverFlowIndex + nMainLen;
8986 virtual weld::Container* get_page(const OString& rIdent) const override
8988 int nPage = get_page_index(rIdent);
8989 if (nPage < 0)
8990 return nullptr;
8992 GtkWidget* pChild;
8993 if (m_bOverFlowBoxIsStart)
8995 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
8996 if (nPage < nOverFlowLen)
8997 pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
8998 else
9000 nPage -= nOverFlowLen;
9001 pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
9004 else
9006 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
9007 if (nPage < nMainLen)
9008 pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
9009 else
9011 nPage -= nMainLen;
9012 pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
9016 unsigned int nPageIndex = static_cast<unsigned int>(nPage);
9017 if (m_aPages.size() < nPageIndex + 1)
9018 m_aPages.resize(nPageIndex + 1);
9019 #if !GTK_CHECK_VERSION(4, 0, 0)
9020 if (!m_aPages[nPageIndex])
9021 m_aPages[nPageIndex].reset(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
9022 #else
9023 if (!m_aPages[nPageIndex])
9024 m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
9025 #endif
9026 return m_aPages[nPageIndex].get();
9029 virtual void set_current_page(int nPage) override
9031 // normally we'd call disable_notify_events/enable_notify_events here,
9032 // but the notebook is complicated by the need to support the
9033 // double-decker hackery so for simplicity just flag that the page
9034 // change is not a directly user-triggered one
9035 bool bInternalPageChange = m_bInternalPageChange;
9036 m_bInternalPageChange = true;
9038 if (m_bOverFlowBoxIsStart)
9040 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
9041 if (nPage < nOverFlowLen)
9042 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
9043 else
9045 nPage -= nOverFlowLen;
9046 gtk_notebook_set_current_page(m_pNotebook, nPage);
9049 else
9051 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
9052 if (nPage < nMainLen)
9053 gtk_notebook_set_current_page(m_pNotebook, nPage);
9054 else
9056 nPage -= nMainLen;
9057 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
9061 m_bInternalPageChange = bInternalPageChange;
9064 virtual void set_current_page(const OString& rIdent) override
9066 gint nPage = get_page_index(rIdent);
9067 set_current_page(nPage);
9070 virtual int get_n_pages() const override
9072 int nLen = gtk_notebook_get_n_pages(m_pNotebook);
9073 if (m_bOverFlowBoxActive)
9074 nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
9075 return nLen;
9078 virtual OUString get_tab_label_text(const OString& rIdent) const override
9080 gint nPageNum = get_page_number(m_pNotebook, rIdent);
9081 if (nPageNum != -1)
9082 return get_tab_label_text(m_pNotebook, nPageNum);
9083 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
9084 if (nPageNum != -1)
9085 return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
9086 return OUString();
9089 virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
9091 gint nPageNum = get_page_number(m_pNotebook, rIdent);
9092 if (nPageNum != -1)
9094 set_tab_label_text(m_pNotebook, nPageNum, rText);
9095 return;
9097 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
9098 if (nPageNum != -1)
9100 set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
9104 virtual void disable_notify_events() override
9106 g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
9107 #if !GTK_CHECK_VERSION(4, 0, 0)
9108 g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
9109 #endif
9110 g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
9111 g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
9112 #if !GTK_CHECK_VERSION(4, 0, 0)
9113 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
9114 #endif
9115 g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
9116 GtkInstanceWidget::disable_notify_events();
9119 virtual void enable_notify_events() override
9121 GtkInstanceWidget::enable_notify_events();
9122 g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
9123 #if !GTK_CHECK_VERSION(4, 0, 0)
9124 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
9125 #endif
9126 g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
9127 g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
9128 #if !GTK_CHECK_VERSION(4, 0, 0)
9129 g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
9130 #endif
9131 g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
9134 void reset_split_data()
9136 // reset overflow and allow it to be recalculated if necessary
9137 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
9138 m_bOverFlowBoxActive = false;
9139 m_nStartTabCount = 0;
9140 m_nEndTabCount = 0;
9143 virtual void remove_page(const OString& rIdent) override
9145 if (m_bOverFlowBoxActive)
9147 unsplit_notebooks();
9148 reset_split_data();
9151 unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
9152 if (nPageIndex < m_aPages.size())
9153 m_aPages.erase(m_aPages.begin() + nPageIndex);
9156 virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override
9158 if (m_bOverFlowBoxActive)
9160 unsplit_notebooks();
9161 reset_split_data();
9164 // reset overflow and allow it to be recalculated if necessary
9165 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
9166 m_bOverFlowBoxActive = false;
9168 insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos);
9171 virtual ~GtkInstanceNotebook() override
9173 if (m_nLaunchSplitTimeoutId)
9174 g_source_remove(m_nLaunchSplitTimeoutId);
9175 #if !GTK_CHECK_VERSION(4, 0, 0)
9176 if (m_nNotebookSizeAllocateSignalId)
9177 g_signal_handler_disconnect(m_pNotebook, m_nNotebookSizeAllocateSignalId);
9178 #else
9179 if (m_pLayout)
9181 // put it back how we found it initially
9182 notifying_layout_stop_watch(m_pLayout);
9184 #endif
9185 g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
9186 #if !GTK_CHECK_VERSION(4, 0, 0)
9187 g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
9188 #endif
9189 g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
9190 g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
9191 #if !GTK_CHECK_VERSION(4, 0, 0)
9192 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
9193 #else
9194 GtkWidget* pOverFlowWidget = GTK_WIDGET(m_pOverFlowNotebook);
9195 g_clear_pointer(&pOverFlowWidget, gtk_widget_unparent);
9196 #endif
9197 if (!m_pOverFlowBox)
9198 return;
9200 // put it back to how we found it initially
9201 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
9202 g_object_ref(m_pNotebook);
9203 container_remove(GTK_WIDGET(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
9204 container_add(GTK_WIDGET(pParent), GTK_WIDGET(m_pNotebook));
9205 g_object_unref(m_pNotebook);
9207 #if !GTK_CHECK_VERSION(4, 0, 0)
9208 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
9209 #else
9210 GtkWidget* pOverFlowBox = GTK_WIDGET(m_pOverFlowBox);
9211 g_clear_pointer(&pOverFlowBox, gtk_widget_unparent);
9212 #endif
9216 #if GTK_CHECK_VERSION(4, 0, 0)
9217 IMPL_LINK_NOARG(GtkInstanceNotebook, SizeAllocateHdl, void*, void)
9219 signal_notebook_size_allocate();
9221 #endif
9224 OUString vcl_font_to_css(const vcl::Font& rFont)
9226 OUStringBuffer sCSS;
9227 sCSS.append("font-family: \"" + rFont.GetFamilyName() + "\"; ");
9228 sCSS.append("font-size: " + OUString::number(rFont.GetFontSize().Height()) + "pt; ");
9229 switch (rFont.GetItalic())
9231 case ITALIC_NONE:
9232 sCSS.append("font-style: normal; ");
9233 break;
9234 case ITALIC_NORMAL:
9235 sCSS.append("font-style: italic; ");
9236 break;
9237 case ITALIC_OBLIQUE:
9238 sCSS.append("font-style: oblique; ");
9239 break;
9240 default:
9241 break;
9243 switch (rFont.GetWeight())
9245 case WEIGHT_ULTRALIGHT:
9246 sCSS.append("font-weight: 200; ");
9247 break;
9248 case WEIGHT_LIGHT:
9249 sCSS.append("font-weight: 300; ");
9250 break;
9251 case WEIGHT_NORMAL:
9252 sCSS.append("font-weight: 400; ");
9253 break;
9254 case WEIGHT_BOLD:
9255 sCSS.append("font-weight: 700; ");
9256 break;
9257 case WEIGHT_ULTRABOLD:
9258 sCSS.append("font-weight: 800; ");
9259 break;
9260 default:
9261 break;
9263 switch (rFont.GetWidthType())
9265 case WIDTH_ULTRA_CONDENSED:
9266 sCSS.append("font-stretch: ultra-condensed; ");
9267 break;
9268 case WIDTH_EXTRA_CONDENSED:
9269 sCSS.append("font-stretch: extra-condensed; ");
9270 break;
9271 case WIDTH_CONDENSED:
9272 sCSS.append("font-stretch: condensed; ");
9273 break;
9274 case WIDTH_SEMI_CONDENSED:
9275 sCSS.append("font-stretch: semi-condensed; ");
9276 break;
9277 case WIDTH_NORMAL:
9278 sCSS.append("font-stretch: normal; ");
9279 break;
9280 case WIDTH_SEMI_EXPANDED:
9281 sCSS.append("font-stretch: semi-expanded; ");
9282 break;
9283 case WIDTH_EXPANDED:
9284 sCSS.append("font-stretch: expanded; ");
9285 break;
9286 case WIDTH_EXTRA_EXPANDED:
9287 sCSS.append("font-stretch: extra-expanded; ");
9288 break;
9289 case WIDTH_ULTRA_EXPANDED:
9290 sCSS.append("font-stretch: ultra-expanded; ");
9291 break;
9292 default:
9293 break;
9295 return sCSS.toString();
9298 void update_attr_list(PangoAttrList* pAttrList, const vcl::Font& rFont)
9300 pango_attr_list_change(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
9301 pango_attr_list_change(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
9303 switch (rFont.GetItalic())
9305 case ITALIC_NONE:
9306 pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
9307 break;
9308 case ITALIC_NORMAL:
9309 pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
9310 break;
9311 case ITALIC_OBLIQUE:
9312 pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
9313 break;
9314 default:
9315 break;
9317 switch (rFont.GetWeight())
9319 case WEIGHT_ULTRALIGHT:
9320 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
9321 break;
9322 case WEIGHT_LIGHT:
9323 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
9324 break;
9325 case WEIGHT_NORMAL:
9326 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
9327 break;
9328 case WEIGHT_BOLD:
9329 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
9330 break;
9331 case WEIGHT_ULTRABOLD:
9332 pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
9333 break;
9334 default:
9335 break;
9337 switch (rFont.GetWidthType())
9339 case WIDTH_ULTRA_CONDENSED:
9340 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
9341 break;
9342 case WIDTH_EXTRA_CONDENSED:
9343 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
9344 break;
9345 case WIDTH_CONDENSED:
9346 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
9347 break;
9348 case WIDTH_SEMI_CONDENSED:
9349 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
9350 break;
9351 case WIDTH_NORMAL:
9352 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
9353 break;
9354 case WIDTH_SEMI_EXPANDED:
9355 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
9356 break;
9357 case WIDTH_EXPANDED:
9358 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
9359 break;
9360 case WIDTH_EXTRA_EXPANDED:
9361 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
9362 break;
9363 case WIDTH_ULTRA_EXPANDED:
9364 pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
9365 break;
9366 default:
9367 break;
9371 gboolean filter_pango_attrs(PangoAttribute *attr, gpointer data)
9373 PangoAttrType* pFilterAttrs = static_cast<PangoAttrType*>(data);
9374 while (*pFilterAttrs)
9376 if (attr->klass->type == *pFilterAttrs)
9377 return true;
9378 ++pFilterAttrs;
9380 return false;
9383 void set_font(GtkLabel* pLabel, const vcl::Font& rFont)
9385 PangoAttrList* pOrigList = gtk_label_get_attributes(pLabel);
9386 PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
9388 if (pOrigList)
9390 // tdf#143443 remove both PANGO_ATTR_ABSOLUTE_SIZE and PANGO_ATTR_SIZE
9391 // because pango_attr_list_change(..., pango_attr_size_new...) isn't
9392 // sufficient on its own to ensure a new size sticks.
9393 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_ABSOLUTE_SIZE, PANGO_ATTR_SIZE, PANGO_ATTR_INVALID};
9394 PangoAttrList* pRemovedAttrs = pango_attr_list_filter(pAttrList, filter_pango_attrs, &aFilterAttrs);
9395 pango_attr_list_unref(pRemovedAttrs);
9398 update_attr_list(pAttrList, rFont);
9399 gtk_label_set_attributes(pLabel, pAttrList);
9400 pango_attr_list_unref(pAttrList);
9405 namespace {
9407 class WidgetBackground
9409 private:
9410 GtkWidget* m_pWidget;
9411 GtkCssProvider* m_pCustomCssProvider;
9412 std::unique_ptr<utl::TempFile> m_xCustomImage;
9414 public:
9415 // See: https://developer.gnome.org/Buttons/
9416 void use_custom_content(const VirtualDevice* pDevice)
9418 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
9420 if (m_pCustomCssProvider)
9422 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider));
9423 m_pCustomCssProvider = nullptr;
9426 m_xCustomImage.reset();
9428 if (!pDevice)
9429 return;
9431 m_xCustomImage.reset(new utl::TempFile);
9432 m_xCustomImage->EnableKillingFile(true);
9434 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
9435 Size aSize = pDevice->GetOutputSizePixel();
9436 cairo_surface_write_to_png(surface, OUStringToOString(m_xCustomImage->GetFileName(), osl_getThreadTextEncoding()).getStr());
9438 m_pCustomCssProvider = gtk_css_provider_new();
9439 OUString aBuffer = "* { background-image: url(\"" + m_xCustomImage->GetURL() + "\"); "
9440 "background-size: " + OUString::number(aSize.Width()) + "px " + OUString::number(aSize.Height()) + "px; "
9441 "border-radius: 0; border-width: 0; }";
9442 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
9443 css_provider_load_from_data(m_pCustomCssProvider, aResult.getStr(), aResult.getLength());
9444 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider),
9445 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
9448 public:
9449 WidgetBackground(GtkWidget* pWidget)
9450 : m_pWidget(pWidget)
9451 , m_pCustomCssProvider(nullptr)
9455 ~WidgetBackground()
9457 if (m_pCustomCssProvider)
9458 use_custom_content(nullptr);
9459 assert(!m_pCustomCssProvider);
9463 class WidgetFont
9465 private:
9466 GtkWidget* m_pWidget;
9467 GtkCssProvider* m_pFontCssProvider;
9468 std::unique_ptr<vcl::Font> m_xFont;
9469 public:
9470 WidgetFont(GtkWidget* pWidget)
9471 : m_pWidget(pWidget)
9472 , m_pFontCssProvider(nullptr)
9476 void use_custom_font(const vcl::Font* pFont, std::u16string_view rCSSSelector)
9478 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
9479 if (m_pFontCssProvider)
9481 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider));
9482 m_pFontCssProvider = nullptr;
9485 m_xFont.reset();
9487 if (!pFont)
9488 return;
9490 m_xFont.reset(new vcl::Font(*pFont));
9491 m_pFontCssProvider = gtk_css_provider_new();
9492 OUString aBuffer = rCSSSelector + OUString::Concat(" { ") + vcl_font_to_css(*pFont) + OUString::Concat(" }");
9493 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
9494 css_provider_load_from_data(m_pFontCssProvider, aResult.getStr(), aResult.getLength());
9495 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider),
9496 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
9499 const vcl::Font* get_custom_font() const
9501 return m_xFont.get();
9504 ~WidgetFont()
9506 if (m_pFontCssProvider)
9507 use_custom_font(nullptr, u"");
9508 assert(!m_pFontCssProvider);
9512 class GtkInstanceButton : public GtkInstanceWidget, public virtual weld::Button
9514 private:
9515 GtkButton* m_pButton;
9516 gulong m_nSignalId;
9517 std::optional<vcl::Font> m_xFont;
9518 WidgetBackground m_aCustomBackground;
9520 static void signalClicked(GtkButton*, gpointer widget)
9522 GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
9523 SolarMutexGuard aGuard;
9524 pThis->signal_clicked();
9527 virtual void ensureMouseEventWidget() override
9529 // The GtkButton is sufficient to get mouse events without an intermediate GtkEventBox
9530 if (!m_pMouseEventBox)
9531 m_pMouseEventBox = m_pWidget;
9534 public:
9535 GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9536 : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
9537 , m_pButton(pButton)
9538 , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
9539 , m_aCustomBackground(GTK_WIDGET(pButton))
9541 g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
9544 virtual void set_label(const OUString& rText) override
9546 ::button_set_label(m_pButton, rText);
9549 virtual void set_image(VirtualDevice* pDevice) override
9551 ::button_set_image(m_pButton, pDevice);
9554 virtual void set_from_icon_name(const OUString& rIconName) override
9556 ::button_set_from_icon_name(m_pButton, rIconName);
9559 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
9561 ::button_set_image(m_pButton, rImage);
9564 virtual void set_custom_button(VirtualDevice* pDevice) override
9566 m_aCustomBackground.use_custom_content(pDevice);
9569 virtual OUString get_label() const override
9571 return ::button_get_label(m_pButton);
9574 virtual void set_font(const vcl::Font& rFont) override
9576 m_xFont = rFont;
9577 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pButton));
9578 ::set_font(pChild, rFont);
9581 virtual vcl::Font get_font() override
9583 if (m_xFont)
9584 return *m_xFont;
9585 return GtkInstanceWidget::get_font();
9588 // allow us to block buttons with click handlers making dialogs return a response
9589 bool has_click_handler() const
9591 return m_aClickHdl.IsSet();
9594 void clear_click_handler()
9596 m_aClickHdl = Link<Button&, void>();
9599 virtual void disable_notify_events() override
9601 g_signal_handler_block(m_pButton, m_nSignalId);
9602 GtkInstanceWidget::disable_notify_events();
9605 virtual void enable_notify_events() override
9607 GtkInstanceWidget::enable_notify_events();
9608 g_signal_handler_unblock(m_pButton, m_nSignalId);
9611 virtual ~GtkInstanceButton() override
9613 g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
9614 g_signal_handler_disconnect(m_pButton, m_nSignalId);
9620 void GtkInstanceDialog::asyncresponse(gint ret)
9622 SolarMutexGuard aGuard;
9624 if (ret == GTK_RESPONSE_HELP)
9626 help();
9627 return;
9630 GtkInstanceButton* pClickHandler = has_click_handler(ret);
9631 if (pClickHandler)
9633 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
9634 if (ret == GTK_RESPONSE_DELETE_EVENT)
9635 close(false);
9636 return;
9639 if (get_modal())
9640 m_aDialogRun.dec_modal_count();
9641 hide();
9643 // move the self pointer, otherwise it might be de-allocated by time we try to reset it
9644 auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
9645 auto xDialogController = std::move(m_xDialogController);
9646 auto aFunc = std::move(m_aFunc);
9648 auto nResponseSignalId = m_nResponseSignalId;
9649 auto nCancelSignalId = m_nCancelSignalId;
9650 auto nSignalDeleteId = m_nSignalDeleteId;
9651 m_nResponseSignalId = 0;
9652 m_nCancelSignalId = 0;
9653 m_nSignalDeleteId = 0;
9655 if (aFunc)
9656 aFunc(GtkToVcl(ret));
9658 if (nResponseSignalId)
9659 g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
9660 if (nCancelSignalId)
9661 g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
9662 if (nSignalDeleteId)
9663 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
9665 xDialogController.reset();
9666 xRunAsyncSelf.reset();
9669 int GtkInstanceDialog::run()
9671 #if !GTK_CHECK_VERSION(4, 0, 0)
9672 if (GTK_IS_DIALOG(m_pDialog))
9673 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
9674 #endif
9675 int ret;
9676 while (true)
9678 ret = m_aDialogRun.run();
9679 if (ret == GTK_RESPONSE_HELP)
9681 help();
9682 continue;
9684 else if (has_click_handler(ret))
9685 continue;
9686 break;
9688 hide();
9689 return GtkToVcl(ret);
9692 weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
9694 GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
9695 if (!pButton)
9696 return nullptr;
9697 return new GtkInstanceButton(pButton, m_pBuilder, false);
9700 void GtkInstanceDialog::response(int nResponse)
9702 int nGtkResponse = VclToGtk(nResponse);
9703 //unblock this response now when activated through code
9704 if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
9706 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
9707 GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
9708 if (pButton)
9709 pButton->clear_click_handler();
9711 if (GTK_IS_DIALOG(m_pDialog))
9712 gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
9713 else if (GTK_IS_ASSISTANT(m_pDialog))
9715 if (!m_aDialogRun.loop_is_running())
9716 asyncresponse(nGtkResponse);
9717 else
9719 m_aDialogRun.m_nResponseId = nGtkResponse;
9720 m_aDialogRun.loop_quit();
9725 void GtkInstanceDialog::close(bool bCloseSignal)
9727 GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
9728 if (pClickHandler)
9730 if (bCloseSignal)
9731 g_signal_stop_emission_by_name(m_pDialog, "close");
9732 // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
9733 // act as if cancel button was pressed
9734 pClickHandler->clicked();
9735 return;
9737 response(RET_CANCEL);
9740 GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
9742 GtkInstanceButton* pButton = nullptr;
9743 // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
9744 nResponse = VclToGtk(GtkToVcl(nResponse));
9745 if (GtkButton* pWidget = get_widget_for_response(nResponse))
9747 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
9748 pButton = static_cast<GtkInstanceButton*>(pData);
9749 if (pButton && !pButton->has_click_handler())
9750 pButton = nullptr;
9752 return pButton;
9755 namespace {
9757 class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
9759 protected:
9760 GtkToggleButton* m_pToggleButton;
9761 gulong m_nToggledSignalId;
9762 private:
9763 static void signalToggled(GtkToggleButton*, gpointer widget)
9765 GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
9766 SolarMutexGuard aGuard;
9767 pThis->signal_toggled();
9769 public:
9770 GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9771 : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
9772 , m_pToggleButton(pButton)
9773 , m_nToggledSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
9777 virtual void set_active(bool active) override
9779 disable_notify_events();
9780 set_inconsistent(false);
9781 gtk_toggle_button_set_active(m_pToggleButton, active);
9782 enable_notify_events();
9785 virtual bool get_active() const override
9787 return gtk_toggle_button_get_active(m_pToggleButton);
9790 virtual void set_inconsistent(bool inconsistent) override
9792 #if GTK_CHECK_VERSION(4, 0, 0)
9793 if (inconsistent)
9794 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT, false);
9795 else
9796 gtk_widget_unset_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT);
9797 #else
9798 gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
9799 #endif
9802 virtual bool get_inconsistent() const override
9804 #if GTK_CHECK_VERSION(4, 0, 0)
9805 return gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)) & GTK_STATE_FLAG_INCONSISTENT;
9806 #else
9807 return gtk_toggle_button_get_inconsistent(m_pToggleButton);
9808 #endif
9811 virtual void disable_notify_events() override
9813 g_signal_handler_block(m_pToggleButton, m_nToggledSignalId);
9814 GtkInstanceButton::disable_notify_events();
9817 virtual void enable_notify_events() override
9819 GtkInstanceButton::enable_notify_events();
9820 g_signal_handler_unblock(m_pToggleButton, m_nToggledSignalId);
9823 virtual ~GtkInstanceToggleButton() override
9825 g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
9831 #if !GTK_CHECK_VERSION(4, 0, 0)
9833 namespace {
9835 void do_grab(GtkWidget* pWidget)
9837 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
9838 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
9839 gdk_seat_grab(pSeat, widget_get_surface(pWidget),
9840 GDK_SEAT_CAPABILITY_KEYBOARD, true, nullptr, nullptr, nullptr, nullptr);
9843 void do_ungrab(GtkWidget* pWidget)
9845 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
9846 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
9847 gdk_seat_ungrab(pSeat);
9850 GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
9851 weld::Placement ePlace, bool bTryShrink)
9853 //place the toplevel just below its launcher button
9854 GtkWidget* pToplevel = widget_get_toplevel(pMenuButton);
9855 gtk_coord x, y, absx, absy;
9856 gtk_widget_translate_coordinates(pMenuButton, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
9857 GdkSurface* pWindow = widget_get_surface(pToplevel);
9858 gdk_window_get_position(pWindow, &absx, &absy);
9860 x += absx;
9861 y += absy;
9863 gint nButtonHeight = rAnchor.height;
9864 gint nButtonWidth = rAnchor.width;
9865 if (ePlace == weld::Placement::Under)
9866 y += nButtonHeight;
9867 else
9868 x += nButtonWidth;
9870 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
9871 gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
9873 gint nMenuWidth, nMenuHeight;
9874 gtk_widget_get_size_request(GTK_WIDGET(pMenu), &nMenuWidth, &nMenuHeight);
9876 if (nMenuWidth == -1 || nMenuHeight == -1)
9878 GtkRequisition req;
9879 gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
9880 if (nMenuWidth == -1)
9881 nMenuWidth = req.width;
9882 if (nMenuHeight == -1)
9883 nMenuHeight = req.height;
9886 bool bSwapForRTL = SwapForRTL(pMenuButton);
9887 if (bSwapForRTL)
9889 if (ePlace == weld::Placement::Under)
9890 x += nButtonWidth;
9891 else
9892 x -= nButtonWidth;
9893 x -= nMenuWidth;
9896 tools::Rectangle aWorkArea(::get_monitor_workarea(pMenuButton));
9898 // shrink it a little, I find it reassuring to see a little margin with a
9899 // long menu to know the menu is fully on screen
9900 aWorkArea.AdjustTop(8);
9901 aWorkArea.AdjustBottom(-8);
9902 aWorkArea.AdjustLeft(8);
9903 aWorkArea.AdjustRight(-8);
9905 GtkPositionType ePosUsed;
9907 if (ePlace == weld::Placement::Under)
9909 gint endx = x + nMenuWidth;
9910 if (endx > aWorkArea.Right())
9911 x -= endx - aWorkArea.Right();
9912 if (x < 0)
9913 x = 0;
9915 ePosUsed = GTK_POS_BOTTOM;
9916 gint endy = y + nMenuHeight;
9917 gint nMissingBelow = endy - aWorkArea.Bottom();
9918 if (nMissingBelow > 0)
9920 gint nNewY = y - (nButtonHeight + nMenuHeight);
9921 gint nMissingAbove = aWorkArea.Top() - nNewY;
9922 if (nMissingAbove > 0)
9924 if (bTryShrink)
9926 if (nMissingBelow <= nMissingAbove)
9927 nMenuHeight -= nMissingBelow;
9928 else
9930 nMenuHeight -= nMissingAbove;
9931 y = aWorkArea.Top();
9932 ePosUsed = GTK_POS_TOP;
9934 gtk_widget_set_size_request(GTK_WIDGET(pMenu), nMenuWidth, nMenuHeight);
9936 else
9938 if (nMissingBelow <= nMissingAbove)
9939 y -= nMissingBelow;
9940 else
9942 y = aWorkArea.Top();
9943 ePosUsed = GTK_POS_TOP;
9947 else
9949 y = nNewY;
9950 ePosUsed = GTK_POS_TOP;
9954 else
9956 if (!bSwapForRTL)
9958 ePosUsed = GTK_POS_RIGHT;
9959 gint endx = x + nMenuWidth;
9960 gint nMissingAfter = endx - aWorkArea.Right();
9961 if (nMissingAfter > 0)
9963 gint nNewX = x - (nButtonWidth + nMenuWidth);
9964 if (nNewX >= aWorkArea.Left())
9966 x = nNewX;
9967 ePosUsed = GTK_POS_LEFT;
9971 else
9973 ePosUsed = GTK_POS_LEFT;
9974 gint startx = x;
9975 gint nMissingBefore = aWorkArea.Left() - startx;
9976 if (nMissingBefore > 0)
9978 gint nNewX = x + (nButtonWidth + nMenuWidth);
9979 if (nNewX + nMenuWidth < aWorkArea.Right())
9981 x = nNewX;
9982 ePosUsed = GTK_POS_RIGHT;
9988 gtk_window_move(pMenu, x, y);
9990 return ePosUsed;
9993 bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu, const GdkRectangle &rAnchor,
9994 weld::Placement ePlace, bool bTryShrink)
9996 static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
9997 GdkGravity, GdkAnchorHints, gint, gint)>(
9998 dlsym(nullptr, "gdk_window_move_to_rect"));
9999 if (!window_move_to_rect)
10000 return false;
10002 // under wayland gdk_window_move_to_rect works great for me, but in my current
10003 // gtk 3.24 under X it leaves part of long menus outside the work area
10004 GdkDisplay *pDisplay = gtk_widget_get_display(pComboBox);
10005 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
10006 return false;
10008 //place the toplevel just below its launcher button
10009 GtkWidget* pToplevel = widget_get_toplevel(pComboBox);
10010 gtk_coord x, y;
10011 gtk_widget_translate_coordinates(pComboBox, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
10013 gtk_widget_realize(GTK_WIDGET(pMenu));
10014 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
10015 gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
10017 bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
10019 GdkGravity rect_anchor;
10020 GdkGravity menu_anchor;
10022 if (ePlace == weld::Placement::Under)
10024 rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
10025 menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
10027 else
10029 rect_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_EAST : GDK_GRAVITY_NORTH_WEST;
10030 menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
10033 GdkAnchorHints anchor_hints = static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE);
10034 if (bTryShrink)
10035 anchor_hints = static_cast<GdkAnchorHints>(anchor_hints | GDK_ANCHOR_RESIZE);
10036 GdkRectangle rect {x, y, rAnchor.width, rAnchor.height};
10037 GdkSurface* toplevel = widget_get_surface(GTK_WIDGET(pMenu));
10039 window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor, anchor_hints,
10040 0, 0);
10042 return true;
10045 GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
10046 weld::Placement ePlace, bool bTryShrink)
10048 // we only use ePosUsed in the replacement-for-X-popover case of a
10049 // MenuButton, so we only need it when show_menu_older_gtk is used
10050 GtkPositionType ePosUsed = GTK_POS_BOTTOM;
10052 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
10053 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
10054 // implemented as popups, which means that we cannot show any popup if there is a
10055 // visible tooltip.
10056 GtkWidget* pParent = widget_get_toplevel(pMenuButton);
10057 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
10058 if (pFrame)
10060 // hide any current tooltip
10061 pFrame->HideTooltip();
10062 // don't allow any more to appear until menu is dismissed
10063 pFrame->BlockTooltip();
10066 // try with gdk_window_move_to_rect, but if that's not available, try without
10067 if (!show_menu_newer_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink))
10068 ePosUsed = show_menu_older_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink);
10069 gtk_widget_show_all(GTK_WIDGET(pMenu));
10070 gtk_widget_grab_focus(GTK_WIDGET(pMenu));
10071 do_grab(GTK_WIDGET(pMenu));
10073 return ePosUsed;
10077 #endif
10079 namespace {
10081 #if !GTK_CHECK_VERSION(4, 0, 0)
10082 bool button_event_is_outside(GtkWidget* pMenuHack, GdkEventButton* pEvent)
10084 //we want to pop down if the button was released outside our popup
10085 gdouble x = pEvent->x_root;
10086 gdouble y = pEvent->y_root;
10088 gint window_x, window_y;
10089 GdkSurface* pWindow = widget_get_surface(pMenuHack);
10090 gdk_window_get_position(pWindow, &window_x, &window_y);
10092 GtkAllocation alloc;
10093 gtk_widget_get_allocation(pMenuHack, &alloc);
10094 gint x1 = window_x;
10095 gint y1 = window_y;
10096 gint x2 = x1 + alloc.width;
10097 gint y2 = y1 + alloc.height;
10099 if (x > x1 && x < x2 && y > y1 && y < y2)
10100 return false;
10102 return true;
10105 GtkPositionType MovePopoverContentsToWindow(GtkWidget* pPopover, GtkWindow* pMenuHack, GtkWidget* pAnchor,
10106 const GdkRectangle& rAnchor, weld::Placement ePlace)
10108 //set border width
10109 gtk_container_set_border_width(GTK_CONTAINER(pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(pPopover)));
10111 //steal popover contents and smuggle into toplevel display window
10112 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pPopover));
10113 g_object_ref(pChild);
10114 gtk_container_remove(GTK_CONTAINER(pPopover), pChild);
10115 gtk_container_add(GTK_CONTAINER(pMenuHack), pChild);
10116 g_object_unref(pChild);
10118 GtkPositionType eRet = show_menu(pAnchor, pMenuHack, rAnchor, ePlace, false);
10120 gtk_grab_add(GTK_WIDGET(pMenuHack));
10122 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
10123 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
10125 return eRet;
10128 void MoveWindowContentsToPopover(GtkWindow* pMenuHack, GtkWidget* pPopover, GtkWidget* pAnchor)
10130 bool bHadFocus = gtk_window_has_toplevel_focus(pMenuHack);
10132 do_ungrab(GTK_WIDGET(pMenuHack));
10134 gtk_grab_remove(GTK_WIDGET(pMenuHack));
10136 gtk_widget_hide(GTK_WIDGET(pMenuHack));
10137 //put contents back from where the came from
10138 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pMenuHack));
10139 g_object_ref(pChild);
10140 gtk_container_remove(GTK_CONTAINER(pMenuHack), pChild);
10141 gtk_container_add(GTK_CONTAINER(pPopover), pChild);
10142 g_object_unref(pChild);
10144 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
10145 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
10147 // so gdk_window_move_to_rect will work again the next time
10148 gtk_widget_unrealize(GTK_WIDGET(pMenuHack));
10150 gtk_widget_set_size_request(GTK_WIDGET(pMenuHack), -1, -1);
10152 // undo show_menu tooltip blocking
10153 GtkWidget* pParent = widget_get_toplevel(pAnchor);
10154 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
10155 if (pFrame)
10156 pFrame->UnblockTooltip();
10158 if (bHadFocus)
10160 GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
10161 void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
10162 if (pParentIsPopover)
10163 do_grab(pAnchor);
10164 gtk_widget_grab_focus(pAnchor);
10168 #endif
10170 /* four types of uses of this
10171 a) textual menubutton, always with pan-down symbol, e.g. math, format, font, modify
10172 b) image + text, always with additional pan-down symbol, e.g. writer, format, watermark
10173 c) gear menu, never with text and without pan-down symbol where there is a replacement
10174 icon for pan-down, e.g. file, new, templates
10175 d) image, always with additional pan-down symbol, e.g. calc, insert, header/footer */
10176 #if !GTK_CHECK_VERSION(4, 0, 0)
10177 class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
10178 #else
10179 class GtkInstanceMenuButton : public GtkInstanceWidget, public MenuHelper, public virtual weld::MenuButton
10180 #endif
10182 protected:
10183 GtkMenuButton* m_pMenuButton;
10184 private:
10185 GtkBox* m_pBox;
10186 #if !GTK_CHECK_VERSION(4, 0, 0)
10187 GtkImage* m_pImage;
10188 #else
10189 GtkPicture* m_pImage;
10190 GtkToggleButton* m_pMenuButtonToggleButton;
10191 #endif
10192 GtkWidget* m_pLabel;
10193 #if !GTK_CHECK_VERSION(4, 0, 0)
10194 //popover cannot escape dialog under X so stick up own window instead
10195 GtkWindow* m_pMenuHack;
10196 //when doing so, if it's a toolbar menubutton align the menu to the full toolitem
10197 GtkWidget* m_pMenuHackAlign;
10198 bool m_nButtonPressSeen;
10199 gulong m_nSignalId;
10200 #endif
10201 GtkWidget* m_pPopover;
10202 #if GTK_CHECK_VERSION(4, 0, 0)
10203 gulong m_nToggledSignalId;
10204 std::optional<vcl::Font> m_xFont;
10205 WidgetBackground m_aCustomBackground;
10206 #endif
10208 #if !GTK_CHECK_VERSION(4, 0, 0)
10209 static void signalMenuButtonToggled(GtkWidget*, gpointer widget)
10211 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10212 SolarMutexGuard aGuard;
10213 pThis->menu_toggled();
10215 #endif
10217 #if !GTK_CHECK_VERSION(4, 0, 0)
10218 void menu_toggled()
10220 if (!m_pMenuHack)
10221 return;
10222 if (!get_active())
10224 m_nButtonPressSeen = false;
10225 MoveWindowContentsToPopover(m_pMenuHack, m_pPopover, GTK_WIDGET(m_pMenuButton));
10227 else
10229 GtkWidget* pAnchor = m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton);
10230 GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pAnchor), gtk_widget_get_allocated_height(pAnchor) };
10231 GtkPositionType ePosUsed = MovePopoverContentsToWindow(m_pPopover, m_pMenuHack, pAnchor, aAnchor, weld::Placement::Under);
10232 // tdf#132540 keep the placeholder popover on this same side as the replacement menu
10233 gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton), ePosUsed);
10236 #endif
10238 #if !GTK_CHECK_VERSION(4, 0, 0)
10239 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
10241 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10242 pThis->grab_broken(pEvent);
10245 void grab_broken(const GdkEventGrabBroken *event)
10247 if (event->grab_window == nullptr)
10249 set_active(false);
10251 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
10253 //try and regrab, so when we lose the grab to the menu of the color palette
10254 //combobox we regain it so the color palette doesn't itself disappear on next
10255 //click on the color palette combobox
10256 do_grab(GTK_WIDGET(m_pMenuHack));
10260 static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
10262 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10263 pThis->m_nButtonPressSeen = true;
10264 return false;
10267 static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
10269 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10270 if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
10271 pThis->set_active(false);
10272 return false;
10275 static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
10277 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10278 return pThis->key_press(pEvent);
10281 bool key_press(const GdkEventKey* pEvent)
10283 if (pEvent->keyval == GDK_KEY_Escape)
10285 set_active(false);
10286 return true;
10288 return false;
10290 #endif
10292 void ensure_image_widget()
10294 if (m_pImage)
10295 return;
10297 #if !GTK_CHECK_VERSION(4, 0, 0)
10298 m_pImage = GTK_IMAGE(gtk_image_new());
10299 gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
10300 gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
10301 #else
10302 m_pImage = GTK_PICTURE(gtk_picture_new());
10303 gtk_widget_set_halign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
10304 gtk_widget_set_valign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
10305 gtk_box_prepend(m_pBox, GTK_WIDGET(m_pImage));
10306 gtk_widget_set_halign(m_pLabel, GTK_ALIGN_START);
10307 #endif
10308 gtk_widget_show(GTK_WIDGET(m_pImage));
10311 static void signalFlagsChanged(GtkToggleButton* pToggleButton, GtkStateFlags flags, gpointer widget)
10313 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
10314 bool bOldChecked = flags & GTK_STATE_FLAG_CHECKED;
10315 bool bNewChecked = gtk_widget_get_state_flags(GTK_WIDGET(pToggleButton)) & GTK_STATE_FLAG_CHECKED;
10316 if (bOldChecked == bNewChecked)
10317 return;
10318 if (bOldChecked && gtk_widget_get_focus_on_click(GTK_WIDGET(pToggleButton)))
10320 // grab focus back to the toggle button if the menu was popped down
10321 gtk_widget_grab_focus(GTK_WIDGET(pToggleButton));
10323 SolarMutexGuard aGuard;
10324 pThis->signal_toggled();
10327 public:
10328 #if !GTK_CHECK_VERSION(4, 0, 0)
10329 GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10330 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
10331 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
10332 #else
10333 GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10334 : GtkInstanceWidget(GTK_WIDGET(pMenuButton), pBuilder, bTakeOwnership)
10335 , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
10336 #endif
10337 , m_pMenuButton(pMenuButton)
10338 , m_pImage(nullptr)
10339 #if !GTK_CHECK_VERSION(4, 0, 0)
10340 , m_pMenuHack(nullptr)
10341 , m_pMenuHackAlign(pMenuAlign)
10342 , m_nButtonPressSeen(true)
10343 , m_nSignalId(0)
10344 #endif
10345 , m_pPopover(nullptr)
10346 #if GTK_CHECK_VERSION(4, 0, 0)
10347 , m_aCustomBackground(GTK_WIDGET(pMenuButton))
10348 #endif
10350 #if !GTK_CHECK_VERSION(4, 0, 0)
10351 // tdf#142924 "toggled" is too late to use to populate changes to the menu,
10352 // so use "state-flag-changed" on GTK_STATE_FLAG_CHECKED instead which
10353 // happens before "toggled"
10354 g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
10355 m_nToggledSignalId = g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
10357 m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
10358 m_pImage = get_image_widget(GTK_WIDGET(m_pMenuButton));
10359 m_pBox = formatMenuButton(m_pLabel);
10360 #else
10361 GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
10362 assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
10363 m_pMenuButtonToggleButton = GTK_TOGGLE_BUTTON(pToggleButton);
10364 m_nToggledSignalId = g_signal_connect(m_pMenuButtonToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
10365 GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(pToggleButton));
10366 m_pBox = GTK_IS_BOX(pChild) ? GTK_BOX(pChild) : nullptr;
10367 m_pLabel = m_pBox ? gtk_widget_get_first_child(GTK_WIDGET(m_pBox)) : nullptr;
10368 (void)pMenuAlign;
10369 #endif
10371 #if GTK_CHECK_VERSION(4, 0, 0)
10372 gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton), "menu", m_pActionGroup);
10374 update_action_group_from_popover_model();
10375 #endif
10378 virtual void set_size_request(int nWidth, int nHeight) override
10380 // tweak the label to get a narrower size to stick
10381 if (GTK_IS_LABEL(m_pLabel))
10382 gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
10383 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
10386 virtual void set_label(const OUString& rText) override
10388 ::set_label(GTK_LABEL(m_pLabel), rText);
10391 virtual OUString get_label() const override
10393 return ::get_label(GTK_LABEL(m_pLabel));
10396 virtual void set_image(VirtualDevice* pDevice) override
10398 ensure_image_widget();
10399 #if GTK_CHECK_VERSION(4, 0, 0)
10400 picture_set_from_virtual_device(m_pImage, pDevice);
10401 #else
10402 image_set_from_virtual_device(m_pImage, pDevice);
10403 #endif
10406 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
10408 ensure_image_widget();
10409 #if GTK_CHECK_VERSION(4, 0, 0)
10410 picture_set_from_xgraphic(m_pImage, rImage);
10411 #else
10412 image_set_from_xgraphic(m_pImage, rImage);
10413 #endif
10416 #if GTK_CHECK_VERSION(4, 0, 0)
10417 virtual void set_from_icon_name(const OUString& rIconName) override
10419 ensure_image_widget();
10420 picture_set_from_icon_name(m_pImage, rIconName);
10423 virtual void set_custom_button(VirtualDevice* pDevice) override
10425 m_aCustomBackground.use_custom_content(pDevice);
10428 virtual void set_inconsistent(bool inconsistent) override
10430 if (inconsistent)
10431 gtk_widget_set_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT, false);
10432 else
10433 gtk_widget_unset_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT);
10436 virtual bool get_inconsistent() const override
10438 return gtk_widget_get_state_flags(GTK_WIDGET(m_pMenuButton)) & GTK_STATE_FLAG_INCONSISTENT;
10441 virtual void set_active(bool active) override
10443 disable_notify_events();
10444 set_inconsistent(false);
10445 if (active)
10446 gtk_menu_button_popup(m_pMenuButton);
10447 else
10448 gtk_menu_button_popdown(m_pMenuButton);
10449 enable_notify_events();
10452 virtual bool get_active() const override
10454 GtkPopover* pPopover = gtk_menu_button_get_popover(m_pMenuButton);
10455 return pPopover && gtk_widget_get_visible(GTK_WIDGET(pPopover));
10458 virtual void set_font(const vcl::Font& rFont) override
10460 m_xFont = rFont;
10461 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pMenuButton));
10462 ::set_font(pChild, rFont);
10465 virtual vcl::Font get_font() override
10467 if (m_xFont)
10468 return *m_xFont;
10469 return GtkInstanceWidget::get_font();
10471 #else
10472 virtual void set_active(bool bActive) override
10474 bool bWasActive = get_active();
10475 GtkInstanceToggleButton::set_active(bActive);
10476 if (bWasActive && !bActive && gtk_widget_get_focus_on_click(GTK_WIDGET(m_pMenuButton)))
10478 // grab focus back to the toggle button if the menu was popped down
10479 gtk_widget_grab_focus(GTK_WIDGET(m_pMenuButton));
10482 #endif
10484 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
10485 const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
10487 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
10490 virtual void insert_separator(int pos, const OUString& rId) override
10492 MenuHelper::insert_separator(pos, rId);
10495 virtual void remove_item(const OString& rId) override
10497 MenuHelper::remove_item(rId);
10500 virtual void clear() override
10502 MenuHelper::clear_items();
10505 virtual void set_item_active(const OString& rIdent, bool bActive) override
10507 MenuHelper::set_item_active(rIdent, bActive);
10510 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
10512 MenuHelper::set_item_sensitive(rIdent, bSensitive);
10515 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
10517 MenuHelper::set_item_label(rIdent, rLabel);
10520 virtual OUString get_item_label(const OString& rIdent) const override
10522 return MenuHelper::get_item_label(rIdent);
10525 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
10527 MenuHelper::set_item_visible(rIdent, bVisible);
10530 virtual void signal_item_activate(const OString& rIdent) override
10532 signal_selected(rIdent);
10535 virtual void set_popover(weld::Widget* pPopover) override
10537 GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
10538 m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
10540 #if GTK_CHECK_VERSION(4, 0, 0)
10541 gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
10542 update_action_group_from_popover_model();
10543 return;
10544 #else
10546 if (!m_pPopover)
10548 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
10549 return;
10552 if (!m_pMenuHack)
10554 //under wayland a Popover will work to "escape" the parent dialog, not
10555 //so under X, so come up with this hack to use a raw GtkWindow
10556 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
10557 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay) && gtk_popover_get_constrain_to(GTK_POPOVER(m_pPopover)) == GTK_POPOVER_CONSTRAINT_NONE)
10559 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
10560 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
10561 // See writer "format, watermark" for true here. Can't interact with the replacement popover otherwise.
10562 gtk_window_set_modal(m_pMenuHack, true);
10563 gtk_window_set_resizable(m_pMenuHack, false);
10564 m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalMenuButtonToggled), this);
10565 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
10566 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
10567 g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
10568 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
10572 if (m_pMenuHack)
10574 GtkWidget* pPlaceHolder = gtk_popover_new(GTK_WIDGET(m_pMenuButton));
10575 gtk_popover_set_transitions_enabled(GTK_POPOVER(pPlaceHolder), false);
10577 // tdf#132540 theme the unwanted popover into invisibility
10578 GtkStyleContext *pPopoverContext = gtk_widget_get_style_context(pPlaceHolder);
10579 GtkCssProvider *pProvider = gtk_css_provider_new();
10580 static const gchar data[] = "popover { box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0; border-image: none; border-image-width: 0 0 0 0; background-image: none; background-color: transparent; border-radius: 0 0 0 0; border-width: 0 0 0 0; border-style: none; border-color: transparent; opacity: 0; min-height: 0; min-width: 0; }";
10581 css_provider_load_from_data(pProvider, data, -1);
10582 gtk_style_context_add_provider(pPopoverContext, GTK_STYLE_PROVIDER(pProvider),
10583 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
10585 gtk_menu_button_set_popover(m_pMenuButton, pPlaceHolder);
10587 else
10589 gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
10590 gtk_widget_show_all(m_pPopover);
10592 #endif
10595 void set_menu(weld::Menu* pMenu);
10597 static GtkBox* formatMenuButton(GtkWidget* pLabel)
10599 // format the GtkMenuButton "manually" so we can have the dropdown image in GtkMenuButtons shown
10600 // on the right at the same time as an image is shown on the left
10601 g_object_ref(pLabel);
10602 GtkWidget* pContainer = gtk_widget_get_parent(pLabel);
10603 #if !GTK_CHECK_VERSION(4, 0, 0)
10604 gtk_container_remove(GTK_CONTAINER(pContainer), pLabel);
10605 #else
10606 gtk_box_remove(GTK_BOX(pContainer), pLabel);
10607 #endif
10609 gint nImageSpacing(2);
10610 #if !GTK_CHECK_VERSION(4, 0, 0)
10611 GtkStyleContext *pContext = gtk_widget_get_style_context(pContainer);
10612 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
10613 #endif
10614 GtkBox* pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
10616 #if !GTK_CHECK_VERSION(4, 0, 0)
10617 gtk_box_pack_start(pBox, pLabel, true, true, 0);
10618 #else
10619 gtk_widget_set_halign(pLabel, GTK_ALIGN_START);
10620 gtk_box_prepend(pBox, pLabel);
10621 #endif
10622 g_object_unref(pLabel);
10624 #if !GTK_CHECK_VERSION(4, 0, 0)
10625 if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(pContainer)))
10626 gtk_box_pack_end(pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
10627 #endif
10629 #if !GTK_CHECK_VERSION(4, 0, 0)
10630 gtk_container_add(GTK_CONTAINER(pContainer), GTK_WIDGET(pBox));
10631 #else
10632 gtk_box_prepend(GTK_BOX(pContainer), GTK_WIDGET(pBox));
10633 #endif
10634 #if !GTK_CHECK_VERSION(4, 0, 0)
10635 gtk_widget_show_all(GTK_WIDGET(pBox));
10636 #else
10637 gtk_widget_show(GTK_WIDGET(pBox));
10638 #endif
10640 return pBox;
10643 #if GTK_CHECK_VERSION(4, 0, 0)
10644 virtual void disable_notify_events() override
10646 g_signal_handler_block(m_pMenuButtonToggleButton, m_nToggledSignalId);
10647 GtkInstanceWidget::disable_notify_events();
10650 virtual void enable_notify_events() override
10652 GtkInstanceWidget::enable_notify_events();
10653 g_signal_handler_unblock(m_pMenuButtonToggleButton, m_nToggledSignalId);
10655 #endif
10657 virtual ~GtkInstanceMenuButton() override
10659 #if GTK_CHECK_VERSION(4, 0, 0)
10660 g_signal_handler_disconnect(m_pMenuButtonToggleButton, m_nToggledSignalId);
10661 #else
10662 if (m_pMenuHack)
10664 g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
10665 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
10666 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
10668 #endif
10672 class GtkInstanceMenuToggleButton : public GtkInstanceToggleButton, public MenuHelper
10673 , public virtual weld::MenuToggleButton
10675 private:
10676 GtkBox* m_pContainer;
10677 GtkButton* m_pToggleMenuButton;
10678 GtkMenuButton* m_pMenuButton;
10679 gulong m_nMenuBtnClickedId;
10680 gulong m_nToggleStateFlagsChangedId;
10681 gulong m_nMenuBtnStateFlagsChangedId;
10683 static void signalToggleStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
10685 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
10686 // mirror togglebutton state to menubutton
10687 gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleMenuButton), gtk_widget_get_state_flags(pWidget), true);
10690 static void signalMenuBtnStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
10692 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
10693 // mirror menubutton to togglebutton, keeping depressed state of menubutton
10694 GtkStateFlags eToggleFlags = gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pToggleButton));
10695 GtkStateFlags eFlags = gtk_widget_get_state_flags(pWidget);
10696 GtkStateFlags eFinalFlags = static_cast<GtkStateFlags>((eFlags & ~GTK_STATE_FLAG_ACTIVE) |
10697 (eToggleFlags & GTK_STATE_FLAG_ACTIVE));
10698 gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleButton), eFinalFlags, true);
10701 static void signalMenuBtnClicked(GtkButton*, gpointer widget)
10703 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
10704 pThis->launch_menu();
10707 void launch_menu()
10709 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleMenuButton), gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)), true);
10710 GtkWidget* pWidget = GTK_WIDGET(m_pToggleButton);
10712 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
10713 //it during DispatchCommand, returning now to the outer loop causes the
10714 //launching PopupMenu to be destroyed, instead run the subloop here
10715 //until the gtk menu is destroyed
10716 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
10718 #if GTK_CHECK_VERSION(4, 0, 0)
10719 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
10721 g_object_ref(m_pMenu);
10722 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
10723 gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
10724 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
10725 gtk_popover_popup(GTK_POPOVER(m_pMenu));
10726 #else
10727 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
10729 #if GTK_CHECK_VERSION(3,22,0)
10730 if (gtk_check_version(3, 22, 0) == nullptr)
10732 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
10733 // before trying to launch the menu
10734 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
10735 // Fixed in GTK 2.34
10736 GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
10737 gtk_main_do_event(pKeyEvent);
10739 GdkEvent *pTriggerEvent = gtk_get_current_event();
10740 if (!pTriggerEvent)
10741 pTriggerEvent = pKeyEvent;
10743 gtk_menu_popup_at_widget(m_pMenu, pWidget, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
10745 gdk_event_free(pKeyEvent);
10747 else
10748 #endif
10750 guint nButton;
10751 guint32 nTime;
10753 //typically there is an event, and we can then distinguish if this was
10754 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
10755 //doesn't)
10756 GdkEvent *pEvent = gtk_get_current_event();
10757 if (pEvent)
10759 gdk_event_get_button(pEvent, &nButton);
10760 nTime = gdk_event_get_time(pEvent);
10762 else
10764 nButton = 0;
10765 nTime = GtkSalFrame::GetLastInputEventTime();
10768 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
10770 #endif
10772 if (g_main_loop_is_running(pLoop))
10773 main_loop_run(pLoop);
10775 g_main_loop_unref(pLoop);
10776 g_signal_handler_disconnect(m_pMenu, nSignalId);
10778 #if GTK_CHECK_VERSION(4, 0, 0)
10779 gtk_widget_unparent(GTK_WIDGET(m_pMenu));
10780 gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
10781 g_object_unref(m_pMenu);
10782 #endif
10786 static gboolean signalMenuToggleButton(GtkWidget*, gboolean bGroupCycling, gpointer widget)
10788 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
10789 return gtk_widget_mnemonic_activate(GTK_WIDGET(pThis->m_pToggleButton), bGroupCycling);
10792 public:
10793 GtkInstanceMenuToggleButton(GtkBuilder* pMenuToggleButtonBuilder, GtkMenuButton* pMenuButton,
10794 GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10795 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "togglebutton")),
10796 pBuilder, bTakeOwnership)
10797 #if !GTK_CHECK_VERSION(4, 0, 0)
10798 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
10799 #else
10800 , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
10801 #endif
10802 , m_pContainer(GTK_BOX(gtk_builder_get_object(pMenuToggleButtonBuilder, "box")))
10803 , m_pToggleMenuButton(GTK_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "menubutton")))
10804 , m_pMenuButton(pMenuButton)
10805 , m_nMenuBtnClickedId(g_signal_connect(m_pToggleMenuButton, "clicked", G_CALLBACK(signalMenuBtnClicked), this))
10806 , m_nToggleStateFlagsChangedId(g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalToggleStateFlagsChanged), this))
10807 , m_nMenuBtnStateFlagsChangedId(g_signal_connect(m_pToggleMenuButton, "state-flags-changed", G_CALLBACK(signalMenuBtnStateFlagsChanged), this))
10809 #if !GTK_CHECK_VERSION(4, 0, 0)
10810 GtkInstanceMenuButton::formatMenuButton(gtk_bin_get_child(GTK_BIN(m_pMenuButton)));
10811 #endif
10813 insertAsParent(GTK_WIDGET(m_pMenuButton), GTK_WIDGET(m_pContainer));
10814 gtk_widget_hide(GTK_WIDGET(m_pMenuButton));
10816 // move the first GtkMenuButton child, as created by GtkInstanceMenuButton ctor, into the GtkToggleButton
10817 // instead, leaving just the indicator behind in the GtkMenuButton
10818 #if !GTK_CHECK_VERSION(4, 0, 0)
10819 GtkWidget* pButtonBox = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
10820 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pButtonBox));
10821 int nGroup = 0;
10822 for (GList* pChild = g_list_first(pChildren); pChild && nGroup < 2; pChild = g_list_next(pChild), ++nGroup)
10824 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
10825 g_object_ref(pWidget);
10826 gtk_container_remove(GTK_CONTAINER(pButtonBox), pWidget);
10827 if (nGroup == 0)
10828 gtk_container_add(GTK_CONTAINER(m_pToggleButton), pWidget);
10829 else
10830 gtk_container_add(GTK_CONTAINER(m_pToggleMenuButton), pWidget);
10831 gtk_widget_show_all(pWidget);
10832 g_object_unref(pWidget);
10834 g_list_free(pChildren);
10835 #else
10836 GtkWidget* pChild;
10837 if (gtk_check_version(4, 5, 0) == nullptr)
10839 pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
10840 pChild = gtk_widget_get_first_child(pChild);
10841 pChild = gtk_widget_get_first_child(pChild);
10843 else
10844 pChild = gtk_widget_get_last_child(GTK_WIDGET(m_pMenuButton));
10845 g_object_ref(pChild);
10846 gtk_widget_unparent(pChild);
10847 gtk_button_set_child(GTK_BUTTON(m_pToggleButton), pChild);
10848 g_object_unref(pChild);
10849 #endif
10851 // match the GtkToggleButton relief to the GtkMenuButton
10852 #if !GTK_CHECK_VERSION(4, 0, 0)
10853 const GtkReliefStyle eStyle = gtk_button_get_relief(GTK_BUTTON(m_pMenuButton));
10854 gtk_button_set_relief(GTK_BUTTON(m_pToggleButton), eStyle);
10855 gtk_button_set_relief(GTK_BUTTON(m_pToggleMenuButton), eStyle);
10856 #else
10857 const bool bStyle = gtk_menu_button_get_has_frame(GTK_MENU_BUTTON(m_pMenuButton));
10858 gtk_button_set_has_frame(GTK_BUTTON(m_pToggleButton), bStyle);
10859 gtk_button_set_has_frame(GTK_BUTTON(m_pToggleMenuButton), bStyle);
10860 #endif
10862 // move the GtkMenuButton margins up to the new parent
10863 gtk_widget_set_margin_top(GTK_WIDGET(m_pContainer),
10864 gtk_widget_get_margin_top(GTK_WIDGET(m_pMenuButton)));
10865 gtk_widget_set_margin_bottom(GTK_WIDGET(m_pContainer),
10866 gtk_widget_get_margin_bottom(GTK_WIDGET(m_pMenuButton)));
10867 gtk_widget_set_margin_start(GTK_WIDGET(m_pContainer),
10868 gtk_widget_get_margin_start(GTK_WIDGET(m_pMenuButton)));
10869 gtk_widget_set_margin_end(GTK_WIDGET(m_pContainer),
10870 gtk_widget_get_margin_end(GTK_WIDGET(m_pMenuButton)));
10872 #if !GTK_CHECK_VERSION(4, 0, 0)
10873 gtk_menu_detach(m_pMenu);
10874 gtk_menu_attach_to_widget(m_pMenu, GTK_WIDGET(m_pToggleButton), nullptr);
10875 #else
10876 gtk_widget_insert_action_group(GTK_WIDGET(m_pContainer), "menu", m_pActionGroup);
10878 update_action_group_from_popover_model();
10879 #endif
10881 g_signal_connect(m_pContainer, "mnemonic-activate", G_CALLBACK(signalMenuToggleButton), this);
10884 virtual void disable_notify_events() override
10886 g_signal_handler_block(m_pToggleMenuButton, m_nMenuBtnClickedId);
10887 GtkInstanceToggleButton::disable_notify_events();
10890 virtual void enable_notify_events() override
10892 GtkInstanceToggleButton::enable_notify_events();
10893 g_signal_handler_unblock(m_pToggleMenuButton, m_nMenuBtnClickedId);
10896 virtual ~GtkInstanceMenuToggleButton()
10898 g_signal_handler_disconnect(m_pToggleButton, m_nToggleStateFlagsChangedId);
10899 g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnStateFlagsChangedId);
10900 g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnClickedId);
10902 #if GTK_CHECK_VERSION(4, 0, 0)
10903 GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(m_pToggleButton));
10904 g_object_ref(pChild);
10905 gtk_button_set_child(GTK_BUTTON(m_pToggleButton), nullptr);
10906 gtk_widget_unparent(pChild);
10907 gtk_widget_set_parent(pChild, GTK_WIDGET(m_pMenuButton));
10908 g_object_unref(pChild);
10909 #endif
10912 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
10913 const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
10915 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
10918 virtual void insert_separator(int pos, const OUString& rId) override
10920 MenuHelper::insert_separator(pos, rId);
10923 virtual void remove_item(const OString& rId) override
10925 MenuHelper::remove_item(rId);
10928 virtual void clear() override
10930 MenuHelper::clear_items();
10933 virtual void set_item_active(const OString& rIdent, bool bActive) override
10935 MenuHelper::set_item_active(rIdent, bActive);
10938 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
10940 MenuHelper::set_item_sensitive(rIdent, bSensitive);
10943 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
10945 MenuHelper::set_item_label(rIdent, rLabel);
10948 virtual OUString get_item_label(const OString& rIdent) const override
10950 return MenuHelper::get_item_label(rIdent);
10953 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
10955 MenuHelper::set_item_visible(rIdent, bVisible);
10958 virtual void signal_item_activate(const OString& rIdent) override
10960 signal_selected(rIdent);
10963 virtual void set_popover(weld::Widget* /*pPopover*/) override
10965 assert(false && "not implemented");
10969 class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
10971 protected:
10972 #if !GTK_CHECK_VERSION(4, 0, 0)
10973 std::vector<GtkMenuItem*> m_aExtraItems;
10974 #endif
10975 OString m_sActivated;
10976 #if !GTK_CHECK_VERSION(4, 0, 0)
10977 MenuHelper* m_pTopLevelMenuHelper;
10978 #endif
10980 private:
10981 virtual void signal_item_activate(const OString& rIdent) override
10983 m_sActivated = rIdent;
10984 weld::Menu::signal_activate(m_sActivated);
10987 #if !GTK_CHECK_VERSION(4, 0, 0)
10988 void clear_extras()
10990 if (m_aExtraItems.empty())
10991 return;
10992 if (m_pTopLevelMenuHelper)
10994 for (auto a : m_aExtraItems)
10995 m_pTopLevelMenuHelper->remove_from_map(a);
10997 m_aExtraItems.clear();
10999 #endif
11001 public:
11002 #if !GTK_CHECK_VERSION(4, 0, 0)
11003 GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
11004 #else
11005 GtkInstanceMenu(GtkPopoverMenu* pMenu, bool bTakeOwnership)
11006 #endif
11007 : MenuHelper(pMenu, bTakeOwnership)
11008 #if !GTK_CHECK_VERSION(4, 0, 0)
11009 , m_pTopLevelMenuHelper(nullptr)
11010 #endif
11012 g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
11013 #if !GTK_CHECK_VERSION(4, 0, 0)
11014 // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
11015 // then find that MenuButton parent so that when adding items to this
11016 // menu we can inform the MenuButton of their addition
11017 GtkMenu* pTopLevelMenu = pMenu;
11018 while (true)
11020 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
11021 if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
11022 break;
11023 GtkWidget* pParent = gtk_widget_get_parent(pAttached);
11024 if (!pParent || !GTK_IS_MENU(pParent))
11025 break;
11026 pTopLevelMenu = GTK_MENU(pParent);
11028 if (pTopLevelMenu == pMenu)
11029 return;
11031 // maybe the toplevel is a menubutton
11032 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
11033 if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
11035 void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
11036 m_pTopLevelMenuHelper = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
11038 // or maybe a menu
11039 if (!m_pTopLevelMenuHelper)
11041 void* pData = g_object_get_data(G_OBJECT(pTopLevelMenu), "g-lo-GtkInstanceMenu");
11042 m_pTopLevelMenuHelper = static_cast<GtkInstanceMenu*>(pData);
11044 #else
11045 update_action_group_from_popover_model();
11046 #endif
11049 virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
11051 m_sActivated.clear();
11053 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
11054 assert(pGtkWidget);
11055 GtkWidget* pWidget = pGtkWidget->getWidget();
11057 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
11058 //it during DispatchCommand, returning now to the outer loop causes the
11059 //launching PopupMenu to be destroyed, instead run the subloop here
11060 //until the gtk menu is destroyed
11061 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
11063 #if GTK_CHECK_VERSION(4, 0, 0)
11064 gtk_widget_insert_action_group(pWidget, "menu", m_pActionGroup);
11066 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
11068 GdkRectangle aRect;
11069 pWidget = getPopupRect(pWidget, rRect, aRect);
11071 GtkWidget* pOrigParent = gtk_widget_get_parent(GTK_WIDGET(m_pMenu));
11072 gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
11073 gtk_popover_set_pointing_to(GTK_POPOVER(m_pMenu), &aRect);
11074 if (ePlace == weld::Placement::Under)
11075 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
11076 else
11078 if (SwapForRTL(pWidget))
11079 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_LEFT);
11080 else
11081 gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_RIGHT);
11083 gtk_popover_popup(GTK_POPOVER(m_pMenu));
11084 #else
11085 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
11087 #if GTK_CHECK_VERSION(3,22,0)
11088 if (gtk_check_version(3, 22, 0) == nullptr)
11090 GdkRectangle aRect;
11091 pWidget = getPopupRect(pWidget, rRect, aRect);
11092 gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
11094 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
11095 // before trying to launch the menu
11096 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
11097 // Fixed in GTK 2.34
11098 GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
11099 gtk_main_do_event(pKeyEvent);
11101 GdkEvent *pTriggerEvent = gtk_get_current_event();
11102 if (!pTriggerEvent)
11103 pTriggerEvent = pKeyEvent;
11105 bool bSwapForRTL = SwapForRTL(pWidget);
11107 if (ePlace == weld::Placement::Under)
11109 if (bSwapForRTL)
11110 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
11111 else
11112 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
11114 else
11116 if (bSwapForRTL)
11117 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
11118 else
11119 gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
11122 gdk_event_free(pKeyEvent);
11124 else
11125 #else
11126 (void) rRect;
11127 #endif
11129 gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
11131 guint nButton;
11132 guint32 nTime;
11134 //typically there is an event, and we can then distinguish if this was
11135 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
11136 //doesn't)
11137 GdkEvent *pEvent = gtk_get_current_event();
11138 if (pEvent)
11140 if (!gdk_event_get_button(pEvent, &nButton))
11141 nButton = 0;
11142 nTime = gdk_event_get_time(pEvent);
11144 else
11146 nButton = 0;
11147 nTime = GtkSalFrame::GetLastInputEventTime();
11150 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
11152 #endif
11154 if (g_main_loop_is_running(pLoop))
11155 main_loop_run(pLoop);
11157 g_main_loop_unref(pLoop);
11158 g_signal_handler_disconnect(m_pMenu, nSignalId);
11160 #if GTK_CHECK_VERSION(4, 0, 0)
11161 if (!pOrigParent)
11162 gtk_widget_unparent(GTK_WIDGET(m_pMenu));
11163 else
11164 gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pOrigParent);
11166 gtk_widget_insert_action_group(pWidget, "menu", nullptr);
11167 #else
11168 gtk_menu_detach(m_pMenu);
11169 #endif
11171 return m_sActivated;
11174 virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
11176 set_item_sensitive(rIdent, bSensitive);
11179 virtual bool get_sensitive(const OString& rIdent) const override
11181 return get_item_sensitive(rIdent);
11184 virtual void set_active(const OString& rIdent, bool bActive) override
11186 set_item_active(rIdent, bActive);
11189 virtual bool get_active(const OString& rIdent) const override
11191 return get_item_active(rIdent);
11194 virtual void set_visible(const OString& rIdent, bool bShow) override
11196 set_item_visible(rIdent, bShow);
11199 virtual void set_label(const OString& rIdent, const OUString& rLabel) override
11201 set_item_label(rIdent, rLabel);
11204 virtual OUString get_label(const OString& rIdent) const override
11206 return get_item_label(rIdent);
11209 virtual void insert_separator(int pos, const OUString& rId) override
11211 MenuHelper::insert_separator(pos, rId);
11214 virtual void clear() override
11216 #if !GTK_CHECK_VERSION(4, 0, 0)
11217 clear_extras();
11218 #endif
11219 MenuHelper::clear_items();
11222 virtual void insert(int pos, const OUString& rId, const OUString& rStr,
11223 const OUString* pIconName, VirtualDevice* pImageSurface,
11224 const css::uno::Reference<css::graphic::XGraphic>& rGraphic,
11225 TriState eCheckRadioFalse) override
11227 #if !GTK_CHECK_VERSION(4, 0, 0)
11228 GtkWidget* pImage = nullptr;
11229 if (pIconName)
11231 if (GdkPixbuf* pixbuf = load_icon_by_name(*pIconName))
11233 pImage = gtk_image_new_from_pixbuf(pixbuf);
11234 g_object_unref(pixbuf);
11237 else if (pImageSurface)
11239 pImage = image_new_from_virtual_device(*pImageSurface);
11241 else if (rGraphic)
11243 if (GdkPixbuf* pixbuf = getPixbuf(rGraphic))
11245 pImage = gtk_image_new_from_pixbuf(pixbuf);
11246 g_object_unref(pixbuf);
11250 GtkWidget *pItem;
11251 if (pImage)
11253 GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
11254 GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
11255 gtk_label_set_xalign(GTK_LABEL(pLabel), 0.0);
11256 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
11257 gtk_box_pack_start(pBox, pImage, false, true, 0);
11258 gtk_box_pack_start(pBox, pLabel, true, true, 0);
11259 gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
11260 gtk_widget_show_all(pItem);
11262 else
11264 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
11265 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
11268 if (eCheckRadioFalse == TRISTATE_FALSE)
11269 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
11271 ::set_buildable_id(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8));
11272 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
11273 gtk_widget_show(pItem);
11274 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
11275 m_aExtraItems.push_back(pMenuItem);
11276 add_to_map(pMenuItem);
11277 if (m_pTopLevelMenuHelper)
11278 m_pTopLevelMenuHelper->add_to_map(pMenuItem);
11279 if (pos != -1)
11280 gtk_menu_reorder_child(m_pMenu, pItem, pos);
11281 #else
11282 SAL_WARN("vcl.gtk", "needs to be implemented for gtk4");
11283 (void)pos;
11284 (void)rId;
11285 (void)rStr;
11286 (void)pIconName;
11287 (void)pImageSurface;
11288 (void)rGraphic;
11289 (void)eCheckRadioFalse;
11290 #endif
11293 virtual OString get_id(int pos) const override
11295 return get_item_id(pos);
11298 virtual int n_children() const override
11300 return get_n_children();
11303 void remove(const OString& rIdent) override
11305 #if !GTK_CHECK_VERSION(4, 0, 0)
11306 if (!m_aExtraItems.empty())
11308 GtkMenuItem* pMenuItem = m_aMap[rIdent];
11309 auto iter = std::find(m_aExtraItems.begin(), m_aExtraItems.end(), pMenuItem);
11310 if (iter != m_aExtraItems.end())
11312 if (m_pTopLevelMenuHelper)
11313 m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
11314 m_aExtraItems.erase(iter);
11317 #endif
11318 MenuHelper::remove_item(rIdent);
11321 virtual ~GtkInstanceMenu() override
11323 #if !GTK_CHECK_VERSION(4, 0, 0)
11324 clear_extras();
11325 #endif
11326 g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
11330 #if !GTK_CHECK_VERSION(4, 0, 0)
11331 vcl::ImageType GtkToVcl(GtkIconSize eSize)
11333 vcl::ImageType eRet;
11334 switch (eSize)
11336 #if !GTK_CHECK_VERSION(4, 0, 0)
11337 case GTK_ICON_SIZE_MENU:
11338 case GTK_ICON_SIZE_SMALL_TOOLBAR:
11339 case GTK_ICON_SIZE_BUTTON:
11340 eRet = vcl::ImageType::Size16;
11341 break;
11342 case GTK_ICON_SIZE_LARGE_TOOLBAR:
11343 eRet = vcl::ImageType::Size26;
11344 break;
11345 case GTK_ICON_SIZE_DND:
11346 case GTK_ICON_SIZE_DIALOG:
11347 eRet = vcl::ImageType::Size32;
11348 break;
11349 default:
11350 case GTK_ICON_SIZE_INVALID:
11351 eRet = vcl::ImageType::Small;
11352 break;
11353 #else
11354 case GTK_ICON_SIZE_LARGE:
11355 eRet = vcl::ImageType::Size32;
11356 break;
11357 case GTK_ICON_SIZE_NORMAL:
11358 default:
11359 eRet = vcl::ImageType::Size16;
11360 break;
11361 #endif
11363 return eRet;
11366 GtkIconSize VclToGtk(vcl::ImageType eSize)
11368 GtkIconSize eRet;
11369 #if !GTK_CHECK_VERSION(4, 0, 0)
11370 switch (eSize)
11372 case vcl::ImageType::Size16:
11373 eRet = GTK_ICON_SIZE_SMALL_TOOLBAR;
11374 break;
11375 case vcl::ImageType::Size26:
11376 eRet = GTK_ICON_SIZE_LARGE_TOOLBAR;
11377 break;
11378 case vcl::ImageType::Size32:
11379 eRet = GTK_ICON_SIZE_DIALOG;
11380 break;
11381 default:
11382 O3TL_UNREACHABLE;
11384 #else
11385 switch (eSize)
11387 case vcl::ImageType::Size26:
11388 case vcl::ImageType::Size32:
11389 eRet = GTK_ICON_SIZE_LARGE;
11390 break;
11391 case vcl::ImageType::Size16:
11392 default:
11393 eRet = GTK_ICON_SIZE_NORMAL;
11394 break;
11396 #endif
11397 return eRet;
11399 #endif
11402 void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
11404 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
11405 m_pPopover = nullptr;
11406 m_pMenu = pPopoverWidget ? pPopoverWidget->getMenu() : nullptr;
11408 #if !GTK_CHECK_VERSION(4, 0, 0)
11409 gtk_menu_button_set_popup(m_pMenuButton, GTK_WIDGET(m_pMenu));
11410 #else
11411 gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
11412 update_action_group_from_popover_model();
11413 #endif
11416 namespace {
11418 class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
11420 private:
11421 #if !GTK_CHECK_VERSION(4, 0, 0)
11422 GtkToolbar* m_pToolbar;
11423 #else
11424 GtkBox* m_pToolbar;
11425 vcl::ImageType m_eImageType;
11426 #endif
11427 GtkCssProvider *m_pMenuButtonProvider;
11429 std::map<OString, GtkWidget*> m_aMap;
11430 std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
11432 #if !GTK_CHECK_VERSION(4, 0, 0)
11433 // at the time of writing there is no gtk_menu_tool_button_set_popover available
11434 // though there will be in the future
11435 // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
11436 static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
11438 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
11440 GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
11441 *ppToggleButton = pWidget;
11443 else if (GTK_IS_CONTAINER(pWidget))
11444 gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
11447 static void find_menupeer_button(GtkWidget *pWidget, gpointer user_data)
11449 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkButton") == 0)
11451 GtkWidget **ppButton = static_cast<GtkWidget**>(user_data);
11452 *ppButton = pWidget;
11454 else if (GTK_IS_CONTAINER(pWidget))
11455 gtk_container_forall(GTK_CONTAINER(pWidget), find_menupeer_button, user_data);
11457 #endif
11459 static void collect(GtkWidget* pItem, gpointer widget)
11461 #if !GTK_CHECK_VERSION(4, 0, 0)
11462 if (!GTK_IS_TOOL_ITEM(pItem))
11463 return;
11464 #endif
11465 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
11467 GtkMenuButton* pMenuButton = nullptr;
11468 #if !GTK_CHECK_VERSION(4, 0, 0)
11469 if (GTK_IS_MENU_TOOL_BUTTON(pItem))
11470 find_menu_button(pItem, &pMenuButton);
11471 #else
11472 if (GTK_IS_MENU_BUTTON(pItem))
11473 pMenuButton = GTK_MENU_BUTTON(pItem);
11474 #endif
11476 pThis->add_to_map(pItem, pMenuButton);
11479 void add_to_map(GtkWidget* pToolItem, GtkMenuButton* pMenuButton)
11481 OString id = ::get_buildable_id(GTK_BUILDABLE(pToolItem));
11482 m_aMap[id] = pToolItem;
11483 if (pMenuButton)
11485 m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, GTK_WIDGET(pToolItem), m_pBuilder, false);
11486 // so that, e.g. with focus initially in writer main document then
11487 // after clicking the heading menu in the writer navigator focus is
11488 // left in the main document and not in the toolbar
11489 #if !GTK_CHECK_VERSION(4, 0, 0)
11490 gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
11491 g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
11492 #else
11493 gtk_widget_set_focus_on_click(GTK_WIDGET(pMenuButton), false);
11495 GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(pMenuButton));
11496 assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
11497 g_signal_connect(pToggleButton, "toggled", G_CALLBACK(signalItemToggled), this);
11498 #endif
11500 // by default the GtkMenuButton down arrow button is as wide as
11501 // a normal button and LibreOffice's original ones are very
11502 // narrow, that assumption is fairly baked into the toolbar and
11503 // sidebar designs, try and minimize the width of the dropdown
11504 // zone.
11505 GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pMenuButton));
11507 if (!m_pMenuButtonProvider)
11509 m_pMenuButtonProvider = gtk_css_provider_new();
11510 static const gchar data[] = "* { "
11511 "padding: 0;"
11512 "margin-left: 0px;"
11513 "margin-right: 0px;"
11514 "min-width: 4px;"
11515 "}";
11516 css_provider_load_from_data(m_pMenuButtonProvider, data, -1);
11519 gtk_style_context_add_provider(pButtonContext,
11520 GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
11521 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
11523 #if !GTK_CHECK_VERSION(4, 0, 0)
11524 if (!GTK_IS_TOOL_BUTTON(pToolItem))
11525 #else
11526 if (!GTK_IS_BUTTON(pToolItem))
11527 #endif
11529 return;
11531 g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
11534 #if !GTK_CHECK_VERSION(4, 0, 0)
11535 static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
11536 #else
11537 static void signalItemClicked(GtkButton* pItem, gpointer widget)
11538 #endif
11540 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
11541 SolarMutexGuard aGuard;
11542 pThis->signal_item_clicked(pItem);
11545 #if !GTK_CHECK_VERSION(4, 0, 0)
11546 void signal_item_clicked(GtkToolButton* pItem)
11547 #else
11548 void signal_item_clicked(GtkButton* pItem)
11549 #endif
11551 signal_clicked(::get_buildable_id(GTK_BUILDABLE(pItem)));
11554 static void signalItemToggled(GtkToggleButton* pItem, gpointer widget)
11556 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
11557 SolarMutexGuard aGuard;
11558 pThis->signal_item_toggled(pItem);
11561 void signal_item_toggled(GtkToggleButton* pItem)
11563 for (const auto& a : m_aMenuButtonMap)
11565 #if !GTK_CHECK_VERSION(4, 0, 0)
11566 if (a.second->getWidget() == GTK_WIDGET(pItem))
11567 #else
11568 if (a.second->getWidget() == gtk_widget_get_parent(GTK_WIDGET(pItem)))
11569 #endif
11571 signal_toggle_menu(a.first);
11572 break;
11577 #if GTK_CHECK_VERSION(4, 0, 0)
11578 static void set_item_image(GtkWidget* pItem, GtkWidget* pImage)
11580 if (GTK_IS_BUTTON(pItem))
11581 gtk_button_set_child(GTK_BUTTON(pItem), pImage);
11582 else if (GTK_IS_MENU_BUTTON(pItem))
11584 // TODO after gtk 4.6 is released require that version and drop this
11585 static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
11586 if (menu_button_set_child)
11587 menu_button_set_child(GTK_MENU_BUTTON(pItem), pImage);
11589 // versions of gtk4 > 4.2.1 might do this on their own
11590 gtk_widget_remove_css_class(pItem, "text-button");
11592 #endif
11594 #if !GTK_CHECK_VERSION(4, 0, 0)
11595 static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
11596 #else
11597 static void set_item_image(GtkWidget* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
11598 #endif
11600 GtkWidget* pImage = nullptr;
11602 if (GdkPixbuf* pixbuf = getPixbuf(rIcon))
11604 pImage = gtk_image_new_from_pixbuf(pixbuf);
11605 g_object_unref(pixbuf);
11606 gtk_widget_show(pImage);
11609 #if !GTK_CHECK_VERSION(4, 0, 0)
11610 gtk_tool_button_set_icon_widget(pItem, pImage);
11611 #else
11612 set_item_image(pItem, pImage);
11613 #endif
11616 #if !GTK_CHECK_VERSION(4, 0, 0)
11617 void set_item_image(GtkToolButton* pItem, const VirtualDevice* pDevice)
11618 #else
11619 void set_item_image(GtkWidget* pItem, const VirtualDevice* pDevice)
11620 #endif
11622 GtkWidget* pImage = nullptr;
11624 if (pDevice)
11626 #if GTK_CHECK_VERSION(4, 0, 0)
11627 pImage = picture_new_from_virtual_device(*pDevice);
11628 #else
11629 pImage = image_new_from_virtual_device(*pDevice);
11630 #endif
11631 gtk_widget_show(pImage);
11634 #if !GTK_CHECK_VERSION(4, 0, 0)
11635 gtk_tool_button_set_icon_widget(pItem, pImage);
11636 #else
11637 set_item_image(pItem, pImage);
11638 #endif
11639 gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
11642 #if !GTK_CHECK_VERSION(4, 0, 0)
11643 GtkWidget* toolbar_get_nth_item(int nIndex) const
11645 return GTK_WIDGET(gtk_toolbar_get_nth_item(m_pToolbar, nIndex));
11647 #else
11648 GtkWidget* toolbar_get_nth_item(int nIndex) const
11650 int i = 0;
11651 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
11652 pChild; pChild = gtk_widget_get_next_sibling(pChild))
11654 if (i == nIndex)
11655 return pChild;
11656 ++i;
11658 return nullptr;
11660 #endif
11661 public:
11662 #if !GTK_CHECK_VERSION(4, 0, 0)
11663 GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
11664 #else
11665 GtkInstanceToolbar(GtkBox* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
11666 #endif
11667 : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
11668 , m_pToolbar(pToolbar)
11669 #if GTK_CHECK_VERSION(4, 0, 0)
11670 , m_eImageType(vcl::ImageType::Size16)
11671 #endif
11672 , m_pMenuButtonProvider(nullptr)
11674 #if GTK_CHECK_VERSION(4, 0, 0)
11675 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pToolbar));
11676 pChild; pChild = gtk_widget_get_next_sibling(pChild))
11678 collect(pChild, this);
11680 #else
11681 gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
11682 #endif
11685 void disable_item_notify_events()
11687 for (auto& a : m_aMap)
11689 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
11693 void enable_item_notify_events()
11695 for (auto& a : m_aMap)
11697 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
11701 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
11703 disable_item_notify_events();
11704 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
11705 enable_item_notify_events();
11708 virtual bool get_item_sensitive(const OString& rIdent) const override
11710 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
11713 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
11715 disable_item_notify_events();
11716 gtk_widget_set_visible(GTK_WIDGET(m_aMap[rIdent]), bVisible);
11717 enable_item_notify_events();
11720 virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
11722 ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
11725 virtual bool get_item_visible(const OString& rIdent) const override
11727 return gtk_widget_get_visible(GTK_WIDGET(m_aMap.find(rIdent)->second));
11730 virtual void set_item_active(const OString& rIdent, bool bActive) override
11732 disable_item_notify_events();
11734 GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
11736 #if !GTK_CHECK_VERSION(4, 0, 0)
11737 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
11738 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
11739 else
11741 GtkButton* pButton = nullptr;
11742 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
11743 // to emulate one
11744 find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
11745 if (pButton)
11747 auto eState = gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & ~GTK_STATE_FLAG_CHECKED;
11748 if (bActive)
11749 eState |= GTK_STATE_FLAG_CHECKED;
11750 gtk_widget_set_state_flags(GTK_WIDGET(pButton), static_cast<GtkStateFlags>(eState), true);
11753 #else
11754 GtkWidget* pWidget;
11755 if (GTK_IS_MENU_BUTTON(pToolButton))
11757 pWidget = gtk_widget_get_first_child(pToolButton);
11758 assert(GTK_IS_TOGGLE_BUTTON(pWidget));
11760 else
11761 pWidget = pToolButton;
11762 auto eState = gtk_widget_get_state_flags(pWidget) & ~GTK_STATE_FLAG_CHECKED;
11763 if (bActive)
11764 eState |= GTK_STATE_FLAG_CHECKED;
11765 gtk_widget_set_state_flags(pWidget, static_cast<GtkStateFlags>(eState), true);
11766 #endif
11768 enable_item_notify_events();
11771 virtual bool get_item_active(const OString& rIdent) const override
11773 GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
11775 #if !GTK_CHECK_VERSION(4, 0, 0)
11776 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
11777 return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
11778 else
11780 GtkButton* pButton = nullptr;
11781 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
11782 // to emulate one
11783 find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
11784 if (pButton)
11786 return gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & GTK_STATE_FLAG_CHECKED;
11789 #else
11790 GtkWidget* pWidget;
11791 if (GTK_IS_MENU_BUTTON(pToolButton))
11793 pWidget = gtk_widget_get_first_child(pToolButton);
11794 assert(GTK_IS_TOGGLE_BUTTON(pWidget));
11796 else
11797 pWidget = pToolButton;
11798 return gtk_widget_get_state_flags(pWidget) & GTK_STATE_FLAG_CHECKED;
11799 #endif
11801 return false;
11804 virtual void set_menu_item_active(const OString& rIdent, bool bActive) override
11806 disable_item_notify_events();
11808 auto aFind = m_aMenuButtonMap.find(rIdent);
11809 assert (aFind != m_aMenuButtonMap.end());
11810 aFind->second->set_active(bActive);
11812 enable_item_notify_events();
11815 virtual bool get_menu_item_active(const OString& rIdent) const override
11817 auto aFind = m_aMenuButtonMap.find(rIdent);
11818 assert (aFind != m_aMenuButtonMap.end());
11819 return aFind->second->get_active();
11822 virtual void insert_item(int pos, const OUString& rId) override
11824 OString sId = OUStringToOString(rId, RTL_TEXTENCODING_UTF8);
11825 #if !GTK_CHECK_VERSION(4, 0, 0)
11826 GtkToolItem* pItem = gtk_tool_button_new(nullptr, sId.getStr());
11827 #else
11828 GtkWidget* pItem = gtk_button_new();
11829 #endif
11830 ::set_buildable_id(GTK_BUILDABLE(pItem), sId);
11831 #if !GTK_CHECK_VERSION(4, 0, 0)
11832 gtk_toolbar_insert(m_pToolbar, pItem, pos);
11833 #else
11834 gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
11835 #endif
11836 gtk_widget_show(GTK_WIDGET(pItem));
11837 add_to_map(GTK_WIDGET(pItem), nullptr);
11840 virtual void insert_separator(int pos, const OUString& rId) override
11842 OString sId = OUStringToOString(rId, RTL_TEXTENCODING_UTF8);
11843 #if !GTK_CHECK_VERSION(4, 0, 0)
11844 GtkToolItem* pItem = gtk_separator_tool_item_new();
11845 #else
11846 GtkWidget* pItem = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
11847 #endif
11848 ::set_buildable_id(GTK_BUILDABLE(pItem), sId);
11849 #if !GTK_CHECK_VERSION(4, 0, 0)
11850 gtk_toolbar_insert(m_pToolbar, pItem, pos);
11851 #else
11852 gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
11853 #endif
11854 gtk_widget_show(GTK_WIDGET(pItem));
11857 virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
11859 m_aMenuButtonMap[rIdent]->set_popover(pPopover);
11862 virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
11864 m_aMenuButtonMap[rIdent]->set_menu(pMenu);
11867 virtual int get_n_items() const override
11869 #if !GTK_CHECK_VERSION(4, 0, 0)
11870 return gtk_toolbar_get_n_items(m_pToolbar);
11871 #else
11872 int n_items = 0;
11873 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
11874 pChild; pChild = gtk_widget_get_next_sibling(pChild))
11876 ++n_items;
11878 return n_items;
11879 #endif
11882 virtual OString get_item_ident(int nIndex) const override
11884 auto* pItem = toolbar_get_nth_item(nIndex);
11885 return ::get_buildable_id(GTK_BUILDABLE(pItem));
11888 virtual void set_item_ident(int nIndex, const OString& rIdent) override
11890 OString sOldIdent(get_item_ident(nIndex));
11891 m_aMap.erase(m_aMap.find(sOldIdent));
11893 auto* pItem = toolbar_get_nth_item(nIndex);
11894 ::set_buildable_id(GTK_BUILDABLE(pItem), rIdent);
11896 // to keep the ids unique, if the new id is already in use by an item,
11897 // change the id of that item to the now unused old ident of this item
11898 auto aFind = m_aMap.find(rIdent);
11899 if (aFind != m_aMap.end())
11901 GtkWidget* pDupIdItem = aFind->second;
11902 ::set_buildable_id(GTK_BUILDABLE(pDupIdItem), sOldIdent);
11903 m_aMap[sOldIdent] = pDupIdItem;
11906 m_aMap[rIdent] = pItem;
11909 virtual void set_item_label(int nIndex, const OUString& rLabel) override
11911 auto* pItem = toolbar_get_nth_item(nIndex);
11912 #if !GTK_CHECK_VERSION(4, 0, 0)
11913 if (!GTK_IS_TOOL_BUTTON(pItem))
11914 return;
11915 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
11916 #else
11917 if (!GTK_IS_BUTTON(pItem))
11918 return;
11919 ::button_set_label(GTK_BUTTON(pItem), rLabel);
11920 #endif
11923 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
11925 GtkWidget* pItem = m_aMap[rIdent];
11926 #if !GTK_CHECK_VERSION(4, 0, 0)
11927 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
11928 return;
11929 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
11930 #else
11931 if (!pItem || !GTK_IS_BUTTON(pItem))
11932 return;
11933 ::button_set_label(GTK_BUTTON(pItem), rLabel);
11934 #endif
11937 OUString get_item_label(const OString& rIdent) const override
11939 #if !GTK_CHECK_VERSION(4, 0, 0)
11940 const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
11941 #else
11942 const gchar* pText = gtk_button_get_label(GTK_BUTTON(m_aMap.find(rIdent)->second));
11943 #endif
11944 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
11947 virtual void set_item_icon_name(const OString& rIdent, const OUString& rIconName) override
11949 GtkWidget* pItem = m_aMap[rIdent];
11950 #if !GTK_CHECK_VERSION(4, 0, 0)
11951 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
11952 return;
11953 #else
11954 if (!pItem || !GTK_IS_BUTTON(pItem))
11955 return;
11956 #endif
11958 GtkWidget* pImage = nullptr;
11960 if (GdkPixbuf* pixbuf = getPixbuf(rIconName))
11962 pImage = gtk_image_new_from_pixbuf(pixbuf);
11963 g_object_unref(pixbuf);
11964 gtk_widget_show(pImage);
11967 #if !GTK_CHECK_VERSION(4, 0, 0)
11968 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
11969 #else
11970 gtk_button_set_child(GTK_BUTTON(pItem), pImage);
11971 // versions of gtk4 > 4.2.1 might do this on their own
11972 gtk_widget_remove_css_class(GTK_WIDGET(pItem), "text-button");
11973 #endif
11976 virtual void set_item_image(const OString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
11978 GtkWidget* pItem = m_aMap[rIdent];
11979 #if !GTK_CHECK_VERSION(4, 0, 0)
11980 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
11981 return;
11982 set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
11983 #else
11984 if (!pItem)
11985 return;
11986 set_item_image(pItem, rIcon);
11987 #endif
11990 virtual void set_item_image(const OString& rIdent, VirtualDevice* pDevice) override
11992 GtkWidget* pItem = m_aMap[rIdent];
11993 #if !GTK_CHECK_VERSION(4, 0, 0)
11994 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
11995 return;
11996 set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
11997 #else
11998 if (!pItem)
11999 return;
12000 set_item_image(pItem, pDevice);
12001 #endif
12004 virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
12006 auto* pItem = toolbar_get_nth_item(nIndex);
12007 #if !GTK_CHECK_VERSION(4, 0, 0)
12008 if (!GTK_IS_TOOL_BUTTON(pItem))
12009 return;
12010 set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
12011 #else
12012 set_item_image(pItem, rIcon);
12013 #endif
12016 virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
12018 auto* pItem = toolbar_get_nth_item(nIndex);
12019 gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
12022 virtual void set_item_tooltip_text(const OString& rIdent, const OUString& rTip) override
12024 GtkWidget* pItem = GTK_WIDGET(m_aMap[rIdent]);
12025 gtk_widget_set_tooltip_text(pItem, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
12028 virtual OUString get_item_tooltip_text(const OString& rIdent) const override
12030 GtkWidget* pItem = GTK_WIDGET(m_aMap.find(rIdent)->second);
12031 const gchar* pStr = gtk_widget_get_tooltip_text(pItem);
12032 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12035 virtual vcl::ImageType get_icon_size() const override
12037 #if GTK_CHECK_VERSION(4, 0, 0)
12038 return m_eImageType;
12039 #else
12040 return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
12041 #endif
12044 virtual void set_icon_size(vcl::ImageType eType) override
12046 #if GTK_CHECK_VERSION(4, 0, 0)
12047 m_eImageType = eType;
12048 #else
12049 gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
12050 #endif
12053 virtual sal_uInt16 get_modifier_state() const override
12055 #if GTK_CHECK_VERSION(4, 0, 0)
12056 GdkDisplay* pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pToolbar));
12057 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
12058 GdkDevice* pDevice = gdk_seat_get_keyboard(pSeat);
12059 guint nState = gdk_device_get_modifier_state(pDevice);
12060 #else
12061 GdkKeymap* pKeymap = gdk_keymap_get_default();
12062 guint nState = gdk_keymap_get_modifier_state(pKeymap);
12063 #endif
12064 return GtkSalFrame::GetKeyModCode(nState);
12067 virtual int get_drop_index(const Point& rPoint) const override
12069 #if !GTK_CHECK_VERSION(4, 0, 0)
12070 return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
12071 #else
12072 GtkWidget* pToolbar = GTK_WIDGET(m_pToolbar);
12073 GtkWidget* pTarget = gtk_widget_pick(pToolbar, rPoint.X(), rPoint.Y(), GTK_PICK_DEFAULT);
12074 if (!pTarget || pTarget == pToolbar)
12075 return -1;
12076 int i = 0;
12077 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
12078 pChild; pChild = gtk_widget_get_next_sibling(pChild))
12080 if (pChild == pTarget)
12081 return i;
12082 ++i;
12084 return -1;
12085 #endif
12088 virtual bool has_focus() const override
12090 if (gtk_widget_has_focus(m_pWidget))
12091 return true;
12093 GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
12094 if (!GTK_IS_WINDOW(pTopLevel))
12095 return false;
12096 GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pTopLevel));
12097 if (!pFocus)
12098 return false;
12099 return gtk_widget_is_ancestor(pFocus, m_pWidget);
12102 virtual void grab_focus() override
12104 if (has_focus())
12105 return;
12106 gtk_widget_grab_focus(m_pWidget);
12107 #if GTK_CHECK_VERSION(4, 0, 0)
12108 bool bHasFocusChild = gtk_widget_get_focus_child(m_pWidget);
12109 #else
12110 bool bHasFocusChild = gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget));
12111 #endif
12112 if (!bHasFocusChild)
12114 if (auto* pItem = toolbar_get_nth_item(0))
12116 #if GTK_CHECK_VERSION(4, 0, 0)
12117 gtk_widget_set_focus_child(m_pWidget, GTK_WIDGET(pItem));
12118 #else
12119 gtk_container_set_focus_child(GTK_CONTAINER(m_pWidget), GTK_WIDGET(pItem));
12120 #endif
12121 bHasFocusChild = true;
12124 if (bHasFocusChild)
12126 #if GTK_CHECK_VERSION(4, 0, 0)
12127 gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
12128 #else
12129 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
12130 #endif
12134 virtual ~GtkInstanceToolbar() override
12136 for (auto& a : m_aMap)
12137 g_signal_handlers_disconnect_by_data(a.second, this);
12143 namespace {
12145 class GtkInstanceLinkButton : public GtkInstanceWidget, public virtual weld::LinkButton
12147 private:
12148 GtkLinkButton* m_pButton;
12149 gulong m_nSignalId;
12151 static bool signalActivateLink(GtkButton*, gpointer widget)
12153 GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
12154 SolarMutexGuard aGuard;
12155 return pThis->signal_activate_link();
12158 public:
12159 GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12160 : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
12161 , m_pButton(pButton)
12162 , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
12166 virtual void set_label(const OUString& rText) override
12168 ::button_set_label(GTK_BUTTON(m_pButton), rText);
12171 virtual OUString get_label() const override
12173 return ::button_get_label(GTK_BUTTON(m_pButton));
12176 virtual void set_uri(const OUString& rText) override
12178 gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12181 virtual OUString get_uri() const override
12183 const gchar* pStr = gtk_link_button_get_uri(m_pButton);
12184 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12187 virtual void disable_notify_events() override
12189 g_signal_handler_block(m_pButton, m_nSignalId);
12190 GtkInstanceWidget::disable_notify_events();
12193 virtual void enable_notify_events() override
12195 GtkInstanceWidget::enable_notify_events();
12196 g_signal_handler_unblock(m_pButton, m_nSignalId);
12199 virtual ~GtkInstanceLinkButton() override
12201 g_signal_handler_disconnect(m_pButton, m_nSignalId);
12207 namespace {
12209 class GtkInstanceCheckButton : public GtkInstanceWidget, public virtual weld::CheckButton
12211 private:
12212 GtkCheckButton* m_pCheckButton;
12213 gulong m_nSignalId;
12215 static void signalToggled(void*, gpointer widget)
12217 GtkInstanceCheckButton* pThis = static_cast<GtkInstanceCheckButton*>(widget);
12218 SolarMutexGuard aGuard;
12219 pThis->signal_toggled();
12222 public:
12223 GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12224 : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
12225 , m_pCheckButton(pButton)
12226 , m_nSignalId(g_signal_connect(m_pCheckButton, "toggled", G_CALLBACK(signalToggled), this))
12230 virtual void set_active(bool active) override
12232 disable_notify_events();
12233 #if GTK_CHECK_VERSION(4, 0, 0)
12234 gtk_check_button_set_inconsistent(m_pCheckButton, false);
12235 gtk_check_button_set_active(m_pCheckButton, active);
12236 #else
12237 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), false);
12238 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pCheckButton), active);
12239 #endif
12240 enable_notify_events();
12243 virtual bool get_active() const override
12245 #if GTK_CHECK_VERSION(4, 0, 0)
12246 return gtk_check_button_get_active(m_pCheckButton);
12247 #else
12248 return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pCheckButton));
12249 #endif
12252 virtual void set_inconsistent(bool inconsistent) override
12254 #if GTK_CHECK_VERSION(4, 0, 0)
12255 gtk_check_button_set_inconsistent(m_pCheckButton, inconsistent);
12256 #else
12257 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), inconsistent);
12258 #endif
12261 virtual bool get_inconsistent() const override
12263 #if GTK_CHECK_VERSION(4, 0, 0)
12264 return gtk_check_button_get_inconsistent(m_pCheckButton);
12265 #else
12266 return gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton));
12267 #endif
12270 virtual void set_label(const OUString& rText) override
12272 #if GTK_CHECK_VERSION(4, 0, 0)
12273 gtk_check_button_set_label(m_pCheckButton, MapToGtkAccelerator(rText).getStr());
12274 #else
12275 ::button_set_label(GTK_BUTTON(m_pCheckButton), rText);
12276 #endif
12279 virtual OUString get_label() const override
12281 #if GTK_CHECK_VERSION(4, 0, 0)
12282 const gchar* pStr = gtk_check_button_get_label(m_pCheckButton);
12283 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12284 #else
12285 return ::button_get_label(GTK_BUTTON(m_pCheckButton));
12286 #endif
12289 virtual void set_label_wrap(bool bWrap) override
12291 GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pCheckButton));
12292 ::set_label_wrap(pChild, bWrap);
12295 virtual void disable_notify_events() override
12297 g_signal_handler_block(m_pCheckButton, m_nSignalId);
12298 GtkInstanceWidget::disable_notify_events();
12301 virtual void enable_notify_events() override
12303 GtkInstanceWidget::enable_notify_events();
12304 g_signal_handler_unblock(m_pCheckButton, m_nSignalId);
12307 virtual ~GtkInstanceCheckButton() override
12309 g_signal_handler_disconnect(m_pCheckButton, m_nSignalId);
12313 class GtkInstanceRadioButton : public GtkInstanceCheckButton, public virtual weld::RadioButton
12315 public:
12316 #if GTK_CHECK_VERSION(4, 0, 0)
12317 GtkInstanceRadioButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12318 : GtkInstanceCheckButton(pButton, pBuilder, bTakeOwnership)
12319 #else
12320 GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12321 : GtkInstanceCheckButton(GTK_CHECK_BUTTON(pButton), pBuilder, bTakeOwnership)
12322 #endif
12329 namespace {
12331 class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
12333 private:
12334 GtkScale* m_pScale;
12335 gulong m_nValueChangedSignalId;
12337 static void signalValueChanged(GtkScale*, gpointer widget)
12339 GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
12340 SolarMutexGuard aGuard;
12341 pThis->signal_value_changed();
12344 public:
12345 GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12346 : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
12347 , m_pScale(pScale)
12348 , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
12352 virtual void disable_notify_events() override
12354 g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
12355 GtkInstanceWidget::disable_notify_events();
12358 virtual void enable_notify_events() override
12360 GtkInstanceWidget::enable_notify_events();
12361 g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
12364 virtual void set_value(int value) override
12366 disable_notify_events();
12367 gtk_range_set_value(GTK_RANGE(m_pScale), value);
12368 enable_notify_events();
12371 virtual void set_range(int min, int max) override
12373 disable_notify_events();
12374 gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
12375 enable_notify_events();
12378 virtual void set_increments(int step, int page) override
12380 disable_notify_events();
12381 gtk_range_set_increments(GTK_RANGE(m_pScale), step, page);
12382 enable_notify_events();
12385 virtual void get_increments(int& step, int& page) const override
12387 GtkAdjustment* pAdjustment = gtk_range_get_adjustment(GTK_RANGE(m_pScale));
12388 step = gtk_adjustment_get_step_increment(pAdjustment);
12389 page = gtk_adjustment_get_page_increment(pAdjustment);
12392 virtual int get_value() const override
12394 return gtk_range_get_value(GTK_RANGE(m_pScale));
12397 virtual ~GtkInstanceScale() override
12399 g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
12403 class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
12405 private:
12406 GtkProgressBar* m_pProgressBar;
12408 public:
12409 GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12410 : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
12411 , m_pProgressBar(pProgressBar)
12415 virtual void set_percentage(int value) override
12417 gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
12420 virtual OUString get_text() const override
12422 const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
12423 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
12424 return sRet;
12427 virtual void set_text(const OUString& rText) override
12429 gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12433 class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
12435 private:
12436 GtkSpinner* m_pSpinner;
12438 public:
12439 GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12440 : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
12441 , m_pSpinner(pSpinner)
12445 virtual void start() override
12447 gtk_spinner_start(m_pSpinner);
12450 virtual void stop() override
12452 gtk_spinner_stop(m_pSpinner);
12456 class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
12458 private:
12459 GtkImage* m_pImage;
12461 public:
12462 GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12463 : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
12464 , m_pImage(pImage)
12468 virtual void set_from_icon_name(const OUString& rIconName) override
12470 image_set_from_icon_name(m_pImage, rIconName);
12473 virtual void set_image(VirtualDevice* pDevice) override
12475 image_set_from_virtual_device(m_pImage, pDevice);
12478 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
12480 image_set_from_xgraphic(m_pImage, rImage);
12484 #if GTK_CHECK_VERSION(4, 0, 0)
12485 class GtkInstancePicture: public GtkInstanceWidget, public virtual weld::Image
12487 private:
12488 GtkPicture* m_pPicture;
12490 public:
12491 GtkInstancePicture(GtkPicture* pPicture, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12492 : GtkInstanceWidget(GTK_WIDGET(pPicture), pBuilder, bTakeOwnership)
12493 , m_pPicture(pPicture)
12495 gtk_picture_set_can_shrink(m_pPicture, true);
12498 virtual void set_from_icon_name(const OUString& rIconName) override
12500 picture_set_from_icon_name(m_pPicture, rIconName);
12503 virtual void set_image(VirtualDevice* pDevice) override
12505 picture_set_from_virtual_device(m_pPicture, pDevice);
12508 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rPicture) override
12510 picture_set_from_xgraphic(m_pPicture, rPicture);
12513 #endif
12515 class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
12517 private:
12518 GtkCalendar* m_pCalendar;
12519 #if GTK_CHECK_VERSION(4, 0, 0)
12520 GtkEventController* m_pKeyController;
12521 #endif
12522 gulong m_nDaySelectedSignalId;
12523 gulong m_nDaySelectedDoubleClickSignalId;
12524 gulong m_nKeyPressEventSignalId;
12525 #if !GTK_CHECK_VERSION(4, 0, 0)
12526 gulong m_nButtonPressEventSignalId;
12527 #endif
12529 static void signalDaySelected(GtkCalendar*, gpointer widget)
12531 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
12532 SolarMutexGuard aGuard;
12533 pThis->signal_selected();
12536 static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
12538 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
12539 SolarMutexGuard aGuard;
12540 pThis->signal_activated();
12543 bool signal_key_press(guint nKeyVal)
12545 if (nKeyVal == GDK_KEY_Return || nKeyVal == GDK_KEY_KP_Enter)
12547 SolarMutexGuard aGuard;
12548 signal_activated();
12549 return true;
12551 return false;
12554 #if GTK_CHECK_VERSION(4, 0, 0)
12555 static gboolean signalKeyPress(GtkEventControllerKey*, guint nKeyVal, guint /*nKeyCode*/, GdkModifierType, gpointer widget)
12557 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
12558 return pThis->signal_key_press(nKeyVal);
12560 #else
12561 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
12563 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
12564 return pThis->signal_key_press(pEvent->keyval);
12566 #endif
12568 #if !GTK_CHECK_VERSION(4, 0, 0)
12569 static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
12571 // don't let button press get to parent window, for the case of the
12572 // ImplCFieldFloatWin floating window belonging to CalendarField where
12573 // the click on the calendar continues to the parent GtkWindow and
12574 // closePopup is called by GtkSalFrame::signalButton because the click
12575 // window isn't that of the floating parent GtkWindow
12576 return true;
12578 #endif
12580 public:
12581 GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12582 : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
12583 , m_pCalendar(pCalendar)
12584 #if GTK_CHECK_VERSION(4, 0, 0)
12585 , m_pKeyController(gtk_event_controller_key_new())
12586 #endif
12587 , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
12588 , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
12589 #if GTK_CHECK_VERSION(4, 0, 0)
12590 , m_nKeyPressEventSignalId(g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this))
12591 #else
12592 , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
12593 , m_nButtonPressEventSignalId(g_signal_connect_after(pCalendar, "button-press-event", G_CALLBACK(signalButton), this))
12594 #endif
12596 #if GTK_CHECK_VERSION(4, 0, 0)
12597 gtk_widget_add_controller(GTK_WIDGET(m_pCalendar), m_pKeyController);
12598 #endif
12601 virtual void set_date(const Date& rDate) override
12603 if (!rDate.IsValidAndGregorian())
12604 return;
12606 disable_notify_events();
12607 #if GTK_CHECK_VERSION(4, 0, 0)
12608 GDateTime* pDateTime = g_date_time_new_local(rDate.GetYear(), rDate.GetMonth(), rDate.GetDay(), 0, 0, 0);
12609 gtk_calendar_select_day(m_pCalendar, pDateTime);
12610 g_date_time_unref(pDateTime);
12611 #else
12612 gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
12613 gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
12614 #endif
12615 enable_notify_events();
12618 virtual Date get_date() const override
12620 #if GTK_CHECK_VERSION(4, 0, 0)
12621 GDateTime* pDateTime = gtk_calendar_get_date(m_pCalendar);
12622 Date aDate(g_date_time_get_day_of_month(pDateTime),
12623 g_date_time_get_month(pDateTime),
12624 g_date_time_get_year(pDateTime));
12625 g_date_time_unref(pDateTime);
12626 return aDate;
12627 #else
12628 guint year, month, day;
12629 gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
12630 return Date(day, month + 1, year);
12631 #endif
12634 virtual void disable_notify_events() override
12636 g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
12637 g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
12638 GtkInstanceWidget::disable_notify_events();
12641 virtual void enable_notify_events() override
12643 GtkInstanceWidget::enable_notify_events();
12644 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
12645 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
12648 virtual ~GtkInstanceCalendar() override
12650 #if GTK_CHECK_VERSION(4, 0, 0)
12651 g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
12652 #else
12653 g_signal_handler_disconnect(m_pCalendar, m_nButtonPressEventSignalId);
12654 g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
12655 #endif
12656 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
12657 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
12663 namespace
12665 // CSS nodes: entry[.flat][.warning][.error]
12666 void set_widget_css_message_type(GtkWidget* pWidget, weld::EntryMessageType eType)
12668 #if GTK_CHECK_VERSION(4, 0, 0)
12669 gtk_widget_remove_css_class(pWidget, "error");
12670 gtk_widget_remove_css_class(pWidget, "warning");
12671 #else
12672 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(pWidget);
12673 gtk_style_context_remove_class(pWidgetContext, "error");
12674 gtk_style_context_remove_class(pWidgetContext, "warning");
12675 #endif
12677 switch (eType)
12679 case weld::EntryMessageType::Normal:
12680 break;
12681 case weld::EntryMessageType::Warning:
12682 #if GTK_CHECK_VERSION(4, 0, 0)
12683 gtk_widget_add_css_class(pWidget, "warning");
12684 #else
12685 gtk_style_context_add_class(pWidgetContext, "warning");
12686 #endif
12687 break;
12688 case weld::EntryMessageType::Error:
12689 #if GTK_CHECK_VERSION(4, 0, 0)
12690 gtk_widget_add_css_class(pWidget, "error");
12691 #else
12692 gtk_style_context_add_class(pWidgetContext, "error");
12693 #endif
12694 break;
12698 void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
12700 set_widget_css_message_type(GTK_WIDGET(pEntry), eType);
12701 switch (eType)
12703 case weld::EntryMessageType::Normal:
12704 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
12705 break;
12706 case weld::EntryMessageType::Warning:
12707 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
12708 break;
12709 case weld::EntryMessageType::Error:
12710 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
12711 break;
12716 namespace
12719 class GtkInstanceEditable : public GtkInstanceWidget, public virtual weld::Entry
12721 protected:
12722 GtkEditable* m_pEditable;
12723 GtkWidget* m_pDelegate;
12724 WidgetFont m_aCustomFont;
12725 private:
12726 gulong m_nChangedSignalId;
12727 gulong m_nInsertTextSignalId;
12728 gulong m_nCursorPosSignalId;
12729 gulong m_nSelectionPosSignalId;
12730 gulong m_nActivateSignalId;
12732 static void signalChanged(GtkEditable*, gpointer widget)
12734 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
12735 SolarMutexGuard aGuard;
12736 pThis->signal_changed();
12739 static void signalInsertText(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength,
12740 gint* position, gpointer widget)
12742 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
12743 SolarMutexGuard aGuard;
12744 pThis->signal_insert_text(pEditable, pNewText, nNewTextLength, position);
12747 void signal_insert_text(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength, gint* position)
12749 if (!m_aInsertTextHdl.IsSet())
12750 return;
12751 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
12752 const bool bContinue = m_aInsertTextHdl.Call(sText);
12753 if (bContinue && !sText.isEmpty())
12755 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
12756 g_signal_handlers_block_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
12757 gtk_editable_insert_text(pEditable, sFinalText.getStr(), sFinalText.getLength(), position);
12758 g_signal_handlers_unblock_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
12760 g_signal_stop_emission_by_name(pEditable, "insert-text");
12763 static void signalCursorPosition(void*, GParamSpec*, gpointer widget)
12765 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
12766 pThis->signal_cursor_position();
12769 static void signalActivate(void*, gpointer widget)
12771 GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
12772 pThis->signal_activate();
12775 virtual void ensureMouseEventWidget() override
12777 // The GtkEntry is sufficient to get mouse events without an intermediate GtkEventBox
12778 if (!m_pMouseEventBox)
12779 m_pMouseEventBox = m_pDelegate;
12782 protected:
12784 virtual void signal_activate()
12786 if (m_aActivateHdl.IsSet())
12788 SolarMutexGuard aGuard;
12789 if (m_aActivateHdl.Call(*this))
12790 g_signal_stop_emission_by_name(m_pDelegate, "activate");
12794 PangoAttrList* get_attributes()
12796 #if GTK_CHECK_VERSION(4, 0, 0)
12797 return gtk_text_get_attributes(GTK_TEXT(m_pDelegate));
12798 #else
12799 return gtk_entry_get_attributes(GTK_ENTRY(m_pDelegate));
12800 #endif
12803 void set_attributes(PangoAttrList* pAttrs)
12805 #if GTK_CHECK_VERSION(4, 0, 0)
12806 gtk_text_set_attributes(GTK_TEXT(m_pDelegate), pAttrs);
12807 #else
12808 gtk_entry_set_attributes(GTK_ENTRY(m_pDelegate), pAttrs);
12809 #endif
12812 public:
12813 GtkInstanceEditable(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12814 : GtkInstanceWidget(pWidget, pBuilder, bTakeOwnership)
12815 , m_pEditable(GTK_EDITABLE(pWidget))
12816 #if GTK_CHECK_VERSION(4, 0, 0)
12817 , m_pDelegate(GTK_WIDGET(gtk_editable_get_delegate(m_pEditable)))
12818 #else
12819 , m_pDelegate(pWidget)
12820 #endif
12821 , m_aCustomFont(m_pWidget)
12822 , m_nChangedSignalId(g_signal_connect(m_pEditable, "changed", G_CALLBACK(signalChanged), this))
12823 , m_nInsertTextSignalId(g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalInsertText), this))
12824 , m_nCursorPosSignalId(g_signal_connect(m_pEditable, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
12825 , m_nSelectionPosSignalId(g_signal_connect(m_pEditable, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
12826 , m_nActivateSignalId(g_signal_connect(m_pDelegate, "activate", G_CALLBACK(signalActivate), this))
12830 virtual void set_text(const OUString& rText) override
12832 disable_notify_events();
12833 #if GTK_CHECK_VERSION(4, 0, 0)
12834 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12835 #else
12836 gtk_entry_set_text(GTK_ENTRY(m_pDelegate), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12837 #endif
12838 enable_notify_events();
12841 virtual OUString get_text() const override
12843 #if GTK_CHECK_VERSION(4, 0, 0)
12844 const gchar* pText = gtk_editable_get_text(m_pEditable);
12845 #else
12846 const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pDelegate));
12847 #endif
12848 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
12849 return sRet;
12852 virtual void set_width_chars(int nChars) override
12854 disable_notify_events();
12855 #if GTK_CHECK_VERSION(4, 0, 0)
12856 gtk_editable_set_width_chars(m_pEditable, nChars);
12857 gtk_editable_set_max_width_chars(m_pEditable, nChars);
12858 #else
12859 gtk_entry_set_width_chars(GTK_ENTRY(m_pDelegate), nChars);
12860 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pDelegate), nChars);
12861 #endif
12862 enable_notify_events();
12865 virtual int get_width_chars() const override
12867 #if GTK_CHECK_VERSION(4, 0, 0)
12868 return gtk_editable_get_width_chars(m_pEditable);
12869 #else
12870 return gtk_entry_get_width_chars(GTK_ENTRY(m_pDelegate));
12871 #endif
12874 virtual void set_max_length(int nChars) override
12876 disable_notify_events();
12877 #if GTK_CHECK_VERSION(4, 0, 0)
12878 gtk_text_set_max_length(GTK_TEXT(m_pDelegate), nChars);
12879 #else
12880 gtk_entry_set_max_length(GTK_ENTRY(m_pDelegate), nChars);
12881 #endif
12882 enable_notify_events();
12885 virtual void select_region(int nStartPos, int nEndPos) override
12887 disable_notify_events();
12888 gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
12889 enable_notify_events();
12892 bool get_selection_bounds(int& rStartPos, int& rEndPos) override
12894 return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
12897 virtual void replace_selection(const OUString& rText) override
12899 disable_notify_events();
12900 gtk_editable_delete_selection(m_pEditable);
12901 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
12902 gint position = gtk_editable_get_position(m_pEditable);
12903 gtk_editable_insert_text(m_pEditable, sText.getStr(), sText.getLength(),
12904 &position);
12905 enable_notify_events();
12908 virtual void set_position(int nCursorPos) override
12910 disable_notify_events();
12911 gtk_editable_set_position(m_pEditable, nCursorPos);
12912 enable_notify_events();
12915 virtual int get_position() const override
12917 return gtk_editable_get_position(m_pEditable);
12920 virtual void set_editable(bool bEditable) override
12922 gtk_editable_set_editable(m_pEditable, bEditable);
12925 virtual bool get_editable() const override
12927 return gtk_editable_get_editable(m_pEditable);
12930 virtual void set_overwrite_mode(bool bOn) override
12932 #if GTK_CHECK_VERSION(4, 0, 0)
12933 gtk_text_set_overwrite_mode(GTK_TEXT(m_pDelegate), bOn);
12934 #else
12935 gtk_entry_set_overwrite_mode(GTK_ENTRY(m_pDelegate), bOn);
12936 #endif
12939 virtual bool get_overwrite_mode() const override
12941 #if GTK_CHECK_VERSION(4, 0, 0)
12942 return gtk_text_get_overwrite_mode(GTK_TEXT(m_pDelegate));
12943 #else
12944 return gtk_entry_get_overwrite_mode(GTK_ENTRY(m_pDelegate));
12945 #endif
12948 virtual void set_message_type(weld::EntryMessageType eType) override
12950 #if GTK_CHECK_VERSION(4, 0, 0)
12951 if (!GTK_IS_ENTRY(m_pDelegate))
12953 ::set_widget_css_message_type(m_pDelegate, eType);
12954 return;
12956 #endif
12957 ::set_entry_message_type(GTK_ENTRY(m_pDelegate), eType);
12960 virtual void disable_notify_events() override
12962 g_signal_handler_block(m_pDelegate, m_nActivateSignalId);
12963 g_signal_handler_block(m_pEditable, m_nSelectionPosSignalId);
12964 g_signal_handler_block(m_pEditable, m_nCursorPosSignalId);
12965 g_signal_handler_block(m_pEditable, m_nInsertTextSignalId);
12966 g_signal_handler_block(m_pEditable, m_nChangedSignalId);
12967 GtkInstanceWidget::disable_notify_events();
12970 virtual void enable_notify_events() override
12972 GtkInstanceWidget::enable_notify_events();
12973 g_signal_handler_unblock(m_pEditable, m_nChangedSignalId);
12974 g_signal_handler_unblock(m_pEditable, m_nInsertTextSignalId);
12975 g_signal_handler_unblock(m_pEditable, m_nCursorPosSignalId);
12976 g_signal_handler_unblock(m_pEditable, m_nSelectionPosSignalId);
12977 g_signal_handler_unblock(m_pDelegate, m_nActivateSignalId);
12980 virtual vcl::Font get_font() override
12982 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
12983 return *pFont;
12984 return GtkInstanceWidget::get_font();
12987 void set_font_color(const Color& rColor) override
12989 PangoAttrList* pOrigList = get_attributes();
12990 if (rColor == COL_AUTO && !pOrigList) // nothing to do
12991 return;
12993 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_INVALID};
12995 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
12996 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
12998 if (rColor != COL_AUTO)
12999 pango_attr_list_insert(pAttrs, pango_attr_foreground_new(rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0));
13001 set_attributes(pAttrs);
13002 pango_attr_list_unref(pAttrs);
13003 pango_attr_list_unref(pRemovedAttrs);
13006 void fire_signal_changed()
13008 signal_changed();
13011 virtual void cut_clipboard() override
13013 #if GTK_CHECK_VERSION(4, 0, 0)
13014 gtk_widget_activate_action(m_pDelegate, "cut.clipboard", nullptr);
13015 #else
13016 gtk_editable_cut_clipboard(m_pEditable);
13017 #endif
13020 virtual void copy_clipboard() override
13022 #if GTK_CHECK_VERSION(4, 0, 0)
13023 gtk_widget_activate_action(m_pDelegate, "copy.clipboard", nullptr);
13024 #else
13025 gtk_editable_copy_clipboard(m_pEditable);
13026 #endif
13029 virtual void paste_clipboard() override
13031 #if GTK_CHECK_VERSION(4, 0, 0)
13032 gtk_widget_activate_action(m_pDelegate, "paste.clipboard", nullptr);
13033 #else
13034 gtk_editable_paste_clipboard(m_pEditable);
13035 #endif
13038 virtual void set_placeholder_text(const OUString& rText) override
13040 #if GTK_CHECK_VERSION(4, 0, 0)
13041 gtk_text_set_placeholder_text(GTK_TEXT(m_pDelegate), rText.toUtf8().getStr());
13042 #else
13043 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pDelegate), rText.toUtf8().getStr());
13044 #endif
13047 virtual void grab_focus() override
13049 if (has_focus())
13050 return;
13051 #if GTK_CHECK_VERSION(4, 0, 0)
13052 gtk_text_grab_focus_without_selecting(GTK_TEXT(m_pDelegate));
13053 #else
13054 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(m_pDelegate));
13055 #endif
13058 virtual void set_alignment(TxtAlign eXAlign) override
13060 gfloat xalign = 0;
13061 switch (eXAlign)
13063 case TxtAlign::Left:
13064 xalign = 0.0;
13065 break;
13066 case TxtAlign::Center:
13067 xalign = 0.5;
13068 break;
13069 case TxtAlign::Right:
13070 xalign = 1.0;
13071 break;
13073 #if GTK_CHECK_VERSION(4, 0, 0)
13074 gtk_editable_set_alignment(m_pEditable, xalign);
13075 #else
13076 gtk_entry_set_alignment(GTK_ENTRY(m_pDelegate), xalign);
13077 #endif
13080 virtual ~GtkInstanceEditable() override
13082 g_signal_handler_disconnect(m_pDelegate, m_nActivateSignalId);
13083 g_signal_handler_disconnect(m_pEditable, m_nSelectionPosSignalId);
13084 g_signal_handler_disconnect(m_pEditable, m_nCursorPosSignalId);
13085 g_signal_handler_disconnect(m_pEditable, m_nInsertTextSignalId);
13086 g_signal_handler_disconnect(m_pEditable, m_nChangedSignalId);
13090 class GtkInstanceEntry : public GtkInstanceEditable
13092 public:
13093 GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
13094 : GtkInstanceEditable(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
13098 virtual void set_font(const vcl::Font& rFont) override
13100 m_aCustomFont.use_custom_font(&rFont, u"entry");
13106 namespace
13109 struct Search
13111 OString str;
13112 int index;
13113 int col;
13114 Search(std::u16string_view rText, int nCol)
13115 : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
13116 , index(-1)
13117 , col(nCol)
13122 gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
13124 Search* search = static_cast<Search*>(data);
13125 gchar *pStr = nullptr;
13126 gtk_tree_model_get(model, iter, search->col, &pStr, -1);
13127 bool found = strcmp(pStr, search->str.getStr()) == 0;
13128 if (found)
13130 gint depth;
13131 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
13132 search->index = indices[depth-1];
13134 g_free(pStr);
13135 return found;
13138 void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, std::u16string_view rText, const OUString* pIconName, const VirtualDevice* pDevice)
13140 if (!pIconName && !pDevice)
13142 gtk_list_store_insert_with_values(pListStore, &iter, pos,
13143 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
13144 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
13145 -1);
13147 else
13149 if (pIconName)
13151 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
13153 gtk_list_store_insert_with_values(pListStore, &iter, pos,
13154 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
13155 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
13156 2, pixbuf,
13157 -1);
13159 if (pixbuf)
13160 g_object_unref(pixbuf);
13162 else
13164 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
13166 Size aSize(pDevice->GetOutputSizePixel());
13167 cairo_surface_t* target = cairo_surface_create_similar(surface,
13168 cairo_surface_get_content(surface),
13169 aSize.Width(),
13170 aSize.Height());
13172 cairo_t* cr = cairo_create(target);
13173 cairo_set_source_surface(cr, surface, 0, 0);
13174 cairo_paint(cr);
13175 cairo_destroy(cr);
13177 gtk_list_store_insert_with_values(pListStore, &iter, pos,
13178 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
13179 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
13180 3, target,
13181 -1);
13182 cairo_surface_destroy(target);
13188 namespace
13190 gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
13192 comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
13193 gchar* pName1;
13194 gchar* pName2;
13195 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
13196 gint sort_column_id(0);
13197 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
13198 gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
13199 gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
13200 gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
13201 OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
13202 g_free(pName1);
13203 g_free(pName2);
13204 return ret;
13207 int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
13209 GtkTreeIter iter;
13210 if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
13211 return -1;
13213 const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
13214 int nRet = nStartRow;
13217 gchar* pStr;
13218 gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
13219 OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
13220 g_free(pStr);
13221 const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
13222 if (bMatch)
13223 return nRet;
13224 ++nRet;
13225 } while (gtk_tree_model_iter_next(pTreeModel, &iter));
13227 return -1;
13230 struct GtkInstanceTreeIter : public weld::TreeIter
13232 GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
13234 if (pOrig)
13235 iter = pOrig->iter;
13236 else
13237 memset(&iter, 0, sizeof(iter));
13239 GtkInstanceTreeIter(const GtkTreeIter& rOrig)
13241 memcpy(&iter, &rOrig, sizeof(iter));
13243 virtual bool equal(const TreeIter& rOther) const override
13245 return memcmp(&iter, &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
13247 GtkTreeIter iter;
13250 class GtkInstanceTreeView;
13254 static GtkInstanceTreeView* g_DragSource;
13256 namespace {
13258 struct CompareGtkTreePath
13260 bool operator()(const GtkTreePath* lhs, const GtkTreePath* rhs) const
13262 return gtk_tree_path_compare(lhs, rhs) < 0;
13266 int get_height_row(GtkTreeView* pTreeView, GList* pColumns)
13268 gint nMaxRowHeight = 0;
13269 for (GList* pEntry = g_list_first(pColumns); pEntry; pEntry = g_list_next(pEntry))
13271 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
13272 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
13273 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
13275 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
13276 gint nRowHeight;
13277 gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(pTreeView), nullptr, &nRowHeight);
13278 nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
13280 g_list_free(pRenderers);
13282 return nMaxRowHeight;
13285 int get_height_row_separator(GtkTreeView* pTreeView)
13287 // gtk4: _TREE_VIEW_VERTICAL_SEPARATOR define in gtk/gtktreeview.c
13288 gint nVerticalSeparator = 2;
13289 #if !GTK_CHECK_VERSION(4, 0, 0)
13290 gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
13291 #else
13292 (void)pTreeView;
13293 #endif
13294 return nVerticalSeparator;
13297 int get_height_rows(GtkTreeView* pTreeView, GList* pColumns, int nRows)
13299 gint nMaxRowHeight = get_height_row(pTreeView, pColumns);
13300 gint nVerticalSeparator = get_height_row_separator(pTreeView);
13301 return (nMaxRowHeight * nRows) + (nVerticalSeparator * nRows) / 2;
13304 #if !GTK_CHECK_VERSION(4, 0, 0)
13305 int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
13307 return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
13309 #endif
13311 tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePath* pPath)
13313 tools::Rectangle aRet;
13315 GdkRectangle aRect;
13316 for (GList* pEntry = g_list_last(pColumns); pEntry; pEntry = g_list_previous(pEntry))
13318 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
13319 gtk_tree_view_get_cell_area(pTreeView, pPath, pColumn, &aRect);
13320 aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height));
13323 return aRet;
13326 struct GtkTreeRowReferenceDeleter
13328 void operator()(GtkTreeRowReference* p) const
13330 gtk_tree_row_reference_free(p);
13334 bool separator_function(const GtkTreePath* path, const std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>>& rSeparatorRows)
13336 bool bFound = false;
13337 for (auto& a : rSeparatorRows)
13339 GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get());
13340 if (seppath)
13342 bFound = gtk_tree_path_compare(path, seppath) == 0;
13343 gtk_tree_path_free(seppath);
13345 if (bFound)
13346 break;
13348 return bFound;
13351 void tree_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
13353 va_list args;
13355 va_start(args, pIter);
13356 gtk_tree_store_set_valist(GTK_TREE_STORE(pTreeModel), pIter, args);
13357 va_end(args);
13360 void list_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
13362 va_list args;
13364 va_start(args, pIter);
13365 gtk_list_store_set_valist(GTK_LIST_STORE(pTreeModel), pIter, args);
13366 va_end(args);
13369 void tree_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
13370 gint nTextCol, const gchar* pText,
13371 gint nIdCol, const gchar* pId)
13373 gtk_tree_store_insert_with_values(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPos,
13374 nTextCol, pText, nIdCol, pId, -1);
13377 void list_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
13378 gint nTextCol, const gchar* pText,
13379 gint nIdCol, const gchar* pId)
13381 assert(!pParent); (void)pParent;
13382 gtk_list_store_insert_with_values(GTK_LIST_STORE(pTreeModel), pIter, nPos,
13383 nTextCol, pText, nIdCol, pId, -1);
13386 void tree_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
13388 gtk_tree_store_prepend(GTK_TREE_STORE(pTreeModel), pIter, pParent);
13391 void list_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
13393 assert(!pParent); (void)pParent;
13394 gtk_list_store_prepend(GTK_LIST_STORE(pTreeModel), pIter);
13397 void tree_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
13399 gtk_tree_store_insert(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPosition);
13402 void list_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
13404 assert(!pParent); (void)pParent;
13405 gtk_list_store_insert(GTK_LIST_STORE(pTreeModel), pIter, nPosition);
13408 void tree_store_clear(GtkTreeModel* pTreeModel)
13410 gtk_tree_store_clear(GTK_TREE_STORE(pTreeModel));
13413 void list_store_clear(GtkTreeModel* pTreeModel)
13415 gtk_list_store_clear(GTK_LIST_STORE(pTreeModel));
13418 bool tree_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
13420 return gtk_tree_store_remove(GTK_TREE_STORE(pTreeModel), pIter);
13423 bool list_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
13425 return gtk_list_store_remove(GTK_LIST_STORE(pTreeModel), pIter);
13428 void tree_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
13430 gtk_tree_store_swap(GTK_TREE_STORE(pTreeModel), pIter1, pIter2);
13433 void list_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
13435 gtk_list_store_swap(GTK_LIST_STORE(pTreeModel), pIter1, pIter2);
13438 void tree_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
13440 gtk_tree_store_set_value(GTK_TREE_STORE(pTreeModel), pIter, nColumn, pValue);
13443 void list_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
13445 gtk_list_store_set_value(GTK_LIST_STORE(pTreeModel), pIter, nColumn, pValue);
13448 int promote_arg(bool bArg)
13450 return static_cast<int>(bArg);
13453 class GtkInstanceTreeView : public GtkInstanceWidget, public virtual weld::TreeView
13455 private:
13456 GtkTreeView* m_pTreeView;
13457 GtkTreeModel* m_pTreeModel;
13459 typedef void(*setterFnc)(GtkTreeModel*, GtkTreeIter*, ...);
13460 setterFnc m_Setter;
13462 typedef void(*insertWithValuesFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint, gint, const gchar*, gint, const gchar*);
13463 insertWithValuesFnc m_InsertWithValues;
13465 typedef void(*insertFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint);
13466 insertFnc m_Insert;
13468 typedef void(*prependFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
13469 prependFnc m_Prepend;
13471 typedef void(*clearFnc)(GtkTreeModel*);
13472 clearFnc m_Clear;
13474 typedef bool(*removeFnc)(GtkTreeModel*, GtkTreeIter*);
13475 removeFnc m_Remove;
13477 typedef void(*swapFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
13478 swapFnc m_Swap;
13480 typedef void(*setValueFnc)(GtkTreeModel*, GtkTreeIter*, gint, GValue*);
13481 setValueFnc m_SetValue;
13483 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
13484 GList *m_pColumns;
13485 std::vector<gulong> m_aColumnSignalIds;
13486 // map from toggle column to toggle visibility column
13487 std::map<int, int> m_aToggleVisMap;
13488 // map from toggle column to tristate column
13489 std::map<int, int> m_aToggleTriStateMap;
13490 // map from text column to text weight column
13491 std::map<int, int> m_aWeightMap;
13492 // map from text column to sensitive column
13493 std::map<int, int> m_aSensitiveMap;
13494 // map from text column to indent column
13495 std::map<int, int> m_aIndentMap;
13496 // map from text column to text align column
13497 std::map<int, int> m_aAlignMap;
13498 // currently expanding parent that logically, but not currently physically,
13499 // contain placeholders
13500 o3tl::sorted_vector<GtkTreePath*, CompareGtkTreePath> m_aExpandingPlaceHolderParents;
13501 // which rows are separators (rare)
13502 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
13503 std::vector<GtkSortType> m_aSavedSortTypes;
13504 std::vector<int> m_aSavedSortColumns;
13505 bool m_bWorkAroundBadDragRegion;
13506 bool m_bInDrag;
13507 bool m_bChangedByMouse;
13508 gint m_nTextCol;
13509 gint m_nTextView;
13510 gint m_nImageCol;
13511 gint m_nExpanderToggleCol;
13512 gint m_nExpanderImageCol;
13513 gint m_nIdCol;
13514 int m_nPendingVAdjustment;
13515 gulong m_nChangedSignalId;
13516 gulong m_nRowActivatedSignalId;
13517 gulong m_nTestExpandRowSignalId;
13518 gulong m_nTestCollapseRowSignalId;
13519 gulong m_nVAdjustmentChangedSignalId;
13520 gulong m_nRowDeletedSignalId;
13521 gulong m_nRowInsertedSignalId;
13522 #if !GTK_CHECK_VERSION(4, 0, 0)
13523 gulong m_nPopupMenuSignalId;
13524 gulong m_nKeyPressSignalId;
13525 #endif
13526 gulong m_nQueryTooltipSignalId;
13527 GtkAdjustment* m_pVAdjustment;
13528 ImplSVEvent* m_pChangeEvent;
13530 DECL_LINK(async_signal_changed, void*, void);
13532 void launch_signal_changed()
13534 //tdf#117991 selection change is sent before the focus change, and focus change
13535 //is what will cause a spinbutton that currently has the focus to set its contents
13536 //as the spin button value. So any LibreOffice callbacks on
13537 //signal-change would happen before the spinbutton value-change occurs.
13538 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
13539 //has been processed
13540 if (m_pChangeEvent)
13541 Application::RemoveUserEvent(m_pChangeEvent);
13543 #if !GTK_CHECK_VERSION(4, 0, 0)
13544 GdkEvent *pEvent = gtk_get_current_event();
13545 m_bChangedByMouse = pEvent && categorizeEvent(pEvent) == VclInputFlags::MOUSE;
13546 #else
13547 //TODO maybe iterate over gtk_widget_observe_controllers looking for a motion controller
13548 #endif
13550 m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
13553 static void signalChanged(GtkTreeView*, gpointer widget)
13555 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13556 pThis->launch_signal_changed();
13559 void handle_row_activated()
13561 if (signal_row_activated())
13562 return;
13563 GtkInstanceTreeIter aIter(nullptr);
13564 if (!get_cursor(&aIter))
13565 return;
13566 if (gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter))
13567 get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
13570 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
13572 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13573 SolarMutexGuard aGuard;
13574 pThis->handle_row_activated();
13577 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
13579 return m_aPopupMenuHdl.Call(rCEvt);
13582 void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
13583 const OUString* pIconName, const VirtualDevice* pDevice)
13585 m_InsertWithValues(m_pTreeModel, &iter, const_cast<GtkTreeIter*>(parent), pos,
13586 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
13587 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr());
13589 if (pIconName)
13591 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
13592 m_Setter(m_pTreeModel, &iter, m_nImageCol, pixbuf, -1);
13593 if (pixbuf)
13594 g_object_unref(pixbuf);
13596 else if (pDevice)
13598 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
13600 Size aSize(pDevice->GetOutputSizePixel());
13601 cairo_surface_t* target = cairo_surface_create_similar(surface,
13602 cairo_surface_get_content(surface),
13603 aSize.Width(),
13604 aSize.Height());
13606 cairo_t* cr = cairo_create(target);
13607 cairo_set_source_surface(cr, surface, 0, 0);
13608 cairo_paint(cr);
13609 cairo_destroy(cr);
13611 m_Setter(m_pTreeModel, &iter, m_nImageCol, target, -1);
13612 cairo_surface_destroy(target);
13616 bool separator_function(const GtkTreePath* path)
13618 return ::separator_function(path, m_aSeparatorRows);
13621 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
13623 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13624 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
13625 bool bRet = pThis->separator_function(path);
13626 gtk_tree_path_free(path);
13627 return bRet;
13630 OUString get(const GtkTreeIter& iter, int col) const
13632 gchar* pStr;
13633 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
13634 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
13635 g_free(pStr);
13636 return sRet;
13639 OUString get(int pos, int col) const
13641 OUString sRet;
13642 GtkTreeIter iter;
13643 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13644 sRet = get(iter, col);
13645 return sRet;
13648 gint get_int(const GtkTreeIter& iter, int col) const
13650 gint nRet(-1);
13651 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
13652 return nRet;
13655 gint get_int(int pos, int col) const
13657 gint nRet(-1);
13658 GtkTreeIter iter;
13659 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13660 nRet = get_int(iter, col);
13661 gtk_tree_model_get(m_pTreeModel, &iter, col, &nRet, -1);
13662 return nRet;
13665 bool get_bool(const GtkTreeIter& iter, int col) const
13667 gboolean bRet(false);
13668 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
13669 return bRet;
13672 bool get_bool(int pos, int col) const
13674 bool bRet(false);
13675 GtkTreeIter iter;
13676 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13677 bRet = get_bool(iter, col);
13678 return bRet;
13681 void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
13683 if (col == -1)
13684 col = m_nExpanderToggleCol;
13685 else
13686 col = to_internal_model(col);
13688 if (eState == TRISTATE_INDET)
13690 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
13691 m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
13692 m_aToggleTriStateMap[col], promote_arg(true), // tristate on
13693 -1);
13695 else
13697 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
13698 m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
13699 m_aToggleTriStateMap[col], promote_arg(false), // tristate off
13700 col, promote_arg(eState == TRISTATE_TRUE), // set toggle state
13701 -1);
13705 void set(const GtkTreeIter& iter, int col, std::u16string_view rText)
13707 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
13708 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
13711 void set(int pos, int col, std::u16string_view rText)
13713 GtkTreeIter iter;
13714 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13715 set(iter, col, rText);
13718 void set(const GtkTreeIter& iter, int col, bool bOn)
13720 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, promote_arg(bOn), -1);
13723 void set(int pos, int col, bool bOn)
13725 GtkTreeIter iter;
13726 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13727 set(iter, col, bOn);
13730 void set(const GtkTreeIter& iter, int col, gint bInt)
13732 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
13735 void set(int pos, int col, gint bInt)
13737 GtkTreeIter iter;
13738 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13739 set(iter, col, bInt);
13742 void set(const GtkTreeIter& iter, int col, double fValue)
13744 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
13747 void set(int pos, int col, double fValue)
13749 GtkTreeIter iter;
13750 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
13751 set(iter, col, fValue);
13754 static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
13756 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13757 return !pThis->signal_test_expand_row(*iter);
13760 static gboolean signalTestCollapseRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
13762 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13763 return !pThis->signal_test_collapse_row(*iter);
13766 bool child_is_placeholder(GtkInstanceTreeIter& rGtkIter) const
13768 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &rGtkIter.iter);
13769 bool bExpanding = m_aExpandingPlaceHolderParents.count(pPath);
13770 gtk_tree_path_free(pPath);
13771 if (bExpanding)
13772 return true;
13774 bool bPlaceHolder = false;
13775 GtkTreeIter tmp;
13776 if (gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter))
13778 rGtkIter.iter = tmp;
13779 if (get_text(rGtkIter, -1) == "<dummy>")
13781 bPlaceHolder = true;
13784 return bPlaceHolder;
13787 bool signal_test_expand_row(GtkTreeIter& iter)
13789 disable_notify_events();
13791 // if there's a preexisting placeholder child, required to make this
13792 // potentially expandable in the first place, now we remove it
13793 GtkInstanceTreeIter aIter(iter);
13794 GtkTreePath* pPlaceHolderPath = nullptr;
13795 bool bPlaceHolder = child_is_placeholder(aIter);
13796 if (bPlaceHolder)
13798 m_Remove(m_pTreeModel, &aIter.iter);
13800 pPlaceHolderPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
13801 m_aExpandingPlaceHolderParents.insert(pPlaceHolderPath);
13804 aIter.iter = iter;
13805 bool bRet = signal_expanding(aIter);
13807 if (bPlaceHolder)
13809 //expand disallowed, restore placeholder
13810 if (!bRet)
13812 GtkTreeIter subiter;
13813 OUString sDummy("<dummy>");
13814 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
13816 m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
13817 gtk_tree_path_free(pPlaceHolderPath);
13820 enable_notify_events();
13821 return bRet;
13824 bool signal_test_collapse_row(const GtkTreeIter& iter)
13826 disable_notify_events();
13828 GtkInstanceTreeIter aIter(iter);
13829 bool bRet = signal_collapsing(aIter);
13831 enable_notify_events();
13832 return bRet;
13835 static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
13837 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13838 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
13839 pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
13842 void signal_cell_toggled(const gchar *path, int nCol)
13844 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
13846 // additionally set the cursor into the row the toggled element is in
13847 gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
13849 GtkTreeIter iter;
13850 gtk_tree_model_get_iter(m_pTreeModel, &iter, tree_path);
13852 gboolean bRet(false);
13853 gtk_tree_model_get(m_pTreeModel, &iter, nCol, &bRet, -1);
13854 bRet = !bRet;
13855 m_Setter(m_pTreeModel, &iter, nCol, bRet, -1);
13857 set(iter, m_aToggleTriStateMap[nCol], false);
13859 signal_toggled(iter_col(GtkInstanceTreeIter(iter), to_external_model(nCol)));
13861 gtk_tree_path_free(tree_path);
13864 DECL_LINK(async_stop_cell_editing, void*, void);
13866 static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
13868 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13869 if (!pThis->signal_cell_editing_started(path))
13870 Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
13873 bool signal_cell_editing_started(const gchar *path)
13875 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
13877 GtkInstanceTreeIter aGtkIter(nullptr);
13878 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
13879 gtk_tree_path_free(tree_path);
13881 return signal_editing_started(aGtkIter);
13884 static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
13886 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13887 pThis->signal_cell_edited(pCell, path, pNewText);
13890 static void restoreNonEditable(GObject* pCell)
13892 if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
13894 g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
13895 g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
13899 void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
13901 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
13903 GtkInstanceTreeIter aGtkIter(nullptr);
13904 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
13905 gtk_tree_path_free(tree_path);
13907 OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
13908 if (signal_editing_done(iter_string(aGtkIter, sText)))
13910 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
13911 set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
13914 restoreNonEditable(G_OBJECT(pCell));
13917 static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
13919 restoreNonEditable(G_OBJECT(pCell));
13922 void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
13924 int nIndex(0);
13925 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
13927 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
13928 if (pColumn == pClickedColumn)
13930 TreeView::signal_column_clicked(nIndex);
13931 break;
13933 ++nIndex;
13937 static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
13939 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13940 pThis->signal_column_clicked(pColumn);
13943 static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
13945 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13946 pThis->signal_visible_range_changed();
13949 // The outside concept of a column maps to a gtk CellRenderer, rather than
13950 // a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
13951 // and/or a leading Image Renderer, those are considered special expander
13952 // columns and precede index 0 and can be accessed via outside index -1
13953 int to_external_model(int modelcol) const
13955 if (m_nExpanderToggleCol != -1)
13956 --modelcol;
13957 if (m_nExpanderImageCol != -1)
13958 --modelcol;
13959 return modelcol;
13962 int to_internal_model(int modelcol) const
13964 if (m_nExpanderToggleCol != -1)
13965 ++modelcol;
13966 if (m_nExpanderImageCol != -1)
13967 ++modelcol;
13968 return modelcol;
13971 void set_column_editable(int nCol, bool bEditable)
13973 nCol = to_internal_model(nCol);
13975 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
13977 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
13978 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
13979 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
13981 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
13982 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
13983 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
13985 g_object_set(G_OBJECT(pCellRenderer), "editable", bEditable, "editable-set", true, nullptr);
13986 break;
13989 g_list_free(pRenderers);
13993 static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
13995 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
13996 pThis->signal_model_changed();
13999 static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
14001 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14002 pThis->signal_model_changed();
14005 static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
14007 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14008 return pThis->sort_func(pModel, a, b);
14011 gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
14013 if (m_aCustomSort)
14014 return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
14015 return default_sort_func(pModel, a, b, m_xSorter.get());
14018 #if !GTK_CHECK_VERSION(4, 0, 0)
14019 bool signal_key_press(GdkEventKey* pEvent)
14021 if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
14022 return false;
14024 GtkInstanceTreeIter aIter(nullptr);
14025 if (!get_cursor(&aIter))
14026 return false;
14028 bool bHasChild = gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter);
14030 if (pEvent->keyval == GDK_KEY_Right)
14032 if (bHasChild && !get_row_expanded(aIter))
14034 expand_row(aIter);
14035 return true;
14037 return false;
14040 if (bHasChild && get_row_expanded(aIter))
14042 collapse_row(aIter);
14043 return true;
14046 if (iter_parent(aIter))
14048 unselect_all();
14049 set_cursor(aIter);
14050 select(aIter);
14051 return true;
14054 return false;
14057 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
14059 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14060 return pThis->signal_key_press(pEvent);
14062 #endif
14064 static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
14065 gboolean keyboard_tip, GtkTooltip *tooltip,
14066 gpointer widget)
14068 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14069 GtkTreeIter iter;
14070 GtkTreeView *pTreeView = pThis->m_pTreeView;
14071 GtkTreeModel *pModel = gtk_tree_view_get_model(pTreeView);
14072 GtkTreePath *pPath = nullptr;
14073 #if GTK_CHECK_VERSION(4, 0, 0)
14074 if (!gtk_tree_view_get_tooltip_context(pTreeView, x, y, keyboard_tip, &pModel, &pPath, &iter))
14075 return false;
14076 #else
14077 if (!gtk_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
14078 return false;
14079 #endif
14080 OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
14081 if (!aTooltip.isEmpty())
14083 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
14084 gtk_tree_view_set_tooltip_row(pTreeView, tooltip, pPath);
14086 gtk_tree_path_free(pPath);
14087 return !aTooltip.isEmpty();
14090 void last_child(GtkTreeModel* pModel, GtkTreeIter* result, GtkTreeIter* pParent, int nChildren) const
14092 gtk_tree_model_iter_nth_child(pModel, result, pParent, nChildren - 1);
14093 nChildren = gtk_tree_model_iter_n_children(pModel, result);
14094 if (nChildren)
14096 GtkTreeIter newparent(*result);
14097 last_child(pModel, result, &newparent, nChildren);
14101 GtkTreePath* get_path_of_last_entry(GtkTreeModel *pModel)
14103 GtkTreePath *lastpath;
14104 // find the last entry in the model for comparison
14105 int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
14106 if (!nChildren)
14107 lastpath = gtk_tree_path_new_from_indices(0, -1);
14108 else
14110 GtkTreeIter iter;
14111 last_child(pModel, &iter, nullptr, nChildren);
14112 lastpath = gtk_tree_model_get_path(pModel, &iter);
14114 return lastpath;
14117 void set_font_color(const GtkTreeIter& iter, const Color& rColor)
14119 if (rColor == COL_AUTO)
14120 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
14121 else
14123 GdkRGBA aColor{rColor.GetRed()/255.0f, rColor.GetGreen()/255.0f, rColor.GetBlue()/255.0f, 0};
14124 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
14128 int get_expander_size() const
14130 // gtk4: _TREE_VIEW_EXPANDER_SIZE define in gtk/gtktreeview.c
14131 gint nExpanderSize = 16;
14132 // gtk4: _TREE_VIEW_HORIZONTAL_SEPARATOR define in gtk/gtktreeview.c
14133 gint nHorizontalSeparator = 4;
14135 #if !GTK_CHECK_VERSION(4, 0, 0)
14136 gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
14137 "expander-size", &nExpanderSize,
14138 "horizontal-separator", &nHorizontalSeparator,
14139 nullptr);
14140 #endif
14142 return nExpanderSize + (nHorizontalSeparator/ 2);
14145 void real_vadjustment_set_value(int value)
14147 disable_notify_events();
14148 gtk_adjustment_set_value(m_pVAdjustment, value);
14149 enable_notify_events();
14152 static gboolean setAdjustmentCallback(GtkWidget*, GdkFrameClock*, gpointer widget)
14154 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
14155 if (pThis->m_nPendingVAdjustment != -1)
14157 pThis->real_vadjustment_set_value(pThis->m_nPendingVAdjustment);
14158 pThis->m_nPendingVAdjustment = -1;
14160 return false;
14163 bool iter_next(weld::TreeIter& rIter, bool bOnlyExpanded) const
14165 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
14166 GtkTreeIter tmp;
14167 GtkTreeIter iter = rGtkIter.iter;
14169 bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &iter);
14170 if (ret && bOnlyExpanded && !get_row_expanded(rGtkIter))
14171 ret = false;
14172 rGtkIter.iter = tmp;
14173 if (ret)
14175 //on-demand dummy entry doesn't count
14176 if (get_text(rGtkIter, -1) == "<dummy>")
14177 return iter_next(rGtkIter, bOnlyExpanded);
14178 return true;
14181 tmp = iter;
14182 if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
14184 rGtkIter.iter = tmp;
14185 //on-demand dummy entry doesn't count
14186 if (get_text(rGtkIter, -1) == "<dummy>")
14187 return iter_next(rGtkIter, bOnlyExpanded);
14188 return true;
14190 // Move up level(s) until we find the level where the next node exists.
14191 while (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
14193 iter = tmp;
14194 if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
14196 rGtkIter.iter = tmp;
14197 //on-demand dummy entry doesn't count
14198 if (get_text(rGtkIter, -1) == "<dummy>")
14199 return iter_next(rGtkIter, bOnlyExpanded);
14200 return true;
14203 return false;
14206 public:
14207 GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
14208 : GtkInstanceWidget(GTK_WIDGET(pTreeView), pBuilder, bTakeOwnership)
14209 , m_pTreeView(pTreeView)
14210 , m_pTreeModel(gtk_tree_view_get_model(m_pTreeView))
14211 , m_bWorkAroundBadDragRegion(false)
14212 , m_bInDrag(false)
14213 , m_bChangedByMouse(false)
14214 , m_nTextCol(-1)
14215 , m_nTextView(-1)
14216 , m_nImageCol(-1)
14217 , m_nExpanderToggleCol(-1)
14218 , m_nExpanderImageCol(-1)
14219 , m_nPendingVAdjustment(-1)
14220 , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
14221 G_CALLBACK(signalChanged), this))
14222 , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
14223 , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
14224 , m_nTestCollapseRowSignalId(g_signal_connect(pTreeView, "test-collapse-row", G_CALLBACK(signalTestCollapseRow), this))
14225 , m_nVAdjustmentChangedSignalId(0)
14226 #if !GTK_CHECK_VERSION(4, 0, 0)
14227 , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
14228 , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
14229 #endif
14230 , m_nQueryTooltipSignalId(0)
14231 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
14232 , m_pChangeEvent(nullptr)
14234 if (GTK_IS_TREE_STORE(m_pTreeModel))
14236 m_Setter = tree_store_set;
14237 m_InsertWithValues = tree_store_insert_with_values;
14238 m_Insert = tree_store_insert;
14239 m_Prepend = tree_store_prepend;
14240 m_Remove = tree_store_remove;
14241 m_Swap = tree_store_swap;
14242 m_SetValue = tree_store_set_value;
14243 m_Clear = tree_store_clear;
14245 else
14248 tdf#136559 see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2693
14249 If we only need a list and not a tree we can get a performance boost from using a ListStore
14251 assert(!gtk_tree_view_get_show_expanders(m_pTreeView) && "a liststore can only be used if no tree structure is needed");
14252 m_Setter = list_store_set;
14253 m_InsertWithValues = list_store_insert_with_values;
14254 m_Insert = list_store_insert;
14255 m_Prepend = list_store_prepend;
14256 m_Remove = list_store_remove;
14257 m_Swap = list_store_swap;
14258 m_SetValue = list_store_set_value;
14259 m_Clear = list_store_clear;
14262 /* The outside concept of a column maps to a gtk CellRenderer, rather than
14263 a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
14264 and/or a leading Image Renderer, those are considered special expander
14265 columns and precede index 0 and can be accessed via outside index -1
14267 m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
14268 int nIndex(0);
14269 int nViewColumn(0);
14270 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
14272 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14273 m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
14274 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
14275 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
14277 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
14278 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
14280 if (m_nTextCol == -1)
14282 m_nTextCol = nIndex;
14283 m_nTextView = nViewColumn;
14285 m_aWeightMap[nIndex] = -1;
14286 m_aSensitiveMap[nIndex] = -1;
14287 m_aIndentMap[nIndex] = -1;
14288 m_aAlignMap[nIndex] = -1;
14289 g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
14290 g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
14291 g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
14293 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
14295 const bool bExpander = nIndex == 0 || (nIndex == 1 && m_nExpanderImageCol == 0);
14296 if (bExpander)
14297 m_nExpanderToggleCol = nIndex;
14298 g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
14299 m_aToggleVisMap[nIndex] = -1;
14300 m_aToggleTriStateMap[nIndex] = -1;
14302 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
14304 const bool bExpander = g_list_next(pRenderer) != nullptr;
14305 if (bExpander && m_nExpanderImageCol == -1)
14306 m_nExpanderImageCol = nIndex;
14307 else if (m_nImageCol == -1)
14308 m_nImageCol = nIndex;
14310 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
14311 ++nIndex;
14313 g_list_free(pRenderers);
14314 ++nViewColumn;
14317 m_nIdCol = nIndex++;
14319 for (auto& a : m_aToggleVisMap)
14320 a.second = nIndex++;
14321 for (auto& a : m_aToggleTriStateMap)
14322 a.second = nIndex++;
14323 for (auto& a : m_aWeightMap)
14324 a.second = nIndex++;
14325 for (auto& a : m_aSensitiveMap)
14326 a.second = nIndex++;
14327 for (auto& a : m_aIndentMap)
14328 a.second = nIndex++;
14329 for (auto& a : m_aAlignMap)
14330 a.second = nIndex++;
14332 ensure_drag_begin_end();
14334 m_nRowDeletedSignalId = g_signal_connect(m_pTreeModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
14335 m_nRowInsertedSignalId = g_signal_connect(m_pTreeModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
14338 virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
14340 weld::TreeView::connect_query_tooltip(rLink);
14341 m_nQueryTooltipSignalId = g_signal_connect(m_pTreeView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
14344 virtual void columns_autosize() override
14346 gtk_tree_view_columns_autosize(m_pTreeView);
14349 virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
14351 GList* pEntry = g_list_first(m_pColumns);
14352 for (auto nWidth : rWidths)
14354 assert(pEntry && "wrong count");
14355 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14356 gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
14357 pEntry = g_list_next(pEntry);
14361 virtual void set_column_editables(const std::vector<bool>& rEditables) override
14363 size_t nTabCount = rEditables.size();
14364 for (size_t i = 0 ; i < nTabCount; ++i)
14365 set_column_editable(i, rEditables[i]);
14368 virtual void set_centered_column(int nCol) override
14370 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
14372 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14373 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
14374 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
14376 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
14377 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
14378 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
14380 g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
14381 break;
14384 g_list_free(pRenderers);
14388 virtual int get_column_width(int nColumn) const override
14390 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
14391 assert(pColumn && "wrong count");
14392 int nWidth = gtk_tree_view_column_get_width(pColumn);
14393 // https://github.com/exaile/exaile/issues/580
14394 // after setting fixed_width on a column and requesting width before
14395 // gtk has a chance to do its layout of the column means that the width
14396 // request hasn't come into effect
14397 if (!nWidth)
14398 nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
14399 return nWidth;
14402 virtual OUString get_column_title(int nColumn) const override
14404 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
14405 assert(pColumn && "wrong count");
14406 const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
14407 OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
14408 return sRet;
14411 virtual void set_column_title(int nColumn, const OUString& rTitle) override
14413 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
14414 assert(pColumn && "wrong count");
14415 gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
14418 virtual void set_column_custom_renderer(int nColumn, bool bEnable) override
14420 assert(n_children() == 0 && "tree must be empty");
14421 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
14422 assert(pColumn && "wrong count");
14424 GtkCellRenderer* pExpander = nullptr;
14425 GtkCellRenderer* pToggle = nullptr;
14427 // migrate existing editable setting to the new renderer
14428 gboolean is_editable(false);
14429 void* pEditCellData(nullptr);
14430 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
14431 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
14433 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
14435 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
14436 auto nCellIndex = reinterpret_cast<sal_IntPtr>(pData);
14438 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
14440 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
14441 pEditCellData = pData;
14442 break;
14444 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
14446 if (nCellIndex == m_nExpanderToggleCol)
14448 pToggle = pCellRenderer;
14449 g_object_ref(pToggle);
14452 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
14454 if (nCellIndex == m_nExpanderImageCol)
14456 pExpander = pCellRenderer;
14457 g_object_ref(pExpander);
14462 g_list_free(pRenderers);
14464 GtkCellRenderer* pRenderer;
14466 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
14467 if (pExpander)
14469 gtk_tree_view_column_pack_start(pColumn, pExpander, false);
14470 gtk_tree_view_column_add_attribute(pColumn, pExpander, "pixbuf", m_nExpanderImageCol);
14471 g_object_unref(pExpander);
14473 if (pToggle)
14475 gtk_tree_view_column_pack_start(pColumn, pToggle, false);
14476 gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
14477 gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
14478 gtk_tree_view_column_add_attribute(pColumn, pToggle, "visible", m_aToggleTriStateMap[m_nExpanderToggleCol]);
14479 g_object_unref(pToggle);
14482 if (bEnable)
14484 pRenderer = custom_cell_renderer_new();
14485 GValue value = G_VALUE_INIT;
14486 g_value_init(&value, G_TYPE_POINTER);
14487 g_value_set_pointer(&value, static_cast<gpointer>(this));
14488 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
14489 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
14490 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
14491 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
14493 else
14495 pRenderer = gtk_cell_renderer_text_new();
14496 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
14497 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
14500 if (is_editable)
14502 g_object_set(pRenderer, "editable", true, "editable-set", true, nullptr);
14503 g_object_set_data(G_OBJECT(pRenderer), "g-lo-CellIndex", pEditCellData);
14504 g_signal_connect(pRenderer, "editing-started", G_CALLBACK(signalCellEditingStarted), this);
14505 g_signal_connect(pRenderer, "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
14506 g_signal_connect(pRenderer, "edited", G_CALLBACK(signalCellEdited), this);
14510 virtual void queue_draw() override
14512 gtk_widget_queue_draw(GTK_WIDGET(m_pTreeView));
14515 virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
14516 VirtualDevice* pImageSurface,
14517 bool bChildrenOnDemand, weld::TreeIter* pRet) override
14519 disable_notify_events();
14520 GtkTreeIter iter;
14521 const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
14522 insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface);
14523 if (bChildrenOnDemand)
14525 GtkTreeIter subiter;
14526 OUString sDummy("<dummy>");
14527 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
14529 if (pRet)
14531 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
14532 pGtkRetIter->iter = iter;
14534 enable_notify_events();
14537 virtual void insert_separator(int pos, const OUString& rId) override
14539 disable_notify_events();
14540 GtkTreeIter iter;
14541 if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
14542 gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
14543 insert_row(iter, nullptr, pos, &rId, nullptr, nullptr, nullptr);
14544 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
14545 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
14546 gtk_tree_path_free(pPath);
14547 enable_notify_events();
14550 virtual void set_font_color(int pos, const Color& rColor) override
14552 GtkTreeIter iter;
14553 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
14554 set_font_color(iter, rColor);
14557 virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override
14559 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
14560 set_font_color(rGtkIter.iter, rColor);
14563 virtual void remove(int pos) override
14565 disable_notify_events();
14566 GtkTreeIter iter;
14567 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
14568 m_Remove(m_pTreeModel, &iter);
14569 enable_notify_events();
14572 virtual int find_text(const OUString& rText) const override
14574 Search aSearch(rText, m_nTextCol);
14575 gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
14576 return aSearch.index;
14579 virtual int find_id(const OUString& rId) const override
14581 Search aSearch(rId, m_nIdCol);
14582 gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
14583 return aSearch.index;
14586 virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
14587 const weld::TreeIter* pParent,
14588 const std::vector<int>* pFixedWidths) override
14590 GtkInstanceTreeIter* pGtkIter = const_cast<GtkInstanceTreeIter*>(static_cast<const GtkInstanceTreeIter*>(pParent));
14592 freeze();
14593 if (!pGtkIter)
14594 clear();
14595 else
14597 GtkTreeIter restore(pGtkIter->iter);
14599 if (iter_children(*pGtkIter))
14600 while (m_Remove(m_pTreeModel, &pGtkIter->iter));
14602 pGtkIter->iter = restore;
14604 GtkInstanceTreeIter aGtkIter(nullptr);
14606 if (pFixedWidths)
14607 set_column_fixed_widths(*pFixedWidths);
14609 while (nSourceCount)
14611 // tdf#125241 inserting backwards is massively faster
14612 m_Prepend(m_pTreeModel, &aGtkIter.iter, pGtkIter ? &pGtkIter->iter : nullptr);
14613 func(aGtkIter, --nSourceCount);
14616 thaw();
14619 virtual void swap(int pos1, int pos2) override
14621 disable_notify_events();
14623 GtkTreeIter iter1;
14624 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter1, nullptr, pos1);
14626 GtkTreeIter iter2;
14627 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter2, nullptr, pos2);
14629 m_Swap(m_pTreeModel, &iter1, &iter2);
14631 enable_notify_events();
14634 virtual void clear() override
14636 disable_notify_events();
14637 gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
14638 m_aSeparatorRows.clear();
14639 m_Clear(m_pTreeModel);
14640 enable_notify_events();
14643 virtual void make_sorted() override
14645 // thaw wants to restore sort state of freeze
14646 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14647 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
14648 ::comphelper::getProcessComponentContext(),
14649 Application::GetSettings().GetUILanguageTag().getLocale()));
14650 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14651 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
14652 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
14655 virtual void make_unsorted() override
14657 m_xSorter.reset();
14658 int nSortColumn;
14659 GtkSortType eSortType;
14660 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14661 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
14662 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
14665 virtual void set_sort_order(bool bAscending) override
14667 GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
14669 gint sort_column_id(0);
14670 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14671 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
14672 gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
14675 virtual bool get_sort_order() const override
14677 int nSortColumn;
14678 GtkSortType eSortType;
14680 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14681 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
14682 return nSortColumn != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID && eSortType == GTK_SORT_ASCENDING;
14685 virtual void set_sort_indicator(TriState eState, int col) override
14687 assert(col >= 0 && "cannot sort on expander column");
14689 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
14690 assert(pColumn && "wrong count");
14691 if (eState == TRISTATE_INDET)
14692 gtk_tree_view_column_set_sort_indicator(pColumn, false);
14693 else
14695 gtk_tree_view_column_set_sort_indicator(pColumn, true);
14696 GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
14697 gtk_tree_view_column_set_sort_order(pColumn, eSortType);
14701 virtual TriState get_sort_indicator(int col) const override
14703 assert(col >= 0 && "cannot sort on expander column");
14705 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
14706 if (!gtk_tree_view_column_get_sort_indicator(pColumn))
14707 return TRISTATE_INDET;
14708 return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
14711 virtual int get_sort_column() const override
14713 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14714 gint sort_column_id(0);
14715 if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
14716 return -1;
14717 return to_external_model(sort_column_id);
14720 virtual void set_sort_column(int nColumn) override
14722 if (nColumn == -1)
14724 make_unsorted();
14725 return;
14727 GtkSortType eSortType;
14728 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14729 gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
14730 int nSortCol = to_internal_model(nColumn);
14731 gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
14732 gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
14735 virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
14737 weld::TreeView::set_sort_func(func);
14738 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
14739 gtk_tree_sortable_sort_column_changed(pSortable);
14742 virtual int n_children() const override
14744 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
14747 virtual int iter_n_children(const weld::TreeIter& rIter) const override
14749 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
14750 return gtk_tree_model_iter_n_children(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
14753 virtual void select(int pos) override
14755 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14756 disable_notify_events();
14757 if (pos == -1 || (pos == 0 && n_children() == 0))
14759 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
14761 else
14763 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
14764 gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
14765 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
14766 gtk_tree_path_free(path);
14768 enable_notify_events();
14771 virtual void set_cursor(int pos) override
14773 disable_notify_events();
14774 GtkTreePath* path;
14775 if (pos != -1)
14777 path = gtk_tree_path_new_from_indices(pos, -1);
14778 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
14780 else
14781 path = gtk_tree_path_new();
14782 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
14783 gtk_tree_path_free(path);
14784 enable_notify_events();
14787 virtual void scroll_to_row(int pos) override
14789 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14790 disable_notify_events();
14791 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
14792 gtk_tree_view_expand_to_path(m_pTreeView, path);
14793 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
14794 gtk_tree_path_free(path);
14795 enable_notify_events();
14798 virtual bool is_selected(int pos) const override
14800 GtkTreeIter iter;
14801 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
14802 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
14805 virtual void unselect(int pos) override
14807 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
14808 disable_notify_events();
14809 if (pos == -1 || (pos == 0 && n_children() == 0))
14811 gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
14813 else
14815 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
14816 gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
14817 gtk_tree_path_free(path);
14819 enable_notify_events();
14822 virtual std::vector<int> get_selected_rows() const override
14824 std::vector<int> aRows;
14826 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
14827 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
14829 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
14831 gint depth;
14832 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
14833 int nRow = indices[depth-1];
14835 aRows.push_back(nRow);
14837 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
14839 return aRows;
14842 virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
14844 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
14846 GtkInstanceTreeIter aGtkIter(nullptr);
14847 if (get_iter_first(aGtkIter))
14851 if (func(aGtkIter))
14852 break;
14853 } while (iter_next(aGtkIter));
14856 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
14859 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
14861 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
14863 GtkInstanceTreeIter aGtkIter(nullptr);
14865 GtkTreeModel* pModel;
14866 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
14867 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
14869 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
14870 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
14871 if (func(aGtkIter))
14872 break;
14874 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
14876 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
14879 virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
14881 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
14883 GtkTreePath* start_path;
14884 GtkTreePath* end_path;
14886 if (!gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
14887 return;
14889 GtkInstanceTreeIter aGtkIter(nullptr);
14890 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, start_path);
14894 if (func(aGtkIter))
14895 break;
14896 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &aGtkIter.iter);
14897 bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
14898 gtk_tree_path_free(path);
14899 if (!bContinue)
14900 break;
14901 if (!iter_next(aGtkIter))
14902 break;
14903 } while(true);
14905 gtk_tree_path_free(start_path);
14906 gtk_tree_path_free(end_path);
14908 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
14911 virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
14913 weld::TreeView::connect_visible_range_changed(rLink);
14914 if (!m_nVAdjustmentChangedSignalId)
14916 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
14917 m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
14921 virtual bool is_selected(const weld::TreeIter& rIter) const override
14923 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
14924 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
14927 virtual OUString get_text(int pos, int col) const override
14929 if (col == -1)
14930 col = m_nTextCol;
14931 else
14932 col = to_internal_model(col);
14933 return get(pos, col);
14936 virtual void set_text(int pos, const OUString& rText, int col) override
14938 if (col == -1)
14939 col = m_nTextCol;
14940 else
14941 col = to_internal_model(col);
14942 set(pos, col, rText);
14945 virtual TriState get_toggle(int pos, int col) const override
14947 if (col == -1)
14948 col = m_nExpanderToggleCol;
14949 else
14950 col = to_internal_model(col);
14952 if (get_bool(pos, m_aToggleTriStateMap.find(col)->second))
14953 return TRISTATE_INDET;
14954 return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
14957 virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
14959 if (col == -1)
14960 col = m_nExpanderToggleCol;
14961 else
14962 col = to_internal_model(col);
14964 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
14965 if (get_bool(rGtkIter.iter, m_aToggleTriStateMap.find(col)->second))
14966 return TRISTATE_INDET;
14967 return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
14970 virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
14972 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
14973 set_toggle(rGtkIter.iter, eState, col);
14976 virtual void set_toggle(int pos, TriState eState, int col) override
14978 GtkTreeIter iter;
14979 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14980 set_toggle(iter, eState, col);
14983 virtual void enable_toggle_buttons(weld::ColumnToggleType eType) override
14985 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
14987 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
14988 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
14989 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
14991 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
14992 if (!GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
14993 continue;
14994 GtkCellRendererToggle* pToggle = GTK_CELL_RENDERER_TOGGLE(pCellRenderer);
14995 gtk_cell_renderer_toggle_set_radio(pToggle, eType == weld::ColumnToggleType::Radio);
14997 g_list_free(pRenderers);
15001 virtual void set_clicks_to_toggle(int /*nToggleBehavior*/) override
15005 virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override
15007 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15008 set(rGtkIter.iter, m_aIndentMap[m_nTextCol], nIndentLevel * get_expander_size());
15011 virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
15013 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15014 col = to_internal_model(col);
15015 set(rGtkIter.iter, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
15018 virtual void set_text_emphasis(int pos, bool bOn, int col) override
15020 col = to_internal_model(col);
15021 set(pos, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
15024 virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
15026 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15027 col = to_internal_model(col);
15028 return get_int(rGtkIter.iter, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
15031 virtual bool get_text_emphasis(int pos, int col) const override
15033 col = to_internal_model(col);
15034 return get_int(pos, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
15037 virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override
15039 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15040 col = to_internal_model(col);
15041 set(rGtkIter.iter, m_aAlignMap[col], fAlign);
15044 virtual void set_text_align(int pos, double fAlign, int col) override
15046 col = to_internal_model(col);
15047 set(pos, m_aAlignMap[col], fAlign);
15050 using GtkInstanceWidget::set_sensitive;
15052 virtual void set_sensitive(int pos, bool bSensitive, int col) override
15054 if (col == -1)
15055 col = m_nTextCol;
15056 else
15057 col = to_internal_model(col);
15058 set(pos, m_aSensitiveMap[col], bSensitive);
15061 virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
15063 if (col == -1)
15064 col = m_nTextCol;
15065 else
15066 col = to_internal_model(col);
15067 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15068 set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
15071 void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
15073 if (col == -1)
15074 col = m_nExpanderImageCol;
15075 else
15076 col = to_internal_model(col);
15077 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
15078 if (pixbuf)
15079 g_object_unref(pixbuf);
15082 void set_image(int pos, GdkPixbuf* pixbuf, int col)
15084 GtkTreeIter iter;
15085 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
15087 set_image(iter, col, pixbuf);
15091 virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
15093 set_image(pos, getPixbuf(rImage), col);
15096 virtual void set_image(int pos, const OUString& rImage, int col) override
15098 set_image(pos, getPixbuf(rImage), col);
15101 virtual void set_image(int pos, VirtualDevice& rImage, int col) override
15103 set_image(pos, getPixbuf(rImage), col);
15106 virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
15108 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15109 set_image(rGtkIter.iter, col, getPixbuf(rImage));
15112 virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
15114 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15115 set_image(rGtkIter.iter, col, getPixbuf(rImage));
15118 virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
15120 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15121 set_image(rGtkIter.iter, col, getPixbuf(rImage));
15124 virtual OUString get_id(int pos) const override
15126 return get(pos, m_nIdCol);
15129 virtual void set_id(int pos, const OUString& rId) override
15131 return set(pos, m_nIdCol, rId);
15134 virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
15136 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15138 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15140 gint depth;
15141 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
15142 int nRet = indices[depth-1];
15144 gtk_tree_path_free(path);
15146 return nRet;
15149 virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
15151 const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
15152 const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
15154 GtkTreePath* pathA = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
15155 GtkTreePath* pathB = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
15157 int nRet = gtk_tree_path_compare(pathA, pathB);
15159 gtk_tree_path_free(pathB);
15160 gtk_tree_path_free(pathA);
15162 return nRet;
15165 // by copy and delete of old copy
15166 void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
15168 int nCols = gtk_tree_model_get_n_columns(m_pTreeModel);
15169 GValue value;
15171 GtkTreeIter toiter;
15172 m_Insert(m_pTreeModel, &toiter, pGtkParentIter, nIndexInNewParent);
15174 for (int i = 0; i < nCols; ++i)
15176 memset(&value, 0, sizeof(GValue));
15177 gtk_tree_model_get_value(m_pTreeModel, &rFromIter, i, &value);
15178 m_SetValue(m_pTreeModel, &toiter, i, &value);
15179 g_value_unset(&value);
15182 GtkTreeIter tmpfromiter;
15183 if (gtk_tree_model_iter_children(m_pTreeModel, &tmpfromiter, &rFromIter))
15185 int j = 0;
15188 move_subtree(tmpfromiter, &toiter, j++);
15189 } while (gtk_tree_model_iter_next(m_pTreeModel, &tmpfromiter));
15192 m_Remove(m_pTreeModel, &rFromIter);
15195 virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
15197 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
15198 const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
15199 move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
15202 virtual int get_selected_index() const override
15204 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
15205 int nRet = -1;
15206 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
15207 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
15209 GtkTreeIter iter;
15210 GtkTreeModel* pModel;
15211 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
15213 GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
15215 gint depth;
15216 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
15217 nRet = indices[depth-1];
15219 gtk_tree_path_free(path);
15222 else
15224 auto vec = get_selected_rows();
15225 return vec.empty() ? -1 : vec[0];
15227 return nRet;
15230 bool get_selected_iterator(GtkTreeIter* pIter) const
15232 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
15233 bool bRet = false;
15234 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
15235 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
15236 bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
15237 else
15239 GtkTreeModel* pModel;
15240 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
15241 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
15243 if (pIter)
15245 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
15246 gtk_tree_model_get_iter(pModel, pIter, path);
15248 bRet = true;
15249 break;
15251 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
15253 return bRet;
15256 virtual OUString get_selected_text() const override
15258 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
15259 GtkTreeIter iter;
15260 if (get_selected_iterator(&iter))
15261 return get(iter, m_nTextCol);
15262 return OUString();
15265 virtual OUString get_selected_id() const override
15267 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
15268 GtkTreeIter iter;
15269 if (get_selected_iterator(&iter))
15270 return get(iter, m_nIdCol);
15271 return OUString();
15274 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
15276 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
15279 virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
15281 const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
15282 GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
15283 rGtkDest.iter = rGtkSource.iter;
15286 virtual bool get_selected(weld::TreeIter* pIter) const override
15288 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
15289 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
15292 virtual bool get_cursor(weld::TreeIter* pIter) const override
15294 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
15295 GtkTreePath* path;
15296 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
15297 if (pGtkIter && path)
15299 gtk_tree_model_get_iter(m_pTreeModel, &pGtkIter->iter, path);
15301 if (!path)
15302 return false;
15303 gtk_tree_path_free(path);
15304 return true;
15307 virtual int get_cursor_index() const override
15309 int nRet = -1;
15311 GtkTreePath* path;
15312 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
15313 if (path)
15315 gint depth;
15316 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
15317 nRet = indices[depth-1];
15318 gtk_tree_path_free(path);
15321 return nRet;
15324 virtual void set_cursor(const weld::TreeIter& rIter) override
15326 disable_notify_events();
15327 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15328 GtkTreeIter Iter;
15329 if (gtk_tree_model_iter_parent(m_pTreeModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
15331 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &Iter);
15332 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
15333 gtk_tree_view_expand_to_path(m_pTreeView, path);
15334 gtk_tree_path_free(path);
15336 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15337 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
15338 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
15339 gtk_tree_path_free(path);
15340 enable_notify_events();
15343 virtual bool get_iter_first(weld::TreeIter& rIter) const override
15345 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
15346 return gtk_tree_model_get_iter_first(m_pTreeModel, &rGtkIter.iter);
15349 virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
15351 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
15352 return gtk_tree_model_iter_next(m_pTreeModel, &rGtkIter.iter);
15355 virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
15357 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
15358 return gtk_tree_model_iter_previous(m_pTreeModel, &rGtkIter.iter);
15361 virtual bool iter_next(weld::TreeIter& rIter) const override
15363 return iter_next(rIter, false);
15366 virtual bool iter_previous(weld::TreeIter& rIter) const override
15368 bool ret = false;
15369 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
15370 GtkTreeIter iter = rGtkIter.iter;
15371 GtkTreeIter tmp = iter;
15372 if (gtk_tree_model_iter_previous(m_pTreeModel, &tmp))
15374 // Move down level(s) until we find the level where the last node exists.
15375 int nChildren = gtk_tree_model_iter_n_children(m_pTreeModel, &tmp);
15376 if (!nChildren)
15377 rGtkIter.iter = tmp;
15378 else
15379 last_child(m_pTreeModel, &rGtkIter.iter, &tmp, nChildren);
15380 ret = true;
15382 else
15384 // Move up level
15385 if (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
15387 rGtkIter.iter = tmp;
15388 ret = true;
15392 if (ret)
15394 //on-demand dummy entry doesn't count
15395 if (get_text(rGtkIter, -1) == "<dummy>")
15396 return iter_previous(rGtkIter);
15397 return true;
15400 return false;
15403 virtual bool iter_children(weld::TreeIter& rIter) const override
15405 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
15406 GtkTreeIter tmp;
15407 bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter);
15408 rGtkIter.iter = tmp;
15409 if (ret)
15411 //on-demand dummy entry doesn't count
15412 return get_text(rGtkIter, -1) != "<dummy>";
15414 return ret;
15417 virtual bool iter_parent(weld::TreeIter& rIter) const override
15419 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
15420 GtkTreeIter tmp;
15421 bool ret = gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &rGtkIter.iter);
15422 rGtkIter.iter = tmp;
15423 return ret;
15426 virtual void remove(const weld::TreeIter& rIter) override
15428 disable_notify_events();
15429 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15430 m_Remove(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15431 enable_notify_events();
15434 virtual void remove_selection() override
15436 disable_notify_events();
15438 std::vector<GtkTreeIter> aIters;
15439 GtkTreeModel* pModel;
15440 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
15441 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
15443 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
15444 aIters.emplace_back();
15445 gtk_tree_model_get_iter(pModel, &aIters.back(), path);
15447 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
15449 for (auto& iter : aIters)
15450 m_Remove(m_pTreeModel, &iter);
15452 enable_notify_events();
15455 virtual void select(const weld::TreeIter& rIter) override
15457 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15458 disable_notify_events();
15459 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15460 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
15461 enable_notify_events();
15464 virtual void scroll_to_row(const weld::TreeIter& rIter) override
15466 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15467 disable_notify_events();
15468 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15469 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15470 gtk_tree_view_expand_to_path(m_pTreeView, path);
15471 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
15472 gtk_tree_path_free(path);
15473 enable_notify_events();
15476 virtual void unselect(const weld::TreeIter& rIter) override
15478 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
15479 disable_notify_events();
15480 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15481 gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
15482 enable_notify_events();
15485 virtual int get_iter_depth(const weld::TreeIter& rIter) const override
15487 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15488 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15489 int ret = gtk_tree_path_get_depth(path) - 1;
15490 gtk_tree_path_free(path);
15491 return ret;
15494 virtual bool iter_has_child(const weld::TreeIter& rIter) const override
15496 GtkInstanceTreeIter aTempCopy(static_cast<const GtkInstanceTreeIter*>(&rIter));
15497 return iter_children(aTempCopy);
15500 virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
15502 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15503 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15504 bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
15505 gtk_tree_path_free(path);
15506 return ret;
15509 virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override
15511 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15512 GtkInstanceTreeIter aIter(&rGtkIter);
15513 return child_is_placeholder(aIter);
15516 virtual void set_children_on_demand(const weld::TreeIter& rIter, bool bChildrenOnDemand) override
15518 disable_notify_events();
15520 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15521 GtkInstanceTreeIter aPlaceHolderIter(&rGtkIter);
15523 bool bPlaceHolder = child_is_placeholder(aPlaceHolderIter);
15525 if (bChildrenOnDemand && !bPlaceHolder)
15527 GtkTreeIter subiter;
15528 OUString sDummy("<dummy>");
15529 insert_row(subiter, &rGtkIter.iter, -1, nullptr, &sDummy, nullptr, nullptr);
15531 else if (!bChildrenOnDemand && bPlaceHolder)
15532 remove(aPlaceHolderIter);
15534 enable_notify_events();
15537 virtual void expand_row(const weld::TreeIter& rIter) override
15539 assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
15541 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15542 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15543 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
15544 gtk_tree_view_expand_to_path(m_pTreeView, path);
15545 gtk_tree_path_free(path);
15548 virtual void collapse_row(const weld::TreeIter& rIter) override
15550 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15551 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15552 if (gtk_tree_view_row_expanded(m_pTreeView, path))
15553 gtk_tree_view_collapse_row(m_pTreeView, path);
15554 gtk_tree_path_free(path);
15557 virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
15559 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15560 if (col == -1)
15561 col = m_nTextCol;
15562 else
15563 col = to_internal_model(col);
15564 return get(rGtkIter.iter, col);
15567 virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
15569 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15570 if (col == -1)
15571 col = m_nTextCol;
15572 else
15573 col = to_internal_model(col);
15574 set(rGtkIter.iter, col, rText);
15577 virtual OUString get_id(const weld::TreeIter& rIter) const override
15579 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15580 return get(rGtkIter.iter, m_nIdCol);
15583 virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
15585 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15586 set(rGtkIter.iter, m_nIdCol, rId);
15589 virtual void freeze() override
15591 disable_notify_events();
15592 bool bIsFirstFreeze = IsFirstFreeze();
15593 GtkInstanceWidget::freeze();
15594 if (bIsFirstFreeze)
15596 g_object_ref(m_pTreeModel);
15597 gtk_tree_view_set_model(m_pTreeView, nullptr);
15598 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
15599 if (m_xSorter)
15601 int nSortColumn;
15602 GtkSortType eSortType;
15603 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15604 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
15605 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
15607 m_aSavedSortColumns.push_back(nSortColumn);
15608 m_aSavedSortTypes.push_back(eSortType);
15611 enable_notify_events();
15614 virtual void thaw() override
15616 disable_notify_events();
15617 if (IsLastThaw())
15619 if (m_xSorter)
15621 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15622 gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
15623 m_aSavedSortTypes.pop_back();
15624 m_aSavedSortColumns.pop_back();
15626 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
15627 gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeModel));
15628 g_object_unref(m_pTreeModel);
15630 GtkInstanceWidget::thaw();
15631 enable_notify_events();
15634 virtual int get_height_rows(int nRows) const override
15636 return ::get_height_rows(m_pTreeView, m_pColumns, nRows);
15639 virtual Size get_size_request() const override
15641 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
15642 if (GTK_IS_SCROLLED_WINDOW(pParent))
15644 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
15645 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
15647 int nWidth, nHeight;
15648 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
15649 return Size(nWidth, nHeight);
15652 virtual Size get_preferred_size() const override
15654 Size aRet(-1, -1);
15655 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
15656 if (GTK_IS_SCROLLED_WINDOW(pParent))
15658 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
15659 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
15661 GtkRequisition size;
15662 #if !GTK_CHECK_VERSION(4, 0, 0)
15663 // sometimes gtk gives a bad outcome for gtk_widget_get_preferred_size if GtkTreeView's
15664 // do_validate_rows hasn't been run before querying the preferred size, if we call
15665 // gtk_widget_get_preferred_width first, we can guarantee do_validate_rows gets called
15666 gtk_widget_get_preferred_width(m_pWidget, nullptr, &size.width);
15667 #endif
15668 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
15669 if (aRet.Width() == -1)
15670 aRet.setWidth(size.width);
15671 if (aRet.Height() == -1)
15672 aRet.setHeight(size.height);
15673 return aRet;
15676 virtual void show() override
15678 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
15679 if (GTK_IS_SCROLLED_WINDOW(pParent))
15680 gtk_widget_show(pParent);
15681 gtk_widget_show(m_pWidget);
15684 virtual void hide() override
15686 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
15687 if (GTK_IS_SCROLLED_WINDOW(pParent))
15688 gtk_widget_hide(pParent);
15689 gtk_widget_hide(m_pWidget);
15692 virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
15694 do_enable_drag_source(rHelper, eDNDConstants);
15697 #if !GTK_CHECK_VERSION(4, 0, 0)
15698 virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
15700 if (rGtkTargets.empty() && !eDragAction)
15701 gtk_tree_view_unset_rows_drag_source(m_pTreeView);
15702 else
15703 gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
15705 #endif
15707 virtual void set_selection_mode(SelectionMode eMode) override
15709 disable_notify_events();
15710 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
15711 enable_notify_events();
15714 virtual int count_selected_rows() const override
15716 return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
15719 int starts_with(const OUString& rStr, int nStartRow, bool bCaseSensitive)
15721 return ::starts_with(m_pTreeModel, rStr, m_nTextCol, nStartRow, bCaseSensitive);
15724 virtual void disable_notify_events() override
15726 g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
15727 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
15729 g_signal_handler_block(m_pTreeModel, m_nRowDeletedSignalId);
15730 g_signal_handler_block(m_pTreeModel, m_nRowInsertedSignalId);
15732 GtkInstanceWidget::disable_notify_events();
15735 virtual void enable_notify_events() override
15737 GtkInstanceWidget::enable_notify_events();
15739 g_signal_handler_unblock(m_pTreeModel, m_nRowDeletedSignalId);
15740 g_signal_handler_unblock(m_pTreeModel, m_nRowInsertedSignalId);
15742 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
15743 g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
15746 virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
15748 ensureButtonPressSignal();
15749 weld::TreeView::connect_popup_menu(rLink);
15752 virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode) override
15754 if (rPos.X() < 0 || rPos.Y() < 0)
15756 // short-circuit to avoid "gtk_tree_view_get_dest_row_at_pos: assertion 'drag_x >= 0'" g_assert
15757 return false;
15760 const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
15762 // to keep it simple we'll default to always drop before the current row
15763 // except for the special edge cases
15764 GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
15766 // unhighlight current highlighted row
15767 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
15769 if (m_bWorkAroundBadDragRegion)
15771 #if GTK_CHECK_VERSION(4, 0, 0)
15772 gtk_widget_unset_state_flags(GTK_WIDGET(m_pTreeView), GTK_STATE_FLAG_DROP_ACTIVE);
15773 #else
15774 gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
15775 #endif
15778 GtkTreePath *path = nullptr;
15779 GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
15780 bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
15781 &path, &gtkpos);
15783 // find the last entry in the model for comparison
15784 GtkTreePath *lastpath = get_path_of_last_entry(m_pTreeModel);
15786 if (!ret)
15788 // empty space, draw an indicator at the last entry
15789 assert(!path);
15790 path = gtk_tree_path_copy(lastpath);
15791 pos = GTK_TREE_VIEW_DROP_AFTER;
15793 else if (bDnDMode && gtk_tree_path_compare(path, lastpath) == 0)
15795 // if we're on the last entry, see if gtk thinks
15796 // the drop should be before or after it, and if
15797 // its after, treat it like a drop into empty
15798 // space, i.e. append it
15799 if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
15800 gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
15802 ret = false;
15803 pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
15807 if (ret && pResult)
15809 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
15810 gtk_tree_model_get_iter(m_pTreeModel, &rGtkIter.iter, path);
15813 if (m_bInDrag && bDnDMode)
15815 // highlight the row
15816 gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
15819 assert(path);
15820 gtk_tree_path_free(path);
15821 gtk_tree_path_free(lastpath);
15823 // auto scroll if we're close to the edges
15824 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
15825 double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
15826 if (rPos.Y() < fStep)
15828 double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
15829 if (fValue < 0)
15830 fValue = 0.0;
15831 gtk_adjustment_set_value(pVAdjustment, fValue);
15833 else
15835 GdkRectangle aRect;
15836 gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
15837 if (rPos.Y() > aRect.height - fStep)
15839 double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
15840 double fMax = gtk_adjustment_get_upper(pVAdjustment);
15841 if (fValue > fMax)
15842 fValue = fMax;
15843 gtk_adjustment_set_value(pVAdjustment, fValue);
15847 return ret;
15850 virtual void unset_drag_dest_row() override
15852 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
15855 virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override
15857 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15858 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15859 tools::Rectangle aRet = ::get_row_area(m_pTreeView, m_pColumns, pPath);
15860 gtk_tree_path_free(pPath);
15861 return aRet;
15864 virtual void start_editing(const weld::TreeIter& rIter) override
15866 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, m_nTextView));
15867 assert(pColumn && "wrong column");
15869 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
15870 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
15872 // allow editing of cells which are not usually editable, so we can have double click
15873 // do its usual row-activate but if we explicitly want to edit (remote files dialog)
15874 // we can still do that
15875 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
15876 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
15878 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
15879 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
15881 gboolean is_editable(false);
15882 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
15883 if (!is_editable)
15885 g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
15886 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
15887 break;
15891 g_list_free(pRenderers);
15893 gtk_tree_view_scroll_to_cell(m_pTreeView, path, pColumn, false, 0, 0);
15894 gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
15896 gtk_tree_path_free(path);
15899 virtual void end_editing() override
15901 GtkTreeViewColumn *focus_column = nullptr;
15902 gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
15903 if (focus_column)
15904 gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
15907 virtual TreeView* get_drag_source() const override
15909 return g_DragSource;
15912 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
15914 if (m_aDragBeginHdl.Call(rUnsetDragIcon))
15915 return true;
15916 g_DragSource = this;
15917 return false;
15920 virtual void do_signal_drag_end() override
15922 g_DragSource = nullptr;
15925 // Under gtk 3.24.8 dragging into the TreeView is not highlighting
15926 // entire TreeView widget, just the rectangle which has no entries
15927 // in it, so as a workaround highlight the parent container
15928 // on drag start, and undo it on drag end, and trigger removal
15929 // of the treeview's highlight effort
15930 virtual void drag_started() override
15932 m_bInDrag = true;
15933 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
15934 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
15935 if (GTK_IS_SCROLLED_WINDOW(pParent))
15937 #if GTK_CHECK_VERSION(4, 0, 0)
15938 gtk_widget_unset_state_flags(pWidget, GTK_STATE_FLAG_DROP_ACTIVE);
15939 gtk_widget_set_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE, false);
15940 #else
15941 gtk_drag_unhighlight(pWidget);
15942 gtk_drag_highlight(pParent);
15943 #endif
15944 m_bWorkAroundBadDragRegion = true;
15948 virtual void drag_ended() override
15950 m_bInDrag = false;
15951 if (m_bWorkAroundBadDragRegion)
15953 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
15954 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
15955 #if GTK_CHECK_VERSION(4, 0, 0)
15956 gtk_widget_unset_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE);
15957 #else
15958 gtk_drag_unhighlight(pParent);
15959 #endif
15960 m_bWorkAroundBadDragRegion = false;
15962 // unhighlight the row
15963 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
15966 virtual int vadjustment_get_value() const override
15968 if (m_nPendingVAdjustment != -1)
15969 return m_nPendingVAdjustment;
15970 return gtk_adjustment_get_value(m_pVAdjustment);
15973 virtual void vadjustment_set_value(int value) override
15975 disable_notify_events();
15977 /* This rube goldberg device is to remove flicker from setting the
15978 scroll position of a GtkTreeView directly after clearing it and
15979 filling it. As a specific example the writer navigator with ~100
15980 tables, scroll to the end, right click on an entry near the end
15981 and rename it, the tree is cleared and refilled and an attempt
15982 made to set the scroll position of the freshly refilled tree to
15983 the same point as before the clear.
15986 // This forces the tree to recalculate now its preferred size
15987 // after being cleared
15988 GtkRequisition size;
15989 gtk_widget_get_preferred_size(GTK_WIDGET(m_pTreeView), nullptr, &size);
15991 m_nPendingVAdjustment = value;
15993 // The value set here just has to be different to the final value
15994 // set later so that isn't a no-op
15995 gtk_adjustment_set_value(m_pVAdjustment, value - 0.0001);
15997 // This will set the desired m_nPendingVAdjustment value right
15998 // before the tree gets drawn
15999 gtk_widget_add_tick_callback(GTK_WIDGET(m_pTreeView), setAdjustmentCallback, this, nullptr);
16001 enable_notify_events();
16004 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
16006 signal_custom_render(rOutput, rRect, bSelected, rId);
16009 Size call_signal_custom_get_size(VirtualDevice& rOutput, const OUString& rId)
16011 return signal_custom_get_size(rOutput, rId);
16014 virtual void set_show_expanders(bool bShow) override
16016 gtk_tree_view_set_show_expanders(m_pTreeView, bShow);
16019 virtual bool changed_by_hover() const override
16021 return m_bChangedByMouse;
16024 virtual ~GtkInstanceTreeView() override
16026 if (m_pChangeEvent)
16027 Application::RemoveUserEvent(m_pChangeEvent);
16028 if (m_nQueryTooltipSignalId)
16029 g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
16030 #if !GTK_CHECK_VERSION(4, 0, 0)
16031 g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
16032 g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
16033 #endif
16034 g_signal_handler_disconnect(m_pTreeModel, m_nRowDeletedSignalId);
16035 g_signal_handler_disconnect(m_pTreeModel, m_nRowInsertedSignalId);
16037 if (m_nVAdjustmentChangedSignalId)
16039 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
16040 g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
16043 g_signal_handler_disconnect(m_pTreeView, m_nTestCollapseRowSignalId);
16044 g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
16045 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
16046 g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
16048 GValue value = G_VALUE_INIT;
16049 g_value_init(&value, G_TYPE_POINTER);
16050 g_value_set_pointer(&value, static_cast<gpointer>(nullptr));
16052 for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
16054 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
16055 g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
16056 m_aColumnSignalIds.pop_back();
16058 // unset "instance" to avoid dangling "instance" points in any CustomCellRenderers
16059 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
16060 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
16062 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
16063 if (!CUSTOM_IS_CELL_RENDERER(pCellRenderer))
16064 continue;
16065 g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
16067 g_list_free(pRenderers);
16069 g_list_free(m_pColumns);
16075 IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
16077 m_pChangeEvent = nullptr;
16078 signal_changed();
16079 m_bChangedByMouse = false;
16082 IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
16084 end_editing();
16087 namespace {
16089 class GtkInstanceIconView : public GtkInstanceWidget, public virtual weld::IconView
16091 private:
16092 GtkIconView* m_pIconView;
16093 GtkTreeStore* m_pTreeStore;
16094 gint m_nTextCol;
16095 gint m_nImageCol;
16096 gint m_nIdCol;
16097 gulong m_nSelectionChangedSignalId;
16098 gulong m_nItemActivatedSignalId;
16099 #if !GTK_CHECK_VERSION(4, 0, 0)
16100 gulong m_nPopupMenu;
16101 #endif
16102 gulong m_nQueryTooltipSignalId = 0;
16103 ImplSVEvent* m_pSelectionChangeEvent;
16105 DECL_LINK(async_signal_selection_changed, void*, void);
16107 bool signal_command(const CommandEvent& rCEvt)
16109 return m_aCommandHdl.Call(rCEvt);
16112 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
16114 return signal_command(rCEvt);
16117 void launch_signal_selection_changed()
16119 //tdf#117991 selection change is sent before the focus change, and focus change
16120 //is what will cause a spinbutton that currently has the focus to set its contents
16121 //as the spin button value. So any LibreOffice callbacks on
16122 //signal-change would happen before the spinbutton value-change occurs.
16123 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
16124 //has been processed
16125 if (m_pSelectionChangeEvent)
16126 Application::RemoveUserEvent(m_pSelectionChangeEvent);
16127 m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
16130 static void signalSelectionChanged(GtkIconView*, gpointer widget)
16132 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
16133 pThis->launch_signal_selection_changed();
16136 void handle_item_activated()
16138 if (signal_item_activated())
16139 return;
16142 static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
16144 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
16145 SolarMutexGuard aGuard;
16146 pThis->handle_item_activated();
16149 static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
16150 gboolean keyboard_tip, GtkTooltip* tooltip,
16151 gpointer widget)
16153 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
16154 GtkTreeIter iter;
16155 GtkIconView* pIconView = pThis->m_pIconView;
16156 GtkTreeModel* pModel = gtk_icon_view_get_model(pIconView);
16157 GtkTreePath* pPath = nullptr;
16158 #if GTK_CHECK_VERSION(4, 0, 0)
16159 if (!gtk_icon_view_get_tooltip_context(pIconView, x, y, keyboard_tip, &pModel, &pPath, &iter))
16160 return false;
16161 #else
16162 if (!gtk_icon_view_get_tooltip_context(pIconView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
16163 return false;
16164 #endif
16165 OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
16166 if (!aTooltip.isEmpty())
16168 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
16169 gtk_icon_view_set_tooltip_item(pIconView, tooltip, pPath);
16171 gtk_tree_path_free(pPath);
16172 return !aTooltip.isEmpty();
16175 void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
16177 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
16178 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
16179 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
16180 -1);
16181 if (pIconName)
16183 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
16184 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
16185 if (pixbuf)
16186 g_object_unref(pixbuf);
16190 void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const VirtualDevice* pIcon)
16192 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
16193 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
16194 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
16195 -1);
16196 if (pIcon)
16198 GdkPixbuf* pixbuf = getPixbuf(*pIcon);
16199 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
16200 if (pixbuf)
16201 g_object_unref(pixbuf);
16205 OUString get(const GtkTreeIter& iter, int col) const
16207 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
16208 gchar* pStr;
16209 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
16210 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
16211 g_free(pStr);
16212 return sRet;
16215 bool get_selected_iterator(GtkTreeIter* pIter) const
16217 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
16218 bool bRet = false;
16220 GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
16221 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
16222 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
16224 if (pIter)
16226 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
16227 gtk_tree_model_get_iter(pModel, pIter, path);
16229 bRet = true;
16230 break;
16232 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
16234 return bRet;
16237 public:
16238 GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
16239 : GtkInstanceWidget(GTK_WIDGET(pIconView), pBuilder, bTakeOwnership)
16240 , m_pIconView(pIconView)
16241 , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
16242 , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView))
16243 , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
16244 , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
16245 G_CALLBACK(signalSelectionChanged), this))
16246 , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
16247 #if !GTK_CHECK_VERSION(4, 0, 0)
16248 , m_nPopupMenu(g_signal_connect(pIconView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
16249 #endif
16250 , m_pSelectionChangeEvent(nullptr)
16252 m_nIdCol = m_nTextCol + 1;
16255 virtual int get_item_width() const override
16257 return gtk_icon_view_get_item_width(m_pIconView);
16260 virtual void set_item_width(int width) override
16262 gtk_icon_view_set_item_width(m_pIconView, width);
16265 virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
16267 disable_notify_events();
16268 GtkTreeIter iter;
16269 insert_item(iter, pos, pId, pText, pIconName);
16270 if (pRet)
16272 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
16273 pGtkRetIter->iter = iter;
16275 enable_notify_events();
16278 virtual void insert(int pos, const OUString* pText, const OUString* pId, const VirtualDevice* pIcon, weld::TreeIter* pRet) override
16280 disable_notify_events();
16281 GtkTreeIter iter;
16282 insert_item(iter, pos, pId, pText, pIcon);
16283 if (pRet)
16285 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
16286 pGtkRetIter->iter = iter;
16288 enable_notify_events();
16291 virtual void insert_separator(int /* pos */, const OUString* /* pId */) override
16293 // TODO: can't just copy from GtkInstanceTreeView, since there's
16294 // no IconView analog for gtk_tree_view_get_row_separator_func
16297 virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
16299 weld::IconView::connect_query_tooltip(rLink);
16300 m_nQueryTooltipSignalId = g_signal_connect(m_pIconView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
16303 virtual OUString get_selected_id() const override
16305 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
16306 GtkTreeIter iter;
16307 if (get_selected_iterator(&iter))
16308 return get(iter, m_nIdCol);
16309 return OUString();
16312 virtual void clear() override
16314 disable_notify_events();
16315 gtk_tree_store_clear(m_pTreeStore);
16316 enable_notify_events();
16319 virtual void freeze() override
16321 disable_notify_events();
16322 bool bIsFirstFreeze = IsFirstFreeze();
16323 GtkInstanceWidget::freeze();
16324 if (bIsFirstFreeze)
16326 g_object_ref(m_pTreeStore);
16327 gtk_icon_view_set_model(m_pIconView, nullptr);
16328 g_object_freeze_notify(G_OBJECT(m_pTreeStore));
16330 enable_notify_events();
16333 virtual void thaw() override
16335 disable_notify_events();
16336 if (IsLastThaw())
16338 g_object_thaw_notify(G_OBJECT(m_pTreeStore));
16339 gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
16340 g_object_unref(m_pTreeStore);
16342 GtkInstanceWidget::thaw();
16343 enable_notify_events();
16346 virtual Size get_size_request() const override
16348 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16349 if (GTK_IS_SCROLLED_WINDOW(pParent))
16351 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
16352 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
16354 int nWidth, nHeight;
16355 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
16356 return Size(nWidth, nHeight);
16359 virtual Size get_preferred_size() const override
16361 Size aRet(-1, -1);
16362 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16363 if (GTK_IS_SCROLLED_WINDOW(pParent))
16365 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
16366 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
16368 GtkRequisition size;
16369 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
16370 if (aRet.Width() == -1)
16371 aRet.setWidth(size.width);
16372 if (aRet.Height() == -1)
16373 aRet.setHeight(size.height);
16374 return aRet;
16377 virtual void show() override
16379 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16380 if (GTK_IS_SCROLLED_WINDOW(pParent))
16381 gtk_widget_show(pParent);
16382 gtk_widget_show(m_pWidget);
16385 virtual void hide() override
16387 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
16388 if (GTK_IS_SCROLLED_WINDOW(pParent))
16389 gtk_widget_hide(pParent);
16390 gtk_widget_hide(m_pWidget);
16393 virtual OUString get_selected_text() const override
16395 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
16396 GtkTreeIter iter;
16397 if (get_selected_iterator(&iter))
16398 return get(iter, m_nTextCol);
16399 return OUString();
16402 virtual int count_selected_items() const override
16404 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
16405 int nRet = g_list_length(pList);
16406 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
16407 return nRet;
16410 virtual void select(int pos) override
16412 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16413 disable_notify_events();
16414 if (pos == -1 || (pos == 0 && n_children() == 0))
16416 gtk_icon_view_unselect_all(m_pIconView);
16418 else
16420 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
16421 gtk_icon_view_select_path(m_pIconView, path);
16422 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
16423 gtk_tree_path_free(path);
16425 enable_notify_events();
16428 virtual void unselect(int pos) override
16430 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16431 disable_notify_events();
16432 if (pos == -1 || (pos == 0 && n_children() == 0))
16434 gtk_icon_view_select_all(m_pIconView);
16436 else
16438 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
16439 gtk_icon_view_select_path(m_pIconView, path);
16440 gtk_tree_path_free(path);
16442 enable_notify_events();
16445 virtual bool get_selected(weld::TreeIter* pIter) const override
16447 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
16448 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
16451 virtual bool get_cursor(weld::TreeIter* pIter) const override
16453 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
16454 GtkTreePath* path;
16455 gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
16456 if (pGtkIter && path)
16458 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
16459 gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
16461 return path != nullptr;
16464 virtual void set_cursor(const weld::TreeIter& rIter) override
16466 disable_notify_events();
16467 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16468 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
16469 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16470 gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
16471 gtk_tree_path_free(path);
16472 enable_notify_events();
16475 virtual bool get_iter_first(weld::TreeIter& rIter) const override
16477 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
16478 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
16479 return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
16482 virtual void scroll_to_item(const weld::TreeIter& rIter) override
16484 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
16485 disable_notify_events();
16486 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16487 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
16488 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
16489 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
16490 gtk_tree_path_free(path);
16491 enable_notify_events();
16494 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
16496 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
16499 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
16501 GtkInstanceTreeIter aGtkIter(nullptr);
16503 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
16504 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
16505 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
16507 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
16508 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
16509 if (func(aGtkIter))
16510 break;
16512 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
16515 virtual int n_children() const override
16517 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
16520 virtual OUString get_id(const weld::TreeIter& rIter) const override
16522 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
16523 return get(rGtkIter.iter, m_nIdCol);
16526 virtual void disable_notify_events() override
16528 g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
16529 g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
16531 GtkInstanceWidget::disable_notify_events();
16534 virtual void enable_notify_events() override
16536 GtkInstanceWidget::enable_notify_events();
16538 g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
16539 g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
16542 virtual ~GtkInstanceIconView() override
16544 if (m_pSelectionChangeEvent)
16545 Application::RemoveUserEvent(m_pSelectionChangeEvent);
16547 if (m_nQueryTooltipSignalId)
16548 g_signal_handler_disconnect(m_pIconView, m_nQueryTooltipSignalId);
16550 g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
16551 g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
16552 #if !GTK_CHECK_VERSION(4, 0, 0)
16553 g_signal_handler_disconnect(m_pIconView, m_nPopupMenu);
16554 #endif
16560 IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
16562 m_pSelectionChangeEvent = nullptr;
16563 signal_selection_changed();
16566 namespace {
16568 class GtkInstanceSpinButton : public GtkInstanceEditable, public virtual weld::SpinButton
16570 private:
16571 GtkSpinButton* m_pButton;
16572 gulong m_nValueChangedSignalId;
16573 gulong m_nOutputSignalId;
16574 gulong m_nInputSignalId;
16575 bool m_bFormatting;
16576 bool m_bBlockOutput;
16577 bool m_bBlank;
16579 static void signalValueChanged(GtkSpinButton*, gpointer widget)
16581 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
16582 SolarMutexGuard aGuard;
16583 pThis->m_bBlank = false;
16584 pThis->signal_value_changed();
16587 bool guarded_signal_output()
16589 if (m_bBlockOutput)
16590 return true;
16591 m_bFormatting = true;
16592 bool bRet = signal_output();
16593 m_bFormatting = false;
16594 return bRet;
16597 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
16599 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
16600 SolarMutexGuard aGuard;
16601 return pThis->guarded_signal_output();
16604 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
16606 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
16607 SolarMutexGuard aGuard;
16608 int result;
16609 TriState eHandled = pThis->signal_input(&result);
16610 if (eHandled == TRISTATE_INDET)
16611 return 0;
16612 if (eHandled == TRISTATE_TRUE)
16614 *new_value = pThis->toGtk(result);
16615 return 1;
16617 return GTK_INPUT_ERROR;
16620 virtual void signal_activate() override
16622 gtk_spin_button_update(m_pButton);
16623 GtkInstanceEditable::signal_activate();
16626 double toGtk(sal_Int64 nValue) const
16628 return static_cast<double>(nValue) / Power10(get_digits());
16631 sal_Int64 fromGtk(double fValue) const
16633 return FRound(fValue * Power10(get_digits()));
16636 public:
16637 GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
16638 : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
16639 , m_pButton(pButton)
16640 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
16641 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
16642 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
16643 , m_bFormatting(false)
16644 , m_bBlockOutput(false)
16645 , m_bBlank(false)
16647 #if GTK_CHECK_VERSION(4, 0, 0)
16648 gtk_text_set_activates_default(GTK_TEXT(m_pDelegate), true);
16649 #endif
16652 virtual sal_Int64 get_value() const override
16654 return fromGtk(gtk_spin_button_get_value(m_pButton));
16657 virtual void set_value(sal_Int64 value) override
16659 disable_notify_events();
16660 m_bBlank = false;
16661 gtk_spin_button_set_value(m_pButton, toGtk(value));
16662 enable_notify_events();
16665 virtual void set_text(const OUString& rText) override
16667 disable_notify_events();
16668 // tdf#122786 if we're just formatting a value, then we're done,
16669 // however if set_text has been called directly we want to update our
16670 // value from this new text, but don't want to reformat with that value
16671 if (!m_bFormatting)
16673 #if GTK_CHECK_VERSION(4, 0, 0)
16674 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
16675 #else
16676 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
16677 #endif
16679 m_bBlockOutput = true;
16680 gtk_spin_button_update(m_pButton);
16681 m_bBlank = rText.isEmpty();
16682 m_bBlockOutput = false;
16684 else
16686 bool bKeepBlank = m_bBlank && get_value() == 0;
16687 if (!bKeepBlank)
16689 #if GTK_CHECK_VERSION(4, 0, 0)
16690 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
16691 #else
16692 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
16693 #endif
16694 m_bBlank = false;
16697 enable_notify_events();
16700 virtual void set_range(sal_Int64 min, sal_Int64 max) override
16702 disable_notify_events();
16703 gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
16704 enable_notify_events();
16707 virtual void get_range(sal_Int64& min, sal_Int64& max) const override
16709 double gtkmin, gtkmax;
16710 gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
16711 min = fromGtk(gtkmin);
16712 max = fromGtk(gtkmax);
16715 virtual void set_increments(int step, int page) override
16717 disable_notify_events();
16718 gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
16719 enable_notify_events();
16722 virtual void get_increments(int& step, int& page) const override
16724 double gtkstep, gtkpage;
16725 gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
16726 step = fromGtk(gtkstep);
16727 page = fromGtk(gtkpage);
16730 virtual void set_digits(unsigned int digits) override
16732 disable_notify_events();
16733 gtk_spin_button_set_digits(m_pButton, digits);
16734 enable_notify_events();
16737 virtual unsigned int get_digits() const override
16739 return gtk_spin_button_get_digits(m_pButton);
16742 virtual void set_font(const vcl::Font& rFont) override
16744 m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
16747 virtual void disable_notify_events() override
16749 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
16750 GtkInstanceEditable::disable_notify_events();
16753 virtual void enable_notify_events() override
16755 GtkInstanceEditable::enable_notify_events();
16756 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
16759 virtual ~GtkInstanceSpinButton() override
16761 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
16762 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
16763 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
16769 namespace {
16771 class GtkInstanceFormattedSpinButton : public GtkInstanceEditable, public virtual weld::FormattedSpinButton
16773 private:
16774 GtkSpinButton* m_pButton;
16775 std::unique_ptr<weld::EntryFormatter> m_xOwnFormatter;
16776 weld::EntryFormatter* m_pFormatter;
16777 gulong m_nValueChangedSignalId;
16778 gulong m_nOutputSignalId;
16779 gulong m_nInputSignalId;
16780 bool m_bEmptyField;
16781 bool m_bSyncingValue;
16782 double m_dValueWhenEmpty;
16784 bool signal_output()
16786 double fValue = gtk_spin_button_get_value(m_pButton);
16787 m_bEmptyField &= fValue == m_dValueWhenEmpty;
16788 if (!m_bEmptyField)
16789 GetFormatter().SetValue(fValue);
16790 return true;
16793 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
16795 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
16796 SolarMutexGuard aGuard;
16797 return pThis->signal_output();
16800 gint signal_input(double* value)
16802 Formatter& rFormatter = GetFormatter();
16803 rFormatter.Modify();
16804 // if the blank-mode is enabled then if the input is empty don't parse
16805 // the input but keep the value as it is. store what the value the
16806 // blank is associated with and until the value is changed, or the text
16807 // is updated from the outside, don't output that value
16808 m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && get_text().isEmpty();
16809 if (m_bEmptyField)
16811 m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
16812 *value = m_dValueWhenEmpty;
16814 else
16815 *value = rFormatter.GetValue();
16816 return 1;
16819 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
16821 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
16822 SolarMutexGuard aGuard;
16823 return pThis->signal_input(new_value);
16826 static void signalValueChanged(GtkSpinButton*, gpointer widget)
16828 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
16829 SolarMutexGuard aGuard;
16830 pThis->signal_value_changed();
16833 public:
16834 GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
16835 : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
16836 , m_pButton(pButton)
16837 , m_pFormatter(nullptr)
16838 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
16839 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
16840 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
16841 , m_bEmptyField(false)
16842 , m_bSyncingValue(false)
16843 , m_dValueWhenEmpty(0.0)
16847 virtual void set_text(const OUString& rText) override
16849 GtkInstanceEditable::set_text(rText);
16850 Formatter& rFormatter = GetFormatter();
16851 m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && rText.isEmpty();
16852 if (m_bEmptyField)
16853 m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
16856 virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override
16858 if (!m_pFormatter) // once a formatter is set, it takes over "changed"
16860 GtkInstanceEditable::connect_changed(rLink);
16861 return;
16863 m_pFormatter->connect_changed(rLink);
16866 virtual void connect_focus_out(const Link<weld::Widget&, void>& rLink) override
16868 if (!m_pFormatter) // once a formatter is set, it takes over "focus-out"
16870 GtkInstanceEditable::connect_focus_out(rLink);
16871 return;
16873 m_pFormatter->connect_focus_out(rLink);
16876 virtual void SetFormatter(weld::EntryFormatter* pFormatter) override
16878 m_xOwnFormatter.reset();
16879 m_pFormatter = pFormatter;
16880 sync_range_from_formatter();
16881 sync_value_from_formatter();
16882 sync_increments_from_formatter();
16885 virtual weld::EntryFormatter& GetFormatter() override
16887 if (!m_pFormatter)
16889 auto aFocusOutHdl = m_aFocusOutHdl;
16890 m_aFocusOutHdl = Link<weld::Widget&, void>();
16891 auto aChangeHdl = m_aChangeHdl;
16892 m_aChangeHdl = Link<weld::Entry&, void>();
16894 double fValue = gtk_spin_button_get_value(m_pButton);
16895 double fMin, fMax;
16896 gtk_spin_button_get_range(m_pButton, &fMin, &fMax);
16897 double fStep;
16898 gtk_spin_button_get_increments(m_pButton, &fStep, nullptr);
16899 m_xOwnFormatter.reset(new weld::EntryFormatter(*this));
16900 m_xOwnFormatter->SetMinValue(fMin);
16901 m_xOwnFormatter->SetMaxValue(fMax);
16902 m_xOwnFormatter->SetSpinSize(fStep);
16903 m_xOwnFormatter->SetValue(fValue);
16905 m_xOwnFormatter->connect_focus_out(aFocusOutHdl);
16906 m_xOwnFormatter->connect_changed(aChangeHdl);
16908 m_pFormatter = m_xOwnFormatter.get();
16910 return *m_pFormatter;
16913 virtual void sync_value_from_formatter() override
16915 if (!m_pFormatter)
16916 return;
16917 // tdf#135317 avoid reenterence
16918 if (m_bSyncingValue)
16919 return;
16920 m_bSyncingValue = true;
16921 disable_notify_events();
16922 // tdf#138519 use gtk_adjustment_set_value instead of gtk_spin_button_set_value because the
16923 // latter doesn't change the value if the new value is less than an EPSILON diff of 1e-10
16924 // from the old value
16925 gtk_adjustment_set_value(gtk_spin_button_get_adjustment(m_pButton), m_pFormatter->GetValue());
16926 enable_notify_events();
16927 m_bSyncingValue = false;
16930 virtual void sync_range_from_formatter() override
16932 if (!m_pFormatter)
16933 return;
16934 disable_notify_events();
16935 double fMin = m_pFormatter->HasMinValue() ? m_pFormatter->GetMinValue() : std::numeric_limits<double>::lowest();
16936 double fMax = m_pFormatter->HasMaxValue() ? m_pFormatter->GetMaxValue() : std::numeric_limits<double>::max();
16937 gtk_spin_button_set_range(m_pButton, fMin, fMax);
16938 enable_notify_events();
16941 virtual void sync_increments_from_formatter() override
16943 if (!m_pFormatter)
16944 return;
16945 disable_notify_events();
16946 double fSpinSize = m_pFormatter->GetSpinSize();
16947 gtk_spin_button_set_increments(m_pButton, fSpinSize, fSpinSize * 10);
16948 enable_notify_events();
16951 virtual void set_font(const vcl::Font& rFont) override
16953 m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
16956 virtual void disable_notify_events() override
16958 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
16959 GtkInstanceEditable::disable_notify_events();
16962 virtual void enable_notify_events() override
16964 GtkInstanceEditable::enable_notify_events();
16965 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
16968 virtual ~GtkInstanceFormattedSpinButton() override
16970 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
16971 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
16972 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
16974 m_pFormatter = nullptr;
16975 m_xOwnFormatter.reset();
16981 namespace {
16983 class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
16985 private:
16986 GtkLabel* m_pLabel;
16988 void set_text_background_color(const Color& rColor)
16990 guint16 nRed = rColor.GetRed() << 8;
16991 guint16 nGreen = rColor.GetGreen() << 8;
16992 guint16 nBlue = rColor.GetBlue() << 8;
16994 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_BACKGROUND, PANGO_ATTR_INVALID};
16996 PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
16997 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
16998 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
16999 pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
17000 gtk_label_set_attributes(m_pLabel, pAttrs);
17001 pango_attr_list_unref(pAttrs);
17002 pango_attr_list_unref(pRemovedAttrs);
17005 void set_text_foreground_color(const Color& rColor, bool bSetBold)
17007 guint16 nRed = rColor.GetRed() << 8;
17008 guint16 nGreen = rColor.GetGreen() << 8;
17009 guint16 nBlue = rColor.GetBlue() << 8;
17011 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_WEIGHT, PANGO_ATTR_INVALID};
17013 if (!bSetBold)
17014 aFilterAttrs[1] = PANGO_ATTR_INVALID;
17016 PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
17017 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
17018 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
17019 if (rColor != COL_AUTO)
17020 pango_attr_list_insert(pAttrs, pango_attr_foreground_new(nRed, nGreen, nBlue));
17021 if (bSetBold)
17022 pango_attr_list_insert(pAttrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
17023 gtk_label_set_attributes(m_pLabel, pAttrs);
17024 pango_attr_list_unref(pAttrs);
17025 pango_attr_list_unref(pRemovedAttrs);
17028 public:
17029 GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
17030 : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
17031 , m_pLabel(pLabel)
17035 virtual void set_label(const OUString& rText) override
17037 ::set_label(m_pLabel, rText);
17040 virtual OUString get_label() const override
17042 return ::get_label(m_pLabel);
17045 virtual void set_mnemonic_widget(Widget* pTarget) override
17047 assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
17048 GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
17049 gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
17052 virtual void set_label_type(weld::LabelType eType) override
17054 switch (eType)
17056 case weld::LabelType::Normal:
17057 gtk_label_set_attributes(m_pLabel, nullptr);
17058 break;
17059 case weld::LabelType::Warning:
17060 set_text_background_color(Application::GetSettings().GetStyleSettings().GetWarningColor());
17061 break;
17062 case weld::LabelType::Error:
17063 set_text_background_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
17064 break;
17065 case weld::LabelType::Title:
17066 set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetLightColor(), true);
17067 break;
17071 virtual void set_font(const vcl::Font& rFont) override
17073 ::set_font(m_pLabel, rFont);
17076 virtual void set_font_color(const Color& rColor) override
17078 set_text_foreground_color(rColor, false);
17084 std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
17086 GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
17087 if (!pLabel || !GTK_IS_LABEL(pLabel))
17088 return nullptr;
17089 return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
17092 namespace {
17094 GdkClipboard* widget_get_clipboard(GtkWidget* pWidget)
17096 #if GTK_CHECK_VERSION(4, 0, 0)
17097 return gtk_widget_get_clipboard(pWidget);
17098 #else
17099 return gtk_widget_get_clipboard(pWidget, GDK_SELECTION_CLIPBOARD);
17100 #endif
17103 class GtkInstanceTextView : public GtkInstanceWidget, public virtual weld::TextView
17105 private:
17106 GtkTextView* m_pTextView;
17107 GtkTextBuffer* m_pTextBuffer;
17108 GtkAdjustment* m_pVAdjustment;
17109 GtkCssProvider* m_pFgCssProvider;
17110 WidgetFont m_aCustomFont;
17111 int m_nMaxTextLength;
17112 gulong m_nChangedSignalId; // we don't disable/enable this one, it's to implement max-length
17113 gulong m_nInsertTextSignalId;
17114 gulong m_nCursorPosSignalId;
17115 gulong m_nHasSelectionSignalId; // we don't disable/enable this one, it's to implement
17116 // auto-scroll to cursor on losing selection
17117 gulong m_nVAdjustChangedSignalId;
17118 #if !GTK_CHECK_VERSION(4, 0, 0)
17119 gulong m_nButtonPressEvent; // we don't disable/enable this one, it's to block mouse
17120 // click down from getting to (potential) toplevel
17121 // GtkSalFrame parent, which grabs focus away
17123 static gboolean signalButtonPressEvent(GtkWidget*, GdkEventButton*, gpointer)
17125 // e.g. on clicking on the help TextView in OTableDesignHelpBar the currently displayed text shouldn't disappear
17126 return true;
17128 #endif
17130 static void signalChanged(GtkTextBuffer*, gpointer widget)
17132 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
17133 SolarMutexGuard aGuard;
17134 pThis->signal_changed();
17137 static void signalInserText(GtkTextBuffer *pBuffer, GtkTextIter *pLocation, gchar* /*pText*/, gint /*nLen*/, gpointer widget)
17139 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
17140 pThis->insert_text(pBuffer, pLocation);
17143 void insert_text(GtkTextBuffer *pBuffer, GtkTextIter *pLocation)
17145 if (m_nMaxTextLength)
17147 gint nCount = gtk_text_buffer_get_char_count(pBuffer);
17148 if (nCount > m_nMaxTextLength)
17150 GtkTextIter nStart, nEnd;
17151 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &nStart, m_nMaxTextLength);
17152 gtk_text_buffer_get_end_iter(m_pTextBuffer, &nEnd);
17153 gtk_text_buffer_delete(m_pTextBuffer, &nStart, &nEnd);
17154 gtk_text_iter_assign(pLocation, &nStart);
17159 static void signalCursorPosition(GtkTextBuffer*, GParamSpec*, gpointer widget)
17161 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
17162 pThis->signal_cursor_position();
17165 static void signalHasSelection(GtkTextBuffer*, GParamSpec*, gpointer widget)
17167 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
17168 pThis->signal_has_selection();
17171 void signal_has_selection()
17174 in the data browser (Data Sources, shift+ctrl+f4), entering a
17175 multiline cell selects all, on cursoring to the right, the selection
17176 is lost and the cursor is at the end but gtk doesn't auto-scroll to
17177 the cursor so if the text needs scrolling to see the cursor it is off
17178 screen, another cursor makes gtk auto-scroll as wanted. So on losing
17179 selection help gtk out and do the initial scroll ourselves here
17181 if (!gtk_text_buffer_get_has_selection(m_pTextBuffer))
17183 GtkTextMark* pMark = gtk_text_buffer_get_insert(m_pTextBuffer);
17184 gtk_text_view_scroll_mark_onscreen(m_pTextView, pMark);
17188 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
17190 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
17191 SolarMutexGuard aGuard;
17192 pThis->signal_vadjustment_changed();
17195 public:
17196 GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
17197 : GtkInstanceWidget(GTK_WIDGET(pTextView), pBuilder, bTakeOwnership)
17198 , m_pTextView(pTextView)
17199 , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
17200 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
17201 , m_pFgCssProvider(nullptr)
17202 , m_aCustomFont(m_pWidget)
17203 , m_nMaxTextLength(0)
17204 , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
17205 , m_nInsertTextSignalId(g_signal_connect_after(m_pTextBuffer, "insert-text", G_CALLBACK(signalInserText), this))
17206 , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
17207 , m_nHasSelectionSignalId(g_signal_connect(m_pTextBuffer, "notify::has-selection", G_CALLBACK(signalHasSelection), this))
17208 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
17209 #if !GTK_CHECK_VERSION(4, 0, 0)
17210 , m_nButtonPressEvent(g_signal_connect_after(m_pTextView, "button-press-event", G_CALLBACK(signalButtonPressEvent), this))
17211 #endif
17215 virtual void set_size_request(int nWidth, int nHeight) override
17217 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17218 if (GTK_IS_SCROLLED_WINDOW(pParent))
17220 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
17221 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
17222 return;
17224 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
17227 virtual void set_text(const OUString& rText) override
17229 disable_notify_events();
17230 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
17231 gtk_text_buffer_set_text(m_pTextBuffer, sText.getStr(), sText.getLength());
17232 enable_notify_events();
17235 virtual OUString get_text() const override
17237 GtkTextIter start, end;
17238 gtk_text_buffer_get_bounds(m_pTextBuffer, &start, &end);
17239 char* pStr = gtk_text_buffer_get_text(m_pTextBuffer, &start, &end, true);
17240 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
17241 g_free(pStr);
17242 return sRet;
17245 virtual void replace_selection(const OUString& rText) override
17247 disable_notify_events();
17248 gtk_text_buffer_delete_selection(m_pTextBuffer, false, gtk_text_view_get_editable(m_pTextView));
17249 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
17250 gtk_text_buffer_insert_at_cursor(m_pTextBuffer, sText.getStr(), sText.getLength());
17251 enable_notify_events();
17254 virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
17256 GtkTextIter start, end;
17257 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
17258 rStartPos = gtk_text_iter_get_offset(&start);
17259 rEndPos = gtk_text_iter_get_offset(&end);
17260 return rStartPos != rEndPos;
17263 virtual void select_region(int nStartPos, int nEndPos) override
17265 disable_notify_events();
17266 GtkTextIter start, end;
17267 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &start, nStartPos);
17268 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &end, nEndPos);
17269 gtk_text_buffer_select_range(m_pTextBuffer, &start, &end);
17270 GtkTextMark* mark = gtk_text_buffer_create_mark(m_pTextBuffer, "scroll", &end, true);
17271 gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
17272 enable_notify_events();
17275 virtual void set_editable(bool bEditable) override
17277 gtk_text_view_set_editable(m_pTextView, bEditable);
17280 virtual bool get_editable() const override
17282 return gtk_text_view_get_editable(m_pTextView);
17285 virtual void set_max_length(int nChars) override
17287 m_nMaxTextLength = nChars;
17290 virtual void set_monospace(bool bMonospace) override
17292 gtk_text_view_set_monospace(m_pTextView, bMonospace);
17295 virtual void set_font_color(const Color& rColor) override
17297 const bool bRemoveColor = rColor == COL_AUTO;
17298 if (bRemoveColor && !m_pFgCssProvider)
17299 return;
17300 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pTextView));
17301 if (m_pFgCssProvider)
17303 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider));
17304 m_pFgCssProvider = nullptr;
17306 if (bRemoveColor)
17307 return;
17308 OUString sColor = rColor.AsRGBHexString();
17309 m_pFgCssProvider = gtk_css_provider_new();
17310 OUString aBuffer = "textview text { color: #" + sColor + "; }";
17311 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
17312 css_provider_load_from_data(m_pFgCssProvider, aResult.getStr(), aResult.getLength());
17313 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider),
17314 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
17317 virtual void set_font(const vcl::Font& rFont) override
17319 m_aCustomFont.use_custom_font(&rFont, u"textview");
17322 virtual vcl::Font get_font() override
17324 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
17325 return *pFont;
17326 return GtkInstanceWidget::get_font();
17329 virtual void disable_notify_events() override
17331 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
17332 g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
17333 g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
17334 GtkInstanceWidget::disable_notify_events();
17337 virtual void enable_notify_events() override
17339 GtkInstanceWidget::enable_notify_events();
17340 g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
17341 g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
17342 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
17345 // in gtk, 'up' when on the first line, will jump to the start of the line
17346 // if not there already
17347 virtual bool can_move_cursor_with_up() const override
17349 GtkTextIter start, end;
17350 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
17351 return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_start(&start);
17354 // in gtk, 'down' when on the first line, will jump to the end of the line
17355 // if not there already
17356 virtual bool can_move_cursor_with_down() const override
17358 GtkTextIter start, end;
17359 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
17360 return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_end(&end);
17363 virtual void cut_clipboard() override
17365 GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
17366 gtk_text_buffer_cut_clipboard(m_pTextBuffer, pClipboard, get_editable());
17369 virtual void copy_clipboard() override
17371 GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
17372 gtk_text_buffer_copy_clipboard(m_pTextBuffer, pClipboard);
17375 virtual void paste_clipboard() override
17377 GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
17378 gtk_text_buffer_paste_clipboard(m_pTextBuffer, pClipboard, nullptr, get_editable());
17381 virtual void set_alignment(TxtAlign eXAlign) override
17383 GtkJustification eJust = GTK_JUSTIFY_LEFT;
17384 switch (eXAlign)
17386 case TxtAlign::Left:
17387 eJust = GTK_JUSTIFY_LEFT;
17388 break;
17389 case TxtAlign::Center:
17390 eJust = GTK_JUSTIFY_CENTER;
17391 break;
17392 case TxtAlign::Right:
17393 eJust = GTK_JUSTIFY_RIGHT;
17394 break;
17396 gtk_text_view_set_justification(m_pTextView, eJust);
17399 virtual int vadjustment_get_value() const override
17401 return gtk_adjustment_get_value(m_pVAdjustment);
17404 virtual void vadjustment_set_value(int value) override
17406 disable_notify_events();
17407 gtk_adjustment_set_value(m_pVAdjustment, value);
17408 enable_notify_events();
17411 virtual int vadjustment_get_upper() const override
17413 return gtk_adjustment_get_upper(m_pVAdjustment);
17416 virtual int vadjustment_get_lower() const override
17418 return gtk_adjustment_get_lower(m_pVAdjustment);
17421 virtual int vadjustment_get_page_size() const override
17423 return gtk_adjustment_get_page_size(m_pVAdjustment);
17426 virtual void show() override
17428 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17429 if (GTK_IS_SCROLLED_WINDOW(pParent))
17430 gtk_widget_show(pParent);
17431 gtk_widget_show(m_pWidget);
17434 virtual void hide() override
17436 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17437 if (GTK_IS_SCROLLED_WINDOW(pParent))
17438 gtk_widget_hide(pParent);
17439 gtk_widget_hide(m_pWidget);
17442 virtual ~GtkInstanceTextView() override
17444 #if !GTK_CHECK_VERSION(4, 0, 0)
17445 g_signal_handler_disconnect(m_pTextView, m_nButtonPressEvent);
17446 #endif
17447 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
17448 g_signal_handler_disconnect(m_pTextBuffer, m_nInsertTextSignalId);
17449 g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
17450 g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
17451 g_signal_handler_disconnect(m_pTextBuffer, m_nHasSelectionSignalId);
17457 namespace {
17459 // IMHandler
17460 class IMHandler;
17462 #if !GTK_CHECK_VERSION(4, 0, 0)
17463 AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
17464 #endif
17466 class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
17468 private:
17469 GtkDrawingArea* m_pDrawingArea;
17470 a11yref m_xAccessible;
17471 #if !GTK_CHECK_VERSION(4, 0, 0)
17472 AtkObject *m_pAccessible;
17473 #endif
17474 ScopedVclPtrInstance<VirtualDevice> m_xDevice;
17475 std::unique_ptr<IMHandler> m_xIMHandler;
17476 cairo_surface_t* m_pSurface;
17477 #if !GTK_CHECK_VERSION(4, 0, 0)
17478 gulong m_nDrawSignalId;
17479 #endif
17480 gulong m_nQueryTooltip;
17481 #if !GTK_CHECK_VERSION(4, 0, 0)
17482 gulong m_nPopupMenu;
17483 gulong m_nScrollEvent;
17484 #endif
17486 #if GTK_CHECK_VERSION(4, 0, 0)
17487 static void signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer widget)
17488 #else
17489 static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
17490 #endif
17492 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
17493 SolarMutexGuard aGuard;
17494 pThis->signal_draw(cr);
17495 #if !GTK_CHECK_VERSION(4, 0, 0)
17496 return false;
17497 #endif
17499 void signal_draw(cairo_t* cr)
17501 if (!m_pSurface)
17502 return;
17504 GdkRectangle rect;
17505 #if GTK_CHECK_VERSION(4, 0, 0)
17506 double clip_x1, clip_x2, clip_y1, clip_y2;
17507 cairo_clip_extents(cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
17508 rect.x = clip_x1;
17509 rect.y = clip_y1;
17510 rect.width = clip_x2 - clip_x1;
17511 rect.height = clip_y2 - clip_y1;
17512 if (rect.width <= 0 || rect.height <= 0)
17513 return;
17514 #else
17515 if (!gdk_cairo_get_clip_rectangle(cr, &rect))
17516 return;
17517 #endif
17519 tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
17520 aRect = m_xDevice->PixelToLogic(aRect);
17521 m_xDevice->Erase(aRect);
17522 m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
17523 cairo_surface_mark_dirty(m_pSurface);
17525 cairo_set_source_surface(cr, m_pSurface, 0, 0);
17526 cairo_paint(cr);
17528 tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
17529 if (!aFocusRect.IsEmpty())
17531 gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
17532 aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
17535 virtual void signal_size_allocate(guint nWidth, guint nHeight) override
17537 Size aNewSize(nWidth, nHeight);
17538 if (m_pSurface && aNewSize == m_xDevice->GetOutputSizePixel())
17540 // unchanged
17541 return;
17543 m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
17544 m_pSurface = get_underlying_cairo_surface(*m_xDevice);
17545 GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
17547 void signal_style_updated()
17549 m_aStyleUpdatedHdl.Call(*this);
17551 static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
17552 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
17553 gpointer widget)
17555 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
17556 tools::Rectangle aHelpArea(x, y);
17557 OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
17558 if (aTooltip.isEmpty())
17559 return false;
17560 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
17561 GdkRectangle aGdkHelpArea;
17562 aGdkHelpArea.x = aHelpArea.Left();
17563 aGdkHelpArea.y = aHelpArea.Top();
17564 aGdkHelpArea.width = aHelpArea.GetWidth();
17565 aGdkHelpArea.height = aHelpArea.GetHeight();
17566 if (pThis->SwapForRTL())
17567 aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
17568 gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
17569 return true;
17571 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
17573 return signal_command(rCEvt);
17575 #if !GTK_CHECK_VERSION(4, 0, 0)
17576 bool signal_scroll(const GdkEventScroll* pEvent)
17578 SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
17580 if (SwapForRTL())
17581 aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
17583 CommandWheelMode nMode;
17584 sal_uInt16 nCode = aEvt.mnCode;
17585 bool bHorz = aEvt.mbHorz;
17586 if (nCode & KEY_MOD1)
17587 nMode = CommandWheelMode::ZOOM;
17588 else if (nCode & KEY_MOD2)
17589 nMode = CommandWheelMode::DATAZOOM;
17590 else
17592 nMode = CommandWheelMode::SCROLL;
17593 // #i85450# interpret shift-wheel as horizontal wheel action
17594 if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
17595 bHorz = true;
17598 CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
17599 nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
17600 CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
17601 return m_aCommandHdl.Call(aCEvt);
17603 static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
17605 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
17606 return pThis->signal_scroll(pEvent);
17608 #endif
17610 #if GTK_CHECK_VERSION(4, 0, 0)
17611 static void signalResize(GtkDrawingArea*, int nWidth, int nHeight, gpointer widget)
17613 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
17614 SolarMutexGuard aGuard;
17615 pThis->signal_size_allocate(nWidth, nHeight);
17617 #endif
17619 DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
17620 public:
17621 GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
17622 : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
17623 , m_pDrawingArea(pDrawingArea)
17624 , m_xAccessible(rA11y)
17625 #if !GTK_CHECK_VERSION(4, 0, 0)
17626 , m_pAccessible(nullptr)
17627 #endif
17628 , m_xDevice(DeviceFormat::DEFAULT)
17629 , m_pSurface(nullptr)
17630 , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
17631 #if !GTK_CHECK_VERSION(4, 0, 0)
17632 , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
17633 , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
17634 #endif
17636 #if GTK_CHECK_VERSION(4, 0, 0)
17637 gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
17638 #else
17639 m_nDrawSignalId = g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this);
17640 #endif
17641 gtk_widget_set_has_tooltip(m_pWidget, true);
17642 g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
17643 m_xDevice->EnableRTL(get_direction());
17645 ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceDrawingArea, SettingsChangedHdl));
17648 #if !GTK_CHECK_VERSION(4, 0, 0)
17649 AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
17651 if (!m_pAccessible && m_xAccessible.is())
17653 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
17654 m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
17655 if (m_pAccessible)
17656 g_object_ref(m_pAccessible);
17658 return m_pAccessible;
17660 #endif
17662 #if GTK_CHECK_VERSION(4, 0, 0)
17663 virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
17665 m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "resize", G_CALLBACK(signalResize), this);
17666 weld::Widget::connect_size_allocate(rLink);
17668 #endif
17670 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
17672 #if !GTK_CHECK_VERSION(4, 0, 0)
17673 if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_PRESS_MASK))
17674 gtk_widget_add_events(m_pWidget, GDK_BUTTON_PRESS_MASK);
17675 #endif
17676 GtkInstanceWidget::connect_mouse_press(rLink);
17679 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
17681 #if !GTK_CHECK_VERSION(4, 0, 0)
17682 if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_RELEASE_MASK))
17683 gtk_widget_add_events(m_pWidget, GDK_BUTTON_RELEASE_MASK);
17684 #endif
17685 GtkInstanceWidget::connect_mouse_release(rLink);
17688 virtual void set_direction(bool bRTL) override
17690 GtkInstanceWidget::set_direction(bRTL);
17691 m_xDevice->EnableRTL(bRTL);
17694 virtual void set_cursor(PointerStyle ePointerStyle) override
17696 GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
17697 if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
17698 gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
17699 widget_set_cursor(GTK_WIDGET(m_pDrawingArea), pCursor);
17702 virtual Point get_pointer_position() const override
17704 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
17705 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
17706 GdkDevice* pPointer = gdk_seat_get_pointer(pSeat);
17707 double x(-1), y(-1);
17708 GdkSurface* pWin = widget_get_surface(m_pWidget);
17709 surface_get_device_position(pWin, pPointer, x, y, nullptr);
17710 return Point(x, y);
17713 virtual void set_input_context(const InputContext& rInputContext) override;
17715 virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override;
17717 int im_context_get_surrounding(OUString& rSurroundingText)
17719 return signal_im_context_get_surrounding(rSurroundingText);
17722 bool im_context_delete_surrounding(const Selection& rRange)
17724 return signal_im_context_delete_surrounding(rRange);
17727 #if !GTK_CHECK_VERSION(4, 0, 0)
17728 virtual bool do_signal_key_press(const GdkEventKey* pEvent) override;
17729 virtual bool do_signal_key_release(const GdkEventKey* pEvent) override;
17730 #endif
17732 virtual void queue_draw() override
17734 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
17737 virtual void queue_draw_area(int x, int y, int width, int height) override
17739 #if !GTK_CHECK_VERSION(4, 0, 0)
17740 tools::Rectangle aRect(Point(x, y), Size(width, height));
17741 aRect = m_xDevice->LogicToPixel(aRect);
17742 gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
17743 #else
17744 (void)x; (void)y; (void)width; (void)height;
17745 queue_draw();
17746 #endif
17749 virtual a11yref get_accessible_parent() override
17751 //get_accessible_parent should only be needed for the vcl implementation,
17752 //in the gtk impl the native AtkObject parent set via
17753 //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
17754 //should negate the need.
17755 assert(false && "get_accessible_parent should only be called on a vcl impl");
17756 return uno::Reference<css::accessibility::XAccessible>();
17759 virtual a11yrelationset get_accessible_relation_set() override
17761 //get_accessible_relation_set should only be needed for the vcl implementation,
17762 //in the gtk impl the native equivalent should negate the need.
17763 assert(false && "get_accessible_relation_set should only be called on a vcl impl");
17764 return uno::Reference<css::accessibility::XAccessibleRelationSet>();
17767 virtual Point get_accessible_location_on_screen() override
17769 #if !GTK_CHECK_VERSION(4, 0, 0)
17770 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
17771 #endif
17772 gint x(0), y(0);
17773 #if !GTK_CHECK_VERSION(4, 0, 0)
17774 if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
17775 atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_SCREEN);
17776 #endif
17777 return Point(x, y);
17780 virtual void set_accessible_name(const OUString& rName) override
17782 #if !GTK_CHECK_VERSION(4, 0, 0)
17783 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
17784 if (!pAtkObject)
17785 return;
17786 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
17787 #else
17788 (void)rName;
17789 #endif
17792 virtual OUString get_accessible_name() const override
17794 #if !GTK_CHECK_VERSION(4, 0, 0)
17795 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
17796 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
17797 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
17798 #else
17799 return OUString();
17800 #endif
17803 virtual OUString get_accessible_description() const override
17805 #if !GTK_CHECK_VERSION(4, 0, 0)
17806 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
17807 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
17808 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
17809 #else
17810 return OUString();
17811 #endif
17814 virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
17816 do_enable_drag_source(rHelper, eDNDConstants);
17819 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
17821 rUnsetDragIcon = false;
17822 if (m_aDragBeginHdl.Call(*this))
17823 return true;
17824 return false;
17827 virtual ~GtkInstanceDrawingArea() override
17829 ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceDrawingArea, SettingsChangedHdl));
17831 g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
17832 #if !GTK_CHECK_VERSION(4, 0, 0)
17833 if (m_pAccessible)
17834 g_object_unref(m_pAccessible);
17835 #endif
17836 css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
17837 if (xComp.is())
17838 xComp->dispose();
17839 #if !GTK_CHECK_VERSION(4, 0, 0)
17840 g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
17841 #endif
17842 #if !GTK_CHECK_VERSION(4, 0, 0)
17843 g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
17844 #endif
17845 g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
17846 #if GTK_CHECK_VERSION(4, 0, 0)
17847 gtk_drawing_area_set_draw_func(m_pDrawingArea, nullptr, nullptr, nullptr);
17848 #else
17849 g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
17850 #endif
17853 virtual OutputDevice& get_ref_device() override
17855 return *m_xDevice;
17858 bool signal_command(const CommandEvent& rCEvt)
17860 return m_aCommandHdl.Call(rCEvt);
17863 virtual void click(const Point& rPos) override
17865 MouseEvent aEvent(rPos);
17866 m_aMousePressHdl.Call(aEvent);
17867 m_aMouseReleaseHdl.Call(aEvent);
17871 IMPL_LINK(GtkInstanceDrawingArea, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
17873 if (rEvent.GetId() != VclEventId::WindowDataChanged)
17874 return;
17876 DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
17877 if (pData->GetType() == DataChangedEventType::SETTINGS)
17878 signal_style_updated();
17881 class IMHandler
17883 private:
17884 GtkInstanceDrawingArea* m_pArea;
17885 #if GTK_CHECK_VERSION(4, 0, 0)
17886 GtkEventController* m_pFocusController;
17887 #endif
17888 GtkIMContext* m_pIMContext;
17889 OUString m_sPreeditText;
17890 gulong m_nFocusInSignalId;
17891 gulong m_nFocusOutSignalId;
17892 bool m_bExtTextInput;
17894 public:
17895 IMHandler(GtkInstanceDrawingArea* pArea)
17896 : m_pArea(pArea)
17897 , m_pIMContext(gtk_im_multicontext_new())
17898 , m_bExtTextInput(false)
17900 GtkWidget* pWidget = m_pArea->getWidget();
17902 #if GTK_CHECK_VERSION(4, 0, 0)
17903 m_pFocusController = gtk_event_controller_focus_new();
17904 gtk_widget_add_controller(pWidget, m_pFocusController);
17906 m_nFocusInSignalId = g_signal_connect(m_pFocusController, "enter", G_CALLBACK(signalFocusIn), this);
17907 m_nFocusOutSignalId = g_signal_connect(m_pFocusController, "leave", G_CALLBACK(signalFocusOut), this);
17908 #else
17909 m_nFocusInSignalId = g_signal_connect(pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
17910 m_nFocusOutSignalId = g_signal_connect(pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
17911 #endif
17913 g_signal_connect(m_pIMContext, "preedit-start", G_CALLBACK(signalIMPreeditStart), this);
17914 g_signal_connect(m_pIMContext, "preedit-end", G_CALLBACK(signalIMPreeditEnd), this);
17915 g_signal_connect(m_pIMContext, "commit", G_CALLBACK(signalIMCommit), this);
17916 g_signal_connect(m_pIMContext, "preedit-changed", G_CALLBACK(signalIMPreeditChanged), this);
17917 g_signal_connect(m_pIMContext, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding), this);
17918 g_signal_connect(m_pIMContext, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding), this);
17920 if (!gtk_widget_get_realized(pWidget))
17921 gtk_widget_realize(pWidget);
17922 im_context_set_client_widget(m_pIMContext, pWidget);
17923 if (gtk_widget_has_focus(m_pArea->getWidget()))
17924 gtk_im_context_focus_in(m_pIMContext);
17927 void signalFocus(bool bIn)
17929 if (bIn)
17930 gtk_im_context_focus_in(m_pIMContext);
17931 else
17932 gtk_im_context_focus_out(m_pIMContext);
17935 #if GTK_CHECK_VERSION(4, 0, 0)
17936 static void signalFocusIn(GtkEventControllerFocus*, gpointer im_handler)
17937 #else
17938 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
17939 #endif
17941 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
17942 pThis->signalFocus(true);
17943 #if !GTK_CHECK_VERSION(4, 0, 0)
17944 return false;
17945 #endif
17948 #if GTK_CHECK_VERSION(4, 0, 0)
17949 static void signalFocusOut(GtkEventControllerFocus*, gpointer im_handler)
17950 #else
17951 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
17952 #endif
17954 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
17955 pThis->signalFocus(false);
17956 #if !GTK_CHECK_VERSION(4, 0, 0)
17957 return false;
17958 #endif
17961 ~IMHandler()
17963 EndExtTextInput();
17965 #if GTK_CHECK_VERSION(4, 0, 0)
17966 g_signal_handler_disconnect(m_pFocusController, m_nFocusOutSignalId);
17967 g_signal_handler_disconnect(m_pFocusController, m_nFocusInSignalId);
17968 #else
17969 g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
17970 g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
17971 #endif
17973 if (gtk_widget_has_focus(m_pArea->getWidget()))
17974 gtk_im_context_focus_out(m_pIMContext);
17976 // first give IC a chance to deinitialize
17977 im_context_set_client_widget(m_pIMContext, nullptr);
17978 // destroy old IC
17979 g_object_unref(m_pIMContext);
17982 void updateIMSpotLocation()
17984 CommandEvent aCEvt(Point(), CommandEventId::CursorPos);
17985 // we expect set_cursor_location to get triggered by this
17986 m_pArea->signal_command(aCEvt);
17989 void set_cursor_location(const tools::Rectangle& rRect)
17991 GdkRectangle aArea{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
17992 static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
17993 gtk_im_context_set_cursor_location(m_pIMContext, &aArea);
17996 static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler)
17998 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18000 SolarMutexGuard aGuard;
18002 // at least editeng expects to have seen a start before accepting a commit
18003 pThis->StartExtTextInput();
18005 OUString sText(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
18006 CommandExtTextInputData aData(sText, nullptr, sText.getLength(), 0, false);
18007 CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
18008 pThis->m_pArea->signal_command(aCEvt);
18010 pThis->updateIMSpotLocation();
18012 pThis->EndExtTextInput();
18014 pThis->m_sPreeditText.clear();
18017 static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler)
18019 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18021 SolarMutexGuard aGuard;
18023 sal_Int32 nCursorPos(0);
18024 sal_uInt8 nCursorFlags(0);
18025 std::vector<ExtTextInputAttr> aInputFlags;
18026 OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
18028 // change from nothing to nothing -> do not start preedit e.g. this
18029 // will activate input into a calc cell without user input
18030 if (sText.isEmpty() && pThis->m_sPreeditText.isEmpty())
18031 return;
18033 pThis->m_sPreeditText = sText;
18035 CommandExtTextInputData aData(sText, aInputFlags.data(), nCursorPos, nCursorFlags, false);
18036 CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
18037 pThis->m_pArea->signal_command(aCEvt);
18039 pThis->updateIMSpotLocation();
18042 static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler)
18044 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18046 SolarMutexGuard aGuard;
18048 OUString sSurroundingText;
18049 int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
18051 if (nCursorIndex != -1)
18053 OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
18054 std::u16string_view sCursorText(sSurroundingText.subView(0, nCursorIndex));
18055 gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
18056 OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
18059 return true;
18062 static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
18063 gpointer im_handler)
18065 bool bRet = false;
18067 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18069 SolarMutexGuard aGuard;
18071 OUString sSurroundingText;
18072 sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
18074 Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(sSurroundingText, nCursorIndex, nOffset, nChars);
18075 if (aSelection != Selection(SAL_MAX_UINT32, SAL_MAX_UINT32))
18076 bRet = pThis->m_pArea->im_context_delete_surrounding(aSelection);
18077 return bRet;
18080 void StartExtTextInput()
18082 if (m_bExtTextInput)
18083 return;
18084 CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput);
18085 m_pArea->signal_command(aCEvt);
18086 m_bExtTextInput = true;
18089 static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler)
18091 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18092 SolarMutexGuard aGuard;
18093 pThis->StartExtTextInput();
18094 pThis->updateIMSpotLocation();
18097 void EndExtTextInput()
18099 if (!m_bExtTextInput)
18100 return;
18101 CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput);
18102 m_pArea->signal_command(aCEvt);
18103 m_bExtTextInput = false;
18106 static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler)
18108 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
18109 SolarMutexGuard aGuard;
18110 pThis->updateIMSpotLocation();
18111 pThis->EndExtTextInput();
18114 #if !GTK_CHECK_VERSION(4, 0, 0)
18115 bool im_context_filter_keypress(const GdkEventKey* pEvent)
18117 return gtk_im_context_filter_keypress(m_pIMContext, const_cast<GdkEventKey*>(pEvent));
18119 #endif
18122 #if !GTK_CHECK_VERSION(4, 0, 0)
18123 bool GtkInstanceDrawingArea::do_signal_key_press(const GdkEventKey* pEvent)
18125 if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
18126 return true;
18127 return GtkInstanceWidget::do_signal_key_press(pEvent);
18130 bool GtkInstanceDrawingArea::do_signal_key_release(const GdkEventKey* pEvent)
18132 if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
18133 return true;
18134 return GtkInstanceWidget::do_signal_key_release(pEvent);
18136 #endif
18138 void GtkInstanceDrawingArea::set_input_context(const InputContext& rInputContext)
18140 bool bUseIm(rInputContext.GetOptions() & InputContextFlags::Text);
18141 if (!bUseIm)
18143 m_xIMHandler.reset();
18144 return;
18146 // create a new im context
18147 if (!m_xIMHandler)
18148 m_xIMHandler.reset(new IMHandler(this));
18151 void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int /*nExtTextInputWidth*/)
18153 if (!m_xIMHandler)
18154 return;
18155 m_xIMHandler->set_cursor_location(rCursorRect);
18160 #if !GTK_CHECK_VERSION(4, 0, 0)
18161 static gboolean signalEntryInsertSpecialCharKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer)
18163 if ((pEvent->keyval == GDK_KEY_S || pEvent->keyval == GDK_KEY_s) &&
18164 (pEvent->state & GDK_MODIFIER_MASK) == static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK))
18166 if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
18168 weld::Window* pDialogParent = nullptr;
18170 GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pEntry));
18171 if (GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr)
18172 pDialogParent = pFrame->GetFrameWeld();
18174 std::unique_ptr<GtkInstanceWindow> xFrameWeld;
18175 if (!pDialogParent && pTopLevel)
18177 xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel), nullptr, false));
18178 pDialogParent = xFrameWeld.get();
18181 OUString aChars = pImplFncGetSpecialChars(pDialogParent, ::get_font(GTK_WIDGET(pEntry)));
18182 if (!aChars.isEmpty())
18184 gtk_editable_delete_selection(GTK_EDITABLE(pEntry));
18185 gint position = gtk_editable_get_position(GTK_EDITABLE(pEntry));
18186 OString sText(OUStringToOString(aChars, RTL_TEXTENCODING_UTF8));
18187 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sText.getStr(), sText.getLength(),
18188 &position);
18189 gtk_editable_set_position(GTK_EDITABLE(pEntry), position);
18192 return true;
18194 return false;
18196 #endif
18198 namespace {
18200 GtkBuilder* makeMenuToggleButtonBuilder()
18202 #if !GTK_CHECK_VERSION(4, 0, 0)
18203 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton3.ui");
18204 #else
18205 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton4.ui");
18206 #endif
18207 OUString aPath;
18208 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
18209 return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
18212 #if !GTK_CHECK_VERSION(4, 0, 0)
18214 GtkBuilder* makeComboBoxBuilder()
18216 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/combobox.ui");
18217 OUString aPath;
18218 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
18219 return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
18222 // pop down the toplevel combobox menu when something is activated from a custom
18223 // submenu, i.e. wysiwyg style menu
18224 class CustomRenderMenuButtonHelper : public MenuHelper
18226 private:
18227 GtkToggleButton* m_pComboBox;
18228 public:
18229 CustomRenderMenuButtonHelper(GtkMenu* pMenu, GtkToggleButton* pComboBox)
18230 : MenuHelper(pMenu, false)
18231 , m_pComboBox(pComboBox)
18234 virtual void signal_item_activate(const OString& /*rIdent*/) override
18236 gtk_toggle_button_set_active(m_pComboBox, false);
18240 #endif
18242 #if GTK_CHECK_VERSION(4, 0, 0)
18244 class GtkInstanceComboBox : public GtkInstanceWidget, public vcl::ISearchableStringList, public virtual weld::ComboBox
18246 private:
18247 GtkComboBox* m_pComboBox;
18248 // GtkOverlay* m_pOverlay;
18249 // GtkTreeView* m_pTreeView;
18250 // GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
18251 GtkWidget* m_pMenuWindow;
18252 GtkTreeModel* m_pTreeModel;
18253 GtkCellRenderer* m_pButtonTextRenderer;
18254 GtkWidget* m_pEntry;
18255 GtkEditable* m_pEditable;
18256 // GtkCellView* m_pCellView;
18257 GtkEventController* m_pKeyController;
18258 GtkEventController* m_pEntryKeyController;
18259 GtkEventController* m_pMenuKeyController;
18260 GtkEventController* m_pEntryFocusController;
18261 // std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
18262 WidgetFont m_aCustomFont;
18263 std::optional<vcl::Font> m_xEntryFont;
18264 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
18265 vcl::QuickSelectionEngine m_aQuickSelectionEngine;
18266 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
18267 #if 0
18268 OUString m_sMenuButtonRow;
18269 #endif
18270 // bool m_bHoverSelection;
18271 // bool m_bMouseInOverlayButton;
18272 bool m_bPopupActive;
18273 bool m_bAutoComplete;
18274 bool m_bAutoCompleteCaseSensitive;
18275 bool m_bChangedByMenu;
18276 bool m_bCustomRenderer;
18277 bool m_bUserSelectEntry;
18278 gint m_nTextCol;
18279 gint m_nIdCol;
18280 // gulong m_nToggleFocusInSignalId;
18281 // gulong m_nToggleFocusOutSignalId;
18282 // gulong m_nRowActivatedSignalId;
18283 gulong m_nChangedSignalId;
18284 gulong m_nPopupShownSignalId;
18285 gulong m_nKeyPressEventSignalId;
18286 gulong m_nEntryInsertTextSignalId;
18287 gulong m_nEntryActivateSignalId;
18288 gulong m_nEntryFocusInSignalId;
18289 gulong m_nEntryFocusOutSignalId;
18290 gulong m_nEntryKeyPressEventSignalId;
18291 guint m_nAutoCompleteIdleId;
18292 // gint m_nNonCustomLineHeight;
18293 gint m_nPrePopupCursorPos;
18294 int m_nMRUCount;
18295 int m_nMaxMRUCount;
18297 static gboolean idleAutoComplete(gpointer widget)
18299 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18300 pThis->auto_complete();
18301 return false;
18304 void auto_complete()
18306 m_nAutoCompleteIdleId = 0;
18307 OUString aStartText = get_active_text();
18308 int nStartPos, nEndPos;
18309 get_entry_selection_bounds(nStartPos, nEndPos);
18310 int nMaxSelection = std::max(nStartPos, nEndPos);
18311 if (nMaxSelection != aStartText.getLength())
18312 return;
18314 disable_notify_events();
18315 int nActive = get_active();
18316 int nStart = nActive;
18318 if (nStart == -1)
18319 nStart = 0;
18321 int nPos = -1;
18323 int nZeroRow = 0;
18324 if (m_nMRUCount)
18325 nZeroRow += (m_nMRUCount + 1);
18327 if (!m_bAutoCompleteCaseSensitive)
18329 // Try match case insensitive from current position
18330 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
18331 if (nPos == -1 && nStart != 0)
18333 // Try match case insensitive, but from start
18334 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
18338 if (nPos == -1)
18340 // Try match case sensitive from current position
18341 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
18342 if (nPos == -1 && nStart != 0)
18344 // Try match case sensitive, but from start
18345 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
18349 if (nPos != -1)
18351 OUString aText = get_text_including_mru(nPos);
18352 if (aText != aStartText)
18354 SolarMutexGuard aGuard;
18355 set_active_including_mru(nPos, true);
18357 select_entry_region(aText.getLength(), aStartText.getLength());
18359 enable_notify_events();
18362 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
18363 gint* position, gpointer widget)
18365 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18366 SolarMutexGuard aGuard;
18367 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
18370 void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
18372 if (m_bPopupActive) // not entered by the user
18373 return;
18375 // first filter inserted text
18376 if (m_aEntryInsertTextHdl.IsSet())
18378 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
18379 const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
18380 if (bContinue && !sText.isEmpty())
18382 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
18383 g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
18384 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
18385 g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
18387 g_signal_stop_emission_by_name(pEntry, "insert-text");
18390 if (m_bAutoComplete)
18392 // now check for autocompletes
18393 if (m_nAutoCompleteIdleId)
18394 g_source_remove(m_nAutoCompleteIdleId);
18395 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
18399 static void signalChanged(GtkComboBox*, gpointer widget)
18401 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18402 SolarMutexGuard aGuard;
18403 pThis->fire_signal_changed();
18406 void fire_signal_changed()
18408 m_bUserSelectEntry = true;
18409 m_bChangedByMenu = m_bPopupActive;
18410 signal_changed();
18411 m_bChangedByMenu = false;
18414 static void signalPopupToggled(GObject*, GParamSpec*, gpointer widget)
18416 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18417 SolarMutexGuard aGuard;
18418 pThis->signal_popup_toggled();
18421 #if 0
18422 int get_popup_height(gint& rPopupWidth)
18424 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
18426 int nMaxRows = rSettings.GetListBoxMaximumLineCount();
18427 bool bAddScrollWidth = false;
18428 int nRows = get_count_including_mru();
18429 if (nMaxRows < nRows)
18431 nRows = nMaxRows;
18432 bAddScrollWidth = true;
18435 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
18436 gint nRowHeight = get_height_row(m_pTreeView, pColumns);
18437 g_list_free(pColumns);
18439 gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
18440 gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
18442 // if we're using a custom renderer, limit the height to the height nMaxRows would be
18443 // for a normal renderer, and then round down to how many custom rows fit in that
18444 // space
18445 if (m_nNonCustomLineHeight != -1 && nRowHeight)
18447 gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
18448 if (nHeight > nNormalHeight)
18450 gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
18451 gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
18452 nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
18456 if (bAddScrollWidth)
18457 rPopupWidth += rSettings.GetScrollBarSize();
18459 return nHeight;
18461 #endif
18463 bool toggle_button_get_active()
18465 GValue value = G_VALUE_INIT;
18466 g_value_init(&value, G_TYPE_BOOLEAN);
18467 g_object_get_property(G_OBJECT(m_pComboBox), "popup-shown", &value);
18468 return g_value_get_boolean(&value);
18471 void menu_toggled()
18473 if (!m_bPopupActive)
18475 #if 0
18476 if (m_bHoverSelection)
18478 // turn hover selection back off until mouse is moved again
18479 // *after* menu is shown again
18480 gtk_tree_view_set_hover_selection(m_pTreeView, false);
18481 m_bHoverSelection = false;
18483 #endif
18485 if (!m_bUserSelectEntry)
18486 set_active_including_mru(m_nPrePopupCursorPos, true);
18488 #if 0
18489 // undo show_menu tooltip blocking
18490 GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
18491 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
18492 if (pFrame)
18493 pFrame->UnblockTooltip();
18494 #endif
18496 else
18498 m_nPrePopupCursorPos = get_active();
18500 m_bUserSelectEntry = false;
18502 // if we are in mru mode always start with the cursor at the top of the menu
18503 if (m_nMaxMRUCount)
18504 set_active_including_mru(0, true);
18508 virtual void signal_popup_toggled() override
18510 m_aQuickSelectionEngine.Reset();
18512 bool bOldPopupActive = m_bPopupActive;
18513 m_bPopupActive = toggle_button_get_active();
18515 menu_toggled();
18517 if (bOldPopupActive != m_bPopupActive)
18519 ComboBox::signal_popup_toggled();
18520 // restore focus to the GtkEntry when the popup is gone, which
18521 // is what the vcl case does, to ease the transition a little,
18522 // but don't do it if the focus was moved out of togglebutton
18523 // by something else already (e.g. font combobox in toolbar
18524 // on a "direct pick" from the menu which moves focus into
18525 // the main document
18526 if (!m_bPopupActive && m_pEntry && has_child_focus())
18528 disable_notify_events();
18529 gtk_widget_grab_focus(m_pEntry);
18530 enable_notify_events();
18535 #if GTK_CHECK_VERSION(4, 0, 0)
18536 static void signalEntryFocusIn(GtkEventControllerFocus*, gpointer widget)
18538 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18539 SolarMutexGuard aGuard;
18540 pThis->signal_entry_focus_in();
18542 #else
18543 static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
18545 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18546 pThis->signal_entry_focus_in();
18547 return false;
18549 #endif
18551 void signal_entry_focus_in()
18553 signal_focus_in();
18556 #if GTK_CHECK_VERSION(4, 0, 0)
18557 static void signalEntryFocusOut(GtkEventControllerFocus*, gpointer widget)
18559 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18560 SolarMutexGuard aGuard;
18561 pThis->signal_entry_focus_out();
18563 #else
18564 static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
18566 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18567 pThis->signal_entry_focus_out();
18568 return false;
18570 #endif
18572 void signal_entry_focus_out()
18574 // if we have an untidy selection on losing focus remove the selection
18575 int nStartPos, nEndPos;
18576 if (get_entry_selection_bounds(nStartPos, nEndPos))
18578 int nMin = std::min(nStartPos, nEndPos);
18579 int nMax = std::max(nStartPos, nEndPos);
18580 if (nMin != 0 || nMax != get_active_text().getLength())
18581 select_entry_region(0, 0);
18583 signal_focus_out();
18586 static void signalEntryActivate(GtkEntry*, gpointer widget)
18588 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18589 pThis->signal_entry_activate();
18592 void signal_entry_activate()
18594 if (m_aEntryActivateHdl.IsSet())
18596 SolarMutexGuard aGuard;
18597 if (m_aEntryActivateHdl.Call(*this))
18598 g_signal_stop_emission_by_name(m_pEntry, "activate");
18600 update_mru();
18603 OUString get(int pos, int col) const
18605 OUString sRet;
18606 GtkTreeIter iter;
18607 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
18609 gchar* pStr;
18610 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
18611 sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
18612 g_free(pStr);
18614 return sRet;
18617 void set(int pos, int col, std::u16string_view rText)
18619 GtkTreeIter iter;
18620 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
18622 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
18623 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
18627 int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
18629 GtkTreeIter iter;
18630 if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
18631 return -1;
18633 int nRet = 0;
18635 if (!bSearchMRUArea && m_nMRUCount)
18637 if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
18638 return -1;
18639 nRet += (m_nMRUCount + 1);
18642 OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
18645 gchar* pStr;
18646 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
18647 const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
18648 g_free(pStr);
18649 if (bEqual)
18650 return nRet;
18651 ++nRet;
18652 } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
18654 return -1;
18657 bool separator_function(const GtkTreePath* path)
18659 return ::separator_function(path, m_aSeparatorRows);
18662 bool separator_function(int pos)
18664 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
18665 bool bRet = separator_function(path);
18666 gtk_tree_path_free(path);
18667 return bRet;
18670 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
18672 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18673 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
18674 bool bRet = pThis->separator_function(path);
18675 gtk_tree_path_free(path);
18676 return bRet;
18679 // https://gitlab.gnome.org/GNOME/gtk/issues/310
18681 // in the absence of a built-in solution
18682 // a) support typeahead for the case where there is no entry widget, typing ahead
18683 // into the button itself will select via the vcl selection engine, a matching
18684 // entry
18685 static gboolean signalKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
18687 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18688 SolarMutexGuard aGuard;
18689 return pThis->signal_key_press(CreateKeyEvent(keyval, keycode, state, 0));
18692 // tdf#131076 we want return in a ComboBox to act like return in a
18693 // GtkEntry and activate the default dialog/assistant button
18694 bool combobox_activate()
18696 GtkWidget *pComboBox = GTK_WIDGET(m_pComboBox);
18697 GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
18698 GtkWindow *pWindow = GTK_WINDOW(pToplevel);
18699 if (!pWindow)
18700 return false;
18701 if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
18702 return false;
18703 bool bDone = false;
18704 GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
18705 if (pDefaultWidget && pDefaultWidget != pComboBox && gtk_widget_get_sensitive(pDefaultWidget))
18706 bDone = gtk_widget_activate(pDefaultWidget);
18707 return bDone;
18710 static gboolean signalEntryKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
18712 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18713 LocalizeDecimalSeparator(keyval);
18714 return pThis->signal_entry_key_press(CreateKeyEvent(keyval, keycode, state, 0));
18717 bool signal_entry_key_press(const KeyEvent& rKEvt)
18719 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
18721 bool bDone = false;
18723 auto nCode = aKeyCode.GetCode();
18724 switch (nCode)
18726 case KEY_DOWN:
18728 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
18729 if (!nKeyMod)
18731 int nCount = get_count_including_mru();
18732 int nActive = get_active_including_mru() + 1;
18733 while (nActive < nCount && separator_function(nActive))
18734 ++nActive;
18735 if (nActive < nCount)
18736 set_active_including_mru(nActive, true);
18737 bDone = true;
18739 else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
18741 gtk_combo_box_popup(m_pComboBox);
18742 bDone = true;
18744 break;
18746 case KEY_UP:
18748 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
18749 if (!nKeyMod)
18751 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
18752 int nActive = get_active_including_mru() - 1;
18753 while (nActive >= nStartBound && separator_function(nActive))
18754 --nActive;
18755 if (nActive >= nStartBound)
18756 set_active_including_mru(nActive, true);
18757 bDone = true;
18759 break;
18761 case KEY_PAGEUP:
18763 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
18764 if (!nKeyMod)
18766 int nCount = get_count_including_mru();
18767 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
18768 int nActive = nStartBound;
18769 while (nActive < nCount && separator_function(nActive))
18770 ++nActive;
18771 if (nActive < nCount)
18772 set_active_including_mru(nActive, true);
18773 bDone = true;
18775 break;
18777 case KEY_PAGEDOWN:
18779 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
18780 if (!nKeyMod)
18782 int nActive = get_count_including_mru() - 1;
18783 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
18784 while (nActive >= nStartBound && separator_function(nActive))
18785 --nActive;
18786 if (nActive >= nStartBound)
18787 set_active_including_mru(nActive, true);
18788 bDone = true;
18790 break;
18792 default:
18793 break;
18796 return bDone;
18799 bool signal_key_press(const KeyEvent& rKEvt)
18801 #if 0
18802 if (m_bHoverSelection)
18804 // once a key is pressed, turn off hover selection until mouse is
18805 // moved again otherwise when the treeview scrolls it jumps to the
18806 // position under the mouse.
18807 gtk_tree_view_set_hover_selection(m_pTreeView, false);
18808 m_bHoverSelection = false;
18810 #endif
18812 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
18814 bool bDone = false;
18816 auto nCode = aKeyCode.GetCode();
18817 switch (nCode)
18819 case KEY_DOWN:
18820 case KEY_UP:
18821 case KEY_PAGEUP:
18822 case KEY_PAGEDOWN:
18823 case KEY_HOME:
18824 case KEY_END:
18825 case KEY_LEFT:
18826 case KEY_RIGHT:
18827 case KEY_RETURN:
18829 m_aQuickSelectionEngine.Reset();
18830 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
18831 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
18832 if (nCode == KEY_RETURN && !nKeyMod)
18834 if (!m_bPopupActive)
18835 bDone = combobox_activate();
18836 else
18838 // treat 'return' as if the active entry was clicked on
18839 signalChanged(m_pComboBox, this);
18840 gtk_combo_box_popdown(m_pComboBox);
18841 bDone = true;
18844 else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
18846 gtk_combo_box_popdown(m_pComboBox);
18847 bDone = true;
18849 else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
18851 gtk_combo_box_popup(m_pComboBox);
18852 bDone = true;
18854 break;
18856 case KEY_ESCAPE:
18858 m_aQuickSelectionEngine.Reset();
18859 if (m_bPopupActive)
18861 gtk_combo_box_popdown(m_pComboBox);
18862 bDone = true;
18864 break;
18866 default:
18867 // tdf#131076 let base space toggle menu popup when it's not already visible
18868 if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
18869 bDone = false;
18870 else
18871 bDone = m_aQuickSelectionEngine.HandleKeyEvent(rKEvt);
18872 break;
18875 if (!bDone)
18877 if (!m_pEntry)
18878 bDone = signal_entry_key_press(rKEvt);
18879 else
18881 // with gtk4-4.2.1 the unconsumed keystrokes don't appear to get to
18882 // the GtkEntry for up/down to move to the next entry without this extra help
18883 // (which means this extra indirection is probably effectively
18884 // the same as if calling signal_entry_key_press directly here)
18885 bDone = gtk_event_controller_key_forward(GTK_EVENT_CONTROLLER_KEY(m_pMenuKeyController), m_pEntry);
18889 return bDone;
18892 vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
18894 int nEntryCount(get_count_including_mru());
18895 if (nPos >= nEntryCount)
18896 nPos = 0;
18897 out_entryText = get_text_including_mru(nPos);
18899 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
18900 // => normalize
18901 return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
18904 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
18906 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
18907 return reinterpret_cast<sal_Int64>(entry) - 1;
18910 int tree_view_get_cursor() const
18912 int nRet = -1;
18913 #if 0
18914 GtkTreePath* path;
18915 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
18916 if (path)
18918 gint depth;
18919 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
18920 nRet = indices[depth-1];
18921 gtk_tree_path_free(path);
18923 #endif
18925 return nRet;
18928 int get_selected_entry() const
18930 if (m_bPopupActive)
18931 return tree_view_get_cursor();
18932 else
18933 return get_active_including_mru();
18936 void set_typeahead_selected_entry(int nSelect)
18938 set_active_including_mru(nSelect, true);
18941 virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
18943 int nCurrentPos = get_selected_entry();
18944 return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
18947 virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
18949 int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
18950 return typeahead_getEntry(nNextPos, out_entryText);
18953 virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
18955 int nSelect = typeahead_getEntryPos(entry);
18956 if (nSelect == get_selected_entry())
18958 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
18959 // to select the given entry by typing its starting letters. No need to act.
18960 return;
18963 // normalize
18964 int nCount = get_count_including_mru();
18965 if (nSelect >= nCount)
18966 nSelect = nCount ? nCount-1 : -1;
18968 set_typeahead_selected_entry(nSelect);
18971 #if 0
18972 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
18974 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18975 pThis->grab_broken(pEvent);
18978 void grab_broken(const GdkEventGrabBroken *event)
18980 if (event->grab_window == nullptr)
18982 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
18984 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
18986 //try and regrab, so when we lose the grab to the menu of the color palette
18987 //combobox we regain it so the color palette doesn't itself disappear on next
18988 //click on the color palette combobox
18989 do_grab(GTK_WIDGET(m_pMenuWindow));
18993 static gboolean signalButtonPress(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
18995 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
18996 return pThis->button_press(pWidget, pEvent);
18999 bool button_press(GtkWidget* pWidget, GdkEventButton* pEvent)
19001 //we want to pop down if the button was pressed outside our popup
19002 gdouble x = pEvent->x_root;
19003 gdouble y = pEvent->y_root;
19004 gint xoffset, yoffset;
19005 gdk_window_get_root_origin(widget_get_surface(pWidget), &xoffset, &yoffset);
19007 GtkAllocation alloc;
19008 gtk_widget_get_allocation(pWidget, &alloc);
19009 xoffset += alloc.x;
19010 yoffset += alloc.y;
19012 gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow), &alloc);
19013 gint x1 = alloc.x + xoffset;
19014 gint y1 = alloc.y + yoffset;
19015 gint x2 = x1 + alloc.width;
19016 gint y2 = y1 + alloc.height;
19018 if (x > x1 && x < x2 && y > y1 && y < y2)
19019 return false;
19021 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
19023 return false;
19026 static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
19028 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19029 pThis->signal_motion();
19030 return false;
19033 void signal_motion()
19035 // if hover-selection was disabled after pressing a key, then turn it back on again
19036 if (!m_bHoverSelection && !m_bMouseInOverlayButton)
19038 gtk_tree_view_set_hover_selection(m_pTreeView, true);
19039 m_bHoverSelection = true;
19042 #endif
19044 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
19046 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19047 pThis->handle_row_activated();
19050 void handle_row_activated()
19052 m_bUserSelectEntry = true;
19053 m_bChangedByMenu = true;
19054 disable_notify_events();
19055 int nActive = get_active();
19056 if (m_pEditable)
19057 gtk_editable_set_text(m_pEditable, OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
19058 #if 0
19059 else
19060 tree_view_set_cursor(nActive);
19061 #endif
19062 enable_notify_events();
19063 // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
19064 fire_signal_changed();
19065 update_mru();
19068 void do_clear()
19070 disable_notify_events();
19071 gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
19072 m_aSeparatorRows.clear();
19073 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
19074 m_nMRUCount = 0;
19075 enable_notify_events();
19078 virtual int get_max_mru_count() const override
19080 return m_nMaxMRUCount;
19083 virtual void set_max_mru_count(int nMaxMRUCount) override
19085 m_nMaxMRUCount = nMaxMRUCount;
19086 update_mru();
19089 void update_mru()
19091 int nMRUCount = m_nMRUCount;
19093 if (m_nMaxMRUCount)
19095 OUString sActiveText = get_active_text();
19096 OUString sActiveId = get_active_id();
19097 insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
19098 ++m_nMRUCount;
19100 for (int i = 1; i < m_nMRUCount - 1; ++i)
19102 if (get_text_including_mru(i) == sActiveText)
19104 remove_including_mru(i);
19105 --m_nMRUCount;
19106 break;
19111 while (m_nMRUCount > m_nMaxMRUCount)
19113 remove_including_mru(m_nMRUCount - 1);
19114 --m_nMRUCount;
19117 if (m_nMRUCount && !nMRUCount)
19118 insert_separator_including_mru(m_nMRUCount, "separator");
19119 else if (!m_nMRUCount && nMRUCount)
19120 remove_including_mru(m_nMRUCount); // remove separator
19123 int get_count_including_mru() const
19125 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
19128 int get_active_including_mru() const
19130 return gtk_combo_box_get_active(m_pComboBox);
19133 void set_active_including_mru(int pos, bool bInteractive)
19135 disable_notify_events();
19137 gtk_combo_box_set_active(m_pComboBox, pos);
19139 m_bChangedByMenu = false;
19140 enable_notify_events();
19142 if (bInteractive && !m_bPopupActive)
19143 signal_changed();
19146 int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
19148 return find(rStr, m_nTextCol, bSearchMRU);
19151 int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
19153 return find(rId, m_nIdCol, bSearchMRU);
19156 OUString get_text_including_mru(int pos) const
19158 return get(pos, m_nTextCol);
19161 OUString get_id_including_mru(int pos) const
19163 return get(pos, m_nIdCol);
19166 void set_id_including_mru(int pos, std::u16string_view rId)
19168 set(pos, m_nIdCol, rId);
19171 void remove_including_mru(int pos)
19173 disable_notify_events();
19174 GtkTreeIter iter;
19175 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
19176 if (!m_aSeparatorRows.empty())
19178 bool bFound = false;
19180 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
19182 for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
19184 GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
19185 if (seppath)
19187 if (gtk_tree_path_compare(pPath, seppath) == 0)
19188 bFound = true;
19189 gtk_tree_path_free(seppath);
19191 if (bFound)
19193 m_aSeparatorRows.erase(aIter);
19194 break;
19198 gtk_tree_path_free(pPath);
19200 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
19201 enable_notify_events();
19204 void insert_separator_including_mru(int pos, const OUString& rId)
19206 disable_notify_events();
19207 GtkTreeIter iter;
19208 if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
19209 gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
19210 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
19211 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
19212 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
19213 gtk_tree_path_free(pPath);
19214 enable_notify_events();
19217 void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
19219 disable_notify_events();
19220 GtkTreeIter iter;
19221 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
19222 enable_notify_events();
19225 #if 0
19226 static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
19228 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19229 return pThis->signal_get_child_position(pAllocation);
19232 bool signal_get_child_position(GdkRectangle* pAllocation)
19234 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
19235 return false;
19236 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
19237 return false;
19238 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
19239 if (nRow == -1)
19240 return false;
19242 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
19244 GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
19245 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
19246 tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
19247 gtk_tree_path_free(pPath);
19248 g_list_free(pColumns);
19250 pAllocation->x = aRect.Right() - pAllocation->width;
19251 pAllocation->y = aRect.Top();
19252 pAllocation->height = aRect.GetHeight();
19254 return true;
19257 static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
19259 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
19260 pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
19261 return false;
19264 void signal_overlay_button_crossing(bool bEnter)
19266 m_bMouseInOverlayButton = bEnter;
19267 if (!bEnter)
19268 return;
19270 if (m_bHoverSelection)
19272 // once toggled button is pressed, turn off hover selection until
19273 // mouse leaves the overlay button
19274 gtk_tree_view_set_hover_selection(m_pTreeView, false);
19275 m_bHoverSelection = false;
19277 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
19278 assert(nRow != -1);
19279 tree_view_set_cursor(nRow); // select the buttons row
19281 #endif
19283 int include_mru(int pos)
19285 if (m_nMRUCount && pos != -1)
19286 pos += (m_nMRUCount + 1);
19287 return pos;
19290 public:
19291 GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
19292 : GtkInstanceWidget(GTK_WIDGET(pComboBox), pBuilder, bTakeOwnership)
19293 , m_pComboBox(pComboBox)
19294 // , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
19295 // , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
19296 // , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
19297 , m_pMenuWindow(nullptr)
19298 , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
19299 , m_pButtonTextRenderer(nullptr)
19300 // , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
19301 , m_pEntry(GTK_IS_ENTRY(gtk_combo_box_get_child(pComboBox)) ? gtk_combo_box_get_child(pComboBox) : nullptr)
19302 , m_pEditable(GTK_EDITABLE(m_pEntry))
19303 , m_aCustomFont(m_pWidget)
19304 // , m_pCellView(nullptr)
19305 , m_aQuickSelectionEngine(*this)
19306 // , m_bHoverSelection(false)
19307 // , m_bMouseInOverlayButton(false)
19308 , m_bPopupActive(false)
19309 , m_bAutoComplete(false)
19310 , m_bAutoCompleteCaseSensitive(false)
19311 , m_bChangedByMenu(false)
19312 , m_bCustomRenderer(false)
19313 , m_bUserSelectEntry(false)
19314 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
19315 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
19316 // , m_nToggleFocusInSignalId(0)
19317 // , m_nToggleFocusOutSignalId(0)
19318 // , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
19319 , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this))
19320 , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this))
19321 , m_nAutoCompleteIdleId(0)
19322 // , m_nNonCustomLineHeight(-1)
19323 , m_nPrePopupCursorPos(-1)
19324 , m_nMRUCount(0)
19325 , m_nMaxMRUCount(0)
19327 for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pComboBox));
19328 pChild; pChild = gtk_widget_get_next_sibling(pChild))
19330 if (GTK_IS_POPOVER(pChild))
19332 m_pMenuWindow = pChild;
19333 break;
19336 SAL_WARN_IF(!m_pMenuWindow, "vcl.gtk", "GtkInstanceComboBox: couldn't find popup menu");
19338 bool bHasEntry = gtk_combo_box_get_has_entry(m_pComboBox);
19339 bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
19341 bool bFindButtonTextRenderer = !bHasEntry;
19342 GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(m_pComboBox);
19343 GList* cells = gtk_cell_layout_get_cells(pCellLayout);
19344 guint i = g_list_length(cells) - 1;;
19345 // reorder the cell renderers
19346 for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
19348 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
19349 gtk_cell_layout_reorder(pCellLayout, pCellRenderer, i--);
19350 if (bFindButtonTextRenderer)
19352 m_pButtonTextRenderer = pCellRenderer;
19353 bFindButtonTextRenderer = false;
19357 // Seeing as GtkCellRendererPixbuf no longer takes a surface, then insert our own replacement
19358 // to render that instead here
19359 if (bPixbufUsedSurface)
19361 GtkCellRenderer* pSurfaceRenderer = surface_cell_renderer_new();
19362 gtk_cell_layout_pack_start(pCellLayout, pSurfaceRenderer, false);
19363 gtk_cell_layout_reorder(pCellLayout, pSurfaceRenderer, 0);
19364 gtk_cell_layout_set_attributes(pCellLayout, pSurfaceRenderer, "surface", 3, nullptr);
19367 if (bHasEntry)
19369 m_bAutoComplete = true;
19370 m_nEntryInsertTextSignalId = g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalEntryInsertText), this);
19371 m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
19372 m_pEntryFocusController = GTK_EVENT_CONTROLLER(gtk_event_controller_focus_new());
19373 m_nEntryFocusInSignalId = g_signal_connect(m_pEntryFocusController, "enter", G_CALLBACK(signalEntryFocusIn), this);
19374 m_nEntryFocusOutSignalId = g_signal_connect(m_pEntryFocusController, "leave", G_CALLBACK(signalEntryFocusOut), this);
19375 gtk_widget_add_controller(m_pEntry, m_pEntryFocusController);
19376 m_pEntryKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
19377 m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntryKeyController, "key-pressed", G_CALLBACK(signalEntryKeyPress), this);
19378 gtk_widget_add_controller(m_pEntry, m_pEntryKeyController);
19379 m_nKeyPressEventSignalId = 0;
19380 m_pKeyController = nullptr;
19382 else
19384 m_nEntryInsertTextSignalId = 0;
19385 m_nEntryActivateSignalId = 0;
19386 m_pEntryFocusController = nullptr;
19387 m_nEntryFocusInSignalId = 0;
19388 m_nEntryFocusOutSignalId = 0;
19389 m_pEntryKeyController = nullptr;
19390 m_nEntryKeyPressEventSignalId = 0;
19391 m_pKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
19392 m_nKeyPressEventSignalId = g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
19393 gtk_widget_add_controller(GTK_WIDGET(m_pComboBox), m_pKeyController);
19396 // g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
19397 // g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
19398 // g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
19400 // support typeahead for the menu itself, typing into the menu will
19401 // select via the vcl selection engine, a matching entry.
19402 if (m_pMenuWindow)
19404 m_pMenuKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
19405 g_signal_connect(m_pMenuKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
19406 gtk_widget_add_controller(m_pMenuWindow, m_pMenuKeyController);
19408 else
19409 m_pMenuKeyController = nullptr;
19410 #if 0
19411 g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
19412 gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
19413 g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
19414 g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
19415 #endif
19418 virtual int get_active() const override
19420 int nActive = get_active_including_mru();
19421 if (nActive == -1)
19422 return -1;
19424 if (m_nMRUCount)
19426 if (nActive < m_nMRUCount)
19427 nActive = find_text(get_text_including_mru(nActive));
19428 else
19429 nActive -= (m_nMRUCount + 1);
19432 return nActive;
19435 virtual OUString get_active_id() const override
19437 int nActive = get_active();
19438 return nActive != -1 ? get_id(nActive) : OUString();
19441 virtual void set_active_id(const OUString& rStr) override
19443 set_active(find_id(rStr));
19444 m_bChangedByMenu = false;
19447 virtual void set_size_request(int nWidth, int nHeight) override
19449 if (m_pButtonTextRenderer)
19451 // tweak the cell render to get a narrower size to stick
19452 if (nWidth != -1)
19454 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
19455 // the popup menu render them in full, in the interim ellipse both of them
19456 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
19458 // to find out how much of the width of the combobox belongs to the cell, set
19459 // the cell and widget to the min cell width and see what the difference is
19460 int min;
19461 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
19462 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
19463 gtk_widget_set_size_request(m_pWidget, min, -1);
19464 int nNonCellWidth = get_preferred_size().Width() - min;
19466 int nCellWidth = nWidth - nNonCellWidth;
19467 if (nCellWidth >= 0)
19469 // now set the cell to the max width which it can be within the
19470 // requested widget width
19471 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
19474 else
19476 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
19477 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
19481 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
19484 virtual void set_active(int pos) override
19486 set_active_including_mru(include_mru(pos), false);
19489 virtual OUString get_active_text() const override
19491 if (m_pEditable)
19493 const gchar* pText = gtk_editable_get_text(m_pEditable);
19494 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
19497 int nActive = get_active();
19498 if (nActive == -1)
19499 return OUString();
19501 return get_text(nActive);
19504 virtual OUString get_text(int pos) const override
19506 if (m_nMRUCount)
19507 pos += (m_nMRUCount + 1);
19508 return get_text_including_mru(pos);
19511 virtual OUString get_id(int pos) const override
19513 if (m_nMRUCount)
19514 pos += (m_nMRUCount + 1);
19515 return get_id_including_mru(pos);
19518 virtual void set_id(int pos, const OUString& rId) override
19520 if (m_nMRUCount)
19521 pos += (m_nMRUCount + 1);
19522 set_id_including_mru(pos, rId);
19525 virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
19527 freeze();
19529 int nInsertionPoint;
19530 if (!bKeepExisting)
19532 clear();
19533 nInsertionPoint = 0;
19535 else
19536 nInsertionPoint = get_count();
19538 GtkTreeIter iter;
19539 // tdf#125241 inserting backwards is faster
19540 for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
19542 const auto& rItem = *aI;
19543 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
19544 rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
19547 thaw();
19550 virtual void remove(int pos) override
19552 if (m_nMRUCount)
19553 pos += (m_nMRUCount + 1);
19554 remove_including_mru(pos);
19557 virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
19559 insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
19562 virtual void insert_separator(int pos, const OUString& rId) override
19564 pos = pos == -1 ? get_count() : pos;
19565 if (m_nMRUCount)
19566 pos += (m_nMRUCount + 1);
19567 insert_separator_including_mru(pos, rId);
19570 virtual int get_count() const override
19572 int nCount = get_count_including_mru();
19573 if (m_nMRUCount)
19574 nCount -= (m_nMRUCount + 1);
19575 return nCount;
19578 virtual int find_text(const OUString& rStr) const override
19580 int nPos = find_text_including_mru(rStr, false);
19581 if (nPos != -1 && m_nMRUCount)
19582 nPos -= (m_nMRUCount + 1);
19583 return nPos;
19586 virtual int find_id(const OUString& rId) const override
19588 int nPos = find_id_including_mru(rId, false);
19589 if (nPos != -1 && m_nMRUCount)
19590 nPos -= (m_nMRUCount + 1);
19591 return nPos;
19594 virtual void clear() override
19596 do_clear();
19599 virtual void make_sorted() override
19601 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
19602 ::comphelper::getProcessComponentContext(),
19603 Application::GetSettings().GetUILanguageTag().getLocale()));
19604 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
19605 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
19606 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
19609 virtual bool has_entry() const override
19611 return gtk_combo_box_get_has_entry(m_pComboBox);
19614 virtual void set_entry_message_type(weld::EntryMessageType eType) override
19616 assert(m_pEntry);
19617 ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
19620 virtual void set_entry_text(const OUString& rText) override
19622 assert(m_pEditable);
19623 disable_notify_events();
19624 gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
19625 enable_notify_events();
19628 virtual void set_entry_width_chars(int nChars) override
19630 assert(m_pEditable);
19631 disable_notify_events();
19632 gtk_editable_set_width_chars(m_pEditable, nChars);
19633 gtk_editable_set_max_width_chars(m_pEditable, nChars);
19634 enable_notify_events();
19637 virtual void set_entry_max_length(int nChars) override
19639 assert(m_pEntry);
19640 disable_notify_events();
19641 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
19642 enable_notify_events();
19645 virtual void select_entry_region(int nStartPos, int nEndPos) override
19647 assert(m_pEditable);
19648 disable_notify_events();
19649 gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
19650 enable_notify_events();
19653 virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
19655 assert(m_pEditable);
19656 return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
19659 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
19661 m_bAutoComplete = bEnable;
19662 m_bAutoCompleteCaseSensitive = bCaseSensitive;
19665 virtual void set_entry_placeholder_text(const OUString& rText) override
19667 assert(m_pEntry);
19668 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
19671 virtual void set_entry_editable(bool bEditable) override
19673 assert(m_pEditable);
19674 gtk_editable_set_editable(m_pEditable, bEditable);
19677 virtual void cut_entry_clipboard() override
19679 assert(m_pEntry);
19680 gtk_widget_activate_action(m_pEntry, "cut.clipboard", nullptr);
19683 virtual void copy_entry_clipboard() override
19685 assert(m_pEntry);
19686 gtk_widget_activate_action(m_pEntry, "copy.clipboard", nullptr);
19689 virtual void paste_entry_clipboard() override
19691 assert(m_pEntry);
19692 gtk_widget_activate_action(m_pEntry, "paste.clipboard", nullptr);
19695 virtual void set_font(const vcl::Font& rFont) override
19697 m_aCustomFont.use_custom_font(&rFont, u"combobox");
19700 virtual vcl::Font get_font() override
19702 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
19703 return *pFont;
19704 return GtkInstanceWidget::get_font();
19707 virtual void set_entry_font(const vcl::Font& rFont) override
19709 m_xEntryFont = rFont;
19710 assert(m_pEntry);
19711 PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
19712 PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
19713 update_attr_list(pAttrList, rFont);
19714 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
19715 pango_attr_list_unref(pAttrList);
19718 virtual vcl::Font get_entry_font() override
19720 if (m_xEntryFont)
19721 return *m_xEntryFont;
19722 assert(m_pEntry);
19723 PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
19724 return pango_to_vcl(pango_context_get_font_description(pContext),
19725 Application::GetSettings().GetUILanguageTag().getLocale());
19728 virtual void disable_notify_events() override
19730 if (m_pEditable)
19732 g_signal_handler_block(m_pEditable, m_nEntryInsertTextSignalId);
19733 g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
19734 g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusInSignalId);
19735 g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusOutSignalId);
19736 g_signal_handler_block(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
19738 else
19739 g_signal_handler_block(m_pKeyController, m_nKeyPressEventSignalId);
19741 // if (m_nToggleFocusInSignalId)
19742 // g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
19743 // if (m_nToggleFocusOutSignalId)
19744 // g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
19745 // g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
19746 g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
19747 g_signal_handler_block(m_pComboBox, m_nChangedSignalId);
19748 GtkInstanceWidget::disable_notify_events();
19751 virtual void enable_notify_events() override
19753 GtkInstanceWidget::enable_notify_events();
19754 g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
19755 g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId);
19756 // g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
19757 // if (m_nToggleFocusInSignalId)
19758 // g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
19759 // if (m_nToggleFocusOutSignalId)
19760 // g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
19761 if (m_pEditable)
19763 g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
19764 g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusInSignalId);
19765 g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusOutSignalId);
19766 g_signal_handler_unblock(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
19767 g_signal_handler_unblock(m_pEditable, m_nEntryInsertTextSignalId);
19769 else
19770 g_signal_handler_unblock(m_pKeyController, m_nKeyPressEventSignalId);
19773 virtual void freeze() override
19775 disable_notify_events();
19776 bool bIsFirstFreeze = IsFirstFreeze();
19777 GtkInstanceWidget::freeze();
19778 if (bIsFirstFreeze)
19780 g_object_ref(m_pTreeModel);
19781 // gtk_tree_view_set_model(m_pTreeView, nullptr);
19782 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
19783 if (m_xSorter)
19785 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
19786 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
19789 enable_notify_events();
19792 virtual void thaw() override
19794 disable_notify_events();
19795 if (IsLastThaw())
19797 if (m_xSorter)
19799 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
19800 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
19802 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
19803 // gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
19804 g_object_unref(m_pTreeModel);
19806 GtkInstanceWidget::thaw();
19807 enable_notify_events();
19810 virtual bool get_popup_shown() const override
19812 return m_bPopupActive;
19815 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
19817 // if (!m_nToggleFocusInSignalId)
19818 // m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
19819 GtkInstanceWidget::connect_focus_in(rLink);
19822 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
19824 // if (!m_nToggleFocusOutSignalId)
19825 // m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
19826 GtkInstanceWidget::connect_focus_out(rLink);
19829 virtual void grab_focus() override
19831 if (has_focus())
19832 return;
19833 if (m_pEntry)
19834 gtk_widget_grab_focus(m_pEntry);
19835 else
19837 // gtk_widget_grab_focus(m_pToggleButton);
19838 gtk_widget_grab_focus(GTK_WIDGET(m_pComboBox));
19842 virtual bool has_focus() const override
19844 if (m_pEntry && gtk_widget_has_focus(m_pEntry))
19845 return true;
19847 // if (gtk_widget_has_focus(m_pToggleButton))
19848 // return true;
19850 #if 0
19851 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
19853 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
19854 return true;
19856 #endif
19858 return GtkInstanceWidget::has_focus();
19861 virtual bool changed_by_direct_pick() const override
19863 return m_bChangedByMenu;
19866 virtual void set_custom_renderer(bool bOn) override
19868 if (bOn == m_bCustomRenderer)
19869 return;
19870 #if 0
19871 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
19872 // keep the original height around for optimal popup height calculation
19873 m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
19874 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
19875 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
19876 if (bOn)
19878 GtkCellRenderer *pRenderer = custom_cell_renderer_new();
19879 GValue value = G_VALUE_INIT;
19880 g_value_init(&value, G_TYPE_POINTER);
19881 g_value_set_pointer(&value, static_cast<gpointer>(this));
19882 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
19883 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
19884 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
19885 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
19887 else
19889 GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
19890 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
19891 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
19893 g_list_free(pColumns);
19894 m_bCustomRenderer = bOn;
19895 #endif
19898 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
19900 signal_custom_render(rOutput, rRect, bSelected, rId);
19903 Size call_signal_custom_get_size(VirtualDevice& rOutput)
19905 return signal_custom_get_size(rOutput);
19908 VclPtr<VirtualDevice> create_render_virtual_device() const override
19910 return create_virtual_device();
19913 virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
19915 #if 0
19916 m_xCustomMenuButtonHelper.reset();
19917 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
19918 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
19919 gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
19920 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
19921 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
19922 if (pMenuWidget)
19923 m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
19924 m_sMenuButtonRow = OUString::fromUtf8(rIdent);
19925 #else
19926 (void)rIdent; (void)pMenu;
19927 #endif
19930 OUString get_mru_entries() const override
19932 const sal_Unicode cSep = ';';
19934 OUStringBuffer aEntries;
19935 for (sal_Int32 n = 0; n < m_nMRUCount; n++)
19937 aEntries.append(get_text_including_mru(n));
19938 if (n < m_nMRUCount - 1)
19939 aEntries.append(cSep);
19941 return aEntries.makeStringAndClear();
19944 virtual void set_mru_entries(const OUString& rEntries) override
19946 const sal_Unicode cSep = ';';
19948 // Remove old MRU entries
19949 for (sal_Int32 n = m_nMRUCount; n;)
19950 remove_including_mru(--n);
19952 sal_Int32 nMRUCount = 0;
19953 sal_Int32 nIndex = 0;
19956 OUString aEntry = rEntries.getToken(0, cSep, nIndex);
19957 // Accept only existing entries
19958 int nPos = find_text(aEntry);
19959 if (nPos != -1)
19961 OUString sId = get_id(nPos);
19962 insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
19963 ++nMRUCount;
19966 while (nIndex >= 0);
19968 if (nMRUCount && !m_nMRUCount)
19969 insert_separator_including_mru(nMRUCount, "separator");
19970 else if (!nMRUCount && m_nMRUCount)
19971 remove_including_mru(m_nMRUCount); // remove separator
19973 m_nMRUCount = nMRUCount;
19976 int get_menu_button_width() const override
19978 #if 0
19979 bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
19980 if (!bVisible)
19981 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
19982 gint nWidth;
19983 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
19984 if (!bVisible)
19985 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
19986 return nWidth;
19987 #else
19988 return 0;
19989 #endif
19992 virtual ~GtkInstanceComboBox() override
19994 // m_xCustomMenuButtonHelper.reset();
19995 do_clear();
19996 if (m_nAutoCompleteIdleId)
19997 g_source_remove(m_nAutoCompleteIdleId);
19998 if (m_pEditable)
20000 g_signal_handler_disconnect(m_pEditable, m_nEntryInsertTextSignalId);
20001 g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
20002 g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusInSignalId);
20003 g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusOutSignalId);
20004 g_signal_handler_disconnect(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
20006 else
20007 g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
20008 // if (m_nToggleFocusInSignalId)
20009 // g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
20010 // if (m_nToggleFocusOutSignalId)
20011 // g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
20012 // g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
20013 g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
20014 g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId);
20016 // gtk_tree_view_set_model(m_pTreeView, nullptr);
20021 #else
20023 class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
20025 private:
20026 GtkBuilder* m_pComboBuilder;
20027 GtkComboBox* m_pComboBox;
20028 GtkOverlay* m_pOverlay;
20029 GtkTreeView* m_pTreeView;
20030 GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
20031 GtkWindow* m_pMenuWindow;
20032 GtkTreeModel* m_pTreeModel;
20033 GtkCellRenderer* m_pButtonTextRenderer;
20034 GtkCellRenderer* m_pMenuTextRenderer;
20035 GtkWidget* m_pToggleButton;
20036 GtkWidget* m_pEntry;
20037 GtkCellView* m_pCellView;
20038 WidgetFont m_aCustomFont;
20039 std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
20040 std::optional<vcl::Font> m_xEntryFont;
20041 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
20042 vcl::QuickSelectionEngine m_aQuickSelectionEngine;
20043 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
20044 OUString m_sMenuButtonRow;
20045 bool m_bHoverSelection;
20046 bool m_bMouseInOverlayButton;
20047 bool m_bPopupActive;
20048 bool m_bAutoComplete;
20049 bool m_bAutoCompleteCaseSensitive;
20050 bool m_bChangedByMenu;
20051 bool m_bCustomRenderer;
20052 bool m_bActivateCalled;
20053 gint m_nTextCol;
20054 gint m_nIdCol;
20055 gulong m_nToggleFocusInSignalId;
20056 gulong m_nToggleFocusOutSignalId;
20057 gulong m_nRowActivatedSignalId;
20058 gulong m_nChangedSignalId;
20059 gulong m_nPopupShownSignalId;
20060 gulong m_nKeyPressEventSignalId;
20061 gulong m_nEntryInsertTextSignalId;
20062 gulong m_nEntryActivateSignalId;
20063 gulong m_nEntryFocusInSignalId;
20064 gulong m_nEntryFocusOutSignalId;
20065 gulong m_nEntryKeyPressEventSignalId;
20066 guint m_nAutoCompleteIdleId;
20067 gint m_nNonCustomLineHeight;
20068 gint m_nPrePopupCursorPos;
20069 int m_nMRUCount;
20070 int m_nMaxMRUCount;
20072 static gboolean idleAutoComplete(gpointer widget)
20074 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20075 pThis->auto_complete();
20076 return false;
20079 void auto_complete()
20081 m_nAutoCompleteIdleId = 0;
20082 OUString aStartText = get_active_text();
20083 int nStartPos, nEndPos;
20084 get_entry_selection_bounds(nStartPos, nEndPos);
20085 int nMaxSelection = std::max(nStartPos, nEndPos);
20086 if (nMaxSelection != aStartText.getLength())
20087 return;
20089 disable_notify_events();
20090 int nActive = get_active();
20091 int nStart = nActive;
20093 if (nStart == -1)
20094 nStart = 0;
20096 int nPos = -1;
20098 int nZeroRow = 0;
20099 if (m_nMRUCount)
20100 nZeroRow += (m_nMRUCount + 1);
20102 if (!m_bAutoCompleteCaseSensitive)
20104 // Try match case insensitive from current position
20105 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
20106 if (nPos == -1 && nStart != 0)
20108 // Try match case insensitive, but from start
20109 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
20113 if (nPos == -1)
20115 // Try match case sensitive from current position
20116 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
20117 if (nPos == -1 && nStart != 0)
20119 // Try match case sensitive, but from start
20120 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
20124 if (nPos != -1)
20126 OUString aText = get_text_including_mru(nPos);
20127 if (aText != aStartText)
20129 SolarMutexGuard aGuard;
20130 set_active_including_mru(nPos, true);
20132 select_entry_region(aText.getLength(), aStartText.getLength());
20134 enable_notify_events();
20137 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
20138 gint* position, gpointer widget)
20140 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20141 SolarMutexGuard aGuard;
20142 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
20145 void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
20147 // first filter inserted text
20148 if (m_aEntryInsertTextHdl.IsSet())
20150 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
20151 const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
20152 if (bContinue && !sText.isEmpty())
20154 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
20155 g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
20156 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
20157 g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
20159 g_signal_stop_emission_by_name(pEntry, "insert-text");
20161 if (m_bAutoComplete)
20163 // now check for autocompletes
20164 if (m_nAutoCompleteIdleId)
20165 g_source_remove(m_nAutoCompleteIdleId);
20166 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
20170 static void signalChanged(GtkEntry*, gpointer widget)
20172 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20173 SolarMutexGuard aGuard;
20174 pThis->fire_signal_changed();
20177 void fire_signal_changed()
20179 signal_changed();
20180 m_bChangedByMenu = false;
20183 static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget)
20185 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20186 pThis->signal_popup_toggled();
20189 int get_popup_height(gint& rPopupWidth)
20191 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
20193 int nMaxRows = rSettings.GetListBoxMaximumLineCount();
20194 bool bAddScrollWidth = false;
20195 int nRows = get_count_including_mru();
20196 if (nMaxRows < nRows)
20198 nRows = nMaxRows;
20199 bAddScrollWidth = true;
20202 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
20203 gint nRowHeight = get_height_row(m_pTreeView, pColumns);
20204 g_list_free(pColumns);
20206 gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
20207 gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
20209 // if we're using a custom renderer, limit the height to the height nMaxRows would be
20210 // for a normal renderer, and then round down to how many custom rows fit in that
20211 // space
20212 if (m_nNonCustomLineHeight != -1 && nRowHeight)
20214 gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
20215 if (nHeight > nNormalHeight)
20217 gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
20218 gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
20219 nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
20223 if (bAddScrollWidth)
20224 rPopupWidth += rSettings.GetScrollBarSize();
20226 return nHeight;
20229 void menu_toggled()
20231 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)))
20233 if (m_bHoverSelection)
20235 // turn hover selection back off until mouse is moved again
20236 // *after* menu is shown again
20237 gtk_tree_view_set_hover_selection(m_pTreeView, false);
20238 m_bHoverSelection = false;
20241 bool bHadFocus = gtk_window_has_toplevel_focus(m_pMenuWindow);
20243 do_ungrab(GTK_WIDGET(m_pMenuWindow));
20245 gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
20247 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
20248 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
20250 // so gdk_window_move_to_rect will work again the next time
20251 gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow));
20253 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), -1, -1);
20255 if (!m_bActivateCalled)
20256 tree_view_set_cursor(m_nPrePopupCursorPos);
20258 // undo show_menu tooltip blocking
20259 GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
20260 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
20261 if (pFrame)
20262 pFrame->UnblockTooltip();
20264 if (bHadFocus)
20266 GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
20267 void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
20268 if (pParentIsPopover)
20269 do_grab(m_pToggleButton);
20270 gtk_widget_grab_focus(m_pToggleButton);
20273 else
20275 GtkWidget* pComboBox = GTK_WIDGET(getContainer());
20277 gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
20278 GtkRequisition size;
20279 gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow), nullptr, &size);
20281 gint nPopupWidth = size.width;
20282 gint nPopupHeight = get_popup_height(nPopupWidth);
20283 nPopupWidth = std::max(nPopupWidth, nComboWidth);
20285 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight);
20287 m_nPrePopupCursorPos = get_active();
20289 m_bActivateCalled = false;
20291 // if we are in mru mode always start with the cursor at the top of the menu
20292 if (m_nMaxMRUCount)
20293 tree_view_set_cursor(0);
20295 GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pComboBox), gtk_widget_get_allocated_height(pComboBox) };
20296 show_menu(pComboBox, m_pMenuWindow, aAnchor, weld::Placement::Under, true);
20297 GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
20298 g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
20302 virtual void signal_popup_toggled() override
20304 m_aQuickSelectionEngine.Reset();
20306 menu_toggled();
20308 bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
20309 if (m_bPopupActive == bIsShown)
20310 return;
20312 m_bPopupActive = bIsShown;
20313 ComboBox::signal_popup_toggled();
20314 if (!m_bPopupActive && m_pEntry)
20316 disable_notify_events();
20317 //restore focus to the GtkEntry when the popup is gone, which
20318 //is what the vcl case does, to ease the transition a little
20319 gtk_widget_grab_focus(m_pEntry);
20320 enable_notify_events();
20324 static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
20326 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20327 pThis->signal_entry_focus_in();
20328 return false;
20331 void signal_entry_focus_in()
20333 signal_focus_in();
20336 static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
20338 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20339 pThis->signal_entry_focus_out();
20340 return false;
20343 void signal_entry_focus_out()
20345 // if we have an untidy selection on losing focus remove the selection
20346 int nStartPos, nEndPos;
20347 if (get_entry_selection_bounds(nStartPos, nEndPos))
20349 int nMin = std::min(nStartPos, nEndPos);
20350 int nMax = std::max(nStartPos, nEndPos);
20351 if (nMin != 0 || nMax != get_active_text().getLength())
20352 select_entry_region(0, 0);
20354 signal_focus_out();
20357 static void signalEntryActivate(GtkEntry*, gpointer widget)
20359 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20360 pThis->signal_entry_activate();
20363 void signal_entry_activate()
20365 if (m_aEntryActivateHdl.IsSet())
20367 SolarMutexGuard aGuard;
20368 if (m_aEntryActivateHdl.Call(*this))
20369 g_signal_stop_emission_by_name(m_pEntry, "activate");
20371 update_mru();
20374 OUString get(int pos, int col) const
20376 OUString sRet;
20377 GtkTreeIter iter;
20378 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
20380 gchar* pStr;
20381 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
20382 sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
20383 g_free(pStr);
20385 return sRet;
20388 void set(int pos, int col, std::u16string_view rText)
20390 GtkTreeIter iter;
20391 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
20393 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
20394 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
20398 int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
20400 GtkTreeIter iter;
20401 if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
20402 return -1;
20404 int nRet = 0;
20406 if (!bSearchMRUArea && m_nMRUCount)
20408 if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
20409 return -1;
20410 nRet += (m_nMRUCount + 1);
20413 OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
20416 gchar* pStr;
20417 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
20418 const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
20419 g_free(pStr);
20420 if (bEqual)
20421 return nRet;
20422 ++nRet;
20423 } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
20425 return -1;
20428 bool separator_function(const GtkTreePath* path)
20430 return ::separator_function(path, m_aSeparatorRows);
20433 bool separator_function(int pos)
20435 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
20436 bool bRet = separator_function(path);
20437 gtk_tree_path_free(path);
20438 return bRet;
20441 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
20443 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20444 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
20445 bool bRet = pThis->separator_function(path);
20446 gtk_tree_path_free(path);
20447 return bRet;
20450 // https://gitlab.gnome.org/GNOME/gtk/issues/310
20452 // in the absence of a built-in solution
20453 // a) support typeahead for the case where there is no entry widget, typing ahead
20454 // into the button itself will select via the vcl selection engine, a matching
20455 // entry
20456 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
20458 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20459 return pThis->signal_key_press(pEvent);
20462 // tdf#131076 we want return in a ComboBox to act like return in a
20463 // GtkEntry and activate the default dialog/assistant button
20464 bool combobox_activate()
20466 GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton);
20467 GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
20468 GtkWindow *pWindow = GTK_WINDOW(pToplevel);
20469 if (!pWindow)
20470 return false;
20471 if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
20472 return false;
20473 bool bDone = false;
20474 GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
20475 if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget))
20476 bDone = gtk_widget_activate(pDefaultWidget);
20477 return bDone;
20480 static gboolean signalEntryKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer widget)
20482 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20483 LocalizeDecimalSeparator(pEvent->keyval);
20484 if (signalEntryInsertSpecialCharKeyPress(pEntry, pEvent, nullptr))
20485 return true;
20486 return pThis->signal_entry_key_press(pEvent);
20489 bool signal_entry_key_press(const GdkEventKey* pEvent)
20491 KeyEvent aKEvt(GtkToVcl(*pEvent));
20493 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
20495 bool bDone = false;
20497 auto nCode = aKeyCode.GetCode();
20498 switch (nCode)
20500 case KEY_DOWN:
20502 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
20503 if (!nKeyMod)
20505 int nCount = get_count_including_mru();
20506 int nActive = get_active_including_mru() + 1;
20507 while (nActive < nCount && separator_function(nActive))
20508 ++nActive;
20509 if (nActive < nCount)
20510 set_active_including_mru(nActive, true);
20511 bDone = true;
20513 else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
20515 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
20516 bDone = true;
20518 break;
20520 case KEY_UP:
20522 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
20523 if (!nKeyMod)
20525 int nStartBound = m_bPopupActive || !m_nMRUCount ? 0 : (m_nMRUCount + 1);
20526 int nActive = get_active_including_mru() - 1;
20527 while (nActive >= nStartBound && separator_function(nActive))
20528 --nActive;
20529 if (nActive >= nStartBound)
20530 set_active_including_mru(nActive, true);
20531 bDone = true;
20533 break;
20535 case KEY_PAGEUP:
20537 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
20538 if (!nKeyMod)
20540 int nCount = get_count_including_mru();
20541 int nStartBound = m_bPopupActive || !m_nMaxMRUCount ? 0 : (m_nMRUCount + 1);
20542 int nActive = nStartBound;
20543 while (nActive < nCount && separator_function(nActive))
20544 ++nActive;
20545 if (nActive < nCount)
20546 set_active_including_mru(nActive, true);
20547 bDone = true;
20549 break;
20551 case KEY_PAGEDOWN:
20553 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
20554 if (!nKeyMod)
20556 int nActive = get_count_including_mru() - 1;
20557 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
20558 while (nActive >= nStartBound && separator_function(nActive))
20559 --nActive;
20560 if (nActive >= nStartBound)
20561 set_active_including_mru(nActive, true);
20562 bDone = true;
20564 break;
20566 default:
20567 break;
20570 return bDone;
20573 bool signal_key_press(const GdkEventKey* pEvent)
20575 if (m_bHoverSelection)
20577 // once a key is pressed, turn off hover selection until mouse is
20578 // moved again otherwise when the treeview scrolls it jumps to the
20579 // position under the mouse.
20580 gtk_tree_view_set_hover_selection(m_pTreeView, false);
20581 m_bHoverSelection = false;
20584 KeyEvent aKEvt(GtkToVcl(*pEvent));
20586 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
20588 bool bDone = false;
20590 auto nCode = aKeyCode.GetCode();
20591 switch (nCode)
20593 case KEY_DOWN:
20594 case KEY_UP:
20595 case KEY_PAGEUP:
20596 case KEY_PAGEDOWN:
20597 case KEY_HOME:
20598 case KEY_END:
20599 case KEY_LEFT:
20600 case KEY_RIGHT:
20601 case KEY_RETURN:
20603 m_aQuickSelectionEngine.Reset();
20604 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
20605 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
20606 if (nCode == KEY_RETURN && !nKeyMod && !m_bPopupActive)
20607 bDone = combobox_activate();
20608 else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
20610 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20611 bDone = true;
20613 else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
20615 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
20616 bDone = true;
20618 break;
20620 case KEY_ESCAPE:
20622 m_aQuickSelectionEngine.Reset();
20623 if (m_bPopupActive)
20625 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20626 bDone = true;
20628 break;
20630 default:
20631 // tdf#131076 let base space toggle menu popup when it's not already visible
20632 if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
20633 bDone = false;
20634 else
20635 bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
20636 break;
20639 if (!bDone && !m_pEntry)
20640 bDone = signal_entry_key_press(pEvent);
20642 return bDone;
20645 vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
20647 int nEntryCount(get_count_including_mru());
20648 if (nPos >= nEntryCount)
20649 nPos = 0;
20650 out_entryText = get_text_including_mru(nPos);
20652 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
20653 // => normalize
20654 return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
20657 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
20659 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
20660 return reinterpret_cast<sal_Int64>(entry) - 1;
20663 void tree_view_set_cursor(int pos)
20665 if (pos == -1)
20667 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
20668 if (m_pCellView)
20669 gtk_cell_view_set_displayed_row(m_pCellView, nullptr);
20671 else
20673 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
20674 if (gtk_tree_view_get_model(m_pTreeView))
20675 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
20676 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
20677 if (m_pCellView)
20678 gtk_cell_view_set_displayed_row(m_pCellView, path);
20679 gtk_tree_path_free(path);
20683 int tree_view_get_cursor() const
20685 int nRet = -1;
20687 GtkTreePath* path;
20688 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
20689 if (path)
20691 gint depth;
20692 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
20693 nRet = indices[depth-1];
20694 gtk_tree_path_free(path);
20697 return nRet;
20700 int get_selected_entry() const
20702 if (m_bPopupActive)
20703 return tree_view_get_cursor();
20704 else
20705 return get_active_including_mru();
20708 void set_typeahead_selected_entry(int nSelect)
20710 if (m_bPopupActive)
20711 tree_view_set_cursor(nSelect);
20712 else
20713 set_active_including_mru(nSelect, true);
20716 virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
20718 int nCurrentPos = get_selected_entry();
20719 return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
20722 virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
20724 int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
20725 return typeahead_getEntry(nNextPos, out_entryText);
20728 virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
20730 int nSelect = typeahead_getEntryPos(entry);
20731 if (nSelect == get_selected_entry())
20733 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
20734 // to select the given entry by typing its starting letters. No need to act.
20735 return;
20738 // normalize
20739 int nCount = get_count_including_mru();
20740 if (nSelect >= nCount)
20741 nSelect = nCount ? nCount-1 : -1;
20743 set_typeahead_selected_entry(nSelect);
20746 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
20748 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20749 pThis->grab_broken(pEvent);
20752 void grab_broken(const GdkEventGrabBroken *event)
20754 if (event->grab_window == nullptr)
20756 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20758 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
20760 //try and regrab, so when we lose the grab to the menu of the color palette
20761 //combobox we regain it so the color palette doesn't itself disappear on next
20762 //click on the color palette combobox
20763 do_grab(GTK_WIDGET(m_pMenuWindow));
20767 static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
20769 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20770 return pThis->button_press(pEvent);
20773 bool button_press(GdkEventButton* pEvent)
20775 //we want to pop down if the button was pressed outside our popup
20776 if (button_event_is_outside(GTK_WIDGET(m_pMenuWindow), pEvent))
20777 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20778 return false;
20781 static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
20783 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20784 pThis->signal_motion();
20785 return false;
20788 void signal_motion()
20790 // if hover-selection was disabled after pressing a key, then turn it back on again
20791 if (!m_bHoverSelection && !m_bMouseInOverlayButton)
20793 gtk_tree_view_set_hover_selection(m_pTreeView, true);
20794 m_bHoverSelection = true;
20798 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
20800 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20801 pThis->handle_row_activated();
20804 void handle_row_activated()
20806 m_bActivateCalled = true;
20807 m_bChangedByMenu = true;
20808 disable_notify_events();
20809 int nActive = get_active();
20810 if (m_pEntry)
20811 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
20812 else
20813 tree_view_set_cursor(nActive);
20814 enable_notify_events();
20815 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
20816 fire_signal_changed();
20817 update_mru();
20820 void do_clear()
20822 disable_notify_events();
20823 gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
20824 m_aSeparatorRows.clear();
20825 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
20826 m_nMRUCount = 0;
20827 enable_notify_events();
20830 virtual int get_max_mru_count() const override
20832 return m_nMaxMRUCount;
20835 virtual void set_max_mru_count(int nMaxMRUCount) override
20837 m_nMaxMRUCount = nMaxMRUCount;
20838 update_mru();
20841 void update_mru()
20843 int nMRUCount = m_nMRUCount;
20845 if (m_nMaxMRUCount)
20847 OUString sActiveText = get_active_text();
20848 OUString sActiveId = get_active_id();
20849 insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
20850 ++m_nMRUCount;
20852 for (int i = 1; i < m_nMRUCount - 1; ++i)
20854 if (get_text_including_mru(i) == sActiveText)
20856 remove_including_mru(i);
20857 --m_nMRUCount;
20858 break;
20863 while (m_nMRUCount > m_nMaxMRUCount)
20865 remove_including_mru(m_nMRUCount - 1);
20866 --m_nMRUCount;
20869 if (m_nMRUCount && !nMRUCount)
20870 insert_separator_including_mru(m_nMRUCount, "separator");
20871 else if (!m_nMRUCount && nMRUCount)
20872 remove_including_mru(m_nMRUCount); // remove separator
20875 int get_count_including_mru() const
20877 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
20880 int get_active_including_mru() const
20882 return tree_view_get_cursor();
20885 void set_active_including_mru(int pos, bool bInteractive)
20887 disable_notify_events();
20889 tree_view_set_cursor(pos);
20891 if (m_pEntry)
20893 if (pos != -1)
20894 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr());
20895 else
20896 gtk_entry_set_text(GTK_ENTRY(m_pEntry), "");
20899 m_bChangedByMenu = false;
20900 enable_notify_events();
20902 if (bInteractive && !m_bPopupActive)
20903 signal_changed();
20906 int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
20908 return find(rStr, m_nTextCol, bSearchMRU);
20911 int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
20913 return find(rId, m_nIdCol, bSearchMRU);
20916 OUString get_text_including_mru(int pos) const
20918 return get(pos, m_nTextCol);
20921 OUString get_id_including_mru(int pos) const
20923 return get(pos, m_nIdCol);
20926 void set_id_including_mru(int pos, std::u16string_view rId)
20928 set(pos, m_nIdCol, rId);
20931 void remove_including_mru(int pos)
20933 disable_notify_events();
20934 GtkTreeIter iter;
20935 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
20936 if (!m_aSeparatorRows.empty())
20938 bool bFound = false;
20940 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
20942 for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
20944 GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
20945 if (seppath)
20947 if (gtk_tree_path_compare(pPath, seppath) == 0)
20948 bFound = true;
20949 gtk_tree_path_free(seppath);
20951 if (bFound)
20953 m_aSeparatorRows.erase(aIter);
20954 break;
20958 gtk_tree_path_free(pPath);
20960 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
20961 enable_notify_events();
20964 void insert_separator_including_mru(int pos, const OUString& rId)
20966 disable_notify_events();
20967 GtkTreeIter iter;
20968 if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
20969 gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
20970 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
20971 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
20972 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
20973 gtk_tree_path_free(pPath);
20974 enable_notify_events();
20977 void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
20979 disable_notify_events();
20980 GtkTreeIter iter;
20981 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
20982 enable_notify_events();
20985 static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
20987 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
20988 return pThis->signal_get_child_position(pAllocation);
20991 bool signal_get_child_position(GdkRectangle* pAllocation)
20993 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
20994 return false;
20995 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
20996 return false;
20997 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
20998 if (nRow == -1)
20999 return false;
21001 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
21003 GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
21004 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
21005 tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
21006 gtk_tree_path_free(pPath);
21007 g_list_free(pColumns);
21009 pAllocation->x = aRect.Right() - pAllocation->width;
21010 pAllocation->y = aRect.Top();
21011 pAllocation->height = aRect.GetHeight();
21013 return true;
21016 static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
21018 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21019 pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
21020 return false;
21023 void signal_overlay_button_crossing(bool bEnter)
21025 m_bMouseInOverlayButton = bEnter;
21026 if (!bEnter)
21027 return;
21029 if (m_bHoverSelection)
21031 // once toggled button is pressed, turn off hover selection until
21032 // mouse leaves the overlay button
21033 gtk_tree_view_set_hover_selection(m_pTreeView, false);
21034 m_bHoverSelection = false;
21036 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
21037 assert(nRow != -1);
21038 tree_view_set_cursor(nRow); // select the buttons row
21041 void signal_combo_mnemonic_activate()
21043 if (m_pEntry)
21044 gtk_widget_grab_focus(m_pEntry);
21045 else
21046 gtk_widget_grab_focus(m_pToggleButton);
21049 static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
21051 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
21052 pThis->signal_combo_mnemonic_activate();
21053 return true;
21056 int include_mru(int pos)
21058 if (m_nMRUCount && pos != -1)
21059 pos += (m_nMRUCount + 1);
21060 return pos;
21063 public:
21064 GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
21065 : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership)
21066 , m_pComboBuilder(pComboBuilder)
21067 , m_pComboBox(pComboBox)
21068 , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
21069 , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
21070 , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
21071 , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder, "popup")))
21072 , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
21073 , m_pButtonTextRenderer(nullptr)
21074 , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
21075 , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry")))
21076 , m_pCellView(nullptr)
21077 , m_aCustomFont(m_pWidget)
21078 , m_aQuickSelectionEngine(*this)
21079 , m_bHoverSelection(false)
21080 , m_bMouseInOverlayButton(false)
21081 , m_bPopupActive(false)
21082 , m_bAutoComplete(false)
21083 , m_bAutoCompleteCaseSensitive(false)
21084 , m_bChangedByMenu(false)
21085 , m_bCustomRenderer(false)
21086 , m_bActivateCalled(false)
21087 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
21088 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
21089 , m_nToggleFocusInSignalId(0)
21090 , m_nToggleFocusOutSignalId(0)
21091 , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
21092 , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this))
21093 , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this))
21094 , m_nAutoCompleteIdleId(0)
21095 , m_nNonCustomLineHeight(-1)
21096 , m_nPrePopupCursorPos(-1)
21097 , m_nMRUCount(0)
21098 , m_nMaxMRUCount(0)
21100 int nActive = gtk_combo_box_get_active(m_pComboBox);
21102 if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button"))
21103 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button");
21105 insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer()));
21106 gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false);
21107 gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true);
21109 gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
21110 /* tdf#136455 gtk_combo_box_set_model with a null Model should be good
21111 enough. But in practice, while the ComboBox model is unset, GTK
21112 doesn't unset the ComboBox menus model, so that remains listening to
21113 additions to the ListStore and slowing things down massively.
21114 Using a new model does reset the menu to listen to that unused one instead */
21115 gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)));
21117 GtkTreeViewColumn* pCol = gtk_tree_view_column_new();
21118 gtk_tree_view_append_column(m_pTreeView, pCol);
21120 bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
21122 GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
21123 // move the cell renderers from the combobox to the replacement treeview
21124 m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
21125 for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
21127 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
21128 bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer;
21129 gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer);
21130 if (!bTextRenderer)
21132 if (bPixbufUsedSurface)
21133 gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr);
21134 else
21135 gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr);
21139 gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr);
21141 if (gtk_combo_box_get_has_entry(m_pComboBox))
21143 m_bAutoComplete = true;
21144 m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
21145 m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
21146 m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
21147 m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
21148 m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this);
21149 m_nKeyPressEventSignalId = 0;
21151 else
21153 gtk_widget_set_visible(m_pEntry, false);
21154 m_pEntry = nullptr;
21156 GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow"));
21157 gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr);
21159 auto m_pCellArea = gtk_cell_area_box_new();
21160 m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr));
21161 gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true);
21162 GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow));
21164 gint nImageSpacing(2);
21165 GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton));
21166 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
21167 gtk_box_set_spacing(pBox, nImageSpacing);
21169 gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0);
21171 gtk_cell_view_set_fit_model(m_pCellView, true);
21172 gtk_cell_view_set_model(m_pCellView, m_pTreeModel);
21174 m_pButtonTextRenderer = gtk_cell_renderer_text_new();
21175 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true);
21176 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr);
21177 if (g_list_length(cells) > 1)
21179 GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new();
21180 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false);
21181 if (bPixbufUsedSurface)
21182 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr);
21183 else
21184 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr);
21187 gtk_widget_show_all(GTK_WIDGET(m_pCellView));
21189 m_nEntryInsertTextSignalId = 0;
21190 m_nEntryActivateSignalId = 0;
21191 m_nEntryFocusInSignalId = 0;
21192 m_nEntryFocusOutSignalId = 0;
21193 m_nEntryKeyPressEventSignalId = 0;
21194 m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
21197 g_list_free(cells);
21199 if (nActive != -1)
21200 tree_view_set_cursor(nActive);
21202 g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this);
21204 g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
21205 g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
21206 g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
21207 // support typeahead for the menu itself, typing into the menu will
21208 // select via the vcl selection engine, a matching entry.
21209 g_signal_connect(m_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this);
21211 g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
21212 gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
21213 g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
21214 g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
21217 virtual int get_active() const override
21219 int nActive = get_active_including_mru();
21220 if (nActive == -1)
21221 return -1;
21223 if (m_nMRUCount)
21225 if (nActive < m_nMRUCount)
21226 nActive = find_text(get_text_including_mru(nActive));
21227 else
21228 nActive -= (m_nMRUCount + 1);
21231 return nActive;
21234 virtual OUString get_active_id() const override
21236 int nActive = get_active();
21237 return nActive != -1 ? get_id(nActive) : OUString();
21240 virtual void set_active_id(const OUString& rStr) override
21242 set_active(find_id(rStr));
21243 m_bChangedByMenu = false;
21246 virtual void set_size_request(int nWidth, int nHeight) override
21248 if (m_pButtonTextRenderer)
21250 // tweak the cell render to get a narrower size to stick
21251 if (nWidth != -1)
21253 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
21254 // the popup menu render them in full, in the interim ellipse both of them
21255 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
21257 // to find out how much of the width of the combobox belongs to the cell, set
21258 // the cell and widget to the min cell width and see what the difference is
21259 int min;
21260 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
21261 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
21262 gtk_widget_set_size_request(m_pWidget, min, -1);
21263 int nNonCellWidth = get_preferred_size().Width() - min;
21265 int nCellWidth = nWidth - nNonCellWidth;
21266 if (nCellWidth >= 0)
21268 // now set the cell to the max width which it can be within the
21269 // requested widget width
21270 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
21273 else
21275 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
21276 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
21280 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
21283 virtual void set_active(int pos) override
21285 set_active_including_mru(include_mru(pos), false);
21288 virtual OUString get_active_text() const override
21290 if (m_pEntry)
21292 const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry));
21293 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
21296 int nActive = get_active();
21297 if (nActive == -1)
21298 return OUString();
21300 return get_text(nActive);
21303 virtual OUString get_text(int pos) const override
21305 if (m_nMRUCount)
21306 pos += (m_nMRUCount + 1);
21307 return get_text_including_mru(pos);
21310 virtual OUString get_id(int pos) const override
21312 if (m_nMRUCount)
21313 pos += (m_nMRUCount + 1);
21314 return get_id_including_mru(pos);
21317 virtual void set_id(int pos, const OUString& rId) override
21319 if (m_nMRUCount)
21320 pos += (m_nMRUCount + 1);
21321 set_id_including_mru(pos, rId);
21324 virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
21326 freeze();
21328 int nInsertionPoint;
21329 if (!bKeepExisting)
21331 clear();
21332 nInsertionPoint = 0;
21334 else
21335 nInsertionPoint = get_count();
21337 GtkTreeIter iter;
21338 // tdf#125241 inserting backwards is faster
21339 for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
21341 const auto& rItem = *aI;
21342 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
21343 rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
21346 thaw();
21349 virtual void remove(int pos) override
21351 if (m_nMRUCount)
21352 pos += (m_nMRUCount + 1);
21353 remove_including_mru(pos);
21356 virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
21358 insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
21361 virtual void insert_separator(int pos, const OUString& rId) override
21363 pos = pos == -1 ? get_count() : pos;
21364 if (m_nMRUCount)
21365 pos += (m_nMRUCount + 1);
21366 insert_separator_including_mru(pos, rId);
21369 virtual int get_count() const override
21371 int nCount = get_count_including_mru();
21372 if (m_nMRUCount)
21373 nCount -= (m_nMRUCount + 1);
21374 return nCount;
21377 virtual int find_text(const OUString& rStr) const override
21379 int nPos = find_text_including_mru(rStr, false);
21380 if (nPos != -1 && m_nMRUCount)
21381 nPos -= (m_nMRUCount + 1);
21382 return nPos;
21385 virtual int find_id(const OUString& rId) const override
21387 int nPos = find_id_including_mru(rId, false);
21388 if (nPos != -1 && m_nMRUCount)
21389 nPos -= (m_nMRUCount + 1);
21390 return nPos;
21393 virtual void clear() override
21395 do_clear();
21398 virtual void make_sorted() override
21400 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
21401 ::comphelper::getProcessComponentContext(),
21402 Application::GetSettings().GetUILanguageTag().getLocale()));
21403 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
21404 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
21405 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
21408 virtual bool has_entry() const override
21410 return gtk_combo_box_get_has_entry(m_pComboBox);
21413 virtual void set_entry_message_type(weld::EntryMessageType eType) override
21415 assert(m_pEntry);
21416 ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
21419 virtual void set_entry_text(const OUString& rText) override
21421 assert(m_pEntry);
21422 disable_notify_events();
21423 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
21424 enable_notify_events();
21427 virtual void set_entry_width_chars(int nChars) override
21429 assert(m_pEntry);
21430 disable_notify_events();
21431 gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars);
21432 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars);
21433 enable_notify_events();
21436 virtual void set_entry_max_length(int nChars) override
21438 assert(m_pEntry);
21439 disable_notify_events();
21440 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
21441 enable_notify_events();
21444 virtual void select_entry_region(int nStartPos, int nEndPos) override
21446 assert(m_pEntry);
21447 disable_notify_events();
21448 gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
21449 enable_notify_events();
21452 virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
21454 assert(m_pEntry);
21455 return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
21458 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
21460 m_bAutoComplete = bEnable;
21461 m_bAutoCompleteCaseSensitive = bCaseSensitive;
21464 virtual void set_entry_placeholder_text(const OUString& rText) override
21466 assert(m_pEntry);
21467 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
21470 virtual void set_entry_editable(bool bEditable) override
21472 assert(m_pEntry);
21473 gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
21476 virtual void cut_entry_clipboard() override
21478 assert(m_pEntry);
21479 gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
21482 virtual void copy_entry_clipboard() override
21484 assert(m_pEntry);
21485 gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
21488 virtual void paste_entry_clipboard() override
21490 assert(m_pEntry);
21491 gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
21494 virtual void set_font(const vcl::Font& rFont) override
21496 m_aCustomFont.use_custom_font(&rFont, u"box#combobox");
21499 virtual vcl::Font get_font() override
21501 if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
21502 return *pFont;
21503 return GtkInstanceWidget::get_font();
21506 virtual void set_entry_font(const vcl::Font& rFont) override
21508 m_xEntryFont = rFont;
21509 assert(m_pEntry);
21510 PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
21511 PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
21512 update_attr_list(pAttrList, rFont);
21513 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
21514 pango_attr_list_unref(pAttrList);
21517 virtual vcl::Font get_entry_font() override
21519 if (m_xEntryFont)
21520 return *m_xEntryFont;
21521 assert(m_pEntry);
21522 PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
21523 return pango_to_vcl(pango_context_get_font_description(pContext),
21524 Application::GetSettings().GetUILanguageTag().getLocale());
21527 virtual void disable_notify_events() override
21529 if (m_pEntry)
21531 g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId);
21532 g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
21533 g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId);
21534 g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId);
21535 g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId);
21536 g_signal_handler_block(m_pEntry, m_nChangedSignalId);
21538 else
21539 g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId);
21540 if (m_nToggleFocusInSignalId)
21541 g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
21542 if (m_nToggleFocusOutSignalId)
21543 g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
21544 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
21545 g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId);
21546 GtkInstanceContainer::disable_notify_events();
21549 virtual void enable_notify_events() override
21551 GtkInstanceContainer::enable_notify_events();
21552 g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId);
21553 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
21554 if (m_nToggleFocusInSignalId)
21555 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
21556 if (m_nToggleFocusOutSignalId)
21557 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
21558 if (m_pEntry)
21560 g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
21561 g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
21562 g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId);
21563 g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId);
21564 g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId);
21565 g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId);
21567 else
21568 g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId);
21571 virtual void freeze() override
21573 disable_notify_events();
21574 bool bIsFirstFreeze = IsFirstFreeze();
21575 GtkInstanceContainer::freeze();
21576 if (bIsFirstFreeze)
21578 g_object_ref(m_pTreeModel);
21579 gtk_tree_view_set_model(m_pTreeView, nullptr);
21580 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
21581 if (m_xSorter)
21583 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
21584 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
21587 enable_notify_events();
21590 virtual void thaw() override
21592 disable_notify_events();
21593 if (IsLastThaw())
21595 if (m_xSorter)
21597 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
21598 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
21600 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
21601 gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
21602 g_object_unref(m_pTreeModel);
21604 GtkInstanceContainer::thaw();
21605 enable_notify_events();
21608 virtual bool get_popup_shown() const override
21610 return m_bPopupActive;
21613 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
21615 if (!m_nToggleFocusInSignalId)
21616 m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
21617 GtkInstanceContainer::connect_focus_in(rLink);
21620 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
21622 if (!m_nToggleFocusOutSignalId)
21623 m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
21624 GtkInstanceContainer::connect_focus_out(rLink);
21627 virtual void grab_focus() override
21629 if (has_focus())
21630 return;
21631 if (m_pEntry)
21632 gtk_widget_grab_focus(m_pEntry);
21633 else
21634 gtk_widget_grab_focus(m_pToggleButton);
21637 virtual bool has_focus() const override
21639 if (m_pEntry && gtk_widget_has_focus(m_pEntry))
21640 return true;
21642 if (gtk_widget_has_focus(m_pToggleButton))
21643 return true;
21645 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
21647 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
21648 return true;
21651 return GtkInstanceWidget::has_focus();
21654 virtual bool changed_by_direct_pick() const override
21656 return m_bChangedByMenu;
21659 virtual void set_custom_renderer(bool bOn) override
21661 if (bOn == m_bCustomRenderer)
21662 return;
21663 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
21664 // keep the original height around for optimal popup height calculation
21665 m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
21666 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
21667 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
21668 if (bOn)
21670 GtkCellRenderer *pRenderer = custom_cell_renderer_new();
21671 GValue value = G_VALUE_INIT;
21672 g_value_init(&value, G_TYPE_POINTER);
21673 g_value_set_pointer(&value, static_cast<gpointer>(this));
21674 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
21675 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
21676 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
21677 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
21679 else
21681 GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
21682 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
21683 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
21685 g_list_free(pColumns);
21686 m_bCustomRenderer = bOn;
21689 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
21691 signal_custom_render(rOutput, rRect, bSelected, rId);
21694 Size call_signal_custom_get_size(VirtualDevice& rOutput)
21696 return signal_custom_get_size(rOutput);
21699 VclPtr<VirtualDevice> create_render_virtual_device() const override
21701 return create_virtual_device();
21704 virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
21706 m_xCustomMenuButtonHelper.reset();
21707 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
21708 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
21709 gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
21710 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
21711 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
21712 if (pMenuWidget)
21713 m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
21714 m_sMenuButtonRow = OUString::fromUtf8(rIdent);
21717 OUString get_mru_entries() const override
21719 const sal_Unicode cSep = ';';
21721 OUStringBuffer aEntries;
21722 for (sal_Int32 n = 0; n < m_nMRUCount; n++)
21724 aEntries.append(get_text_including_mru(n));
21725 if (n < m_nMRUCount - 1)
21726 aEntries.append(cSep);
21728 return aEntries.makeStringAndClear();
21731 virtual void set_mru_entries(const OUString& rEntries) override
21733 const sal_Unicode cSep = ';';
21735 // Remove old MRU entries
21736 for (sal_Int32 n = m_nMRUCount; n;)
21737 remove_including_mru(--n);
21739 sal_Int32 nMRUCount = 0;
21740 sal_Int32 nIndex = 0;
21743 OUString aEntry = rEntries.getToken(0, cSep, nIndex);
21744 // Accept only existing entries
21745 int nPos = find_text(aEntry);
21746 if (nPos != -1)
21748 OUString sId = get_id(nPos);
21749 insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
21750 ++nMRUCount;
21753 while (nIndex >= 0);
21755 if (nMRUCount && !m_nMRUCount)
21756 insert_separator_including_mru(nMRUCount, "separator");
21757 else if (!nMRUCount && m_nMRUCount)
21758 remove_including_mru(m_nMRUCount); // remove separator
21760 m_nMRUCount = nMRUCount;
21763 int get_menu_button_width() const override
21765 bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
21766 if (!bVisible)
21767 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
21768 gint nWidth;
21769 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
21770 if (!bVisible)
21771 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
21772 return nWidth;
21775 virtual ~GtkInstanceComboBox() override
21777 m_xCustomMenuButtonHelper.reset();
21778 do_clear();
21779 if (m_nAutoCompleteIdleId)
21780 g_source_remove(m_nAutoCompleteIdleId);
21781 if (m_pEntry)
21783 g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
21784 g_signal_handler_disconnect(m_pEntry, m_nEntryInsertTextSignalId);
21785 g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
21786 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
21787 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
21788 g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId);
21790 else
21791 g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
21792 if (m_nToggleFocusInSignalId)
21793 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
21794 if (m_nToggleFocusOutSignalId)
21795 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
21796 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
21797 g_signal_handler_disconnect(m_pToggleButton, m_nPopupShownSignalId);
21799 gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
21800 gtk_tree_view_set_model(m_pTreeView, nullptr);
21802 // restore original hierarchy in dtor so a new GtkInstanceComboBox will
21803 // result in the same layout each time
21805 DisconnectMouseEvents();
21807 g_object_ref(m_pComboBox);
21809 GtkContainer* pContainer = getContainer();
21811 gtk_container_remove(pContainer, GTK_WIDGET(m_pComboBox));
21813 replaceWidget(GTK_WIDGET(pContainer), GTK_WIDGET(m_pComboBox));
21815 g_object_unref(m_pComboBox);
21818 g_object_unref(m_pComboBuilder);
21822 #endif
21826 void custom_cell_renderer_ensure_device(CustomCellRenderer *cellsurface, gpointer user_data)
21828 if (!cellsurface->device)
21830 cellsurface->device = VclPtr<VirtualDevice>::Create();
21831 cellsurface->device->SetBackground(COL_TRANSPARENT);
21832 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
21833 // expand the point size of the desired font to the equivalent pixel size
21834 weld::SetPointFont(*cellsurface->device, pWidget->get_font());
21838 Size custom_cell_renderer_get_size(VirtualDevice& rDevice, const OUString& rCellId, gpointer user_data)
21840 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
21841 if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
21842 return pTreeView->call_signal_custom_get_size(rDevice, rCellId);
21843 else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
21844 return pComboBox->call_signal_custom_get_size(rDevice);
21845 return Size();
21848 void custom_cell_renderer_render(VirtualDevice& rDevice, const tools::Rectangle& rRect, bool bSelected, const OUString& rCellId, gpointer user_data)
21850 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
21851 if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
21852 pTreeView->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
21853 else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
21854 pComboBox->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
21857 namespace {
21859 class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
21861 private:
21862 GtkInstanceEntry* m_pEntry;
21863 GtkInstanceTreeView* m_pTreeView;
21864 #if !GTK_CHECK_VERSION(4, 0, 0)
21865 gulong m_nKeyPressSignalId;
21866 #endif
21867 gulong m_nEntryInsertTextSignalId;
21868 guint m_nAutoCompleteIdleId;
21869 bool m_bAutoCompleteCaseSensitive;
21870 bool m_bTreeChange;
21872 #if !GTK_CHECK_VERSION(4, 0, 0)
21873 bool signal_key_press(GdkEventKey* pEvent)
21875 if (GtkSalFrame::GetMouseModCode(pEvent->state)) // only with no modifiers held
21876 return false;
21878 if (pEvent->keyval == GDK_KEY_KP_Up || pEvent->keyval == GDK_KEY_Up || pEvent->keyval == GDK_KEY_KP_Page_Up || pEvent->keyval == GDK_KEY_Page_Up ||
21879 pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
21881 gboolean ret;
21882 disable_notify_events();
21883 GtkWidget* pWidget = m_pTreeView->getWidget();
21884 if (m_pTreeView->get_selected_index() == -1)
21886 m_pTreeView->set_cursor(0);
21887 m_pTreeView->select(0);
21888 m_xEntry->set_text(m_xTreeView->get_selected_text());
21890 else
21892 gtk_widget_grab_focus(pWidget);
21893 g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
21894 m_xEntry->set_text(m_xTreeView->get_selected_text());
21895 gtk_widget_grab_focus(m_pEntry->getWidget());
21897 m_xEntry->select_region(0, -1);
21898 enable_notify_events();
21899 m_bTreeChange = true;
21900 m_pEntry->fire_signal_changed();
21901 m_bTreeChange = false;
21902 return true;
21904 return false;
21907 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
21909 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
21910 return pThis->signal_key_press(pEvent);
21912 #endif
21914 static gboolean idleAutoComplete(gpointer widget)
21916 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
21917 pThis->auto_complete();
21918 return false;
21921 void auto_complete()
21923 m_nAutoCompleteIdleId = 0;
21924 OUString aStartText = get_active_text();
21925 int nStartPos, nEndPos;
21926 get_entry_selection_bounds(nStartPos, nEndPos);
21927 int nMaxSelection = std::max(nStartPos, nEndPos);
21928 if (nMaxSelection != aStartText.getLength())
21929 return;
21931 disable_notify_events();
21932 int nActive = get_active();
21933 int nStart = nActive;
21935 if (nStart == -1)
21936 nStart = 0;
21938 // Try match case sensitive from current position
21939 int nPos = m_pTreeView->starts_with(aStartText, nStart, true);
21940 if (nPos == -1 && nStart != 0)
21942 // Try match case insensitive, but from start
21943 nPos = m_pTreeView->starts_with(aStartText, 0, true);
21946 if (!m_bAutoCompleteCaseSensitive)
21948 // Try match case insensitive from current position
21949 nPos = m_pTreeView->starts_with(aStartText, nStart, false);
21950 if (nPos == -1 && nStart != 0)
21952 // Try match case insensitive, but from start
21953 nPos = m_pTreeView->starts_with(aStartText, 0, false);
21957 if (nPos == -1)
21959 // Try match case sensitive from current position
21960 nPos = m_pTreeView->starts_with(aStartText, nStart, true);
21961 if (nPos == -1 && nStart != 0)
21963 // Try match case sensitive, but from start
21964 nPos = m_pTreeView->starts_with(aStartText, 0, true);
21968 if (nPos != -1)
21970 OUString aText = get_text(nPos);
21971 if (aText != aStartText)
21972 set_active_text(aText);
21973 select_entry_region(aText.getLength(), aStartText.getLength());
21975 enable_notify_events();
21978 void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
21980 // now check for autocompletes
21981 if (m_nAutoCompleteIdleId)
21982 g_source_remove(m_nAutoCompleteIdleId);
21983 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
21986 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
21987 gint* position, gpointer widget)
21989 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
21990 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
21994 public:
21995 #if GTK_CHECK_VERSION(4, 0, 0)
21996 GtkInstanceEntryTreeView(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
21997 std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
21998 #else
21999 GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
22000 std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
22001 #endif
22002 : EntryTreeView(std::move(xEntry), std::move(xTreeView))
22003 , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
22004 , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
22005 , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
22006 , m_nAutoCompleteIdleId(0)
22007 , m_bAutoCompleteCaseSensitive(false)
22008 , m_bTreeChange(false)
22010 assert(m_pEntry);
22011 GtkWidget* pWidget = m_pEntry->getWidget();
22012 #if !GTK_CHECK_VERSION(4, 0, 0)
22013 m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
22014 #endif
22015 m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
22018 virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
22020 assert(false);
22023 virtual void make_sorted() override
22025 GtkWidget* pTreeView = m_pTreeView->getWidget();
22026 GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
22027 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
22028 gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
22031 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
22033 assert(!bEnable && "not implemented yet"); (void)bEnable;
22034 m_bAutoCompleteCaseSensitive = bCaseSensitive;
22037 virtual void set_entry_placeholder_text(const OUString& rText) override
22039 m_xEntry->set_placeholder_text(rText);
22042 virtual void set_entry_editable(bool bEditable) override
22044 m_xEntry->set_editable(bEditable);
22047 virtual void cut_entry_clipboard() override
22049 m_xEntry->cut_clipboard();
22052 virtual void copy_entry_clipboard() override
22054 m_xEntry->copy_clipboard();
22057 virtual void paste_entry_clipboard() override
22059 m_xEntry->paste_clipboard();
22062 virtual void set_font(const vcl::Font&) override
22064 assert(false && "not implemented");
22067 virtual void set_entry_font(const vcl::Font& rFont) override
22069 m_xEntry->set_font(rFont);
22072 virtual vcl::Font get_entry_font() override
22074 return m_xEntry->get_font();
22077 virtual void grab_focus() override { m_xEntry->grab_focus(); }
22079 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
22081 m_xEntry->connect_focus_in(rLink);
22084 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
22086 m_xEntry->connect_focus_out(rLink);
22089 virtual void disable_notify_events() override
22091 GtkWidget* pWidget = m_pEntry->getWidget();
22092 g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
22093 #if !GTK_CHECK_VERSION(4, 0, 0)
22094 g_signal_handler_block(pWidget, m_nKeyPressSignalId);
22095 #endif
22096 m_pTreeView->disable_notify_events();
22097 GtkInstanceContainer::disable_notify_events();
22100 virtual void enable_notify_events() override
22102 GtkWidget* pWidget = m_pEntry->getWidget();
22103 #if !GTK_CHECK_VERSION(4, 0, 0)
22104 g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
22105 #endif
22106 g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
22107 m_pTreeView->enable_notify_events();
22108 GtkInstanceContainer::disable_notify_events();
22111 virtual bool changed_by_direct_pick() const override
22113 return m_bTreeChange;
22116 virtual void set_custom_renderer(bool /*bOn*/) override
22118 assert(false && "not implemented");
22121 virtual int get_max_mru_count() const override
22123 assert(false && "not implemented");
22124 return 0;
22127 virtual void set_max_mru_count(int) override
22129 assert(false && "not implemented");
22132 virtual OUString get_mru_entries() const override
22134 assert(false && "not implemented");
22135 return OUString();
22138 virtual void set_mru_entries(const OUString&) override
22140 assert(false && "not implemented");
22143 virtual void set_item_menu(const OString&, weld::Menu*) override
22145 assert(false && "not implemented");
22148 VclPtr<VirtualDevice> create_render_virtual_device() const override
22150 return create_virtual_device();
22153 int get_menu_button_width() const override
22155 assert(false && "not implemented");
22156 return 0;
22159 virtual ~GtkInstanceEntryTreeView() override
22161 if (m_nAutoCompleteIdleId)
22162 g_source_remove(m_nAutoCompleteIdleId);
22163 GtkWidget* pWidget = m_pEntry->getWidget();
22164 #if !GTK_CHECK_VERSION(4, 0, 0)
22165 g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
22166 #endif
22167 g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
22173 namespace {
22175 class GtkInstanceExpander : public GtkInstanceWidget, public virtual weld::Expander
22177 private:
22178 GtkExpander* m_pExpander;
22179 gulong m_nSignalId;
22180 #if !GTK_CHECK_VERSION(4, 0, 0)
22181 gulong m_nButtonPressEventSignalId;
22182 gulong m_nMappedSignalId;
22183 #endif
22185 static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
22187 GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
22188 SolarMutexGuard aGuard;
22190 #if !GTK_CHECK_VERSION(4, 0, 0)
22191 if (gtk_expander_get_resize_toplevel(pExpander))
22193 GtkWidget *pToplevel = widget_get_toplevel(GTK_WIDGET(pExpander));
22195 // https://gitlab.gnome.org/GNOME/gtk/issues/70
22196 // I imagine at some point a release with a fix will be available in which
22197 // case this can be avoided depending on version number
22198 if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
22200 int nToplevelWidth, nToplevelHeight;
22201 int nChildHeight;
22203 GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
22204 gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
22205 gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
22207 if (pThis->get_expanded())
22208 nToplevelHeight += nChildHeight;
22209 else
22210 nToplevelHeight -= nChildHeight;
22212 gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
22215 #else
22216 (void)pExpander;
22217 #endif
22219 pThis->signal_expanded();
22222 #if !GTK_CHECK_VERSION(4, 0, 0)
22223 static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
22225 // don't let button press get to parent window, for the case of the
22226 // an expander in a sidebar where otherwise single click to expand
22227 // doesn't work
22228 return true;
22231 /* tdf#141186 if the expander is initially collapsed then when mapped all its
22232 children are mapped too. If they are mapped then the mnemonics of the
22233 children are taken into account on shortcuts and non-visible children in a
22234 collapsed expander can be triggered which is confusing.
22236 If the expander is expanded and collapsed the child is unmapped and the
22237 problem doesn't occur.
22239 So to avoid the problem of an initially collapsed expander, listen to
22240 the map event and if the expander is mapped but collapsed then unmap the
22241 child of the expander.
22243 This problem was seen in gtk3-3.24.33 and not with gtk4-4.6.4 so a gtk3
22244 fix only needed.
22246 static void signalMap(GtkWidget*, gpointer widget)
22248 GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
22249 if (!gtk_expander_get_expanded(pThis->m_pExpander))
22251 if (GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pThis->m_pExpander)))
22252 gtk_widget_unmap(pChild);
22255 #endif
22257 public:
22258 GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
22259 : GtkInstanceWidget(GTK_WIDGET(pExpander), pBuilder, bTakeOwnership)
22260 , m_pExpander(pExpander)
22261 , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
22262 #if !GTK_CHECK_VERSION(4, 0, 0)
22263 , m_nButtonPressEventSignalId(g_signal_connect_after(m_pExpander, "button-press-event", G_CALLBACK(signalButton), this))
22264 , m_nMappedSignalId(g_signal_connect_after(m_pExpander, "map", G_CALLBACK(signalMap), this))
22265 #endif
22269 virtual void set_label(const OUString& rText) override
22271 ::set_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)), rText);
22274 virtual OUString get_label() const override
22276 return ::get_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)));
22279 virtual bool get_expanded() const override
22281 return gtk_expander_get_expanded(m_pExpander);
22284 virtual void set_expanded(bool bExpand) override
22286 gtk_expander_set_expanded(m_pExpander, bExpand);
22289 virtual ~GtkInstanceExpander() override
22291 #if !GTK_CHECK_VERSION(4, 0, 0)
22292 g_signal_handler_disconnect(m_pExpander, m_nMappedSignalId);
22293 g_signal_handler_disconnect(m_pExpander, m_nButtonPressEventSignalId);
22294 #endif
22295 g_signal_handler_disconnect(m_pExpander, m_nSignalId);
22301 namespace {
22303 gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
22304 gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
22306 const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
22307 if (aHelpData.mbBalloonHelp) // extended tips
22309 #if !GTK_CHECK_VERSION(4, 0, 0)
22310 // by default use accessible description
22311 AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
22312 const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
22313 if (pDesc && pDesc[0])
22315 gtk_tooltip_set_text(tooltip, pDesc);
22316 return true;
22318 #endif
22320 // fallback to the mechanism which needs help installed
22321 OString sHelpId = ::get_help_id(pWidget);
22322 Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
22323 if (pHelp)
22325 OUString sHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr));
22326 if (!sHelpText.isEmpty())
22328 gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
22329 return true;
22334 const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
22335 if (pDesc && pDesc[0])
22337 gtk_tooltip_set_text(tooltip, pDesc);
22338 return true;
22341 return false;
22346 namespace {
22348 class GtkInstancePopover : public GtkInstanceContainer, public virtual weld::Popover
22350 private:
22351 #if !GTK_CHECK_VERSION(4, 0, 0)
22352 //popover cannot escape dialog under X so we might need to stick up own window instead
22353 GtkWindow* m_pMenuHack;
22354 bool m_bMenuPoppedUp;
22355 bool m_nButtonPressSeen;
22356 #endif
22357 GtkPopover* m_pPopover;
22358 gulong m_nSignalId;
22359 ImplSVEvent* m_pClosedEvent;
22361 static void signalClosed(GtkPopover*, gpointer widget)
22363 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22364 // call signal-closed async so the closed callback isn't called
22365 // while the GtkPopover handler is still in-execution
22366 pThis->launch_signal_closed();
22369 DECL_LINK(async_signal_closed, void*, void);
22371 void launch_signal_closed()
22373 if (m_pClosedEvent)
22374 Application::RemoveUserEvent(m_pClosedEvent);
22375 m_pClosedEvent = Application::PostUserEvent(LINK(this, GtkInstancePopover, async_signal_closed));
22378 #if !GTK_CHECK_VERSION(4, 0, 0)
22379 static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
22381 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22382 return pThis->key_press(pEvent);
22385 bool key_press(const GdkEventKey* pEvent)
22387 if (pEvent->keyval == GDK_KEY_Escape)
22389 popdown();
22390 return true;
22392 return false;
22395 static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
22397 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22398 pThis->m_nButtonPressSeen = true;
22399 return false;
22402 static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
22404 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22405 if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
22406 pThis->popdown();
22407 return false;
22410 bool forward_event_if_popup_under_mouse(GdkEvent* pEvent)
22412 GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
22413 GtkWidget* pTopLevel = widget_get_toplevel(pEventWidget);
22415 if (pTopLevel == GTK_WIDGET(m_pMenuHack))
22416 return false;
22418 GdkSurface* pSurface = widget_get_surface(pTopLevel);
22419 void* pMouseEnteredAnotherPopup = g_object_get_data(G_OBJECT(pSurface), "g-lo-InstancePopup");
22420 if (!pMouseEnteredAnotherPopup)
22421 return false;
22423 return gtk_widget_event(pEventWidget, pEvent);
22426 static gboolean signalButtonCrossing(GtkWidget*, GdkEvent* pEvent, gpointer widget)
22428 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22429 return pThis->forward_event_if_popup_under_mouse(pEvent);
22432 static gboolean signalMotion(GtkWidget*, GdkEvent* pEvent, gpointer widget)
22434 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22435 return pThis->forward_event_if_popup_under_mouse(pEvent);
22438 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
22440 GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
22441 pThis->grab_broken(pEvent);
22444 void grab_broken(const GdkEventGrabBroken *event)
22446 if (event->grab_window == nullptr)
22448 popdown();
22450 else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
22452 //try and regrab, so when we lose the grab to the menu of the color palette
22453 //combobox we regain it so the color palette doesn't itself disappear on next
22454 //click on the color palette combobox
22455 do_grab(GTK_WIDGET(m_pMenuHack));
22459 #endif
22461 public:
22462 GtkInstancePopover(GtkPopover* pPopover, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
22463 #if !GTK_CHECK_VERSION(4, 0, 0)
22464 : GtkInstanceContainer(GTK_CONTAINER(pPopover), pBuilder, bTakeOwnership)
22465 , m_pMenuHack(nullptr)
22466 , m_bMenuPoppedUp(false)
22467 , m_nButtonPressSeen(false)
22468 #else
22469 : GtkInstanceContainer(GTK_WIDGET(pPopover), pBuilder, bTakeOwnership)
22470 #endif
22471 , m_pPopover(pPopover)
22472 , m_nSignalId(g_signal_connect(m_pPopover, "closed", G_CALLBACK(signalClosed), this))
22473 , m_pClosedEvent(nullptr)
22475 #if !GTK_CHECK_VERSION(4, 0, 0)
22476 //under wayland a Popover will work to "escape" the parent dialog, not
22477 //so under X, so come up with this hack to use a raw GtkWindow
22478 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
22479 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
22481 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
22482 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
22483 gtk_window_set_resizable(m_pMenuHack, false);
22484 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
22485 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
22486 g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
22487 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
22488 // to emulate a modeless popover we forward the leave/enter/motion events to the widgets
22489 // they would have gone to a if we were really modeless
22490 if (!gtk_popover_get_modal(m_pPopover))
22492 g_signal_connect(m_pMenuHack, "leave-notify-event", G_CALLBACK(signalButtonCrossing), this);
22493 g_signal_connect(m_pMenuHack, "enter-notify-event", G_CALLBACK(signalButtonCrossing), this);
22494 g_signal_connect(m_pMenuHack, "motion-notify-event", G_CALLBACK(signalMotion), this);
22497 #endif
22500 virtual void popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
22502 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
22503 assert(pGtkWidget);
22505 GtkWidget* pWidget = pGtkWidget->getWidget();
22507 GdkRectangle aRect;
22508 pWidget = getPopupRect(pWidget, rRect, aRect);
22510 #if GTK_CHECK_VERSION(4, 0, 0)
22511 gtk_widget_set_parent(GTK_WIDGET(m_pPopover), pWidget);
22512 #else
22513 gtk_popover_set_relative_to(m_pPopover, pWidget);
22514 #endif
22515 gtk_popover_set_pointing_to(m_pPopover, &aRect);
22517 if (ePlace == weld::Placement::Under)
22518 gtk_popover_set_position(m_pPopover, GTK_POS_BOTTOM);
22519 else
22521 if (::SwapForRTL(pWidget))
22522 gtk_popover_set_position(m_pPopover, GTK_POS_LEFT);
22523 else
22524 gtk_popover_set_position(m_pPopover, GTK_POS_RIGHT);
22527 #if !GTK_CHECK_VERSION(4, 0, 0)
22528 //under wayland a Popover will work to "escape" the parent dialog, not
22529 //so under X, so come up with this hack to use a raw GtkWindow
22530 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
22531 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
22533 if (!m_bMenuPoppedUp)
22535 MovePopoverContentsToWindow(GTK_WIDGET(m_pPopover), m_pMenuHack, pWidget, aRect, ePlace);
22536 m_bMenuPoppedUp = true;
22538 return;
22540 #endif
22542 gtk_popover_popup(m_pPopover);
22545 #if !GTK_CHECK_VERSION(4, 0, 0)
22546 virtual bool get_visible() const override
22548 if (m_pMenuHack)
22549 return gtk_widget_get_visible(GTK_WIDGET(m_pMenuHack));
22550 return gtk_widget_get_visible(m_pWidget);
22553 virtual void ensureMouseEventWidget() override
22555 if (!m_pMouseEventBox && m_pMenuHack)
22557 m_pMouseEventBox = GTK_WIDGET(m_pMenuHack);
22558 return;
22560 GtkInstanceContainer::ensureMouseEventWidget();
22562 #endif
22564 virtual void popdown() override
22566 #if !GTK_CHECK_VERSION(4, 0, 0)
22567 //under wayland a Popover will work to "escape" the parent dialog, not
22568 //so under X, so come up with this hack to use a raw GtkWindow
22569 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
22570 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
22572 if (m_bMenuPoppedUp)
22574 m_nButtonPressSeen = false;
22575 MoveWindowContentsToPopover(m_pMenuHack, GTK_WIDGET(m_pPopover), gtk_popover_get_relative_to(m_pPopover));
22576 m_bMenuPoppedUp = false;
22577 signal_closed();
22579 return;
22581 #endif
22583 gtk_popover_popdown(m_pPopover);
22586 void PopdownAndFlushClosedSignal()
22588 if (get_visible())
22589 popdown();
22590 if (m_pClosedEvent)
22592 Application::RemoveUserEvent(m_pClosedEvent);
22593 async_signal_closed(nullptr);
22597 virtual void resize_to_request() override
22599 // resizing to request is what gtk does automatically
22602 virtual ~GtkInstancePopover() override
22604 PopdownAndFlushClosedSignal();
22605 DisconnectMouseEvents();
22606 #if !GTK_CHECK_VERSION(4, 0, 0)
22607 if (m_pMenuHack)
22608 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
22609 #endif
22610 g_signal_handler_disconnect(m_pPopover, m_nSignalId);
22614 IMPL_LINK_NOARG(GtkInstancePopover, async_signal_closed, void*, void)
22616 m_pClosedEvent = nullptr;
22617 signal_closed();
22622 #if !GTK_CHECK_VERSION(4, 0, 0)
22624 namespace
22627 AtkObject* drawing_area_get_accessible(GtkWidget *pWidget)
22629 AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
22630 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
22631 GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
22632 AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
22633 if (pAtkObj)
22634 return pAtkObj;
22635 return pDefaultAccessible;
22638 void ensure_intercept_drawing_area_accessibility()
22640 static bool bDone;
22641 if (!bDone)
22643 gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
22644 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
22645 default_drawing_area_get_accessible = pWidgetClass->get_accessible;
22646 pWidgetClass->get_accessible = drawing_area_get_accessible;
22647 g_type_class_unref(pClass);
22648 bDone = true;
22652 void ensure_disable_ctrl_page_up_down(GType eType)
22654 gpointer pClass = g_type_class_ref(eType);
22655 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
22656 GtkBindingSet* pBindingSet = gtk_binding_set_by_class(pWidgetClass);
22657 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, GDK_CONTROL_MASK);
22658 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
22659 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, GDK_CONTROL_MASK);
22660 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
22661 g_type_class_unref(pClass);
22664 // tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
22665 // keystrokes are consumed by the surrounding notebook bindings instead
22666 void ensure_disable_ctrl_page_up_down_bindings()
22668 static bool bDone;
22669 if (!bDone)
22671 ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW);
22672 ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON);
22673 bDone = true;
22678 #endif
22680 namespace {
22682 bool IsAllowedBuiltInIcon(std::u16string_view iconName)
22684 // limit the named icons to those known by VclBuilder
22685 return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
22690 namespace {
22692 void load_ui_file(GtkBuilder* pBuilder, const OUString& rUri)
22694 #if GTK_CHECK_VERSION(4, 0, 0)
22695 builder_add_from_gtk3_file(pBuilder, rUri);
22696 #else
22697 OUString aPath;
22698 osl::FileBase::getSystemPathFromFileURL(rUri, aPath);
22699 GError *err = nullptr;
22700 auto rc = gtk_builder_add_from_file(pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), &err);
22702 if (!rc)
22704 SAL_WARN( "vcl.gtk", "GtkInstanceBuilder: error when calling gtk_builder_add_from_file: " << err->message);
22705 g_error_free(err);
22707 assert(rc && "could not load UI file");
22708 #endif
22711 class GtkInstanceBuilder : public weld::Builder
22713 private:
22714 ResHookProc m_pStringReplace;
22715 OString m_aUtf8HelpRoot;
22716 OUString m_aIconTheme;
22717 OUString m_aUILang;
22718 GtkBuilder* m_pBuilder;
22719 GSList* m_pObjectList;
22720 GtkWidget* m_pParentWidget;
22721 gulong m_nNotifySignalId;
22722 std::vector<GtkButton*> m_aMnemonicButtons;
22723 #if GTK_CHECK_VERSION(4, 0, 0)
22724 std::vector<GtkCheckButton*> m_aMnemonicCheckButtons;
22725 #endif
22726 std::vector<GtkLabel*> m_aMnemonicLabels;
22728 VclPtr<SystemChildWindow> m_xInterimGlue;
22729 bool m_bAllowCycleFocusOut;
22731 void postprocess_widget(GtkWidget* pWidget)
22733 const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
22734 officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
22736 //fixup icons
22737 //wanted: better way to do this, e.g. make gtk use gio for
22738 //loading from a filename and provide gio protocol handler
22739 //for our image in a zip urls
22741 //unpack the images and keep them as dirs and just
22742 //add the paths to the gtk icon theme dir
22743 if (GTK_IS_IMAGE(pWidget))
22745 GtkImage* pImage = GTK_IMAGE(pWidget);
22746 if (const gchar* icon_name = image_get_icon_name(pImage))
22748 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
22749 if (!IsAllowedBuiltInIcon(aIconName))
22751 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
22753 gtk_image_set_from_pixbuf(pImage, pixbuf);
22754 g_object_unref(pixbuf);
22759 #if GTK_CHECK_VERSION(4, 0, 0)
22760 else if (GTK_IS_PICTURE(pWidget))
22762 GtkPicture* pPicture = GTK_PICTURE(pWidget);
22763 if (GFile* icon_file = gtk_picture_get_file(pPicture))
22765 char* icon_name = g_file_get_uri(icon_file);
22766 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
22767 g_free(icon_name);
22768 assert(aIconName.startsWith("private:///graphicrepository/"));
22769 aIconName.startsWith("private:///graphicrepository/", &aIconName);
22770 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
22772 gtk_picture_set_pixbuf(GTK_PICTURE(pWidget), pixbuf);
22773 g_object_unref(pixbuf);
22777 #endif
22778 #if !GTK_CHECK_VERSION(4, 0, 0)
22779 else if (GTK_IS_TOOL_BUTTON(pWidget))
22781 GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
22782 if (const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton))
22784 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
22785 if (!IsAllowedBuiltInIcon(aIconName))
22787 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
22789 GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
22790 g_object_unref(pixbuf);
22791 gtk_tool_button_set_icon_widget(pToolButton, pImage);
22792 gtk_widget_show(pImage);
22797 // if no tooltip reuse the label as default tooltip
22798 if (!gtk_widget_get_tooltip_text(pWidget))
22800 if (const gchar* label = gtk_tool_button_get_label(pToolButton))
22801 gtk_widget_set_tooltip_text(pWidget, label);
22804 #else
22805 else if (GTK_IS_BUTTON(pWidget))
22807 GtkButton* pButton = GTK_BUTTON(pWidget);
22808 if (const gchar* icon_name = gtk_button_get_icon_name(pButton))
22810 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
22811 if (!IsAllowedBuiltInIcon(aIconName))
22813 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
22815 GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
22816 gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
22817 gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
22818 g_object_unref(pixbuf);
22819 gtk_button_set_child(pButton, pImage);
22820 gtk_widget_show(pImage);
22825 else if (GTK_IS_MENU_BUTTON(pWidget))
22827 GtkMenuButton* pButton = GTK_MENU_BUTTON(pWidget);
22828 if (const gchar* icon_name = gtk_menu_button_get_icon_name(pButton))
22830 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
22831 if (!IsAllowedBuiltInIcon(aIconName))
22833 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
22835 GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
22836 gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
22837 gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
22838 g_object_unref(pixbuf);
22839 // TODO after gtk 4.6 is released require that version and drop this
22840 static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
22841 if (menu_button_set_child)
22842 menu_button_set_child(pButton, pImage);
22843 gtk_widget_show(pImage);
22848 #endif
22850 //set helpids
22851 OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
22852 if (!sBuildableName.isEmpty())
22854 OString sHelpId = m_aUtf8HelpRoot + sBuildableName;
22855 set_help_id(pWidget, sHelpId);
22856 //hook up for extended help
22857 const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
22858 if (aHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
22860 gtk_widget_set_has_tooltip(pWidget, true);
22861 g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
22864 if (bHideHelp && sBuildableName == "help")
22865 gtk_widget_hide(pWidget);
22868 if (m_pStringReplace)
22870 // tdf#136498 %PRODUCTNAME shown in tool tips
22871 const char* pTooltip = gtk_widget_get_tooltip_text(pWidget);
22872 if (pTooltip && pTooltip[0])
22874 OUString aTooltip(pTooltip, strlen(pTooltip), RTL_TEXTENCODING_UTF8);
22875 aTooltip = (*m_pStringReplace)(aTooltip);
22876 gtk_widget_set_tooltip_text(pWidget, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
22880 // expand placeholder and collect potentially missing mnemonics
22881 if (GTK_IS_BUTTON(pWidget))
22883 GtkButton* pButton = GTK_BUTTON(pWidget);
22884 if (m_pStringReplace)
22886 OUString aLabel(button_get_label(pButton));
22887 if (!aLabel.isEmpty())
22888 button_set_label(pButton, (*m_pStringReplace)(aLabel));
22890 if (gtk_button_get_use_underline(pButton))
22891 m_aMnemonicButtons.push_back(pButton);
22893 #if GTK_CHECK_VERSION(4, 0, 0)
22894 else if (GTK_IS_CHECK_BUTTON(pWidget))
22896 GtkCheckButton* pButton = GTK_CHECK_BUTTON(pWidget);
22897 if (m_pStringReplace)
22899 OUString aLabel(get_label(pButton));
22900 if (!aLabel.isEmpty())
22901 set_label(pButton, (*m_pStringReplace)(aLabel));
22903 if (gtk_check_button_get_use_underline(pButton))
22904 m_aMnemonicCheckButtons.push_back(pButton);
22906 #endif
22907 else if (GTK_IS_LABEL(pWidget))
22909 GtkLabel* pLabel = GTK_LABEL(pWidget);
22910 if (m_pStringReplace)
22912 OUString aLabel(get_label(pLabel));
22913 if (!aLabel.isEmpty())
22914 set_label(pLabel, (*m_pStringReplace)(aLabel));
22916 if (gtk_label_get_use_underline(pLabel))
22917 m_aMnemonicLabels.push_back(pLabel);
22919 else if (GTK_IS_TEXT_VIEW(pWidget))
22921 GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
22922 if (m_pStringReplace)
22924 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
22925 GtkTextIter start, end;
22926 gtk_text_buffer_get_bounds(pBuffer, &start, &end);
22927 char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
22928 int nTextLen = pTextStr ? strlen(pTextStr) : 0;
22929 if (nTextLen)
22931 OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
22932 OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
22933 gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
22935 g_free(pTextStr);
22938 #if !GTK_CHECK_VERSION(4, 0, 0)
22939 else if (GTK_IS_ENTRY(pWidget))
22941 g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalEntryInsertSpecialCharKeyPress), nullptr);
22943 #endif
22944 else if (GTK_IS_WINDOW(pWidget))
22946 if (m_pStringReplace)
22948 GtkWindow* pWindow = GTK_WINDOW(pWidget);
22949 set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
22950 if (GTK_IS_MESSAGE_DIALOG(pWindow))
22952 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
22953 set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
22954 set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
22960 //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
22961 //In order for GtkBuilder to find the translations bindtextdomain has to be called
22962 //for the domain. So here on the first setting of "domain" we call Translate::Create
22963 //to make sure that happens. Without this, if some other part of LibreOffice has
22964 //used the translation machinery for this domain it will still work, but if it
22965 //hasn't, e.g. tdf#119929, then the translation fails
22966 void translation_domain_set()
22968 Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
22969 g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
22972 static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
22974 g_return_if_fail(pSpec != nullptr);
22975 if (strcmp(pSpec->name, "translation-domain") == 0)
22977 GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
22978 pBuilder->translation_domain_set();
22982 static void postprocess(gpointer data, gpointer user_data)
22984 GObject* pObject = static_cast<GObject*>(data);
22985 if (!GTK_IS_WIDGET(pObject))
22986 return;
22987 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
22988 pThis->postprocess_widget(GTK_WIDGET(pObject));
22991 void DisallowCycleFocusOut()
22993 assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
22995 GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
22996 assert(pTopLevel);
22997 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
22998 assert(pFrame);
22999 // unhook handler and let gtk cycle its own way through this widget's
23000 // children because it has no non-gtk siblings
23001 pFrame->DisallowCycleFocusOut();
23004 static void signalMap(GtkWidget*, gpointer user_data)
23006 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
23007 // tdf#138047 wait until map to do this because the final SalFrame may
23008 // not be the same as at ctor time
23009 pThis->DisallowCycleFocusOut();
23012 void AllowCycleFocusOut()
23014 assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
23016 GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
23017 assert(pTopLevel);
23018 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
23019 assert(pFrame);
23020 // rehook handler and let vcl cycle its own way through this widget's
23021 // children
23022 pFrame->AllowCycleFocusOut();
23024 // tdf#145567 if the focus is in this hierarchy then, now that we are tearing down,
23025 // move focus to the usual focus candidate for the frame
23026 GtkWindow* pFocusWin = get_active_window();
23027 GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
23028 bool bHasFocus = pFocus && gtk_widget_is_ancestor(pFocus, pTopLevel);
23029 if (bHasFocus)
23030 pFrame->GrabFocus();
23033 static void signalUnmap(GtkWidget*, gpointer user_data)
23035 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
23036 pThis->AllowCycleFocusOut();
23039 public:
23040 GtkInstanceBuilder(GtkWidget* pParent, std::u16string_view rUIRoot, const OUString& rUIFile,
23041 SystemChildWindow* pInterimGlue, bool bAllowCycleFocusOut)
23042 : weld::Builder()
23043 , m_pStringReplace(Translate::GetReadStringHook())
23044 , m_pParentWidget(pParent)
23045 , m_nNotifySignalId(0)
23046 , m_xInterimGlue(pInterimGlue)
23047 , m_bAllowCycleFocusOut(bAllowCycleFocusOut)
23049 OUString sHelpRoot(rUIFile);
23050 #if !GTK_CHECK_VERSION(4, 0, 0)
23051 ensure_intercept_drawing_area_accessibility();
23052 ensure_disable_ctrl_page_up_down_bindings();
23053 #endif
23055 sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
23056 if (nIdx != -1)
23057 sHelpRoot = sHelpRoot.copy(0, nIdx);
23058 sHelpRoot += "/";
23059 m_aUtf8HelpRoot = OUStringToOString(sHelpRoot, RTL_TEXTENCODING_UTF8);
23060 m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
23061 m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
23063 OUString aUri(rUIRoot + rUIFile);
23065 m_pBuilder = gtk_builder_new();
23066 m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
23068 load_ui_file(m_pBuilder, aUri);
23070 m_pObjectList = gtk_builder_get_objects(m_pBuilder);
23071 g_slist_foreach(m_pObjectList, postprocess, this);
23073 GenerateMissingMnemonics();
23075 if (m_xInterimGlue)
23077 assert(m_pParentWidget);
23078 g_object_set_data(G_OBJECT(m_pParentWidget), "InterimWindowGlue", m_xInterimGlue.get());
23080 if (!m_bAllowCycleFocusOut)
23082 g_signal_connect(G_OBJECT(m_pParentWidget), "map", G_CALLBACK(signalMap), this);
23083 g_signal_connect(G_OBJECT(m_pParentWidget), "unmap", G_CALLBACK(signalUnmap), this);
23088 void GenerateMissingMnemonics()
23090 MnemonicGenerator aMnemonicGenerator('_');
23091 for (const auto a : m_aMnemonicButtons)
23092 aMnemonicGenerator.RegisterMnemonic(button_get_label(a));
23093 #if GTK_CHECK_VERSION(4, 0, 0)
23094 for (const auto a : m_aMnemonicCheckButtons)
23095 aMnemonicGenerator.RegisterMnemonic(get_label(a));
23096 #endif
23097 for (const auto a : m_aMnemonicLabels)
23098 aMnemonicGenerator.RegisterMnemonic(get_label(a));
23100 for (const auto a : m_aMnemonicButtons)
23102 OUString aLabel(button_get_label(a));
23103 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
23104 if (aLabel == aNewLabel)
23105 continue;
23106 button_set_label(a, aNewLabel);
23108 #if GTK_CHECK_VERSION(4, 0, 0)
23109 for (const auto a : m_aMnemonicCheckButtons)
23111 OUString aLabel(get_label(a));
23112 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
23113 if (aLabel == aNewLabel)
23114 continue;
23115 set_label(a, aNewLabel);
23117 #endif
23118 for (const auto a : m_aMnemonicLabels)
23120 OUString aLabel(get_label(a));
23121 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
23122 if (aLabel == aNewLabel)
23123 continue;
23124 set_label(a, aNewLabel);
23127 m_aMnemonicLabels.clear();
23128 #if GTK_CHECK_VERSION(4, 0, 0)
23129 m_aMnemonicCheckButtons.clear();
23130 #endif
23131 m_aMnemonicButtons.clear();
23134 OString get_current_page_help_id()
23136 OString sPageHelpId;
23137 // check to see if there is a notebook called tabcontrol and get the
23138 // helpid for the current page of that
23139 std::unique_ptr<weld::Notebook> xNotebook(weld_notebook("tabcontrol"));
23140 if (xNotebook)
23142 if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
23144 GtkWidget* pContainer = pPage->getWidget();
23145 if (GtkWidget* pPageWidget = widget_get_first_child(pContainer))
23146 sPageHelpId = ::get_help_id(pPageWidget);
23149 return sPageHelpId;
23152 virtual ~GtkInstanceBuilder() override
23154 g_slist_free(m_pObjectList);
23155 g_object_unref(m_pBuilder);
23157 if (m_xInterimGlue && !m_bAllowCycleFocusOut)
23158 AllowCycleFocusOut();
23160 m_xInterimGlue.disposeAndClear();
23163 //ideally we would have/use weld::Container add and explicitly
23164 //call add when we want to do this, but in the vcl impl the
23165 //parent has to be set when the child is created, so for the
23166 //gtk impl emulate this by doing this implicitly at weld time
23167 void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
23169 if (GTK_IS_POPOVER(pWidget))
23170 return;
23171 if (GTK_IS_WINDOW(pWidget))
23172 return;
23173 #if GTK_CHECK_VERSION(4, 0, 0)
23174 if (!gtk_widget_get_parent(pWidget))
23175 gtk_widget_set_parent(pWidget, m_pParentWidget);
23176 #else
23177 if (widget_get_toplevel(pWidget) == pWidget)
23178 gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
23179 #endif
23182 virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id) override
23184 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
23185 if (!pMessageDialog)
23186 return nullptr;
23187 gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
23188 return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, true);
23191 virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id) override
23193 GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, id.getStr()));
23194 if (!pAssistant)
23195 return nullptr;
23196 if (m_pParentWidget)
23197 gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
23198 return std::make_unique<GtkInstanceAssistant>(pAssistant, this, true);
23201 virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id) override
23203 GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
23204 if (!pDialog)
23205 return nullptr;
23206 if (m_pParentWidget)
23207 gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
23208 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
23211 virtual std::unique_ptr<weld::Window> create_screenshot_window() override
23213 GtkWidget* pTopLevel = nullptr;
23215 for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
23217 GObject* pObj = static_cast<GObject*>(l->data);
23219 if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
23220 continue;
23222 if (!pTopLevel)
23223 pTopLevel = GTK_WIDGET(pObj);
23224 else if (GTK_IS_WINDOW(pObj))
23225 pTopLevel = GTK_WIDGET(pObj);
23228 if (!pTopLevel)
23229 return nullptr;
23231 GtkWindow* pDialog;
23232 if (GTK_IS_WINDOW(pTopLevel))
23233 pDialog = GTK_WINDOW(pTopLevel);
23234 else
23236 pDialog = GTK_WINDOW(gtk_dialog_new());
23237 ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
23239 GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
23240 #if !GTK_CHECK_VERSION(4, 0, 0)
23241 gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
23242 gtk_widget_show_all(pTopLevel);
23243 #else
23244 gtk_box_append(GTK_BOX(pContentArea), pTopLevel);
23245 gtk_widget_show(pTopLevel);
23246 #endif
23249 if (m_pParentWidget)
23250 gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
23251 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
23254 virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id) override
23256 GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
23257 if (!pWidget)
23258 return nullptr;
23259 auto_add_parentless_widgets_to_container(pWidget);
23260 return std::make_unique<GtkInstanceWidget>(pWidget, this, false);
23263 virtual std::unique_ptr<weld::Container> weld_container(const OString &id) override
23265 #if !GTK_CHECK_VERSION(4, 0, 0)
23266 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
23267 #else
23268 GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
23269 #endif
23270 if (!pContainer)
23271 return nullptr;
23272 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
23273 return std::make_unique<GtkInstanceContainer>(pContainer, this, false);
23276 virtual std::unique_ptr<weld::Box> weld_box(const OString &id) override
23278 GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
23279 if (!pBox)
23280 return nullptr;
23281 auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
23282 return std::make_unique<GtkInstanceBox>(pBox, this, false);
23285 virtual std::unique_ptr<weld::Paned> weld_paned(const OString &id) override
23287 GtkPaned* pPaned = GTK_PANED(gtk_builder_get_object(m_pBuilder, id.getStr()));
23288 if (!pPaned)
23289 return nullptr;
23290 auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned));
23291 return std::make_unique<GtkInstancePaned>(pPaned, this, false);
23294 virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id) override
23296 GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, id.getStr()));
23297 if (!pFrame)
23298 return nullptr;
23299 auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
23300 return std::make_unique<GtkInstanceFrame>(pFrame, this, false);
23303 virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bUserManagedScrolling = false) override
23305 GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
23306 if (!pScrolledWindow)
23307 return nullptr;
23308 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
23309 return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, false, bUserManagedScrolling);
23312 virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id) override
23314 GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, id.getStr()));
23315 if (!pNotebook)
23316 return nullptr;
23317 auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
23318 return std::make_unique<GtkInstanceNotebook>(pNotebook, this, false);
23321 virtual std::unique_ptr<weld::Button> weld_button(const OString &id) override
23323 GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23324 if (!pButton)
23325 return nullptr;
23326 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
23327 return std::make_unique<GtkInstanceButton>(pButton, this, false);
23330 virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id) override
23332 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23333 if (!pButton)
23334 return nullptr;
23335 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
23336 return std::make_unique<GtkInstanceMenuButton>(pButton, nullptr, this, false);
23339 virtual std::unique_ptr<weld::MenuToggleButton> weld_menu_toggle_button(const OString &id) override
23341 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23342 if (!pButton)
23343 return nullptr;
23344 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
23345 // gtk doesn't come with exactly the same concept
23346 GtkBuilder* pMenuToggleButton = makeMenuToggleButtonBuilder();
23347 return std::make_unique<GtkInstanceMenuToggleButton>(pMenuToggleButton, pButton, this, false);
23350 virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id) override
23352 GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23353 if (!pButton)
23354 return nullptr;
23355 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
23356 return std::make_unique<GtkInstanceLinkButton>(pButton, this, false);
23359 virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id) override
23361 GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23362 if (!pToggleButton)
23363 return nullptr;
23364 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
23365 return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, false);
23368 virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id) override
23370 #if GTK_CHECK_VERSION(4, 0, 0)
23371 GtkCheckButton* pRadioButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23372 #else
23373 GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23374 #endif
23375 if (!pRadioButton)
23376 return nullptr;
23377 auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
23378 return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, false);
23381 virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id) override
23383 GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23384 if (!pCheckButton)
23385 return nullptr;
23386 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
23387 return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, false);
23390 virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id) override
23392 GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, id.getStr()));
23393 if (!pScale)
23394 return nullptr;
23395 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
23396 return std::make_unique<GtkInstanceScale>(pScale, this, false);
23399 virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id) override
23401 GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
23402 if (!pProgressBar)
23403 return nullptr;
23404 auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
23405 return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, false);
23408 virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id) override
23410 GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
23411 if (!pSpinner)
23412 return nullptr;
23413 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
23414 return std::make_unique<GtkInstanceSpinner>(pSpinner, this, false);
23417 virtual std::unique_ptr<weld::Image> weld_image(const OString &id) override
23419 GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
23420 if (!pWidget)
23421 return nullptr;
23422 if (GTK_IS_IMAGE(pWidget))
23424 auto_add_parentless_widgets_to_container(pWidget);
23425 return std::make_unique<GtkInstanceImage>(GTK_IMAGE(pWidget), this, false);
23427 #if GTK_CHECK_VERSION(4, 0, 0)
23428 if (GTK_IS_PICTURE(pWidget))
23430 auto_add_parentless_widgets_to_container(pWidget);
23431 return std::make_unique<GtkInstancePicture>(GTK_PICTURE(pWidget), this, false);
23433 #endif
23434 return nullptr;
23437 virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id) override
23439 GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
23440 if (!pCalendar)
23441 return nullptr;
23442 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
23443 return std::make_unique<GtkInstanceCalendar>(pCalendar, this, false);
23446 virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id) override
23448 GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, id.getStr()));
23449 if (!pEntry)
23450 return nullptr;
23451 auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
23452 return std::make_unique<GtkInstanceEntry>(pEntry, this, false);
23455 virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id) override
23457 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23458 if (!pSpinButton)
23459 return nullptr;
23460 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
23461 return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, false);
23464 virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit) override
23466 return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id), eUnit);
23469 virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id) override
23471 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
23472 if (!pSpinButton)
23473 return nullptr;
23474 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
23475 return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, false);
23478 virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id) override
23480 GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
23481 if (!pComboBox)
23482 return nullptr;
23483 auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
23485 #if GTK_CHECK_VERSION(4, 0, 0)
23486 return std::make_unique<GtkInstanceComboBox>(pComboBox, this, false);
23487 #else
23488 /* we replace GtkComboBox because of difficulties with too tall menus
23490 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
23491 has_entry long menus take forever to appear (tdf#125388)
23493 on measuring each row, the GtkComboBox GtkTreeMenu will call
23494 its area_apply_attributes_cb function on the row, but that calls
23495 gtk_tree_menu_get_path_item which then loops through each child of the
23496 menu looking for the widget of the row, so performance drops to useless.
23498 All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
23499 with fragile hackery which assumes that the unwanted callback is the only one with a
23501 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
23502 when a super tall combobox menu is activated, and the selected
23503 entry is sufficiently far down the list, then the menu doesn't
23504 appear under wayland
23506 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
23507 no typeahead support
23509 4) we want to be able to control the width of the button, but have a drop down menu which
23510 is not limited to the width of the button
23512 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
23513 super tall menu doesn't appear under X sometimes
23515 GtkBuilder* pComboBuilder = makeComboBoxBuilder();
23516 return std::make_unique<GtkInstanceComboBox>(pComboBuilder, pComboBox, this, false);
23517 #endif
23520 virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id) override
23522 GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
23523 if (!pTreeView)
23524 return nullptr;
23525 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
23526 return std::make_unique<GtkInstanceTreeView>(pTreeView, this, false);
23529 virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id) override
23531 GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
23532 if (!pIconView)
23533 return nullptr;
23534 auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
23535 return std::make_unique<GtkInstanceIconView>(pIconView, this, false);
23538 virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid) override
23540 #if GTK_CHECK_VERSION(4, 0, 0)
23541 GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
23542 #else
23543 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
23544 #endif
23545 if (!pContainer)
23546 return nullptr;
23547 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
23548 return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, false,
23549 weld_entry(entryid),
23550 weld_tree_view(treeviewid));
23553 virtual std::unique_ptr<weld::Label> weld_label(const OString &id) override
23555 GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, id.getStr()));
23556 if (!pLabel)
23557 return nullptr;
23558 auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
23559 return std::make_unique<GtkInstanceLabel>(pLabel, this, false);
23562 virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id) override
23564 GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
23565 if (!pTextView)
23566 return nullptr;
23567 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
23568 return std::make_unique<GtkInstanceTextView>(pTextView, this, false);
23571 virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id) override
23573 GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, id.getStr()));
23574 if (!pExpander)
23575 return nullptr;
23576 auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
23577 return std::make_unique<GtkInstanceExpander>(pExpander, this, false);
23580 virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
23581 FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/) override
23583 GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, id.getStr()));
23584 if (!pDrawingArea)
23585 return nullptr;
23586 auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
23587 return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, false);
23590 virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id) override
23592 #if GTK_CHECK_VERSION(4, 0, 0)
23593 GtkPopoverMenu* pMenu = GTK_POPOVER_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
23594 #else
23595 GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
23596 #endif
23597 if (!pMenu)
23598 return nullptr;
23599 return std::make_unique<GtkInstanceMenu>(pMenu, true);
23602 virtual std::unique_ptr<weld::Popover> weld_popover(const OString &id) override
23604 GtkPopover* pPopover = GTK_POPOVER(gtk_builder_get_object(m_pBuilder, id.getStr()));
23605 if (!pPopover)
23606 return nullptr;
23607 #if GTK_CHECK_VERSION(4, 0, 0)
23608 return std::make_unique<GtkInstancePopover>(pPopover, this, false);
23609 #else
23610 return std::make_unique<GtkInstancePopover>(pPopover, this, true);
23611 #endif
23614 virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id) override
23616 #if GTK_CHECK_VERSION(4, 0, 0)
23617 GtkBox* pToolbar = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
23618 #else
23619 GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
23620 #endif
23621 if (!pToolbar)
23622 return nullptr;
23623 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
23624 return std::make_unique<GtkInstanceToolbar>(pToolbar, this, false);
23627 virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
23629 return std::make_unique<GtkInstanceSizeGroup>();
23635 void GtkInstanceWindow::help()
23637 //show help for widget with keyboard focus
23638 GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
23639 if (!pWidget)
23640 pWidget = GTK_WIDGET(m_pWindow);
23641 OString sHelpId = ::get_help_id(pWidget);
23642 while (sHelpId.isEmpty())
23644 pWidget = gtk_widget_get_parent(pWidget);
23645 if (!pWidget)
23646 break;
23647 sHelpId = ::get_help_id(pWidget);
23649 std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
23650 weld::Widget* pSource = xTemp ? xTemp.get() : this;
23651 bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
23652 Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
23653 if (!pHelp)
23654 return;
23656 #if !GTK_CHECK_VERSION(4, 0, 0)
23657 // tdf#126007, there's a nice fallback route for offline help where
23658 // the current page of a notebook will get checked when the help
23659 // button is pressed and there was no help for the dialog found.
23661 // But for online help that route doesn't get taken, so bodge this here
23662 // by using the page help id if available and if the help button itself
23663 // was the original id
23664 if (m_pBuilder && sHelpId.endsWith("/help"))
23666 OString sPageId = m_pBuilder->get_current_page_help_id();
23667 if (!sPageId.isEmpty())
23668 sHelpId = sPageId;
23669 else
23671 // tdf#129068 likewise the help for the wrapping dialog is less
23672 // helpful than the help for the content area could be
23673 GtkContainer* pContainer = nullptr;
23674 if (GTK_IS_DIALOG(m_pWindow))
23675 pContainer = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow)));
23676 else if (GTK_IS_ASSISTANT(m_pWindow))
23678 GtkAssistant* pAssistant = GTK_ASSISTANT(m_pWindow);
23679 pContainer = GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant, gtk_assistant_get_current_page(pAssistant)));
23681 if (pContainer)
23683 GtkWidget* pContentWidget = widget_get_first_child(GTK_WIDGET(pContainer));
23684 if (pContentWidget)
23685 sHelpId = ::get_help_id(pContentWidget);
23689 #endif
23690 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
23693 //iterate upwards through the hierarchy from this widgets through its parents
23694 //calling func with their helpid until func returns true or we run out of parents
23695 void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
23697 GtkWidget* pParent = m_pWidget;
23698 while ((pParent = gtk_widget_get_parent(pParent)))
23700 if (func(::get_help_id(pParent)))
23701 return;
23705 std::unique_ptr<weld::Builder> GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
23707 GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
23708 GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
23709 return std::make_unique<GtkInstanceBuilder>(pBuilderParent, rUIRoot, rUIFile, nullptr, true);
23712 #if !GTK_CHECK_VERSION(4, 0, 0)
23713 // tdf#135965 for the case of native widgets inside a GtkSalFrame and F1 pressed, run help
23714 // on gtk widget help ids until we hit a vcl parent and then use vcl window help ids
23715 gboolean GtkSalFrame::NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer pFrame)
23717 Help* pHelp = Application::GetHelp();
23718 if (!pHelp)
23719 return true;
23721 GtkWindow* pWindow = static_cast<GtkWindow*>(pFrame);
23723 vcl::Window* pChildWindow = nullptr;
23725 //show help for widget with keyboard focus
23726 GtkWidget* pWidget = gtk_window_get_focus(pWindow);
23727 if (!pWidget)
23728 pWidget = GTK_WIDGET(pWindow);
23729 OString sHelpId = ::get_help_id(pWidget);
23730 while (sHelpId.isEmpty())
23732 pWidget = gtk_widget_get_parent(pWidget);
23733 if (!pWidget)
23734 break;
23735 pChildWindow = static_cast<vcl::Window*>(g_object_get_data(G_OBJECT(pWidget), "InterimWindowGlue"));
23736 if (pChildWindow)
23738 sHelpId = pChildWindow->GetHelpId();
23739 break;
23741 sHelpId = ::get_help_id(pWidget);
23744 if (pChildWindow)
23746 while (sHelpId.isEmpty())
23748 pChildWindow = pChildWindow->GetParent();
23749 if (!pChildWindow)
23750 break;
23751 sHelpId = pChildWindow->GetHelpId();
23753 if (!pChildWindow)
23754 return true;
23755 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pChildWindow);
23756 return true;
23759 if (!pWidget)
23760 return true;
23761 std::unique_ptr<weld::Widget> xTemp(new GtkInstanceWidget(pWidget, nullptr, false));
23762 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), xTemp.get());
23763 return true;
23765 #endif
23767 std::unique_ptr<weld::Builder> GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
23768 bool bAllowCycleFocusOut, sal_uInt64)
23770 // Create a foreign window which we know is a GtkGrid and make the native widgets a child of that, so we can
23771 // support GtkWidgets within a vcl::Window
23772 SystemWindowData winData = {};
23773 winData.bClipUsingNativeWidget = true;
23774 auto xEmbedWindow = VclPtr<SystemChildWindow>::Create(pParent, 0, &winData, false);
23775 xEmbedWindow->Show(true, ShowFlags::NoActivate);
23776 xEmbedWindow->set_expand(true);
23778 const SystemEnvData* pEnvData = xEmbedWindow->GetSystemData();
23779 if (!pEnvData)
23780 return nullptr;
23782 GtkWidget *pWindow = static_cast<GtkWidget*>(pEnvData->pWidget);
23783 #if !GTK_CHECK_VERSION(4, 0, 0)
23784 gtk_widget_show_all(pWindow);
23785 #else
23786 gtk_widget_show(pWindow);
23787 #endif
23789 // build the widget tree as a child of the GtkEventBox GtkGrid parent
23790 return std::make_unique<GtkInstanceBuilder>(pWindow, rUIRoot, rUIFile, xEmbedWindow.get(), bAllowCycleFocusOut);
23793 weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
23795 GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
23796 GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
23797 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
23798 VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
23799 OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
23800 return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
23803 weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
23805 if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
23806 return pGtkXWindow->getFrameWeld();
23807 return SalInstance::GetFrameWeld(rWindow);
23810 weld::Window* GtkSalFrame::GetFrameWeld() const
23812 if (!m_xFrameWeld)
23813 m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(widget_get_toplevel(getWindow())), nullptr, false));
23814 return m_xFrameWeld.get();
23817 void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
23819 #if ENABLE_GSTREAMER_1_0
23820 auto aSymbol = gstElementFactoryNameSymbol();
23821 if (!aSymbol)
23822 return nullptr;
23824 const SystemEnvData* pEnvData = pWindow->GetSystemData();
23825 if (!pEnvData)
23826 return nullptr;
23828 GstElement* pVideosink = aSymbol("gtksink", "gtksink");
23829 if (!pVideosink)
23830 return nullptr;
23832 GtkWidget *pGstWidget;
23833 g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
23834 gtk_widget_set_vexpand(pGstWidget, true);
23835 gtk_widget_set_hexpand(pGstWidget, true);
23837 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
23838 #if !GTK_CHECK_VERSION(4, 0, 0)
23839 gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
23840 #endif
23841 g_object_unref(pGstWidget);
23842 #if !GTK_CHECK_VERSION(4, 0, 0)
23843 gtk_widget_show_all(pParent);
23844 #else
23845 gtk_widget_show(pParent);
23846 #endif
23848 return pVideosink;
23849 #else
23850 (void)pWindow;
23851 return nullptr;
23852 #endif
23855 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */