1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #include <sal/config.h>
11 
12 #include <deque>
13 #include <stack>
14 #include <string.h>
15 #include <osl/process.h>
16 #include <unx/gtk/gtkdata.hxx>
17 #include <unx/gtk/gtkinst.hxx>
18 #include <unx/salobj.h>
19 #include <unx/gtk/gtkgdi.hxx>
20 #include <unx/gtk/gtkframe.hxx>
21 #include <unx/gtk/gtkobject.hxx>
22 #include <unx/gtk/atkbridge.hxx>
23 #include <unx/gtk/gtkprn.hxx>
24 #include <unx/gtk/gtksalmenu.hxx>
25 #include <headless/svpvd.hxx>
26 #include <headless/svpbmp.hxx>
27 #include <vcl/inputtypes.hxx>
28 #include <unx/genpspgraphics.h>
29 #include <rtl/strbuf.hxx>
30 #include <sal/log.hxx>
31 #include <rtl/uri.hxx>
32 
33 #include <vcl/settings.hxx>
34 
35 #include <dlfcn.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38 
39 #include <unx/gtk/gtkprintwrapper.hxx>
40 
41 #include "a11y/atkwrapper.hxx"
42 #include <com/sun/star/lang/IllegalArgumentException.hpp>
43 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
44 #include <com/sun/star/lang/XServiceInfo.hpp>
45 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
46 #include <com/sun/star/lang/XInitialization.hpp>
47 #include <com/sun/star/datatransfer/XTransferable.hpp>
48 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
49 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
50 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
51 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
52 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
53 #include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
54 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
55 #include <comphelper/lok.hxx>
56 #include <comphelper/processfactory.hxx>
57 #include <comphelper/sequence.hxx>
58 #include <cppuhelper/compbase.hxx>
59 #include <comphelper/string.hxx>
60 #include <cppuhelper/implbase.hxx>
61 #include <cppuhelper/supportsservice.hxx>
62 #include <officecfg/Office/Common.hxx>
63 #include <rtl/bootstrap.hxx>
64 #include <svl/zforlist.hxx>
65 #include <svl/zformat.hxx>
66 #include <tools/helpers.hxx>
67 #include <tools/fract.hxx>
68 #include <tools/stream.hxx>
69 #include <unotools/resmgr.hxx>
70 #include <unx/gstsink.hxx>
71 #include <vcl/ImageTree.hxx>
72 #include <vcl/abstdlg.hxx>
73 #include <vcl/button.hxx>
74 #include <vcl/event.hxx>
75 #include <vcl/i18nhelp.hxx>
76 #include <vcl/quickselectionengine.hxx>
77 #include <vcl/mnemonic.hxx>
78 #include <vcl/pngwrite.hxx>
79 #include <vcl/stdtext.hxx>
80 #include <vcl/syswin.hxx>
81 #include <vcl/virdev.hxx>
82 #include <vcl/weld.hxx>
83 #include <vcl/wrkwin.hxx>
84 #include <strings.hrc>
85 #include <window.h>
86 #include <numeric>
87 
88 using namespace com::sun::star;
89 using namespace com::sun::star::uno;
90 using namespace com::sun::star::lang;
91 
92 extern "C"
93 {
94     #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
GdkThreadsEnter()95     static void GdkThreadsEnter()
96     {
97         GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
98         pYieldMutex->ThreadsEnter();
99     }
GdkThreadsLeave()100     static void GdkThreadsLeave()
101     {
102         GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
103         pYieldMutex->ThreadsLeave();
104     }
105 
create_SalInstance()106     VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
107     {
108         SAL_INFO(
109             "vcl.gtk",
110             "create vcl plugin instance with gtk version " << gtk_major_version
111                 << " " << gtk_minor_version << " " << gtk_micro_version);
112 
113         if (gtk_major_version == 3 && gtk_minor_version < 18)
114         {
115             g_warning("require gtk >= 3.18 for theme expectations");
116             return nullptr;
117         }
118 
119         // for gtk2 it is always built with X support, so this is always called
120         // for gtk3 it is normally built with X and Wayland support, if
121         // X is supported GDK_WINDOWING_X11 is defined and this is always
122         // called, regardless of if we're running under X or Wayland.
123         // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
124         // X, because we need to do it earlier than we have a display
125 #if defined(GDK_WINDOWING_X11)
126         /* #i92121# workaround deadlocks in the X11 implementation
127         */
128         static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
129         /* #i90094#
130            from now on we know that an X connection will be
131            established, so protect X against itself
132         */
133         if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
134             XInitThreads();
135 #endif
136 
137         // init gdk thread protection
138         bool const sup = g_thread_supported();
139             // extracted from the 'if' to avoid Clang -Wunreachable-code
140         if ( !sup )
141             g_thread_init( nullptr );
142 
143         gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
144         SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
145 
146         auto pYieldMutex = std::make_unique<GtkYieldMutex>();
147 
148         gdk_threads_init();
149 
150         GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
151         SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
152 
153         // Create SalData, this does not leak
154         new GtkSalData( pInstance );
155 
156         return pInstance;
157     }
158 }
159 
categorizeEvent(const GdkEvent * pEvent)160 static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
161 {
162     VclInputFlags nType = VclInputFlags::NONE;
163     switch( pEvent->type )
164     {
165     case GDK_MOTION_NOTIFY:
166     case GDK_BUTTON_PRESS:
167     case GDK_2BUTTON_PRESS:
168     case GDK_3BUTTON_PRESS:
169     case GDK_BUTTON_RELEASE:
170     case GDK_ENTER_NOTIFY:
171     case GDK_LEAVE_NOTIFY:
172     case GDK_SCROLL:
173         nType = VclInputFlags::MOUSE;
174         break;
175     case GDK_KEY_PRESS:
176     // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
177         nType = VclInputFlags::KEYBOARD;
178         break;
179     case GDK_EXPOSE:
180         nType = VclInputFlags::PAINT;
181         break;
182     default:
183         nType = VclInputFlags::OTHER;
184         break;
185     }
186     return nType;
187 }
188 
GtkInstance(std::unique_ptr<SalYieldMutex> pMutex)189 GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
190     : SvpSalInstance( std::move(pMutex) )
191     , m_pTimer(nullptr)
192     , bNeedsInit(true)
193     , m_pLastCairoFontOptions(nullptr)
194 {
195 }
196 
197 //We want to defer initializing gtk until we are after uno has been
198 //bootstrapped so we can ask the config what the UI language is so that we can
199 //force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
200 //UI in a LTR locale
AfterAppInit()201 void GtkInstance::AfterAppInit()
202 {
203     EnsureInit();
204 }
205 
EnsureInit()206 void GtkInstance::EnsureInit()
207 {
208     if (!bNeedsInit)
209         return;
210     // initialize SalData
211     GtkSalData *pSalData = GetGtkSalData();
212     pSalData->Init();
213     GtkSalData::initNWF();
214 
215     InitAtkBridge();
216 
217     ImplSVData* pSVData = ImplGetSVData();
218 #ifdef GTK_TOOLKIT_NAME
219     pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
220 #else
221     pSVData->maAppData.mxToolkitName = OUString("gtk3");
222 #endif
223 
224     bNeedsInit = false;
225 }
226 
~GtkInstance()227 GtkInstance::~GtkInstance()
228 {
229     assert( nullptr == m_pTimer );
230     DeInitAtkBridge();
231     ResetLastSeenCairoFontOptions(nullptr);
232 }
233 
CreateFrame(SalFrame * pParent,SalFrameStyleFlags nStyle)234 SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
235 {
236     EnsureInit();
237     return new GtkSalFrame( pParent, nStyle );
238 }
239 
CreateChildFrame(SystemParentData * pParentData,SalFrameStyleFlags)240 SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
241 {
242     EnsureInit();
243     return new GtkSalFrame( pParentData );
244 }
245 
CreateObject(SalFrame * pParent,SystemWindowData *,bool bShow)246 SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* /*pWindowData*/, bool bShow )
247 {
248     EnsureInit();
249     //FIXME: Missing CreateObject functionality ...
250     return new GtkSalObject( static_cast<GtkSalFrame*>(pParent), bShow );
251 }
252 
253 extern "C"
254 {
255     typedef void*(* getDefaultFnc)();
256     typedef void(* addItemFnc)(void *, const char *);
257 }
258 
AddToRecentDocumentList(const OUString & rFileUrl,const OUString &,const OUString &)259 void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
260 {
261     EnsureInit();
262     OString sGtkURL;
263     rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
264     if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
265         sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
266     else
267     {
268         //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
269         //Decode %XX components
270         OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
271         //Convert back to system locale encoding
272         OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
273         //Encode to an escaped ASCII-encoded URI
274         gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
275         sGtkURL = OString(g_uri);
276         g_free(g_uri);
277     }
278     GtkRecentManager *manager = gtk_recent_manager_get_default ();
279     gtk_recent_manager_add_item (manager, sGtkURL.getStr());
280 }
281 
CreateInfoPrinter(SalPrinterQueueInfo * pQueueInfo,ImplJobSetup * pSetupData)282 SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
283     ImplJobSetup* pSetupData )
284 {
285     EnsureInit();
286     mbPrinterInit = true;
287     // create and initialize SalInfoPrinter
288     PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
289     configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
290     return pPrinter;
291 }
292 
CreatePrinter(SalInfoPrinter * pInfoPrinter)293 std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
294 {
295     EnsureInit();
296     mbPrinterInit = true;
297     return std::unique_ptr<SalPrinter>(new GtkSalPrinter( pInfoPrinter ));
298 }
299 
300 /*
301  * These methods always occur in pairs
302  * A ThreadsEnter is followed by a ThreadsLeave
303  * We need to queue up the recursive lock count
304  * for each pair, so we can accurately restore
305  * it later.
306  */
307 thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
308 
ThreadsEnter()309 void GtkYieldMutex::ThreadsEnter()
310 {
311     acquire();
312     if (!yieldCounts.empty()) {
313         auto n = yieldCounts.top();
314         yieldCounts.pop();
315         assert(n > 0);
316         n--;
317         if (n > 0)
318             acquire(n);
319     }
320 }
321 
ThreadsLeave()322 void GtkYieldMutex::ThreadsLeave()
323 {
324     assert(m_nCount != 0);
325     yieldCounts.push(m_nCount);
326     release(true);
327 }
328 
CreateVirtualDevice(SalGraphics * pG,long & nDX,long & nDY,DeviceFormat eFormat,const SystemGraphicsData * pGd)329 std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
330                                                     long &nDX, long &nDY,
331                                                     DeviceFormat eFormat,
332                                                     const SystemGraphicsData* pGd )
333 {
334     EnsureInit();
335     SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
336     assert(pSvpSalGraphics);
337     // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
338     cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
339     std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
340     pNew->SetSize( nDX, nDY );
341     return pNew;
342 }
343 
CreateSalBitmap()344 std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
345 {
346     EnsureInit();
347     return SvpSalInstance::CreateSalBitmap();
348 }
349 
CreateMenu(bool bMenuBar,Menu * pVCLMenu)350 std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
351 {
352     EnsureInit();
353     GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
354     pSalMenu->SetMenu( pVCLMenu );
355     return std::unique_ptr<SalMenu>(pSalMenu);
356 }
357 
CreateMenuItem(const SalItemParams & rItemData)358 std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
359 {
360     EnsureInit();
361     return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
362 }
363 
CreateSalTimer()364 SalTimer* GtkInstance::CreateSalTimer()
365 {
366     EnsureInit();
367     assert( nullptr == m_pTimer );
368     if ( nullptr == m_pTimer )
369         m_pTimer = new GtkSalTimer();
370     return m_pTimer;
371 }
372 
RemoveTimer()373 void GtkInstance::RemoveTimer ()
374 {
375     EnsureInit();
376     m_pTimer = nullptr;
377 }
378 
DoYield(bool bWait,bool bHandleAllCurrentEvents)379 bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
380 {
381     EnsureInit();
382     return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
383 }
384 
IsTimerExpired()385 bool GtkInstance::IsTimerExpired()
386 {
387     EnsureInit();
388     return (m_pTimer && m_pTimer->Expired());
389 }
390 
AnyInput(VclInputFlags nType)391 bool GtkInstance::AnyInput( VclInputFlags nType )
392 {
393     EnsureInit();
394     if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
395         return true;
396     if (!gdk_events_pending())
397         return false;
398 
399     if (nType == VCL_INPUT_ANY)
400         return true;
401 
402     bool bRet = false;
403     std::deque<GdkEvent*> aEvents;
404     GdkEvent *pEvent = nullptr;
405     while ((pEvent = gdk_event_get()))
406     {
407         aEvents.push_back(pEvent);
408         VclInputFlags nEventType = categorizeEvent(pEvent);
409         if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
410         {
411             bRet = true;
412         }
413     }
414 
415     while (!aEvents.empty())
416     {
417         pEvent = aEvents.front();
418         gdk_event_put(pEvent);
419         gdk_event_free(pEvent);
420         aEvents.pop_front();
421     }
422     return bRet;
423 }
424 
CreatePrintGraphics()425 std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
426 {
427     EnsureInit();
428     return std::make_unique<GenPspGraphics>();
429 }
430 
431 std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
getPrintWrapper() const432 GtkInstance::getPrintWrapper() const
433 {
434     if (!m_xPrintWrapper)
435         m_xPrintWrapper.reset(new vcl::unx::GtkPrintWrapper);
436     return m_xPrintWrapper;
437 }
438 
GetCairoFontOptions()439 const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
440 {
441     const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
442     if (!m_pLastCairoFontOptions && pCairoFontOptions)
443         m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
444     return pCairoFontOptions;
445 }
446 
GetLastSeenCairoFontOptions() const447 const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
448 {
449     return m_pLastCairoFontOptions;
450 }
451 
ResetLastSeenCairoFontOptions(const cairo_font_options_t * pCairoFontOptions)452 void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
453 {
454     if (m_pLastCairoFontOptions)
455         cairo_font_options_destroy(m_pLastCairoFontOptions);
456     if (pCairoFontOptions)
457         m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
458     else
459         m_pLastCairoFontOptions = nullptr;
460 }
461 
462 
463 namespace
464 {
465     struct TypeEntry
466     {
467         const char*     pNativeType;        // string corresponding to nAtom for the case of nAtom being uninitialized
468         const char*     pType;              // Mime encoding on our side
469     };
470 
471     static const TypeEntry aConversionTab[] =
472     {
473         { "ISO10646-1", "text/plain;charset=utf-16" },
474         { "UTF8_STRING", "text/plain;charset=utf-8" },
475         { "UTF-8", "text/plain;charset=utf-8" },
476         { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
477         // ISO encodings
478         { "ISO8859-2", "text/plain;charset=iso8859-2" },
479         { "ISO8859-3", "text/plain;charset=iso8859-3" },
480         { "ISO8859-4", "text/plain;charset=iso8859-4" },
481         { "ISO8859-5", "text/plain;charset=iso8859-5" },
482         { "ISO8859-6", "text/plain;charset=iso8859-6" },
483         { "ISO8859-7", "text/plain;charset=iso8859-7" },
484         { "ISO8859-8", "text/plain;charset=iso8859-8" },
485         { "ISO8859-9", "text/plain;charset=iso8859-9" },
486         { "ISO8859-10", "text/plain;charset=iso8859-10" },
487         { "ISO8859-13", "text/plain;charset=iso8859-13" },
488         { "ISO8859-14", "text/plain;charset=iso8859-14" },
489         { "ISO8859-15", "text/plain;charset=iso8859-15" },
490         // asian encodings
491         { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
492         { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
493         { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
494         { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
495         { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
496         { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
497         // eastern european encodings
498         { "KOI8-R", "text/plain;charset=koi8-r" },
499         { "KOI8-U", "text/plain;charset=koi8-u" },
500         // String (== iso8859-1)
501         { "STRING", "text/plain;charset=iso8859-1" },
502         // special for compound text
503         { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
504 
505         // PIXMAP
506         { "PIXMAP", "image/bmp" }
507     };
508 
509     class DataFlavorEq
510     {
511     private:
512         const css::datatransfer::DataFlavor& m_rData;
513     public:
DataFlavorEq(const css::datatransfer::DataFlavor & rData)514         explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
operator ()(const css::datatransfer::DataFlavor & rData) const515         bool operator() (const css::datatransfer::DataFlavor& rData) const
516         {
517             return rData.MimeType == m_rData.MimeType &&
518                    rData.DataType  == m_rData.DataType;
519         }
520     };
521 }
522 
getTransferDataFlavorsAsVector(GdkAtom * targets,gint n_targets)523 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
524 {
525     std::vector<css::datatransfer::DataFlavor> aVector;
526 
527     bool bHaveText = false, bHaveUTF16 = false;
528 
529     for (gint i = 0; i < n_targets; ++i)
530     {
531         gchar* pName = gdk_atom_name(targets[i]);
532         const char* pFinalName = pName;
533         css::datatransfer::DataFlavor aFlavor;
534 
535         // omit text/plain;charset=unicode since it is not well defined
536         if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
537         {
538             g_free(pName);
539             continue;
540         }
541 
542         for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
543         {
544             if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
545             {
546                 pFinalName = aConversionTab[j].pType;
547                 break;
548             }
549         }
550 
551         // There are more non-MIME-types reported that are not translated by
552         // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
553         // them out for now before they confuse this code's clients:
554         if (rtl_str_indexOfChar(pFinalName, '/') == -1)
555         {
556             g_free(pName);
557             continue;
558         }
559 
560         aFlavor.MimeType = OUString(pFinalName,
561                                     strlen(pFinalName),
562                                     RTL_TEXTENCODING_UTF8);
563 
564         m_aMimeTypeToAtom[aFlavor.MimeType] = targets[i];
565 
566         aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
567 
568         sal_Int32 nIndex(0);
569         if (aFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
570         {
571             bHaveText = true;
572             OUString aToken(aFlavor.MimeType.getToken(0, ';', nIndex));
573             if (aToken == "charset=utf-16")
574             {
575                 bHaveUTF16 = true;
576                 aFlavor.DataType = cppu::UnoType<OUString>::get();
577             }
578         }
579         aVector.push_back(aFlavor);
580         g_free(pName);
581     }
582 
583     //If we have text, but no UTF-16 format which is basically the only
584     //text-format LibreOffice supports for cnp then claim we do and we
585     //will convert on demand
586     if (bHaveText && !bHaveUTF16)
587     {
588         css::datatransfer::DataFlavor aFlavor;
589         aFlavor.MimeType = "text/plain;charset=utf-16";
590         aFlavor.DataType = cppu::UnoType<OUString>::get();
591         aVector.push_back(aFlavor);
592     }
593 
594     return aVector;
595 }
596 
597 
getTransferDataFlavors()598 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
599 {
600     return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
601 }
602 
isDataFlavorSupported(const css::datatransfer::DataFlavor & rFlavor)603 sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
604 {
605     const std::vector<css::datatransfer::DataFlavor> aAll =
606         getTransferDataFlavorsAsVector();
607 
608     return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
609 }
610 
611 class GtkClipboardTransferable : public GtkTransferable
612 {
613 private:
614     GdkAtom m_nSelection;
615 public:
616 
GtkClipboardTransferable(GdkAtom nSelection)617     explicit GtkClipboardTransferable(GdkAtom nSelection)
618         : m_nSelection(nSelection)
619     {
620     }
621 
622     /*
623      * XTransferable
624      */
625 
getTransferData(const css::datatransfer::DataFlavor & rFlavor)626     virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
627     {
628         GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
629         if (rFlavor.MimeType == "text/plain;charset=utf-16")
630         {
631             OUString aStr;
632             gchar *pText = gtk_clipboard_wait_for_text(clipboard);
633             if (pText)
634                 aStr = OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
635             g_free(pText);
636             css::uno::Any aRet;
637             aRet <<= aStr.replaceAll("\r\n", "\n");
638             return aRet;
639         }
640 
641         auto it = m_aMimeTypeToAtom.find(rFlavor.MimeType);
642         if (it == m_aMimeTypeToAtom.end())
643             return css::uno::Any();
644 
645         css::uno::Any aRet;
646         GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
647                                                                  it->second);
648         if (!data)
649         {
650             return css::uno::Any();
651         }
652         gint length;
653         const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
654                                                                         &length);
655         Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
656         gtk_selection_data_free(data);
657         aRet <<= aSeq;
658         return aRet;
659     }
660 
getTransferDataFlavorsAsVector()661     std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
662         override
663     {
664         std::vector<css::datatransfer::DataFlavor> aVector;
665 
666         GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
667 
668         GdkAtom *targets;
669         gint n_targets;
670         if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
671         {
672             aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
673             g_free(targets);
674         }
675 
676         return aVector;
677     }
678 };
679 
680 class VclGtkClipboard :
681         public cppu::WeakComponentImplHelper<
682         datatransfer::clipboard::XSystemClipboard,
683         datatransfer::clipboard::XFlushableClipboard,
684         XServiceInfo>
685 {
686     GdkAtom                                                  m_nSelection;
687     osl::Mutex                                               m_aMutex;
688     gulong                                                   m_nOwnerChangedSignalId;
689     Reference<css::datatransfer::XTransferable>              m_aContents;
690     Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
691     std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
692     std::vector<GtkTargetEntry> m_aGtkTargets;
693     VclToGtkHelper m_aConversionHelper;
694 
695 public:
696 
697     explicit VclGtkClipboard(GdkAtom nSelection);
698     virtual ~VclGtkClipboard() override;
699 
700     /*
701      * XServiceInfo
702      */
703 
704     virtual OUString SAL_CALL getImplementationName() override;
705     virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
706     virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
707 
708     /*
709      * XClipboard
710      */
711 
712     virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
713 
714     virtual void SAL_CALL setContents(
715         const Reference< css::datatransfer::XTransferable >& xTrans,
716         const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
717 
718     virtual OUString SAL_CALL getName() override;
719 
720     /*
721      * XClipboardEx
722      */
723 
724     virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
725 
726     /*
727      * XFlushableClipboard
728      */
729     virtual void SAL_CALL flushClipboard() override;
730 
731     /*
732      * XClipboardNotifier
733      */
734     virtual void SAL_CALL addClipboardListener(
735         const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
736 
737     virtual void SAL_CALL removeClipboardListener(
738         const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
739 
740     void ClipboardGet(GtkSelectionData *selection_data, guint info);
741     void ClipboardClear();
742     void OwnerPossiblyChanged(GtkClipboard *clipboard);
743 };
744 
getImplementationName()745 OUString VclGtkClipboard::getImplementationName()
746 {
747     return "com.sun.star.datatransfer.VclGtkClipboard";
748 }
749 
getSupportedServiceNames()750 Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
751 {
752     Sequence<OUString> aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
753     return aRet;
754 }
755 
supportsService(const OUString & ServiceName)756 sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
757 {
758     return cppu::supportsService(this, ServiceName);
759 }
760 
getContents()761 Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
762 {
763     if (!m_aContents.is())
764     {
765         //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
766         //the owner of the clipboard and have not already fetched it.
767         m_aContents = new GtkClipboardTransferable(m_nSelection);
768     }
769 
770     return m_aContents;
771 }
772 
ClipboardGet(GtkSelectionData * selection_data,guint info)773 void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
774 {
775     if (!m_aContents.is())
776         return;
777     // tdf#129809 take a reference in case m_aContents is replaced during this
778     // call
779     Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
780     m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
781 }
782 
783 namespace
784 {
getPID()785     const OString& getPID()
786     {
787         static OString sPID;
788         if (!sPID.getLength())
789         {
790             oslProcessIdentifier aProcessId = 0;
791             oslProcessInfo info;
792             info.Size = sizeof (oslProcessInfo);
793             if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
794                 aProcessId = info.Ident;
795             sPID = OString::number(aProcessId);
796         }
797         return sPID;
798     }
799 }
800 
801 namespace
802 {
ClipboardGetFunc(GtkClipboard *,GtkSelectionData * selection_data,guint info,gpointer user_data_or_owner)803     void ClipboardGetFunc(GtkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
804                           guint info,
805                           gpointer user_data_or_owner)
806     {
807         VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
808         pThis->ClipboardGet(selection_data, info);
809     }
810 
ClipboardClearFunc(GtkClipboard *,gpointer user_data_or_owner)811     void ClipboardClearFunc(GtkClipboard* /*clipboard*/, gpointer user_data_or_owner)
812     {
813         VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
814         pThis->ClipboardClear();
815     }
816 
handle_owner_change(GtkClipboard * clipboard,GdkEvent *,gpointer user_data)817     void handle_owner_change(GtkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
818     {
819         VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
820         pThis->OwnerPossiblyChanged(clipboard);
821     }
822 }
823 
OwnerPossiblyChanged(GtkClipboard * clipboard)824 void VclGtkClipboard::OwnerPossiblyChanged(GtkClipboard* clipboard)
825 {
826     if (!m_aContents.is())
827         return;
828 
829     //if gdk_display_supports_selection_notification is not supported, e.g. like
830     //right now under wayland, then you only get owner-changed notifications at
831     //opportune times when the selection might have changed. So here
832     //we see if the selection supports a dummy selection type identifying
833     //our pid, in which case it's us.
834     bool bSelf = false;
835 
836     //disconnect and reconnect after gtk_clipboard_wait_for_targets to
837     //avoid possible recursion
838     g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
839 
840     OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
841     GdkAtom *targets;
842     gint n_targets;
843     if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
844     {
845         for (gint i = 0; i < n_targets && !bSelf; ++i)
846         {
847             gchar* pName = gdk_atom_name(targets[i]);
848             if (strcmp(pName, sTunnel.getStr()) == 0)
849             {
850                 bSelf = true;
851             }
852             g_free(pName);
853         }
854 
855         g_free(targets);
856     }
857 
858     m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
859                                                G_CALLBACK(handle_owner_change), this);
860 
861     if (!bSelf)
862     {
863         //null out m_aContents to return control to the system-one which
864         //will be retrieved if getContents is called again
865         setContents(Reference<css::datatransfer::XTransferable>(),
866                     Reference<css::datatransfer::clipboard::XClipboardOwner>());
867     }
868 }
869 
ClipboardClear()870 void VclGtkClipboard::ClipboardClear()
871 {
872     for (auto &a : m_aGtkTargets)
873         g_free(a.target);
874     m_aGtkTargets.clear();
875 }
876 
makeGtkTargetEntry(const css::datatransfer::DataFlavor & rFlavor)877 GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
878 {
879     GtkTargetEntry aEntry;
880     aEntry.target =
881         g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
882     aEntry.flags = 0;
883     auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
884                         DataFlavorEq(rFlavor));
885     if (it != aInfoToFlavor.end())
886         aEntry.info = std::distance(aInfoToFlavor.begin(), it);
887     else
888     {
889         aEntry.info = aInfoToFlavor.size();
890         aInfoToFlavor.push_back(rFlavor);
891     }
892     return aEntry;
893 }
894 
setSelectionData(const Reference<css::datatransfer::XTransferable> & rTrans,GtkSelectionData * selection_data,guint info)895 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
896                                       GtkSelectionData *selection_data, guint info)
897 {
898     GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
899                                                    RTL_TEXTENCODING_UTF8).getStr(),
900                                  false));
901 
902     css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
903     if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
904         aFlavor.MimeType = "text/plain;charset=utf-8";
905 
906     Sequence<sal_Int8> aData;
907     Any aValue;
908 
909     try
910     {
911         aValue = rTrans->getTransferData(aFlavor);
912     }
913     catch (...)
914     {
915     }
916 
917     if (aValue.getValueTypeClass() == TypeClass_STRING)
918     {
919         OUString aString;
920         aValue >>= aString;
921         aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
922     }
923     else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
924     {
925         aValue >>= aData;
926     }
927     else if (aFlavor.MimeType == "text/plain;charset=utf-8")
928     {
929         //didn't have utf-8, try utf-16 and convert
930         aFlavor.MimeType = "text/plain;charset=utf-16";
931         aFlavor.DataType = cppu::UnoType<OUString>::get();
932         try
933         {
934             aValue = rTrans->getTransferData(aFlavor);
935         }
936         catch (...)
937         {
938         }
939         OUString aString;
940         aValue >>= aString;
941         OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
942         gtk_selection_data_set(selection_data, type, 8,
943                                reinterpret_cast<const guchar *>(aUTF8String.getStr()),
944                                aUTF8String.getLength());
945         return;
946     }
947 
948     gtk_selection_data_set(selection_data, type, 8,
949                            reinterpret_cast<const guchar *>(aData.getArray()),
950                            aData.getLength());
951 }
952 
VclGtkClipboard(GdkAtom nSelection)953 VclGtkClipboard::VclGtkClipboard(GdkAtom nSelection)
954     : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
955                                     datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
956         (m_aMutex)
957     , m_nSelection(nSelection)
958 {
959     GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
960     m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
961                                                G_CALLBACK(handle_owner_change), this);
962 }
963 
flushClipboard()964 void VclGtkClipboard::flushClipboard()
965 {
966     SolarMutexGuard aGuard;
967 
968     if (GDK_SELECTION_CLIPBOARD != m_nSelection)
969         return;
970 
971     GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
972     gtk_clipboard_store(clipboard);
973 }
974 
~VclGtkClipboard()975 VclGtkClipboard::~VclGtkClipboard()
976 {
977     GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
978     g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
979     if (!m_aGtkTargets.empty())
980     {
981         gtk_clipboard_clear(clipboard);
982         ClipboardClear();
983     }
984     assert(m_aGtkTargets.empty());
985 }
986 
FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> & rFormats)987 std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
988 {
989     std::vector<GtkTargetEntry> aGtkTargets;
990 
991     bool bHaveText(false), bHaveUTF8(false);
992     for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
993     {
994         sal_Int32 nIndex(0);
995         if (rFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
996         {
997             bHaveText = true;
998             OUString aToken(rFlavor.MimeType.getToken(0, ';', nIndex));
999             if (aToken == "charset=utf-8")
1000             {
1001                 bHaveUTF8 = true;
1002             }
1003         }
1004         GtkTargetEntry aEntry(makeGtkTargetEntry(rFlavor));
1005         aGtkTargets.push_back(aEntry);
1006     }
1007 
1008     if (bHaveText)
1009     {
1010         css::datatransfer::DataFlavor aFlavor;
1011         aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
1012         if (!bHaveUTF8)
1013         {
1014             aFlavor.MimeType = "text/plain;charset=utf-8";
1015             aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1016         }
1017         aFlavor.MimeType = "UTF8_STRING";
1018         aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1019         aFlavor.MimeType = "STRING";
1020         aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1021     }
1022 
1023     return aGtkTargets;
1024 }
1025 
setContents(const Reference<css::datatransfer::XTransferable> & xTrans,const Reference<css::datatransfer::clipboard::XClipboardOwner> & xClipboardOwner)1026 void VclGtkClipboard::setContents(
1027         const Reference< css::datatransfer::XTransferable >& xTrans,
1028         const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
1029 {
1030     css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
1031     if (xTrans.is())
1032     {
1033         aFormats = xTrans->getTransferDataFlavors();
1034     }
1035 
1036     osl::ClearableMutexGuard aGuard( m_aMutex );
1037     Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
1038     Reference< datatransfer::XTransferable > xOldContents( m_aContents );
1039     m_aContents = xTrans;
1040     m_aOwner = xClipboardOwner;
1041 
1042     std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
1043     datatransfer::clipboard::ClipboardEvent aEv;
1044 
1045     GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
1046     if (!m_aGtkTargets.empty())
1047     {
1048         gtk_clipboard_clear(clipboard);
1049         ClipboardClear();
1050     }
1051     assert(m_aGtkTargets.empty());
1052     if (m_aContents.is())
1053     {
1054         std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1055         if (!aGtkTargets.empty())
1056         {
1057             GtkTargetEntry aEntry;
1058             OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1059             aEntry.target = g_strdup(sTunnel.getStr());
1060             aEntry.flags = 0;
1061             aEntry.info = 0;
1062             aGtkTargets.push_back(aEntry);
1063 
1064             gtk_clipboard_set_with_data(clipboard, aGtkTargets.data(), aGtkTargets.size(),
1065                                         ClipboardGetFunc, ClipboardClearFunc, this);
1066             gtk_clipboard_set_can_store(clipboard, aGtkTargets.data(), aGtkTargets.size());
1067         }
1068 
1069         m_aGtkTargets = aGtkTargets;
1070     }
1071 
1072     aEv.Contents = getContents();
1073 
1074     aGuard.clear();
1075 
1076     if (xOldOwner.is() && xOldOwner != xClipboardOwner)
1077         xOldOwner->lostOwnership( this, xOldContents );
1078     for (auto const& listener : aListeners)
1079     {
1080         listener->changedContents( aEv );
1081     }
1082 }
1083 
getName()1084 OUString VclGtkClipboard::getName()
1085 {
1086     return (m_nSelection == GDK_SELECTION_CLIPBOARD) ? OUString("CLIPBOARD") : OUString("PRIMARY");
1087 }
1088 
getRenderingCapabilities()1089 sal_Int8 VclGtkClipboard::getRenderingCapabilities()
1090 {
1091     return 0;
1092 }
1093 
addClipboardListener(const Reference<datatransfer::clipboard::XClipboardListener> & listener)1094 void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1095 {
1096     osl::ClearableMutexGuard aGuard( m_aMutex );
1097 
1098     m_aListeners.push_back( listener );
1099 }
1100 
removeClipboardListener(const Reference<datatransfer::clipboard::XClipboardListener> & listener)1101 void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1102 {
1103     osl::ClearableMutexGuard aGuard( m_aMutex );
1104 
1105     m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
1106 }
1107 
CreateClipboard(const Sequence<Any> & arguments)1108 Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
1109 {
1110     OUString sel;
1111     if (!arguments.hasElements()) {
1112         sel = "CLIPBOARD";
1113     } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
1114         throw css::lang::IllegalArgumentException(
1115             "bad GtkInstance::CreateClipboard arguments",
1116             css::uno::Reference<css::uno::XInterface>(), -1);
1117     }
1118 
1119     GdkAtom nSelection = (sel == "CLIPBOARD") ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY;
1120 
1121     auto it = m_aClipboards.find(nSelection);
1122     if (it != m_aClipboards.end())
1123         return it->second;
1124 
1125     Reference<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(new VclGtkClipboard(nSelection)));
1126     m_aClipboards[nSelection] = xClipboard;
1127 
1128     return xClipboard;
1129 }
1130 
GtkDropTarget()1131 GtkDropTarget::GtkDropTarget()
1132     : WeakComponentImplHelper(m_aMutex)
1133     , m_pFrame(nullptr)
1134     , m_pFormatConversionRequest(nullptr)
1135     , m_bActive(false)
1136     , m_bInDrag(false)
1137     , m_nDefaultActions(0)
1138 {
1139 }
1140 
getImplementationName()1141 OUString SAL_CALL GtkDropTarget::getImplementationName()
1142 {
1143     return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
1144 }
1145 
supportsService(OUString const & ServiceName)1146 sal_Bool SAL_CALL GtkDropTarget::supportsService(OUString const & ServiceName)
1147 {
1148     return cppu::supportsService(this, ServiceName);
1149 }
1150 
getSupportedServiceNames()1151 css::uno::Sequence<OUString> SAL_CALL GtkDropTarget::getSupportedServiceNames()
1152 {
1153     Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
1154     return aRet;
1155 }
1156 
~GtkDropTarget()1157 GtkDropTarget::~GtkDropTarget()
1158 {
1159     if (m_pFrame)
1160         m_pFrame->deregisterDropTarget(this);
1161 }
1162 
deinitialize()1163 void GtkDropTarget::deinitialize()
1164 {
1165     m_pFrame = nullptr;
1166     m_bActive = false;
1167 }
1168 
initialize(const Sequence<Any> & rArguments)1169 void GtkDropTarget::initialize(const Sequence<Any>& rArguments)
1170 {
1171     if (rArguments.getLength() < 2)
1172     {
1173         throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
1174                                static_cast<OWeakObject*>(this));
1175     }
1176 
1177     sal_IntPtr nFrame = 0;
1178     rArguments.getConstArray()[1] >>= nFrame;
1179 
1180     if (!nFrame)
1181     {
1182         throw RuntimeException("DropTarget::initialize: missing SalFrame",
1183                                static_cast<OWeakObject*>(this));
1184     }
1185 
1186     m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1187     m_pFrame->registerDropTarget(this);
1188     m_bActive = true;
1189 }
1190 
addDropTargetListener(const Reference<css::datatransfer::dnd::XDropTargetListener> & xListener)1191 void GtkDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1192 {
1193     ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1194 
1195     m_aListeners.push_back( xListener );
1196 }
1197 
removeDropTargetListener(const Reference<css::datatransfer::dnd::XDropTargetListener> & xListener)1198 void GtkDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1199 {
1200     ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1201 
1202     m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end());
1203 }
1204 
fire_drop(const css::datatransfer::dnd::DropTargetDropEvent & dtde)1205 void GtkDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
1206 {
1207     osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
1208     std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1209     aGuard.clear();
1210 
1211     for (auto const& listener : aListeners)
1212     {
1213         listener->drop( dtde );
1214     }
1215 }
1216 
fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent & dtde)1217 void GtkDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
1218 {
1219     osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1220     std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1221     aGuard.clear();
1222 
1223     for (auto const& listener : aListeners)
1224     {
1225         listener->dragEnter( dtde );
1226     }
1227 }
1228 
fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent & dtde)1229 void GtkDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
1230 {
1231     osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1232     std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1233     aGuard.clear();
1234 
1235     for (auto const& listener : aListeners)
1236     {
1237         listener->dragOver( dtde );
1238     }
1239 }
1240 
fire_dragExit(const css::datatransfer::dnd::DropTargetEvent & dte)1241 void GtkDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
1242 {
1243     osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1244     std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1245     aGuard.clear();
1246 
1247     for (auto const& listener : aListeners)
1248     {
1249         listener->dragExit( dte );
1250     }
1251 }
1252 
isActive()1253 sal_Bool GtkDropTarget::isActive()
1254 {
1255     return m_bActive;
1256 }
1257 
setActive(sal_Bool bActive)1258 void GtkDropTarget::setActive(sal_Bool bActive)
1259 {
1260     m_bActive = bActive;
1261 }
1262 
getDefaultActions()1263 sal_Int8 GtkDropTarget::getDefaultActions()
1264 {
1265     return m_nDefaultActions;
1266 }
1267 
setDefaultActions(sal_Int8 nDefaultActions)1268 void GtkDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
1269 {
1270     m_nDefaultActions = nDefaultActions;
1271 }
1272 
CreateDropTarget()1273 Reference< XInterface > GtkInstance::CreateDropTarget()
1274 {
1275     return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new GtkDropTarget));
1276 }
1277 
~GtkDragSource()1278 GtkDragSource::~GtkDragSource()
1279 {
1280     if (m_pFrame)
1281         m_pFrame->deregisterDragSource(this);
1282 
1283     if (GtkDragSource::g_ActiveDragSource == this)
1284     {
1285         SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkDragSource before dtor");
1286         GtkDragSource::g_ActiveDragSource = nullptr;
1287     }
1288 }
1289 
deinitialize()1290 void GtkDragSource::deinitialize()
1291 {
1292     m_pFrame = nullptr;
1293 }
1294 
isDragImageSupported()1295 sal_Bool GtkDragSource::isDragImageSupported()
1296 {
1297     return true;
1298 }
1299 
getDefaultCursor(sal_Int8)1300 sal_Int32 GtkDragSource::getDefaultCursor( sal_Int8 )
1301 {
1302     return 0;
1303 }
1304 
initialize(const css::uno::Sequence<css::uno::Any> & rArguments)1305 void GtkDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
1306 {
1307     if (rArguments.getLength() < 2)
1308     {
1309         throw RuntimeException("DragSource::initialize: Cannot install window event handler",
1310                                static_cast<OWeakObject*>(this));
1311     }
1312 
1313     sal_IntPtr nFrame = 0;
1314     rArguments.getConstArray()[1] >>= nFrame;
1315 
1316     if (!nFrame)
1317     {
1318         throw RuntimeException("DragSource::initialize: missing SalFrame",
1319                                static_cast<OWeakObject*>(this));
1320     }
1321 
1322     m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1323     m_pFrame->registerDragSource(this);
1324 }
1325 
getImplementationName()1326 OUString SAL_CALL GtkDragSource::getImplementationName()
1327 {
1328     return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
1329 }
1330 
supportsService(OUString const & ServiceName)1331 sal_Bool SAL_CALL GtkDragSource::supportsService(OUString const & ServiceName)
1332 {
1333     return cppu::supportsService(this, ServiceName);
1334 }
1335 
getSupportedServiceNames()1336 css::uno::Sequence<OUString> SAL_CALL GtkDragSource::getSupportedServiceNames()
1337 {
1338     Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
1339     return aRet;
1340 }
1341 
CreateDragSource()1342 Reference< XInterface > GtkInstance::CreateDragSource()
1343 {
1344     return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new GtkDragSource()) );
1345 }
1346 
1347 class GtkOpenGLContext : public OpenGLContext
1348 {
1349     GLWindow m_aGLWin;
1350     GtkWidget *m_pGLArea;
1351     GdkGLContext *m_pContext;
1352     guint m_nAreaFrameBuffer;
1353     guint m_nFrameBuffer;
1354     guint m_nRenderBuffer;
1355     guint m_nDepthBuffer;
1356     guint m_nFrameScratchBuffer;
1357     guint m_nRenderScratchBuffer;
1358     guint m_nDepthScratchBuffer;
1359 
1360 public:
GtkOpenGLContext()1361     GtkOpenGLContext()
1362         : OpenGLContext()
1363         , m_pGLArea(nullptr)
1364         , m_pContext(nullptr)
1365         , m_nAreaFrameBuffer(0)
1366         , m_nFrameBuffer(0)
1367         , m_nRenderBuffer(0)
1368         , m_nDepthBuffer(0)
1369         , m_nFrameScratchBuffer(0)
1370         , m_nRenderScratchBuffer(0)
1371         , m_nDepthScratchBuffer(0)
1372     {
1373     }
1374 
initWindow()1375     virtual void initWindow() override
1376     {
1377         if( !m_pChildWindow )
1378         {
1379             SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
1380             m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
1381         }
1382 
1383         if (m_pChildWindow)
1384         {
1385             InitChildWindow(m_pChildWindow.get());
1386         }
1387     }
1388 
1389 private:
getOpenGLWindow() const1390     virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
getModifiableOpenGLWindow()1391     virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
1392 
signalDestroy(GtkWidget *,gpointer context)1393     static void signalDestroy(GtkWidget*, gpointer context)
1394     {
1395         GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
1396         pThis->m_pGLArea = nullptr;
1397     }
1398 
signalRender(GtkGLArea *,GdkGLContext *,gpointer window)1399     static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
1400     {
1401         GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
1402 
1403         int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
1404         int width = pThis->m_aGLWin.Width * scale;
1405         int height = pThis->m_aGLWin.Height * scale;
1406 
1407         glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1408 
1409         glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
1410         glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1411 
1412         glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1413                           GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1414 
1415         gdk_gl_context_make_current(pThis->m_pContext);
1416         return true;
1417     }
1418 
adjustToNewSize()1419     virtual void adjustToNewSize() override
1420     {
1421         if (!m_pGLArea)
1422             return;
1423 
1424         int scale = gtk_widget_get_scale_factor(m_pGLArea);
1425         int width = m_aGLWin.Width * scale;
1426         int height = m_aGLWin.Height * scale;
1427 
1428         // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
1429         int allocwidth = std::max(width, 1);
1430         int allocheight = std::max(height, 1);
1431 
1432         gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1433         if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1434         {
1435             SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1436             return;
1437         }
1438 
1439         glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1440         glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1441         glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1442         glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1443         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
1444 
1445         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1446                                      GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1447         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1448                                      GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1449 
1450         gdk_gl_context_make_current(m_pContext);
1451         glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1452         glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1453         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
1454 
1455         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1456                                      GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1457         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1458                                      GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1459         glViewport(0, 0, width, height);
1460 
1461         glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1462         glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1463         glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1464         glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1465         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1466 
1467         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1468                                      GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1469         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1470                                      GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1471 
1472         glViewport(0, 0, width, height);
1473     }
1474 
ImplInit()1475     virtual bool ImplInit() override
1476     {
1477         const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
1478         GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
1479         m_pGLArea = gtk_gl_area_new();
1480         g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
1481         g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
1482         gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
1483         gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
1484         gtk_widget_set_hexpand(m_pGLArea, true);
1485         gtk_widget_set_vexpand(m_pGLArea, true);
1486         gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
1487         gtk_widget_show_all(pParent);
1488 
1489         gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1490         if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1491         {
1492             SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1493             return false;
1494         }
1495 
1496         gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
1497         glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
1498 
1499         GdkWindow *pWindow = gtk_widget_get_window(pParent);
1500         m_pContext = gdk_window_create_gl_context(pWindow, nullptr);
1501         if (!m_pContext)
1502             return false;
1503 
1504         if (!gdk_gl_context_realize(m_pContext, nullptr))
1505             return false;
1506 
1507         gdk_gl_context_make_current(m_pContext);
1508         glGenFramebuffersEXT(1, &m_nFrameBuffer);
1509         glGenRenderbuffersEXT(1, &m_nRenderBuffer);
1510         glGenRenderbuffersEXT(1, &m_nDepthBuffer);
1511         glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
1512         glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
1513         glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
1514 
1515         bool bRet = InitGL();
1516         InitGLDebugging();
1517         return bRet;
1518     }
1519 
restoreDefaultFramebuffer()1520     virtual void restoreDefaultFramebuffer() override
1521     {
1522         OpenGLContext::restoreDefaultFramebuffer();
1523         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1524         glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1525                                      GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1526     }
1527 
makeCurrent()1528     virtual void makeCurrent() override
1529     {
1530         if (isCurrent())
1531             return;
1532 
1533         clearCurrent();
1534 
1535         if (m_pGLArea)
1536         {
1537             int scale = gtk_widget_get_scale_factor(m_pGLArea);
1538             int width = m_aGLWin.Width * scale;
1539             int height = m_aGLWin.Height * scale;
1540 
1541             gdk_gl_context_make_current(m_pContext);
1542 
1543             glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1544             glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1545             glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1546             glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1547                                          GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1548             glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1549                                          GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1550             glViewport(0, 0, width, height);
1551         }
1552 
1553         registerAsCurrent();
1554     }
1555 
destroyCurrentContext()1556     virtual void destroyCurrentContext() override
1557     {
1558         gdk_gl_context_clear_current();
1559     }
1560 
isCurrent()1561     virtual bool isCurrent() override
1562     {
1563         return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
1564     }
1565 
sync()1566     virtual void sync() override
1567     {
1568     }
1569 
resetCurrent()1570     virtual void resetCurrent() override
1571     {
1572         clearCurrent();
1573         gdk_gl_context_clear_current();
1574     }
1575 
swapBuffers()1576     virtual void swapBuffers() override
1577     {
1578         int scale = gtk_widget_get_scale_factor(m_pGLArea);
1579         int width = m_aGLWin.Width * scale;
1580         int height = m_aGLWin.Height * scale;
1581 
1582         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
1583         glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1584 
1585         glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
1586         glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1587 
1588         glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1589                           GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1590 
1591         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
1592         glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1593 
1594         gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
1595         BuffersSwapped();
1596     }
1597 
~GtkOpenGLContext()1598     virtual ~GtkOpenGLContext() override
1599     {
1600         if (m_pContext)
1601         {
1602             g_clear_object(&m_pContext);
1603         }
1604     }
1605 };
1606 
CreateOpenGLContext()1607 OpenGLContext* GtkInstance::CreateOpenGLContext()
1608 {
1609     return new GtkOpenGLContext;
1610 }
1611 
1612 // tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay * pDisplay)1613 bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
1614 {
1615     auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
1616     if (!get_type)
1617         return false;
1618     return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
1619 }
1620 
DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay * pDisplay)1621 bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
1622 {
1623     auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
1624     if (!get_type)
1625         return false;
1626     return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
1627 }
1628 
1629 class GtkInstanceBuilder;
1630 
1631 namespace
1632 {
set_help_id(const GtkWidget * pWidget,const OString & rHelpId)1633     void set_help_id(const GtkWidget *pWidget, const OString& rHelpId)
1634     {
1635         gchar *helpid = g_strdup(rHelpId.getStr());
1636         g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
1637     }
1638 
get_help_id(const GtkWidget * pWidget)1639     OString get_help_id(const GtkWidget *pWidget)
1640     {
1641         void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
1642         const gchar* pStr = static_cast<const gchar*>(pData);
1643         return OString(pStr, pStr ? strlen(pStr) : 0);
1644     }
1645 
GtkToVcl(const GdkEventKey & rEvent)1646     KeyEvent GtkToVcl(const GdkEventKey& rEvent)
1647     {
1648         sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(rEvent.keyval);
1649         if (nKeyCode == 0)
1650         {
1651             guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), rEvent.hardware_keycode, rEvent.group);
1652             nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
1653         }
1654         nKeyCode |= GtkSalFrame::GetKeyModCode(rEvent.state);
1655         return KeyEvent(gdk_keyval_to_unicode(rEvent.keyval), nKeyCode, 0);
1656     }
1657 }
1658 
ImplGetMouseButtonMode(sal_uInt16 nButton,sal_uInt16 nCode)1659 static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
1660 {
1661     MouseEventModifiers nMode = MouseEventModifiers::NONE;
1662     if ( nButton == MOUSE_LEFT )
1663         nMode |= MouseEventModifiers::SIMPLECLICK;
1664     if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
1665         nMode |= MouseEventModifiers::SELECT;
1666     if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
1667          !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
1668         nMode |= MouseEventModifiers::MULTISELECT;
1669     if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
1670          !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
1671         nMode |= MouseEventModifiers::RANGESELECT;
1672     return nMode;
1673 }
1674 
ImplGetMouseMoveMode(sal_uInt16 nCode)1675 static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
1676 {
1677     MouseEventModifiers nMode = MouseEventModifiers::NONE;
1678     if ( !nCode )
1679         nMode |= MouseEventModifiers::SIMPLEMOVE;
1680     if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
1681         nMode |= MouseEventModifiers::DRAGMOVE;
1682     if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
1683         nMode |= MouseEventModifiers::DRAGCOPY;
1684     return nMode;
1685 }
1686 
1687 namespace
1688 {
1689 #if GTK_CHECK_VERSION(3,22,0)
SwapForRTL(GtkWidget * pWidget)1690     bool SwapForRTL(GtkWidget* pWidget)
1691     {
1692         GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
1693         if (eDir == GTK_TEXT_DIR_RTL)
1694             return true;
1695         if (eDir == GTK_TEXT_DIR_LTR)
1696             return false;
1697         return AllSettings::GetLayoutRTL();
1698     }
1699 #endif
1700 
ensureEventWidget(GtkWidget * pWidget)1701     GtkWidget* ensureEventWidget(GtkWidget* pWidget)
1702     {
1703         if (!pWidget)
1704             return nullptr;
1705 
1706         GtkWidget* pMouseEventBox;
1707         // not every widget has a GdkWindow and can get any event, so if we
1708         // want an event it doesn't have, insert a GtkEventBox so we can get
1709         // those
1710         if (gtk_widget_get_has_window(pWidget))
1711             pMouseEventBox = pWidget;
1712         else
1713         {
1714             // remove the widget and replace it with an eventbox and put the old
1715             // widget into it
1716             GtkWidget* pParent = gtk_widget_get_parent(pWidget);
1717 
1718             g_object_ref(pWidget);
1719 
1720             gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
1721             if (GTK_IS_GRID(pParent))
1722             {
1723                 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
1724                         "left-attach", &nTopAttach,
1725                         "top-attach", &nLeftAttach,
1726                         "width", &nWidth,
1727                         "height", &nHeight,
1728                         nullptr);
1729             }
1730 
1731             gboolean bExpand(false), bFill(false);
1732             GtkPackType ePackType(GTK_PACK_START);
1733             guint nPadding(0);
1734             gint nPosition(0);
1735             if (GTK_IS_BOX(pParent))
1736             {
1737                 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
1738                         "expand", &bExpand,
1739                         "fill", &bFill,
1740                         "pack-type", &ePackType,
1741                         "padding", &nPadding,
1742                         "position", &nPosition,
1743                         nullptr);
1744             }
1745 
1746             gtk_container_remove(GTK_CONTAINER(pParent), pWidget);
1747 
1748             pMouseEventBox = gtk_event_box_new();
1749             gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
1750             gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
1751             gtk_widget_set_visible(pMouseEventBox, gtk_widget_get_visible(pWidget));
1752 
1753             gtk_container_add(GTK_CONTAINER(pParent), pMouseEventBox);
1754 
1755             if (GTK_IS_GRID(pParent))
1756             {
1757                 gtk_container_child_set(GTK_CONTAINER(pParent), pMouseEventBox,
1758                         "left-attach", nTopAttach,
1759                         "top-attach", nLeftAttach,
1760                         "width", nWidth,
1761                         "height", nHeight,
1762                         nullptr);
1763             }
1764 
1765             if (GTK_IS_BOX(pParent))
1766             {
1767                 gtk_container_child_set(GTK_CONTAINER(pParent), pMouseEventBox,
1768                         "expand", bExpand,
1769                         "fill", bFill,
1770                         "pack-type", ePackType,
1771                         "padding", nPadding,
1772                         "position", nPosition,
1773                         nullptr);
1774             }
1775 
1776             gtk_container_add(GTK_CONTAINER(pMouseEventBox), pWidget);
1777             g_object_unref(pWidget);
1778 
1779             gtk_widget_set_hexpand(pMouseEventBox, gtk_widget_get_hexpand(pWidget));
1780             gtk_widget_set_vexpand(pMouseEventBox, gtk_widget_get_vexpand(pWidget));
1781         }
1782 
1783         return pMouseEventBox;
1784     }
1785 }
1786 
1787 class GtkInstanceWidget : public virtual weld::Widget
1788 {
1789 protected:
1790     GtkWidget* m_pWidget;
1791     GtkWidget* m_pMouseEventBox;
1792     GtkInstanceBuilder* m_pBuilder;
1793 
1794     DECL_LINK(async_signal_focus_in, void*, void);
1795     DECL_LINK(async_signal_focus_out, void*, void);
1796 
launch_signal_focus_in()1797     void launch_signal_focus_in()
1798     {
1799         // in e.g. function wizard RefEdits we want to select all when we get focus
1800         // but there are pending gtk handlers which change selection after our handler
1801         // post our focus in event to happen after those finish
1802         if (m_pFocusInEvent)
1803             Application::RemoveUserEvent(m_pFocusInEvent);
1804         m_pFocusInEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_in));
1805     }
1806 
signalFocusIn(GtkWidget *,GdkEvent *,gpointer widget)1807     static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
1808     {
1809         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1810         pThis->launch_signal_focus_in();
1811         return false;
1812     }
1813 
signal_focus_in()1814     void signal_focus_in()
1815     {
1816         m_aFocusInHdl.Call(*this);
1817     }
1818 
signalMnemonicActivate(GtkWidget *,gboolean,gpointer widget)1819     static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
1820     {
1821         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1822         SolarMutexGuard aGuard;
1823         return pThis->signal_mnemonic_activate();
1824     }
1825 
signal_mnemonic_activate()1826     bool signal_mnemonic_activate()
1827     {
1828         return m_aMnemonicActivateHdl.Call(*this);
1829     }
1830 
launch_signal_focus_out()1831     void launch_signal_focus_out()
1832     {
1833         // tdf#127262 because focus in is async, focus out must not appear out
1834         // of sequence to focus in
1835         if (m_pFocusOutEvent)
1836             Application::RemoveUserEvent(m_pFocusOutEvent);
1837         m_pFocusOutEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_out));
1838     }
1839 
signalFocusOut(GtkWidget *,GdkEvent *,gpointer widget)1840     static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
1841     {
1842         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1843         SolarMutexGuard aGuard;
1844         pThis->launch_signal_focus_out();
1845         return false;
1846     }
1847 
signal_focus_out()1848     void signal_focus_out()
1849     {
1850         m_aFocusOutHdl.Call(*this);
1851     }
1852 
ensureEventWidget()1853     void ensureEventWidget()
1854     {
1855         if (!m_pMouseEventBox)
1856             m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
1857     }
1858 
ensureButtonPressSignal()1859     void ensureButtonPressSignal()
1860     {
1861         if (!m_nButtonPressSignalId)
1862         {
1863             ensureEventWidget();
1864             m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButton), this);
1865         }
1866     }
1867 
signalPopupMenu(GtkWidget * pWidget,gpointer widget)1868     static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
1869     {
1870         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1871         SolarMutexGuard aGuard;
1872         //center it when we don't know where else to use
1873         Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
1874                    gtk_widget_get_allocated_height(pWidget) / 2);
1875         CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
1876         return pThis->signal_popup_menu(aCEvt);
1877     }
1878 
SwapForRTL() const1879     bool SwapForRTL() const
1880     {
1881         GtkTextDirection eDir = gtk_widget_get_direction(m_pWidget);
1882         if (eDir == GTK_TEXT_DIR_RTL)
1883             return true;
1884         if (eDir == GTK_TEXT_DIR_LTR)
1885             return false;
1886         return AllSettings::GetLayoutRTL();
1887     }
1888 
localizeDecimalSeparator()1889     void localizeDecimalSeparator()
1890     {
1891         // tdf#128867 if localize decimal separator is active we will always
1892         // need to be able to change the output of the decimal key press
1893         if (!m_nKeyPressSignalId && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
1894             m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
1895     }
1896 
1897 private:
1898     bool m_bTakeOwnership;
1899     bool m_bFrozen;
1900     bool m_bDraggedOver;
1901     sal_uInt16 m_nLastMouseButton;
1902     sal_uInt16 m_nLastMouseClicks;
1903     ImplSVEvent* m_pFocusInEvent;
1904     ImplSVEvent* m_pFocusOutEvent;
1905     GtkCssProvider* m_pBgCssProvider;
1906     gulong m_nFocusInSignalId;
1907     gulong m_nMnemonicActivateSignalId;
1908     gulong m_nFocusOutSignalId;
1909     gulong m_nKeyPressSignalId;
1910     gulong m_nKeyReleaseSignalId;
1911     gulong m_nSizeAllocateSignalId;
1912     gulong m_nButtonPressSignalId;
1913     gulong m_nMotionSignalId;
1914     gulong m_nLeaveSignalId;
1915     gulong m_nEnterSignalId;
1916     gulong m_nButtonReleaseSignalId;
1917     gulong m_nDragMotionSignalId;
1918     gulong m_nDragDropSignalId;
1919     gulong m_nDragDropReceivedSignalId;
1920     gulong m_nDragLeaveSignalId;
1921 
1922     rtl::Reference<GtkDropTarget> m_xDropTarget;
1923     rtl::Reference<GtkDragSource> m_xDragSource;
1924 
signalSizeAllocate(GtkWidget *,GdkRectangle * allocation,gpointer widget)1925     static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
1926     {
1927         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1928         SolarMutexGuard aGuard;
1929         pThis->signal_size_allocate(allocation->width, allocation->height);
1930     }
1931 
signalKey(GtkWidget *,GdkEventKey * pEvent,gpointer widget)1932     static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
1933     {
1934         // #i1820# use locale specific decimal separator
1935         if (pEvent->keyval == GDK_KEY_KP_Decimal && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
1936         {
1937             OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
1938             pEvent->keyval = aSep[0];
1939         }
1940 
1941         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1942         return pThis->signal_key(pEvent);
1943     }
1944 
signal_popup_menu(const CommandEvent &)1945     virtual bool signal_popup_menu(const CommandEvent&)
1946     {
1947         return false;
1948     }
1949 
signalButton(GtkWidget *,GdkEventButton * pEvent,gpointer widget)1950     static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
1951     {
1952         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1953         SolarMutexGuard aGuard;
1954         return pThis->signal_button(pEvent);
1955     }
1956 
signal_button(GdkEventButton * pEvent)1957     bool signal_button(GdkEventButton* pEvent)
1958     {
1959         Point aPos(pEvent->x, pEvent->y);
1960         if (SwapForRTL())
1961             aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
1962 
1963         if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
1964         {
1965             //if handled for context menu, stop processing
1966             CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
1967             if (signal_popup_menu(aCEvt))
1968                 return true;
1969         }
1970 
1971         if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
1972             return false;
1973 
1974         SalEvent nEventType = SalEvent::NONE;
1975         switch (pEvent->type)
1976         {
1977             case GDK_BUTTON_PRESS:
1978                 if (GdkEvent* pPeekEvent = gdk_event_peek())
1979                 {
1980                     bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
1981                                  pPeekEvent->type == GDK_3BUTTON_PRESS;
1982                     gdk_event_free(pPeekEvent);
1983                     if (bSkip)
1984                     {
1985                         return true;
1986                     }
1987                 }
1988                 nEventType = SalEvent::MouseButtonDown;
1989                 m_nLastMouseClicks = 1;
1990                 break;
1991             case GDK_2BUTTON_PRESS:
1992                 m_nLastMouseClicks = 2;
1993                 nEventType = SalEvent::MouseButtonDown;
1994                 break;
1995             case GDK_3BUTTON_PRESS:
1996                 m_nLastMouseClicks = 3;
1997                 nEventType = SalEvent::MouseButtonDown;
1998                 break;
1999             case GDK_BUTTON_RELEASE:
2000                 nEventType = SalEvent::MouseButtonUp;
2001                 break;
2002             default:
2003                 return false;
2004         }
2005 
2006         switch (pEvent->button)
2007         {
2008             case 1:
2009                 m_nLastMouseButton = MOUSE_LEFT;
2010                 break;
2011             case 2:
2012                 m_nLastMouseButton = MOUSE_MIDDLE;
2013                 break;
2014             case 3:
2015                 m_nLastMouseButton = MOUSE_RIGHT;
2016                 break;
2017             default:
2018                 return false;
2019         }
2020 
2021         sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2022         sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2023         MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
2024 
2025         if (nEventType == SalEvent::MouseButtonDown)
2026         {
2027             if (!m_aMousePressHdl.IsSet())
2028                 return false;
2029             return m_aMousePressHdl.Call(aMEvt);
2030         }
2031 
2032         if (!m_aMouseReleaseHdl.IsSet())
2033             return false;
2034         return m_aMouseReleaseHdl.Call(aMEvt);
2035     }
2036 
signalMotion(GtkWidget *,GdkEventMotion * pEvent,gpointer widget)2037     static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
2038     {
2039         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2040         SolarMutexGuard aGuard;
2041         return pThis->signal_motion(pEvent);
2042     }
2043 
signal_motion(const GdkEventMotion * pEvent)2044     bool signal_motion(const GdkEventMotion* pEvent)
2045     {
2046         if (!m_aMouseMotionHdl.IsSet())
2047             return false;
2048 
2049         Point aPos(pEvent->x, pEvent->y);
2050         if (SwapForRTL())
2051             aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2052         sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2053         sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2054         MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nCode, nCode);
2055 
2056         m_aMouseMotionHdl.Call(aMEvt);
2057         return true;
2058     }
2059 
signalCrossing(GtkWidget *,GdkEventCrossing * pEvent,gpointer widget)2060     static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
2061     {
2062         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2063         SolarMutexGuard aGuard;
2064         return pThis->signal_crossing(pEvent);
2065     }
2066 
signal_crossing(const GdkEventCrossing * pEvent)2067     bool signal_crossing(const GdkEventCrossing* pEvent)
2068     {
2069         if (!m_aMouseMotionHdl.IsSet())
2070             return false;
2071 
2072         Point aPos(pEvent->x, pEvent->y);
2073         if (SwapForRTL())
2074             aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2075         sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2076         sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2077         MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
2078         eModifiers = eModifiers | (pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW);
2079         MouseEvent aMEvt(aPos, 0, eModifiers, nCode, nCode);
2080 
2081         m_aMouseMotionHdl.Call(aMEvt);
2082         return true;
2083     }
2084 
drag_started()2085     virtual void drag_started()
2086     {
2087     }
2088 
signalDragMotion(GtkWidget * pWidget,GdkDragContext * context,gint x,gint y,guint time,gpointer widget)2089     static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
2090     {
2091         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2092         if (!pThis->m_bDraggedOver)
2093         {
2094             pThis->m_bDraggedOver = true;
2095             pThis->drag_started();
2096         }
2097         return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
2098     }
2099 
signalDragDrop(GtkWidget * pWidget,GdkDragContext * context,gint x,gint y,guint time,gpointer widget)2100     static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
2101     {
2102         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2103         return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
2104     }
2105 
signalDragDropReceived(GtkWidget * pWidget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint ttype,guint time,gpointer widget)2106     static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
2107     {
2108         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2109         pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
2110     }
2111 
drag_ended()2112     virtual void drag_ended()
2113     {
2114     }
2115 
signalDragLeave(GtkWidget * pWidget,GdkDragContext * context,guint time,gpointer widget)2116     static void signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer widget)
2117     {
2118         GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2119         pThis->m_xDropTarget->signalDragLeave(pWidget, context, time);
2120         if (pThis->m_bDraggedOver)
2121         {
2122             pThis->m_bDraggedOver = false;
2123             pThis->drag_ended();
2124         }
2125     }
2126 
set_background(const OUString * pColor)2127     void set_background(const OUString* pColor)
2128     {
2129         if (!pColor && !m_pBgCssProvider)
2130             return;
2131         GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
2132         if (m_pBgCssProvider)
2133         {
2134             gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
2135             m_pBgCssProvider = nullptr;
2136         }
2137         if (!pColor)
2138             return;
2139         m_pBgCssProvider = gtk_css_provider_new();
2140         OUString aBuffer = "* { background-color: #" + *pColor + "; }";
2141         OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
2142         gtk_css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
2143         gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
2144                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2145     }
2146 
2147 public:
GtkInstanceWidget(GtkWidget * pWidget,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)2148     GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
2149         : m_pWidget(pWidget)
2150         , m_pMouseEventBox(nullptr)
2151         , m_pBuilder(pBuilder)
2152         , m_bTakeOwnership(bTakeOwnership)
2153         , m_bFrozen(false)
2154         , m_bDraggedOver(false)
2155         , m_nLastMouseButton(0)
2156         , m_nLastMouseClicks(0)
2157         , m_pFocusInEvent(nullptr)
2158         , m_pFocusOutEvent(nullptr)
2159         , m_pBgCssProvider(nullptr)
2160         , m_nFocusInSignalId(0)
2161         , m_nMnemonicActivateSignalId(0)
2162         , m_nFocusOutSignalId(0)
2163         , m_nKeyPressSignalId(0)
2164         , m_nKeyReleaseSignalId(0)
2165         , m_nSizeAllocateSignalId(0)
2166         , m_nButtonPressSignalId(0)
2167         , m_nMotionSignalId(0)
2168         , m_nLeaveSignalId(0)
2169         , m_nEnterSignalId(0)
2170         , m_nButtonReleaseSignalId(0)
2171         , m_nDragMotionSignalId(0)
2172         , m_nDragDropSignalId(0)
2173         , m_nDragDropReceivedSignalId(0)
2174         , m_nDragLeaveSignalId(0)
2175     {
2176     }
2177 
connect_key_press(const Link<const KeyEvent &,bool> & rLink)2178     virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
2179     {
2180         if (!m_nKeyPressSignalId)
2181             m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
2182         weld::Widget::connect_key_press(rLink);
2183     }
2184 
connect_key_release(const Link<const KeyEvent &,bool> & rLink)2185     virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
2186     {
2187         if (!m_nKeyReleaseSignalId)
2188             m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
2189         weld::Widget::connect_key_release(rLink);
2190     }
2191 
connect_mouse_press(const Link<const MouseEvent &,bool> & rLink)2192     virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
2193     {
2194         ensureButtonPressSignal();
2195         weld::Widget::connect_mouse_press(rLink);
2196     }
2197 
connect_mouse_move(const Link<const MouseEvent &,bool> & rLink)2198     virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
2199     {
2200         ensureEventWidget();
2201         if (!m_nMotionSignalId)
2202             m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
2203         if (!m_nLeaveSignalId)
2204             m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
2205         if (!m_nEnterSignalId)
2206             m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
2207         weld::Widget::connect_mouse_move(rLink);
2208     }
2209 
connect_mouse_release(const Link<const MouseEvent &,bool> & rLink)2210     virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
2211     {
2212         ensureEventWidget();
2213         if (!m_nButtonReleaseSignalId)
2214             m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButton), this);
2215         weld::Widget::connect_mouse_release(rLink);
2216     }
2217 
set_sensitive(bool sensitive)2218     virtual void set_sensitive(bool sensitive) override
2219     {
2220         gtk_widget_set_sensitive(m_pWidget, sensitive);
2221     }
2222 
get_sensitive() const2223     virtual bool get_sensitive() const override
2224     {
2225         return gtk_widget_get_sensitive(m_pWidget);
2226     }
2227 
get_visible() const2228     virtual bool get_visible() const override
2229     {
2230         return gtk_widget_get_visible(m_pWidget);
2231     }
2232 
is_visible() const2233     virtual bool is_visible() const override
2234     {
2235         return gtk_widget_is_visible(m_pWidget);
2236     }
2237 
set_can_focus(bool bCanFocus)2238     virtual void set_can_focus(bool bCanFocus) override
2239     {
2240         gtk_widget_set_can_focus(m_pWidget, bCanFocus);
2241     }
2242 
grab_focus()2243     virtual void grab_focus() override
2244     {
2245         disable_notify_events();
2246         gtk_widget_grab_focus(m_pWidget);
2247         enable_notify_events();
2248     }
2249 
has_focus() const2250     virtual bool has_focus() const override
2251     {
2252         return gtk_widget_has_focus(m_pWidget);
2253     }
2254 
is_active() const2255     virtual bool is_active() const override
2256     {
2257         GtkWindow* pTopLevel = GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
2258         return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
2259     }
2260 
set_has_default(bool has_default)2261     virtual void set_has_default(bool has_default) override
2262     {
2263         g_object_set(G_OBJECT(m_pWidget), "has-default", has_default, nullptr);
2264     }
2265 
get_has_default() const2266     virtual bool get_has_default() const override
2267     {
2268         gboolean has_default(false);
2269         g_object_get(G_OBJECT(m_pWidget), "has-default", &has_default, nullptr);
2270         return has_default;
2271     }
2272 
show()2273     virtual void show() override
2274     {
2275         gtk_widget_show(m_pWidget);
2276     }
2277 
hide()2278     virtual void hide() override
2279     {
2280         gtk_widget_hide(m_pWidget);
2281     }
2282 
set_size_request(int nWidth,int nHeight)2283     virtual void set_size_request(int nWidth, int nHeight) override
2284     {
2285         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
2286         if (GTK_IS_VIEWPORT(pParent))
2287             pParent = gtk_widget_get_parent(pParent);
2288         if (GTK_IS_SCROLLED_WINDOW(pParent))
2289         {
2290             gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
2291             gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
2292         }
2293         gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
2294     }
2295 
get_size_request() const2296     virtual Size get_size_request() const override
2297     {
2298         int nWidth, nHeight;
2299         gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
2300         return Size(nWidth, nHeight);
2301     }
2302 
get_preferred_size() const2303     virtual Size get_preferred_size() const override
2304     {
2305         GtkRequisition size;
2306         gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
2307         return Size(size.width, size.height);
2308     }
2309 
get_approximate_digit_width() const2310     virtual float get_approximate_digit_width() const override
2311     {
2312         PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2313         PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
2314                                          pango_context_get_font_description(pContext),
2315                                          pango_context_get_language(pContext));
2316         float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
2317         pango_font_metrics_unref(pMetrics);
2318 
2319         return nDigitWidth / PANGO_SCALE;
2320     }
2321 
get_text_height() const2322     virtual int get_text_height() const override
2323     {
2324         PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2325         PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
2326                                          pango_context_get_font_description(pContext),
2327                                          pango_context_get_language(pContext));
2328         int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
2329         pango_font_metrics_unref(pMetrics);
2330         return nLineHeight / PANGO_SCALE;
2331     }
2332 
get_pixel_size(const OUString & rText) const2333     virtual Size get_pixel_size(const OUString& rText) const override
2334     {
2335         OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
2336         PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
2337         gint nWidth, nHeight;
2338         pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
2339         g_object_unref(pLayout);
2340         return Size(nWidth, nHeight);
2341     }
2342 
get_font()2343     virtual vcl::Font get_font() override
2344     {
2345         PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2346         return pango_to_vcl(pango_context_get_font_description(pContext),
2347                             Application::GetSettings().GetUILanguageTag().getLocale());
2348     }
2349 
set_grid_left_attach(int nAttach)2350     virtual void set_grid_left_attach(int nAttach) override
2351     {
2352         GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2353         gtk_container_child_set(pParent, m_pWidget, "left-attach", nAttach, nullptr);
2354     }
2355 
get_grid_left_attach() const2356     virtual int get_grid_left_attach() const override
2357     {
2358         GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2359         gint nAttach(0);
2360         gtk_container_child_get(pParent, m_pWidget, "left-attach", &nAttach, nullptr);
2361         return nAttach;
2362     }
2363 
set_grid_width(int nCols)2364     virtual void set_grid_width(int nCols) override
2365     {
2366         GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2367         gtk_container_child_set(pParent, m_pWidget, "width", nCols, nullptr);
2368     }
2369 
set_grid_top_attach(int nAttach)2370     virtual void set_grid_top_attach(int nAttach) override
2371     {
2372         GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2373         gtk_container_child_set(pParent, m_pWidget, "top-attach", nAttach, nullptr);
2374     }
2375 
get_grid_top_attach() const2376     virtual int get_grid_top_attach() const override
2377     {
2378         GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2379         gint nAttach(0);
2380         gtk_container_child_get(pParent, m_pWidget, "top-attach", &nAttach, nullptr);
2381         return nAttach;
2382     }
2383 
set_hexpand(bool bExpand)2384     virtual void set_hexpand(bool bExpand) override
2385     {
2386         gtk_widget_set_hexpand(m_pWidget, bExpand);
2387     }
2388 
get_hexpand() const2389     virtual bool get_hexpand() const override
2390     {
2391         return gtk_widget_get_hexpand(m_pWidget);
2392     }
2393 
set_vexpand(bool bExpand)2394     virtual void set_vexpand(bool bExpand) override
2395     {
2396         gtk_widget_set_vexpand(m_pWidget, bExpand);
2397     }
2398 
get_vexpand() const2399     virtual bool get_vexpand() const override
2400     {
2401         return gtk_widget_get_vexpand(m_pWidget);
2402     }
2403 
set_secondary(bool bSecondary)2404     virtual void set_secondary(bool bSecondary) override
2405     {
2406         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
2407         if (pParent && GTK_IS_BUTTON_BOX(pParent))
2408             gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(pParent), m_pWidget, bSecondary);
2409     }
2410 
set_margin_top(int nMargin)2411     virtual void set_margin_top(int nMargin) override
2412     {
2413         gtk_widget_set_margin_top(m_pWidget, nMargin);
2414     }
2415 
set_margin_bottom(int nMargin)2416     virtual void set_margin_bottom(int nMargin) override
2417     {
2418         gtk_widget_set_margin_bottom(m_pWidget, nMargin);
2419     }
2420 
set_margin_left(int nMargin)2421     virtual void set_margin_left(int nMargin) override
2422     {
2423         gtk_widget_set_margin_left(m_pWidget, nMargin);
2424     }
2425 
set_margin_right(int nMargin)2426     virtual void set_margin_right(int nMargin) override
2427     {
2428         gtk_widget_set_margin_right(m_pWidget, nMargin);
2429     }
2430 
get_margin_top() const2431     virtual int get_margin_top() const override
2432     {
2433         return gtk_widget_get_margin_top(m_pWidget);
2434     }
2435 
get_margin_bottom() const2436     virtual int get_margin_bottom() const override
2437     {
2438         return gtk_widget_get_margin_bottom(m_pWidget);
2439     }
2440 
get_margin_left() const2441     virtual int get_margin_left() const override
2442     {
2443         return gtk_widget_get_margin_left(m_pWidget);
2444     }
2445 
get_margin_right() const2446     virtual int get_margin_right() const override
2447     {
2448         return gtk_widget_get_margin_right(m_pWidget);
2449     }
2450 
set_accessible_name(const OUString & rName)2451     virtual void set_accessible_name(const OUString& rName) override
2452     {
2453         AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2454         if (!pAtkObject)
2455             return;
2456         atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
2457     }
2458 
get_accessible_name() const2459     virtual OUString get_accessible_name() const override
2460     {
2461         AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2462         const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
2463         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2464     }
2465 
get_accessible_description() const2466     virtual OUString get_accessible_description() const override
2467     {
2468         AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2469         const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
2470         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2471     }
2472 
set_accessible_relation_labeled_by(weld::Widget * pLabel)2473     virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
2474     {
2475         AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2476         if (!pAtkObject)
2477             return;
2478         AtkObject *pAtkLabel = pLabel ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget()) : nullptr;
2479         AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2480         AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
2481         if (pRelation)
2482             atk_relation_set_remove(pRelationSet, pRelation);
2483         if (pAtkLabel)
2484         {
2485             AtkObject *obj_array[1];
2486             obj_array[0] = pAtkLabel;
2487             pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABELLED_BY);
2488             atk_relation_set_add(pRelationSet, pRelation);
2489         }
2490         g_object_unref(pRelationSet);
2491     }
2492 
set_accessible_relation_label_for(weld::Widget * pLabeled)2493     virtual void set_accessible_relation_label_for(weld::Widget* pLabeled) override
2494     {
2495         AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2496         if (!pAtkObject)
2497             return;
2498         AtkObject *pAtkLabeled = pLabeled ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabeled).getWidget()) : nullptr;
2499         AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2500         AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR);
2501         if (pRelation)
2502             atk_relation_set_remove(pRelationSet, pRelation);
2503         if (pAtkLabeled)
2504         {
2505             AtkObject *obj_array[1];
2506             obj_array[0] = pAtkLabeled;
2507             pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABEL_FOR);
2508             atk_relation_set_add(pRelationSet, pRelation);
2509         }
2510         g_object_unref(pRelationSet);
2511     }
2512 
get_extents_relative_to(weld::Widget & rRelative,int & x,int & y,int & width,int & height)2513     virtual bool get_extents_relative_to(weld::Widget& rRelative, int& x, int &y, int& width, int &height) override
2514     {
2515         //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
2516         //the document underneath to auto-scroll to place content in a visible location
2517         bool ret = gtk_widget_translate_coordinates(m_pWidget,
2518                                                         dynamic_cast<GtkInstanceWidget&>(rRelative).getWidget(),
2519                                                         0, 0, &x, &y);
2520         width = gtk_widget_get_allocated_width(m_pWidget);
2521         height = gtk_widget_get_allocated_height(m_pWidget);
2522         return ret;
2523     }
2524 
set_tooltip_text(const OUString & rTip)2525     virtual void set_tooltip_text(const OUString& rTip) override
2526     {
2527         gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
2528     }
2529 
get_tooltip_text() const2530     virtual OUString get_tooltip_text() const override
2531     {
2532         const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
2533         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2534     }
2535 
2536     virtual std::unique_ptr<weld::Container> weld_parent() const override;
2537 
get_buildable_name() const2538     virtual OString get_buildable_name() const override
2539     {
2540         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
2541         return OString(pStr, pStr ? strlen(pStr) : 0);
2542     }
2543 
set_help_id(const OString & rHelpId)2544     virtual void set_help_id(const OString& rHelpId) override
2545     {
2546         ::set_help_id(m_pWidget, rHelpId);
2547     }
2548 
get_help_id() const2549     virtual OString get_help_id() const override
2550     {
2551         OString sRet = ::get_help_id(m_pWidget);
2552         if (sRet.isEmpty())
2553             sRet = OString("null");
2554         return sRet;
2555     }
2556 
getWidget()2557     GtkWidget* getWidget()
2558     {
2559         return m_pWidget;
2560     }
2561 
getWindow()2562     GtkWindow* getWindow()
2563     {
2564         return GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
2565     }
2566 
connect_focus_in(const Link<Widget &,void> & rLink)2567     virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
2568     {
2569         if (!m_nFocusInSignalId)
2570             m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
2571         weld::Widget::connect_focus_in(rLink);
2572     }
2573 
connect_mnemonic_activate(const Link<Widget &,bool> & rLink)2574     virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
2575     {
2576         if (!m_nMnemonicActivateSignalId)
2577             m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
2578         weld::Widget::connect_mnemonic_activate(rLink);
2579     }
2580 
connect_focus_out(const Link<Widget &,void> & rLink)2581     virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
2582     {
2583         if (!m_nFocusOutSignalId)
2584             m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
2585         weld::Widget::connect_focus_out(rLink);
2586     }
2587 
connect_size_allocate(const Link<const Size &,void> & rLink)2588     virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
2589     {
2590         m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size_allocate", G_CALLBACK(signalSizeAllocate), this);
2591         weld::Widget::connect_size_allocate(rLink);
2592     }
2593 
signal_size_allocate(guint nWidth,guint nHeight)2594     virtual void signal_size_allocate(guint nWidth, guint nHeight)
2595     {
2596         m_aSizeAllocateHdl.Call(Size(nWidth, nHeight));
2597     }
2598 
signal_key(const GdkEventKey * pEvent)2599     gboolean signal_key(const GdkEventKey* pEvent)
2600     {
2601         if (pEvent->type == GDK_KEY_PRESS && m_aKeyPressHdl.IsSet())
2602         {
2603             SolarMutexGuard aGuard;
2604             return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
2605         }
2606         if (pEvent->type == GDK_KEY_RELEASE && m_aKeyReleaseHdl.IsSet())
2607         {
2608             SolarMutexGuard aGuard;
2609             return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
2610         }
2611         return false;
2612     }
2613 
grab_add()2614     virtual void grab_add() override
2615     {
2616         gtk_grab_add(m_pWidget);
2617     }
2618 
has_grab() const2619     virtual bool has_grab() const override
2620     {
2621         return gtk_widget_has_grab(m_pWidget);
2622     }
2623 
grab_remove()2624     virtual void grab_remove() override
2625     {
2626         gtk_grab_remove(m_pWidget);
2627     }
2628 
get_direction() const2629     virtual bool get_direction() const override
2630     {
2631         return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
2632     }
2633 
set_direction(bool bRTL)2634     virtual void set_direction(bool bRTL) override
2635     {
2636         gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
2637     }
2638 
freeze()2639     virtual void freeze() override
2640     {
2641         gtk_widget_freeze_child_notify(m_pWidget);
2642         m_bFrozen = true;
2643     }
2644 
thaw()2645     virtual void thaw() override
2646     {
2647         gtk_widget_thaw_child_notify(m_pWidget);
2648         m_bFrozen = false;
2649     }
2650 
get_frozen() const2651     bool get_frozen() const { return m_bFrozen; }
2652 
get_drop_target()2653     virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
2654     {
2655         if (!m_xDropTarget)
2656         {
2657             m_xDropTarget.set(new GtkDropTarget);
2658             if (!gtk_drag_dest_get_track_motion(m_pWidget))
2659             {
2660                 gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
2661                 gtk_drag_dest_set_track_motion(m_pWidget, true);
2662             }
2663             m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
2664             m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
2665             m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
2666             m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
2667         }
2668         return m_xDropTarget.get();
2669     }
2670 
set_stack_background()2671     virtual void set_stack_background() override
2672     {
2673         OUString sColor = Application::GetSettings().GetStyleSettings().GetWindowColor().AsRGBHexString();
2674         set_background(&sColor);
2675     }
2676 
set_highlight_background()2677     virtual void set_highlight_background() override
2678     {
2679         OUString sColor = Application::GetSettings().GetStyleSettings().GetHighlightColor().AsRGBHexString();
2680         set_background(&sColor);
2681     }
2682 
~GtkInstanceWidget()2683     virtual ~GtkInstanceWidget() override
2684     {
2685         if (m_pFocusInEvent)
2686             Application::RemoveUserEvent(m_pFocusInEvent);
2687         if (m_pFocusOutEvent)
2688             Application::RemoveUserEvent(m_pFocusOutEvent);
2689         if (m_nDragMotionSignalId)
2690             g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
2691         if (m_nDragDropSignalId)
2692             g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
2693         if (m_nDragDropReceivedSignalId)
2694             g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
2695         if (m_nDragLeaveSignalId)
2696             g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
2697         if (m_nKeyPressSignalId)
2698             g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
2699         if (m_nKeyReleaseSignalId)
2700             g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
2701         if (m_nButtonPressSignalId)
2702             g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
2703         if (m_nMotionSignalId)
2704             g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
2705         if (m_nLeaveSignalId)
2706             g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
2707         if (m_nEnterSignalId)
2708             g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
2709         if (m_nButtonReleaseSignalId)
2710             g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
2711         if (m_nFocusInSignalId)
2712             g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
2713         if (m_nMnemonicActivateSignalId)
2714             g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
2715         if (m_nFocusOutSignalId)
2716             g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
2717         if (m_nSizeAllocateSignalId)
2718             g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
2719 
2720         set_background(nullptr);
2721 
2722         if (m_pMouseEventBox && m_pMouseEventBox != m_pWidget)
2723         {
2724             // put things back they way we found them
2725             GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
2726 
2727             g_object_ref(m_pWidget);
2728             gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
2729 
2730             gtk_widget_destroy(m_pMouseEventBox);
2731 
2732             gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
2733             g_object_unref(m_pWidget);
2734         }
2735 
2736         if (m_bTakeOwnership)
2737             gtk_widget_destroy(m_pWidget);
2738     }
2739 
disable_notify_events()2740     virtual void disable_notify_events()
2741     {
2742         if (m_nFocusInSignalId)
2743             g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
2744         if (m_nMnemonicActivateSignalId)
2745             g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
2746         if (m_nFocusOutSignalId)
2747             g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
2748         if (m_nSizeAllocateSignalId)
2749             g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
2750     }
2751 
enable_notify_events()2752     virtual void enable_notify_events()
2753     {
2754         if (m_nSizeAllocateSignalId)
2755             g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
2756         if (m_nFocusOutSignalId)
2757             g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
2758         if (m_nMnemonicActivateSignalId)
2759             g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
2760         if (m_nFocusInSignalId)
2761             g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
2762     }
2763 
2764     virtual void help_hierarchy_foreach(const std::function<bool(const OString&)>& func) override;
2765 
strip_mnemonic(const OUString & rLabel) const2766     virtual OUString strip_mnemonic(const OUString &rLabel) const override
2767     {
2768         return rLabel.replaceFirst("_", "");
2769     }
2770 
create_virtual_device() const2771     virtual VclPtr<VirtualDevice> create_virtual_device() const override
2772     {
2773         // create with no separate alpha layer like everything sane does
2774         auto xRet = VclPtr<VirtualDevice>::Create();
2775         xRet->SetBackground(COL_TRANSPARENT);
2776         return xRet;
2777     }
2778 };
2779 
IMPL_LINK_NOARG(GtkInstanceWidget,async_signal_focus_in,void *,void)2780 IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_in, void*, void)
2781 {
2782     m_pFocusInEvent = nullptr;
2783     signal_focus_in();
2784 }
2785 
IMPL_LINK_NOARG(GtkInstanceWidget,async_signal_focus_out,void *,void)2786 IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_out, void*, void)
2787 {
2788     m_pFocusOutEvent = nullptr;
2789     signal_focus_out();
2790 }
2791 
2792 namespace
2793 {
MapToGtkAccelerator(const OUString & rStr)2794     OString MapToGtkAccelerator(const OUString &rStr)
2795     {
2796         return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
2797     }
2798 
get_label(GtkLabel * pLabel)2799     OUString get_label(GtkLabel* pLabel)
2800     {
2801         const gchar* pStr = gtk_label_get_label(pLabel);
2802         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2803     }
2804 
set_label(GtkLabel * pLabel,const OUString & rText)2805     void set_label(GtkLabel* pLabel, const OUString& rText)
2806     {
2807         gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
2808     }
2809 
get_label(GtkButton * pButton)2810     OUString get_label(GtkButton* pButton)
2811     {
2812         const gchar* pStr = gtk_button_get_label(pButton);
2813         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2814     }
2815 
set_label(GtkButton * pButton,const OUString & rText)2816     void set_label(GtkButton* pButton, const OUString& rText)
2817     {
2818         gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
2819     }
2820 
get_title(GtkWindow * pWindow)2821     OUString get_title(GtkWindow* pWindow)
2822     {
2823         const gchar* pStr = gtk_window_get_title(pWindow);
2824         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2825     }
2826 
set_title(GtkWindow * pWindow,const OUString & rTitle)2827     void set_title(GtkWindow* pWindow, const OUString& rTitle)
2828     {
2829         gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
2830     }
2831 
get_primary_text(GtkMessageDialog * pMessageDialog)2832     OUString get_primary_text(GtkMessageDialog* pMessageDialog)
2833     {
2834         gchar* pText = nullptr;
2835         g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
2836         return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
2837     }
2838 
set_primary_text(GtkMessageDialog * pMessageDialog,const OUString & rText)2839     void set_primary_text(GtkMessageDialog* pMessageDialog, const OUString& rText)
2840     {
2841         g_object_set(G_OBJECT(pMessageDialog), "text",
2842             OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
2843             nullptr);
2844     }
2845 
set_secondary_text(GtkMessageDialog * pMessageDialog,const OUString & rText)2846     void set_secondary_text(GtkMessageDialog* pMessageDialog, const OUString& rText)
2847     {
2848         g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
2849                 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
2850                 nullptr);
2851     }
2852 
get_secondary_text(GtkMessageDialog * pMessageDialog)2853     OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
2854     {
2855         gchar* pText = nullptr;
2856         g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
2857         return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
2858     }
2859 }
2860 
2861 namespace
2862 {
load_icon_from_stream(SvMemoryStream & rStream)2863     GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
2864     {
2865         GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new();
2866         gdk_pixbuf_loader_write(pixbuf_loader, static_cast<const guchar*>(rStream.GetData()),
2867                                 rStream.TellEnd(), nullptr);
2868         gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
2869         GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
2870         if (pixbuf)
2871             g_object_ref(pixbuf);
2872         g_object_unref(pixbuf_loader);
2873         return pixbuf;
2874     }
2875 
load_icon_by_name(const OUString & rIconName,const OUString & rIconTheme,const OUString & rUILang)2876     GdkPixbuf* load_icon_by_name(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
2877     {
2878         auto xMemStm = ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
2879         if (!xMemStm)
2880             return nullptr;
2881         return load_icon_from_stream(*xMemStm);
2882     }
2883 }
2884 
load_icon_by_name(const OUString & rIconName)2885 GdkPixbuf* load_icon_by_name(const OUString& rIconName)
2886 {
2887     OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
2888     OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
2889     return load_icon_by_name(rIconName, sIconTheme, sUILang);
2890 }
2891 
2892 namespace
2893 {
getPixbuf(const css::uno::Reference<css::graphic::XGraphic> & rImage)2894     GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
2895     {
2896         Image aImage(rImage);
2897 
2898         OUString sStock(aImage.GetStock());
2899         if (!sStock.isEmpty())
2900             return ::load_icon_by_name(sStock);
2901 
2902         std::unique_ptr<SvMemoryStream> xMemStm(new SvMemoryStream);
2903         vcl::PNGWriter aWriter(aImage.GetBitmapEx());
2904         aWriter.Write(*xMemStm);
2905 
2906         return load_icon_from_stream(*xMemStm);
2907     }
2908 
getPixbuf(const VirtualDevice & rDevice)2909     GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
2910     {
2911         Size aSize(rDevice.GetOutputSizePixel());
2912         cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
2913         double m_fXScale, m_fYScale;
2914         dl_cairo_surface_get_device_scale(orig_surface, &m_fXScale, &m_fYScale);
2915 
2916         cairo_surface_t* surface;
2917         if (m_fXScale != 1.0 || m_fYScale != -1)
2918         {
2919             surface = cairo_surface_create_similar_image(orig_surface,
2920                                                          CAIRO_FORMAT_ARGB32,
2921                                                          aSize.Width(),
2922                                                          aSize.Height());
2923             cairo_t* cr = cairo_create(surface);
2924             cairo_set_source_surface(cr, orig_surface, 0, 0);
2925             cairo_paint(cr);
2926             cairo_destroy(cr);
2927         }
2928         else
2929             surface = orig_surface;
2930 
2931         GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
2932 
2933         if (surface != orig_surface)
2934             cairo_surface_destroy(surface);
2935 
2936         return pRet;
2937     }
2938 
image_new_from_virtual_device(const VirtualDevice & rImageSurface)2939     GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
2940     {
2941         GtkWidget* pImage = nullptr;
2942         if (gtk_check_version(3, 20, 0) == nullptr)
2943         {
2944             cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
2945 
2946             Size aSize(rImageSurface.GetOutputSizePixel());
2947             cairo_surface_t* target = cairo_surface_create_similar(surface,
2948                                                                    cairo_surface_get_content(surface),
2949                                                                    aSize.Width(),
2950                                                                    aSize.Height());
2951 
2952             cairo_t* cr = cairo_create(target);
2953             cairo_set_source_surface(cr, surface, 0, 0);
2954             cairo_paint(cr);
2955             cairo_destroy(cr);
2956             pImage = gtk_image_new_from_surface(target);
2957             cairo_surface_destroy(target);
2958         }
2959         else
2960         {
2961             GdkPixbuf* pixbuf = getPixbuf(rImageSurface);
2962             pImage = gtk_image_new_from_pixbuf(pixbuf);
2963             g_object_unref(pixbuf);
2964         }
2965         return pImage;
2966     }
2967 }
2968 
2969 class MenuHelper
2970 {
2971 protected:
2972     GtkMenu* m_pMenu;
2973     bool m_bTakeOwnership;
2974     std::map<OString, GtkMenuItem*> m_aMap;
2975 private:
2976 
collect(GtkWidget * pItem,gpointer widget)2977     static void collect(GtkWidget* pItem, gpointer widget)
2978     {
2979         GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
2980         if (GtkWidget* pSubMenu = gtk_menu_item_get_submenu(pMenuItem))
2981             gtk_container_foreach(GTK_CONTAINER(pSubMenu), collect, widget);
2982         MenuHelper* pThis = static_cast<MenuHelper*>(widget);
2983         pThis->add_to_map(pMenuItem);
2984     }
2985 
signalActivate(GtkMenuItem * pItem,gpointer widget)2986     static void signalActivate(GtkMenuItem* pItem, gpointer widget)
2987     {
2988         MenuHelper* pThis = static_cast<MenuHelper*>(widget);
2989         SolarMutexGuard aGuard;
2990         pThis->signal_activate(pItem);
2991     }
2992 
2993     virtual void signal_activate(GtkMenuItem* pItem) = 0;
2994 
2995 public:
MenuHelper(GtkMenu * pMenu,bool bTakeOwnership)2996     MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
2997         : m_pMenu(pMenu)
2998         , m_bTakeOwnership(bTakeOwnership)
2999     {
3000         if (!m_pMenu)
3001             return;
3002         gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
3003     }
3004 
add_to_map(GtkMenuItem * pMenuItem)3005     void add_to_map(GtkMenuItem* pMenuItem)
3006     {
3007         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
3008         OString id(pStr, pStr ? strlen(pStr) : 0);
3009         m_aMap[id] = pMenuItem;
3010         g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
3011     }
3012 
remove_from_map(GtkMenuItem * pMenuItem)3013     void remove_from_map(GtkMenuItem* pMenuItem)
3014     {
3015         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
3016         OString id(pStr, pStr ? strlen(pStr) : 0);
3017         auto iter = m_aMap.find(id);
3018         g_signal_handlers_disconnect_by_data(pMenuItem, this);
3019         m_aMap.erase(iter);
3020     }
3021 
disable_item_notify_events()3022     void disable_item_notify_events()
3023     {
3024         for (auto& a : m_aMap)
3025             g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
3026     }
3027 
enable_item_notify_events()3028     void enable_item_notify_events()
3029     {
3030         for (auto& a : m_aMap)
3031             g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
3032     }
3033 
insert_item(int pos,const OUString & rId,const OUString & rStr,const OUString * pIconName,const VirtualDevice * pImageSurface,bool bCheck)3034     void insert_item(int pos, const OUString& rId, const OUString& rStr,
3035                      const OUString* pIconName, const VirtualDevice* pImageSurface,
3036                      bool bCheck)
3037     {
3038         GtkWidget* pImage = nullptr;
3039         if (pIconName && !pIconName->isEmpty())
3040         {
3041             GdkPixbuf* pixbuf = load_icon_by_name(*pIconName);
3042             if (!pixbuf)
3043             {
3044                 pImage = gtk_image_new_from_pixbuf(pixbuf);
3045                 g_object_unref(pixbuf);
3046             }
3047         }
3048         else if (pImageSurface)
3049             pImage = image_new_from_virtual_device(*pImageSurface);
3050 
3051         GtkWidget *pItem;
3052         if (pImage)
3053         {
3054             GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
3055             GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
3056             pItem = bCheck ? gtk_check_menu_item_new() : gtk_menu_item_new();
3057             gtk_container_add(GTK_CONTAINER(pBox), pImage);
3058             gtk_container_add(GTK_CONTAINER(pBox), pLabel);
3059             gtk_container_add(GTK_CONTAINER(pItem), pBox);
3060             gtk_widget_show_all(pItem);
3061         }
3062         else
3063         {
3064             pItem = bCheck ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
3065                            : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
3066         }
3067         gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
3068         gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
3069         gtk_widget_show(pItem);
3070         add_to_map(GTK_MENU_ITEM(pItem));
3071         if (pos != -1)
3072             gtk_menu_reorder_child(m_pMenu, pItem, pos);
3073     }
3074 
insert_separator(int pos,const OUString & rId)3075     void insert_separator(int pos, const OUString& rId)
3076     {
3077         GtkWidget* pItem = gtk_separator_menu_item_new();
3078         gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
3079         gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
3080         gtk_widget_show(pItem);
3081         add_to_map(GTK_MENU_ITEM(pItem));
3082         if (pos != -1)
3083             gtk_menu_reorder_child(m_pMenu, pItem, pos);
3084     }
3085 
remove_item(const OString & rIdent)3086     void remove_item(const OString& rIdent)
3087     {
3088         GtkMenuItem* pMenuItem = m_aMap[rIdent];
3089         remove_from_map(pMenuItem);
3090         gtk_widget_destroy(GTK_WIDGET(pMenuItem));
3091     }
3092 
set_item_sensitive(const OString & rIdent,bool bSensitive)3093     void set_item_sensitive(const OString& rIdent, bool bSensitive)
3094     {
3095         gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
3096     }
3097 
set_item_active(const OString & rIdent,bool bActive)3098     void set_item_active(const OString& rIdent, bool bActive)
3099     {
3100         disable_item_notify_events();
3101         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
3102         enable_item_notify_events();
3103     }
3104 
get_item_active(const OString & rIdent) const3105     bool get_item_active(const OString& rIdent) const
3106     {
3107         return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
3108     }
3109 
set_item_label(const OString & rIdent,const OUString & rText)3110     void set_item_label(const OString& rIdent, const OUString& rText)
3111     {
3112         gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
3113     }
3114 
get_item_label(const OString & rIdent) const3115     OUString get_item_label(const OString& rIdent) const
3116     {
3117         const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
3118         return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
3119     }
3120 
set_item_help_id(const OString & rIdent,const OString & rHelpId)3121     void set_item_help_id(const OString& rIdent, const OString& rHelpId)
3122     {
3123         set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
3124     }
3125 
get_item_help_id(const OString & rIdent) const3126     OString get_item_help_id(const OString& rIdent) const
3127     {
3128         return get_help_id(GTK_WIDGET(m_aMap.find(rIdent)->second));
3129     }
3130 
set_item_visible(const OString & rIdent,bool bShow)3131     void set_item_visible(const OString& rIdent, bool bShow)
3132     {
3133         GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
3134         if (bShow)
3135             gtk_widget_show(pWidget);
3136         else
3137             gtk_widget_hide(pWidget);
3138     }
3139 
clear_items()3140     void clear_items()
3141     {
3142         for (const auto& a : m_aMap)
3143         {
3144             GtkMenuItem* pMenuItem = a.second;
3145             g_signal_handlers_disconnect_by_data(pMenuItem, this);
3146             gtk_widget_destroy(GTK_WIDGET(pMenuItem));
3147         }
3148         m_aMap.clear();
3149     }
3150 
getMenu() const3151     GtkMenu* getMenu() const
3152     {
3153         return m_pMenu;
3154     }
3155 
~MenuHelper()3156     virtual ~MenuHelper()
3157     {
3158         for (auto& a : m_aMap)
3159             g_signal_handlers_disconnect_by_data(a.second, this);
3160         if (m_bTakeOwnership)
3161             gtk_widget_destroy(GTK_WIDGET(m_pMenu));
3162     }
3163 };
3164 
3165 class GtkInstanceSizeGroup : public weld::SizeGroup
3166 {
3167 private:
3168     GtkSizeGroup* m_pGroup;
3169 public:
GtkInstanceSizeGroup()3170     GtkInstanceSizeGroup()
3171         : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
3172     {
3173     }
add_widget(weld::Widget * pWidget)3174     virtual void add_widget(weld::Widget* pWidget) override
3175     {
3176         GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3177         assert(pVclWidget);
3178         gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
3179     }
set_mode(VclSizeGroupMode eVclMode)3180     virtual void set_mode(VclSizeGroupMode eVclMode) override
3181     {
3182         GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
3183         switch (eVclMode)
3184         {
3185             case VclSizeGroupMode::NONE:
3186                 eGtkMode = GTK_SIZE_GROUP_NONE;
3187                 break;
3188             case VclSizeGroupMode::Horizontal:
3189                 eGtkMode = GTK_SIZE_GROUP_HORIZONTAL;
3190                 break;
3191             case VclSizeGroupMode::Vertical:
3192                 eGtkMode = GTK_SIZE_GROUP_VERTICAL;
3193                 break;
3194             case VclSizeGroupMode::Both:
3195                 eGtkMode = GTK_SIZE_GROUP_BOTH;
3196                 break;
3197         }
3198         gtk_size_group_set_mode(m_pGroup, eGtkMode);
3199     }
~GtkInstanceSizeGroup()3200     virtual ~GtkInstanceSizeGroup() override
3201     {
3202         g_object_unref(m_pGroup);
3203     }
3204 };
3205 
3206 namespace
3207 {
3208     class ChildFrame : public WorkWindow
3209     {
3210     public:
ChildFrame(vcl::Window * pParent,WinBits nStyle)3211         ChildFrame(vcl::Window* pParent, WinBits nStyle)
3212             : WorkWindow(pParent, nStyle)
3213         {
3214         }
Resize()3215         virtual void Resize() override
3216         {
3217             WorkWindow::Resize();
3218             if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
3219                 pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
3220         }
3221     };
3222 }
3223 
3224 class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
3225 {
3226 private:
3227     GtkContainer* m_pContainer;
3228 
implResetDefault(GtkWidget * pWidget,gpointer user_data)3229     static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
3230     {
3231         if (GTK_IS_BUTTON(pWidget))
3232             g_object_set(G_OBJECT(pWidget), "has-default", false, nullptr);
3233         if (GTK_IS_CONTAINER(pWidget))
3234             gtk_container_forall(GTK_CONTAINER(pWidget), implResetDefault, user_data);
3235     }
3236 
3237 public:
GtkInstanceContainer(GtkContainer * pContainer,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)3238     GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3239         : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
3240         , m_pContainer(pContainer)
3241     {
3242     }
3243 
getContainer()3244     GtkContainer* getContainer() { return m_pContainer; }
3245 
move(weld::Widget * pWidget,weld::Container * pNewParent)3246     virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
3247     {
3248         GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3249         assert(pGtkWidget);
3250         GtkWidget* pChild = pGtkWidget->getWidget();
3251         g_object_ref(pChild);
3252         gtk_container_remove(getContainer(), pChild);
3253 
3254         GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
3255         assert(!pNewParent || pNewGtkParent);
3256         if (pNewGtkParent)
3257             gtk_container_add(pNewGtkParent->getContainer(), pChild);
3258         g_object_unref(pChild);
3259     }
3260 
recursively_unset_default_buttons()3261     virtual void recursively_unset_default_buttons() override
3262     {
3263         implResetDefault(GTK_WIDGET(m_pContainer), nullptr);
3264     }
3265 
CreateChildFrame()3266     virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
3267     {
3268         // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
3269         // will create a toplevel GtkEventBox window
3270         auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
3271         SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
3272         GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
3273         assert(pGtkFrame);
3274 
3275         // relocate that toplevel GtkEventBox into this widget
3276         GtkWidget* pWindow = pGtkFrame->getWindow();
3277 
3278         GtkWidget* pParent = gtk_widget_get_parent(pWindow);
3279 
3280         g_object_ref(pWindow);
3281         gtk_container_remove(GTK_CONTAINER(pParent), pWindow);
3282         gtk_container_add(m_pContainer, pWindow);
3283         gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
3284         gtk_widget_set_hexpand(pWindow, true);
3285         gtk_widget_set_vexpand(pWindow, true);
3286         gtk_widget_realize(pWindow);
3287         gtk_widget_set_can_focus(pWindow, true);
3288         g_object_unref(pWindow);
3289 
3290         xEmbedWindow->Show();
3291         css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
3292         return xWindow;
3293     }
3294 };
3295 
weld_parent() const3296 std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
3297 {
3298     GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3299     if (!pParent)
3300         return nullptr;
3301     return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
3302 }
3303 
3304 class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
3305 {
3306 private:
3307     GtkBox* m_pBox;
3308 
3309 public:
GtkInstanceBox(GtkBox * pBox,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)3310     GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3311         : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
3312         , m_pBox(pBox)
3313     {
3314     }
3315 
reorder_child(weld::Widget * pWidget,int nNewPosition)3316     virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
3317     {
3318         GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3319         assert(pGtkWidget);
3320         GtkWidget* pChild = pGtkWidget->getWidget();
3321         gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
3322     }
3323 };
3324 
3325 namespace
3326 {
set_cursor(GtkWidget * pWidget,const char * pName)3327     void set_cursor(GtkWidget* pWidget, const char *pName)
3328     {
3329         if (!gtk_widget_get_realized(pWidget))
3330             gtk_widget_realize(pWidget);
3331         GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
3332         GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
3333         gdk_window_set_cursor(gtk_widget_get_window(pWidget), pCursor);
3334         gdk_display_flush(pDisplay);
3335         if (pCursor)
3336             g_object_unref(pCursor);
3337     }
3338 }
3339 
3340 namespace
3341 {
3342     struct ButtonOrder
3343     {
3344         const char * m_aType;
3345         int m_nPriority;
3346     };
3347 
getButtonPriority(const OString & rType)3348     int getButtonPriority(const OString &rType)
3349     {
3350         static const size_t N_TYPES = 7;
3351         static const ButtonOrder aDiscardCancelSave[N_TYPES] =
3352         {
3353             { "/discard", 0 },
3354             { "/cancel", 1 },
3355             { "/no", 2 },
3356             { "/open", 3 },
3357             { "/save", 3 },
3358             { "/yes", 3 },
3359             { "/ok", 3 }
3360         };
3361 
3362         static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
3363         {
3364             { "/open", 0 },
3365             { "/save", 0 },
3366             { "/yes", 0 },
3367             { "/ok", 0 },
3368             { "/discard", 1 },
3369             { "/no", 1 },
3370             { "/cancel", 2 }
3371         };
3372 
3373         const ButtonOrder* pOrder = &aDiscardCancelSave[0];
3374 
3375         const OUString &rEnv = Application::GetDesktopEnvironment();
3376 
3377         if (rEnv.equalsIgnoreAsciiCase("windows") ||
3378             rEnv.equalsIgnoreAsciiCase("tde") ||
3379             rEnv.startsWithIgnoreAsciiCase("kde"))
3380         {
3381             pOrder = &aSaveDiscardCancel[0];
3382         }
3383 
3384         for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
3385         {
3386             if (rType.endsWith(pOrder->m_aType))
3387                 return pOrder->m_nPriority;
3388         }
3389 
3390         return -1;
3391     }
3392 
sortButtons(const GtkWidget * pA,const GtkWidget * pB)3393     bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
3394     {
3395         //order within groups according to platform rules
3396         return getButtonPriority(::get_help_id(pA)) < getButtonPriority(::get_help_id(pB));
3397     }
3398 
sort_native_button_order(GtkBox * pContainer)3399     void sort_native_button_order(GtkBox* pContainer)
3400     {
3401         std::vector<GtkWidget*> aChildren;
3402         GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
3403         for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
3404             aChildren.push_back(static_cast<GtkWidget*>(pChild->data));
3405         g_list_free(pChildren);
3406 
3407         //sort child order within parent so that we match the platform button order
3408         std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
3409 
3410         for (size_t pos = 0; pos < aChildren.size(); ++pos)
3411             gtk_box_reorder_child(pContainer, aChildren[pos], pos);
3412     }
3413 
get_csd_offset(GtkWidget * pTopLevel)3414     Point get_csd_offset(GtkWidget* pTopLevel)
3415     {
3416         // try and omit drawing CSD under wayland
3417         GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTopLevel));
3418         GList* pChild = g_list_first(pChildren);
3419 
3420         int x, y;
3421         gtk_widget_translate_coordinates(GTK_WIDGET(pChild->data),
3422                                          GTK_WIDGET(pTopLevel),
3423                                          0, 0, &x, &y);
3424 
3425         int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild->data));
3426         g_list_free(pChildren);
3427 
3428         int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
3429         int totalborder = outerborder + innerborder;
3430         x -= totalborder;
3431         y -= totalborder;
3432 
3433         return Point(x, y);
3434     }
3435 
do_collect_screenshot_data(GtkWidget * pItem,gpointer data)3436     void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
3437     {
3438         GtkWidget* pTopLevel = gtk_widget_get_toplevel(pItem);
3439 
3440         int x, y;
3441         gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
3442 
3443         Point aOffset = get_csd_offset(pTopLevel);
3444 
3445         GtkAllocation alloc;
3446         gtk_widget_get_allocation(pItem, &alloc);
3447 
3448         const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
3449         const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
3450 
3451         if (!aCurrentRange.isEmpty())
3452         {
3453             weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
3454             pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
3455         }
3456 
3457         if (GTK_IS_CONTAINER(pItem))
3458             gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
3459     }
3460 }
3461 
3462 class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
3463 {
3464 private:
3465     GtkWindow* m_pWindow;
3466     rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
3467     gulong m_nToplevelFocusChangedSignalId;
3468 
help_pressed(GtkAccelGroup *,GObject *,guint,GdkModifierType,gpointer widget)3469     static void help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
3470     {
3471         GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
3472         pThis->help();
3473     }
3474 
signalToplevelFocusChanged(GtkWindow *,GParamSpec *,gpointer widget)3475     static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
3476     {
3477         GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
3478         pThis->signal_toplevel_focus_changed();
3479     }
3480 
3481 protected:
3482     void help();
3483 public:
GtkInstanceWindow(GtkWindow * pWindow,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)3484     GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3485         : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
3486         , m_pWindow(pWindow)
3487         , m_nToplevelFocusChangedSignalId(0)
3488     {
3489         //hook up F1 to show help
3490         GtkAccelGroup *pGroup = gtk_accel_group_new();
3491         GClosure* closure = g_cclosure_new(G_CALLBACK(help_pressed), this, nullptr);
3492         gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
3493         gtk_window_add_accel_group(pWindow, pGroup);
3494     }
3495 
set_title(const OUString & rTitle)3496     virtual void set_title(const OUString& rTitle) override
3497     {
3498         ::set_title(m_pWindow, rTitle);
3499     }
3500 
get_title() const3501     virtual OUString get_title() const override
3502     {
3503         return ::get_title(m_pWindow);
3504     }
3505 
GetXWindow()3506     virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
3507     {
3508         if (!m_xWindow.is())
3509             m_xWindow.set(new SalGtkXWindow(this, m_pWidget));
3510         return css::uno::Reference<css::awt::XWindow>(m_xWindow.get());
3511     }
3512 
set_busy_cursor(bool bBusy)3513     virtual void set_busy_cursor(bool bBusy) override
3514     {
3515         set_cursor(m_pWidget, bBusy ? "progress" : nullptr);
3516     }
3517 
set_modal(bool bModal)3518     virtual void set_modal(bool bModal) override
3519     {
3520         gtk_window_set_modal(m_pWindow, bModal);
3521     }
3522 
get_modal() const3523     virtual bool get_modal() const override
3524     {
3525         return gtk_window_get_modal(m_pWindow);
3526     }
3527 
resize_to_request()3528     virtual void resize_to_request() override
3529     {
3530         gtk_window_resize(m_pWindow, 1, 1);
3531     }
3532 
window_move(int x,int y)3533     virtual void window_move(int x, int y) override
3534     {
3535         gtk_window_move(m_pWindow, x, y);
3536     }
3537 
get_system_data() const3538     virtual SystemEnvData get_system_data() const override
3539     {
3540         assert(false && "nothing should call this impl, yet anyway, if ever");
3541         return SystemEnvData();
3542     }
3543 
get_size() const3544     virtual Size get_size() const override
3545     {
3546         int current_width, current_height;
3547         gtk_window_get_size(m_pWindow, &current_width, &current_height);
3548         return Size(current_width, current_height);
3549     }
3550 
get_position() const3551     virtual Point get_position() const override
3552     {
3553         int current_x, current_y;
3554         gtk_window_get_position(m_pWindow, &current_x, &current_y);
3555         return Point(current_x, current_y);
3556     }
3557 
get_monitor_workarea() const3558     virtual tools::Rectangle get_monitor_workarea() const override
3559     {
3560         GdkScreen* pScreen = gtk_widget_get_screen(GTK_WIDGET(m_pWindow));
3561         gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, gtk_widget_get_window(GTK_WIDGET(m_pWindow)));
3562         GdkRectangle aRect;
3563         gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
3564         return tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height);
3565     }
3566 
set_centered_on_parent(bool bTrackGeometryRequests)3567     virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
3568     {
3569         if (bTrackGeometryRequests)
3570             gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ALWAYS);
3571         else
3572             gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ON_PARENT);
3573     }
3574 
get_resizable() const3575     virtual bool get_resizable() const override
3576     {
3577         return gtk_window_get_resizable(m_pWindow);
3578     }
3579 
has_toplevel_focus() const3580     virtual bool has_toplevel_focus() const override
3581     {
3582         return gtk_window_has_toplevel_focus(m_pWindow);
3583     }
3584 
present()3585     virtual void present() override
3586     {
3587         gtk_window_present(m_pWindow);
3588     }
3589 
set_window_state(const OString & rStr)3590     virtual void set_window_state(const OString& rStr) override
3591     {
3592         WindowStateData aData;
3593         ImplWindowStateFromStr( aData, rStr );
3594 
3595         auto nMask = aData.GetMask();
3596         auto nState = aData.GetState() & WindowStateState::SystemMask;
3597 
3598         if (nMask & WindowStateMask::Width && nMask & WindowStateMask::Height)
3599         {
3600             gtk_window_set_default_size(m_pWindow, aData.GetWidth(), aData.GetHeight());
3601         }
3602         if (nMask & WindowStateMask::State)
3603         {
3604             if (nState & WindowStateState::Maximized)
3605                 gtk_window_maximize(m_pWindow);
3606             else
3607                 gtk_window_unmaximize(m_pWindow);
3608         }
3609     }
3610 
get_window_state(WindowStateMask nMask) const3611     virtual OString get_window_state(WindowStateMask nMask) const override
3612     {
3613         bool bPositioningAllowed = true;
3614 #if defined(GDK_WINDOWING_WAYLAND)
3615         // drop x/y when under wayland
3616         GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
3617         bPositioningAllowed = !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
3618 #endif
3619 
3620         WindowStateData aData;
3621         WindowStateMask nAvailable = WindowStateMask::State |
3622                                      WindowStateMask::Width | WindowStateMask::Height;
3623         if (bPositioningAllowed)
3624             nAvailable |= WindowStateMask::X | WindowStateMask::Y;
3625         aData.SetMask(nMask & nAvailable);
3626 
3627         if (nMask & WindowStateMask::State)
3628         {
3629             WindowStateState nState = WindowStateState::Normal;
3630             if (gtk_window_is_maximized(m_pWindow))
3631                 nState |= WindowStateState::Maximized;
3632             aData.SetState(nState);
3633         }
3634 
3635         if (bPositioningAllowed && (nMask & (WindowStateMask::X | WindowStateMask::Y)))
3636         {
3637             auto aPos = get_position();
3638             aData.SetX(aPos.X());
3639             aData.SetY(aPos.Y());
3640         }
3641 
3642         if (nMask & (WindowStateMask::Width | WindowStateMask::Height))
3643         {
3644             auto aSize = get_size();
3645             aData.SetWidth(aSize.Width());
3646             aData.SetHeight(aSize.Height());
3647         }
3648 
3649         return aData.ToStr();
3650     }
3651 
connect_toplevel_focus_changed(const Link<weld::Widget &,void> & rLink)3652     virtual void connect_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink) override
3653     {
3654         assert(!m_nToplevelFocusChangedSignalId);
3655         m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
3656         weld::Window::connect_toplevel_focus_changed(rLink);
3657     }
3658 
disable_notify_events()3659     virtual void disable_notify_events() override
3660     {
3661         if (m_nToplevelFocusChangedSignalId)
3662             g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
3663         GtkInstanceContainer::disable_notify_events();
3664     }
3665 
enable_notify_events()3666     virtual void enable_notify_events() override
3667     {
3668         GtkInstanceContainer::enable_notify_events();
3669         if (m_nToplevelFocusChangedSignalId)
3670             g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
3671     }
3672 
draw(VirtualDevice & rOutput)3673     virtual void draw(VirtualDevice& rOutput) override
3674     {
3675         // detect if we have to manually setup its size
3676         bool bAlreadyRealized = gtk_widget_get_realized(GTK_WIDGET(m_pWindow));
3677         // has to be visible for draw to work
3678         bool bAlreadyVisible = gtk_widget_get_visible(GTK_WIDGET(m_pWindow));
3679         if (!bAlreadyVisible)
3680         {
3681             if (GTK_IS_DIALOG(m_pWindow))
3682                 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow))));
3683             gtk_widget_show(GTK_WIDGET(m_pWindow));
3684         }
3685 
3686         if (!bAlreadyRealized)
3687         {
3688             GtkAllocation allocation;
3689             gtk_widget_realize(GTK_WIDGET(m_pWindow));
3690             gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
3691             gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
3692         }
3693 
3694         rOutput.SetOutputSizePixel(get_size());
3695         cairo_surface_t* pSurface = get_underlying_cairo_surface(rOutput);
3696         cairo_t* cr = cairo_create(pSurface);
3697 
3698         Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
3699 
3700 #if defined(GDK_WINDOWING_X11)
3701         GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pWindow));
3702         if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
3703             assert(aOffset.X() == 0 && aOffset.Y() == 0 && "expected offset of 0 under X");
3704 #endif
3705 
3706         cairo_translate(cr, -aOffset.X(), -aOffset.Y());
3707 
3708         gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
3709 
3710         cairo_destroy(cr);
3711 
3712         if (!bAlreadyVisible)
3713             gtk_widget_hide(GTK_WIDGET(m_pWindow));
3714         if (!bAlreadyRealized)
3715             gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
3716     }
3717 
collect_screenshot_data()3718     virtual weld::ScreenShotCollection collect_screenshot_data() override
3719     {
3720         weld::ScreenShotCollection aRet;
3721 
3722         gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
3723 
3724         return aRet;
3725     }
3726 
~GtkInstanceWindow()3727     virtual ~GtkInstanceWindow() override
3728     {
3729         if (m_nToplevelFocusChangedSignalId)
3730             g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
3731         if (m_xWindow.is())
3732             m_xWindow->clear();
3733     }
3734 };
3735 
3736 class GtkInstanceDialog;
3737 
3738 struct DialogRunner
3739 {
3740     GtkWindow* m_pDialog;
3741     GtkInstanceDialog *m_pInstance;
3742     gint m_nResponseId;
3743     GMainLoop *m_pLoop;
3744     VclPtr<vcl::Window> m_xFrameWindow;
3745     int m_nModalDepth;
3746 
DialogRunnerDialogRunner3747     DialogRunner(GtkWindow* pDialog, GtkInstanceDialog* pInstance)
3748        : m_pDialog(pDialog)
3749        , m_pInstance(pInstance)
3750        , m_nResponseId(GTK_RESPONSE_NONE)
3751        , m_pLoop(nullptr)
3752        , m_nModalDepth(0)
3753     {
3754         GtkWindow* pParent = gtk_window_get_transient_for(m_pDialog);
3755         GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
3756         m_xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
3757     }
3758 
loop_is_runningDialogRunner3759     bool loop_is_running() const
3760     {
3761         return m_pLoop && g_main_loop_is_running(m_pLoop);
3762     }
3763 
loop_quitDialogRunner3764     void loop_quit()
3765     {
3766         if (g_main_loop_is_running(m_pLoop))
3767             g_main_loop_quit(m_pLoop);
3768     }
3769 
3770     static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
3771     static void signal_cancel(GtkAssistant*, gpointer data);
3772 
signal_deleteDialogRunner3773     static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
3774     {
3775         DialogRunner* pThis = static_cast<DialogRunner*>(data);
3776         if (GTK_IS_ASSISTANT(pThis->m_pDialog))
3777         {
3778             // An assistant isn't a dialog, but we want to treat it like one
3779             signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
3780         }
3781         else
3782             pThis->loop_quit();
3783         return true; /* Do not destroy */
3784     }
3785 
signal_destroyDialogRunner3786     static void signal_destroy(GtkDialog*, gpointer data)
3787     {
3788         DialogRunner* pThis = static_cast<DialogRunner*>(data);
3789         pThis->loop_quit();
3790     }
3791 
inc_modal_countDialogRunner3792     void inc_modal_count()
3793     {
3794         if (m_xFrameWindow)
3795         {
3796             m_xFrameWindow->IncModalCount();
3797             if (m_nModalDepth == 0)
3798                 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
3799             ++m_nModalDepth;
3800         }
3801     }
3802 
dec_modal_countDialogRunner3803     void dec_modal_count()
3804     {
3805         if (m_xFrameWindow)
3806         {
3807             m_xFrameWindow->DecModalCount();
3808             --m_nModalDepth;
3809             if (m_nModalDepth == 0)
3810                 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
3811         }
3812     }
3813 
3814     // same as gtk_dialog_run except that unmap doesn't auto-respond
3815     // so we can hide the dialog and restore it without a response getting
3816     // triggered
runDialogRunner3817     gint run()
3818     {
3819         g_object_ref(m_pDialog);
3820 
3821         inc_modal_count();
3822 
3823         bool bWasModal = gtk_window_get_modal(m_pDialog);
3824         if (!bWasModal)
3825             gtk_window_set_modal(m_pDialog, true);
3826 
3827         if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
3828             gtk_widget_show(GTK_WIDGET(m_pDialog));
3829 
3830         gulong nSignalResponseId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signal_response), this) : 0;
3831         gulong nSignalCancelId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signal_cancel), this) : 0;
3832         gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
3833         gulong nSignalDestroyId = g_signal_connect(m_pDialog, "destroy", G_CALLBACK(signal_destroy), this);
3834 
3835         m_pLoop = g_main_loop_new(nullptr, false);
3836         m_nResponseId = GTK_RESPONSE_NONE;
3837 
3838         gdk_threads_leave();
3839         g_main_loop_run(m_pLoop);
3840         gdk_threads_enter();
3841 
3842         g_main_loop_unref(m_pLoop);
3843 
3844         m_pLoop = nullptr;
3845 
3846         if (!bWasModal)
3847             gtk_window_set_modal(m_pDialog, false);
3848 
3849         if (nSignalResponseId)
3850             g_signal_handler_disconnect(m_pDialog, nSignalResponseId);
3851         if (nSignalCancelId)
3852             g_signal_handler_disconnect(m_pDialog, nSignalCancelId);
3853         g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
3854         g_signal_handler_disconnect(m_pDialog, nSignalDestroyId);
3855 
3856         dec_modal_count();
3857 
3858         g_object_unref(m_pDialog);
3859 
3860         return m_nResponseId;
3861     }
3862 
~DialogRunnerDialogRunner3863     ~DialogRunner()
3864     {
3865         if (m_xFrameWindow && m_nModalDepth)
3866         {
3867             // if, like the calc validation dialog does, the modality was
3868             // toggled off during execution ensure that on cleanup the parent
3869             // is left in the state it was found
3870             while (m_nModalDepth++ < 0)
3871                 m_xFrameWindow->IncModalCount();
3872         }
3873     }
3874 };
3875 
3876 typedef std::set<GtkWidget*> winset;
3877 
3878 namespace
3879 {
hideUnless(GtkContainer * pTop,const winset & rVisibleWidgets,std::vector<GtkWidget * > & rWasVisibleWidgets)3880     void hideUnless(GtkContainer *pTop, const winset& rVisibleWidgets,
3881         std::vector<GtkWidget*> &rWasVisibleWidgets)
3882     {
3883         GList* pChildren = gtk_container_get_children(pTop);
3884         for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
3885         {
3886             GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
3887             if (!gtk_widget_get_visible(pChild))
3888                 continue;
3889             if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
3890             {
3891                 g_object_ref(pChild);
3892                 rWasVisibleWidgets.emplace_back(pChild);
3893                 gtk_widget_hide(pChild);
3894             }
3895             else if (GTK_IS_CONTAINER(pChild))
3896             {
3897                 hideUnless(GTK_CONTAINER(pChild), rVisibleWidgets, rWasVisibleWidgets);
3898             }
3899         }
3900         g_list_free(pChildren);
3901     }
3902 }
3903 
3904 class GtkInstanceButton;
3905 
3906 class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
3907 {
3908 private:
3909     GtkWindow* m_pDialog;
3910     DialogRunner m_aDialogRun;
3911     std::shared_ptr<weld::DialogController> m_xDialogController;
3912     // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
3913     std::shared_ptr<weld::Dialog> m_xRunAsyncSelf;
3914     std::function<void(sal_Int32)> m_aFunc;
3915     gulong m_nCloseSignalId;
3916     gulong m_nResponseSignalId;
3917     gulong m_nCancelSignalId;
3918     gulong m_nSignalDeleteId;
3919 
3920     // for calc ref dialog that shrink to range selection widgets and resize back
3921     GtkWidget* m_pRefEdit;
3922     std::vector<GtkWidget*> m_aHiddenWidgets;    // vector of hidden Controls
3923     int m_nOldEditWidth;  // Original width of the input field
3924     int m_nOldEditWidthReq; // Original width request of the input field
3925     int m_nOldBorderWidth; // border width for expanded dialog
3926 
signal_close()3927     void signal_close()
3928     {
3929         close(true);
3930     }
3931 
signalClose(GtkWidget *,gpointer widget)3932     static void signalClose(GtkWidget*, gpointer widget)
3933     {
3934         GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3935         pThis->signal_close();
3936     }
3937 
signalAsyncResponse(GtkWidget *,gint ret,gpointer widget)3938     static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
3939     {
3940         GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3941         pThis->asyncresponse(ret);
3942     }
3943 
signalAsyncCancel(GtkAssistant *,gpointer widget)3944     static void signalAsyncCancel(GtkAssistant*, gpointer widget)
3945     {
3946         GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3947         // make esc in an assistant act as if cancel button was pressed
3948         pThis->close(false);
3949     }
3950 
signalAsyncDelete(GtkWidget * pDialog,GdkEventAny *,gpointer widget)3951     static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
3952     {
3953         GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3954         if (GTK_IS_ASSISTANT(pThis->m_pDialog))
3955         {
3956             // An assistant isn't a dialog, but we want to treat it like one
3957             signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
3958         }
3959         return true; /* Do not destroy */
3960     }
3961 
GtkToVcl(int ret)3962     static int GtkToVcl(int ret)
3963     {
3964         if (ret == GTK_RESPONSE_OK)
3965             ret = RET_OK;
3966         else if (ret == GTK_RESPONSE_CANCEL)
3967             ret = RET_CANCEL;
3968         else if (ret == GTK_RESPONSE_DELETE_EVENT)
3969             ret = RET_CANCEL;
3970         else if (ret == GTK_RESPONSE_CLOSE)
3971             ret = RET_CLOSE;
3972         else if (ret == GTK_RESPONSE_YES)
3973             ret = RET_YES;
3974         else if (ret == GTK_RESPONSE_NO)
3975             ret = RET_NO;
3976         else if (ret == GTK_RESPONSE_HELP)
3977             ret = RET_HELP;
3978         return ret;
3979     }
3980 
VclToGtk(int nResponse)3981     static int VclToGtk(int nResponse)
3982     {
3983         if (nResponse == RET_OK)
3984             return GTK_RESPONSE_OK;
3985         else if (nResponse == RET_CANCEL)
3986             return GTK_RESPONSE_CANCEL;
3987         else if (nResponse == RET_CLOSE)
3988             return GTK_RESPONSE_CLOSE;
3989         else if (nResponse == RET_YES)
3990             return GTK_RESPONSE_YES;
3991         else if (nResponse == RET_NO)
3992             return GTK_RESPONSE_NO;
3993         else if (nResponse == RET_HELP)
3994             return GTK_RESPONSE_HELP;
3995         return nResponse;
3996     }
3997 
3998     void asyncresponse(gint ret);
3999 
signalActivate(GtkMenuItem *,gpointer data)4000     static void signalActivate(GtkMenuItem*, gpointer data)
4001     {
4002         bool* pActivate = static_cast<bool*>(data);
4003         *pActivate = true;
4004     }
4005 
signal_screenshot_popup_menu(GdkEventButton * pEvent)4006     bool signal_screenshot_popup_menu(GdkEventButton* pEvent)
4007     {
4008         GtkWidget *pMenu = gtk_menu_new();
4009 
4010         GtkWidget* pMenuItem = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT)).getStr());
4011         gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
4012         bool bActivate(false);
4013         g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), &bActivate);
4014         gtk_widget_show(pMenuItem);
4015 
4016         int button, event_time;
4017         if (pEvent)
4018         {
4019             button = pEvent->button;
4020             event_time = pEvent->time;
4021         }
4022         else
4023         {
4024             button = 0;
4025             event_time = gtk_get_current_event_time();
4026         }
4027 
4028         gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
4029 
4030         GMainLoop* pLoop = g_main_loop_new(nullptr, true);
4031         gulong nSignalId = g_signal_connect_swapped(G_OBJECT(pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
4032 
4033         gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
4034 
4035         if (g_main_loop_is_running(pLoop))
4036         {
4037             gdk_threads_leave();
4038             g_main_loop_run(pLoop);
4039             gdk_threads_enter();
4040         }
4041 
4042         g_main_loop_unref(pLoop);
4043         g_signal_handler_disconnect(pMenu, nSignalId);
4044         gtk_menu_detach(GTK_MENU(pMenu));
4045 
4046         if (bActivate)
4047         {
4048             // open screenshot annotation dialog
4049             VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
4050             VclPtr<AbstractScreenshotAnnotationDlg> xTmp = pFact->CreateScreenshotAnnotationDlg(*this);
4051             ScopedVclPtr<AbstractScreenshotAnnotationDlg> xDialog(xTmp);
4052             xDialog->Execute();
4053         }
4054 
4055         return false;
4056     }
4057 
signalScreenshotPopupMenu(GtkWidget *,gpointer widget)4058     static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
4059     {
4060         GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4061         return pThis->signal_screenshot_popup_menu(nullptr);
4062     }
4063 
signalScreenshotButton(GtkWidget *,GdkEventButton * pEvent,gpointer widget)4064     static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
4065     {
4066         GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4067         SolarMutexGuard aGuard;
4068         return pThis->signal_screenshot_button(pEvent);
4069     }
4070 
signal_screenshot_button(GdkEventButton * pEvent)4071     bool signal_screenshot_button(GdkEventButton* pEvent)
4072     {
4073         if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
4074         {
4075             //if handled for context menu, stop processing
4076             return signal_screenshot_popup_menu(pEvent);
4077         }
4078         return false;
4079     }
4080 
4081 public:
GtkInstanceDialog(GtkWindow * pDialog,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)4082     GtkInstanceDialog(GtkWindow* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4083         : GtkInstanceWindow(pDialog, pBuilder, bTakeOwnership)
4084         , m_pDialog(pDialog)
4085         , m_aDialogRun(pDialog, this)
4086         , m_nResponseSignalId(0)
4087         , m_nCancelSignalId(0)
4088         , m_nSignalDeleteId(0)
4089         , m_pRefEdit(nullptr)
4090         , m_nOldEditWidth(0)
4091         , m_nOldEditWidthReq(0)
4092         , m_nOldBorderWidth(0)
4093     {
4094         if (GTK_IS_DIALOG(m_pDialog) || GTK_IS_ASSISTANT(m_pDialog))
4095             m_nCloseSignalId = g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this);
4096         else
4097             m_nCloseSignalId = 0;
4098         const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
4099         if (bScreenshotMode)
4100         {
4101             g_signal_connect(m_pDialog, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu), this);
4102             g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
4103         }
4104     }
4105 
runAsync(std::shared_ptr<weld::DialogController> rDialogController,const std::function<void (sal_Int32)> & func)4106     virtual bool runAsync(std::shared_ptr<weld::DialogController> rDialogController, const std::function<void(sal_Int32)>& func) override
4107     {
4108         assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4109 
4110         m_xDialogController = rDialogController;
4111         m_aFunc = func;
4112 
4113         if (get_modal())
4114             m_aDialogRun.inc_modal_count();
4115         show();
4116 
4117         m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
4118         m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
4119         m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
4120 
4121         return true;
4122     }
4123 
runAsync(std::shared_ptr<Dialog> const & rxSelf,const std::function<void (sal_Int32)> & func)4124     virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
4125     {
4126         assert( rxSelf.get() == this );
4127         assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4128 
4129         // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
4130         // which is that rxSelf enforces.
4131         m_xRunAsyncSelf = rxSelf;
4132         m_aFunc = func;
4133 
4134         if (get_modal())
4135             m_aDialogRun.inc_modal_count();
4136         show();
4137 
4138         m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
4139         m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
4140         m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
4141 
4142         return true;
4143     }
4144 
4145     GtkInstanceButton* has_click_handler(int nResponse);
4146 
4147     virtual int run() override;
4148 
show()4149     virtual void show() override
4150     {
4151         if (gtk_widget_get_visible(m_pWidget))
4152             return;
4153         if (GTK_IS_DIALOG(m_pDialog))
4154             sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
4155         GtkInstanceWindow::show();
4156     }
4157 
set_modal(bool bModal)4158     virtual void set_modal(bool bModal) override
4159     {
4160         if (get_modal() == bModal)
4161             return;
4162         GtkInstanceWindow::set_modal(bModal);
4163         /* if change the dialog modality while its running, then also change the parent LibreOffice window
4164            modal count, we typically expect the dialog modality to be restored to its original state
4165 
4166            This change modality while running case is for...
4167 
4168            a) the calc/chart dialogs which put up an extra range chooser
4169            dialog, hides the original, the user can select a range of cells and
4170            on completion the original dialog is restored
4171 
4172            b) the validity dialog in calc
4173         */
4174         if (m_aDialogRun.loop_is_running())
4175         {
4176             if (bModal)
4177                 m_aDialogRun.inc_modal_count();
4178             else
4179                 m_aDialogRun.dec_modal_count();
4180         }
4181     }
4182 
4183     virtual void response(int nResponse) override;
4184 
add_button(const OUString & rText,int nResponse,const OString & rHelpId)4185     virtual void add_button(const OUString& rText, int nResponse, const OString& rHelpId) override
4186     {
4187         GtkWidget* pWidget = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), MapToGtkAccelerator(rText).getStr(), VclToGtk(nResponse));
4188         if (!rHelpId.isEmpty())
4189             ::set_help_id(pWidget, rHelpId);
4190     }
4191 
set_default_response(int nResponse)4192     virtual void set_default_response(int nResponse) override
4193     {
4194         gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
4195     }
4196 
get_widget_for_response(int nGtkResponse)4197     virtual GtkButton* get_widget_for_response(int nGtkResponse)
4198     {
4199         return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
4200     }
4201 
4202     virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
4203 
weld_content_area()4204     virtual Container* weld_content_area() override
4205     {
4206         return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
4207     }
4208 
collapse(weld::Widget * pEdit,weld::Widget * pButton)4209     virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
4210     {
4211         GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
4212         assert(pVclEdit);
4213         GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
4214 
4215         GtkWidget* pRefEdit = pVclEdit->getWidget();
4216         GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
4217 
4218         m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
4219 
4220         gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
4221 
4222         //We want just pRefBtn and pRefEdit to be shown
4223         //mark widgets we want to be visible, starting with pRefEdit
4224         //and all its direct parents.
4225         winset aVisibleWidgets;
4226         GtkWidget *pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog));
4227         for (GtkWidget *pCandidate = pRefEdit;
4228             pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
4229             pCandidate = gtk_widget_get_parent(pCandidate))
4230         {
4231             aVisibleWidgets.insert(pCandidate);
4232         }
4233         //same again with pRefBtn, except stop if there's a
4234         //shared parent in the existing widgets
4235         for (GtkWidget *pCandidate = pRefBtn;
4236             pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
4237             pCandidate = gtk_widget_get_parent(pCandidate))
4238         {
4239             if (aVisibleWidgets.insert(pCandidate).second)
4240                 break;
4241         }
4242 
4243         //hide everything except the aVisibleWidgets
4244         hideUnless(GTK_CONTAINER(pContentArea), aVisibleWidgets, m_aHiddenWidgets);
4245 
4246         gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
4247         m_nOldBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(m_pDialog));
4248         gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), 0);
4249         if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
4250             gtk_widget_hide(pActionArea);
4251 
4252         // calc's insert->function is springing back to its original size if the ref-button
4253         // is used to shrink the dialog down and then the user clicks in the calc area to do
4254         // the selection
4255 #if defined(GDK_WINDOWING_WAYLAND)
4256         bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
4257         if (bWorkaroundSizeSpringingBack)
4258             gtk_widget_unmap(GTK_WIDGET(m_pDialog));
4259 #endif
4260 
4261         resize_to_request();
4262 
4263 #if defined(GDK_WINDOWING_WAYLAND)
4264         if (bWorkaroundSizeSpringingBack)
4265             gtk_widget_map(GTK_WIDGET(m_pDialog));
4266 #endif
4267 
4268         m_pRefEdit = pRefEdit;
4269     }
4270 
undo_collapse()4271     virtual void undo_collapse() override
4272     {
4273         // All others: Show();
4274         for (GtkWidget* pWindow : m_aHiddenWidgets)
4275         {
4276             gtk_widget_show(pWindow);
4277             g_object_unref(pWindow);
4278         }
4279         m_aHiddenWidgets.clear();
4280 
4281         gtk_widget_set_size_request(m_pRefEdit, m_nOldEditWidthReq, -1);
4282         m_pRefEdit = nullptr;
4283         gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), m_nOldBorderWidth);
4284         if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
4285             gtk_widget_show(pActionArea);
4286         resize_to_request();
4287         present();
4288     }
4289 
4290     void close(bool bCloseSignal);
4291 
SetInstallLOKNotifierHdl(const Link<void *,vcl::ILibreOfficeKitNotifier * > &)4292     virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
4293     {
4294         //not implemented for the gtk variant
4295     }
4296 
~GtkInstanceDialog()4297     virtual ~GtkInstanceDialog() override
4298     {
4299         if (!m_aHiddenWidgets.empty())
4300         {
4301             for (GtkWidget* pWindow : m_aHiddenWidgets)
4302                 g_object_unref(pWindow);
4303             m_aHiddenWidgets.clear();
4304         }
4305 
4306         if (m_nCloseSignalId)
4307             g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
4308         assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4309     }
4310 };
4311 
signal_response(GtkDialog *,gint nResponseId,gpointer data)4312 void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
4313 {
4314     DialogRunner* pThis = static_cast<DialogRunner*>(data);
4315 
4316     // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
4317     if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
4318     {
4319         pThis->m_pInstance->close(false);
4320         return;
4321     }
4322 
4323     pThis->m_nResponseId = nResponseId;
4324     pThis->loop_quit();
4325 }
4326 
signal_cancel(GtkAssistant *,gpointer data)4327 void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
4328 {
4329     DialogRunner* pThis = static_cast<DialogRunner*>(data);
4330 
4331     // make esc in an assistant act as if cancel button was pressed
4332     pThis->m_pInstance->close(false);
4333 }
4334 
4335 class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
4336 {
4337 private:
4338     GtkMessageDialog* m_pMessageDialog;
4339 public:
GtkInstanceMessageDialog(GtkMessageDialog * pMessageDialog,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)4340     GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4341         : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
4342         , m_pMessageDialog(pMessageDialog)
4343     {
4344     }
4345 
set_primary_text(const OUString & rText)4346     virtual void set_primary_text(const OUString& rText) override
4347     {
4348         ::set_primary_text(m_pMessageDialog, rText);
4349     }
4350 
get_primary_text() const4351     virtual OUString get_primary_text() const override
4352     {
4353         return ::get_primary_text(m_pMessageDialog);
4354     }
4355 
set_secondary_text(const OUString & rText)4356     virtual void set_secondary_text(const OUString& rText) override
4357     {
4358         ::set_secondary_text(m_pMessageDialog, rText);
4359     }
4360 
get_secondary_text() const4361     virtual OUString get_secondary_text() const override
4362     {
4363         return ::get_secondary_text(m_pMessageDialog);
4364     }
4365 
weld_message_area()4366     virtual Container* weld_message_area() override
4367     {
4368         return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
4369     }
4370 };
4371 
4372 class GtkInstanceAboutDialog final : public GtkInstanceDialog, public virtual weld::AboutDialog
4373 {
4374 private:
4375     GtkAboutDialog* m_pAboutDialog;
4376     GtkCssProvider* m_pCssProvider;
4377     std::unique_ptr<utl::TempFile>  mxBackgroundImage;
4378 public:
GtkInstanceAboutDialog(GtkAboutDialog * pAboutDialog,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)4379     GtkInstanceAboutDialog(GtkAboutDialog* pAboutDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4380         : GtkInstanceDialog(GTK_WINDOW(pAboutDialog), pBuilder, bTakeOwnership)
4381         , m_pAboutDialog(pAboutDialog)
4382         , m_pCssProvider(nullptr)
4383     {
4384         // in GtkAboutDialog apply_use_header_bar if headerbar is false it
4385         // automatically adds a default close button which it doesn't if
4386         // headerbar is true and which doesn't appear in the .ui
4387         if (GtkWidget* pDefaultButton = gtk_dialog_get_widget_for_response(GTK_DIALOG(pAboutDialog), GTK_RESPONSE_DELETE_EVENT))
4388             gtk_widget_destroy(pDefaultButton);
4389     }
4390 
set_version(const OUString & rVersion)4391     virtual void set_version(const OUString& rVersion) override
4392     {
4393         gtk_about_dialog_set_version(m_pAboutDialog, OUStringToOString(rVersion, RTL_TEXTENCODING_UTF8).getStr());
4394     }
4395 
set_copyright(const OUString & rCopyright)4396     virtual void set_copyright(const OUString& rCopyright) override
4397     {
4398         gtk_about_dialog_set_copyright(m_pAboutDialog, OUStringToOString(rCopyright, RTL_TEXTENCODING_UTF8).getStr());
4399     }
4400 
set_website(const OUString & rURL)4401     virtual void set_website(const OUString& rURL) override
4402     {
4403         OString sURL(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8));
4404         gtk_about_dialog_set_website(m_pAboutDialog, sURL.isEmpty() ? nullptr : sURL.getStr());
4405     }
4406 
set_website_label(const OUString & rLabel)4407     virtual void set_website_label(const OUString& rLabel) override
4408     {
4409         OString sLabel(OUStringToOString(rLabel, RTL_TEXTENCODING_UTF8));
4410         gtk_about_dialog_set_website_label(m_pAboutDialog, sLabel.isEmpty() ? nullptr : sLabel.getStr());
4411     }
4412 
get_website_label() const4413     virtual OUString get_website_label() const override
4414     {
4415         const gchar* pText = gtk_about_dialog_get_website_label(m_pAboutDialog);
4416         return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
4417     }
4418 
set_logo(const css::uno::Reference<css::graphic::XGraphic> & rImage)4419     virtual void set_logo(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
4420     {
4421         GdkPixbuf* pixbuf = rImage.is() ? getPixbuf(rImage) : nullptr;
4422         if (!pixbuf)
4423             gtk_about_dialog_set_logo(m_pAboutDialog, nullptr);
4424         else
4425         {
4426             gtk_about_dialog_set_logo(m_pAboutDialog, pixbuf);
4427             g_object_unref(pixbuf);
4428         }
4429     }
4430 
set_background(const css::uno::Reference<css::graphic::XGraphic> & rImage)4431     virtual void set_background(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
4432     {
4433         GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pAboutDialog));
4434         if (m_pCssProvider)
4435         {
4436             gtk_style_context_remove_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pCssProvider));
4437             m_pCssProvider= nullptr;
4438         }
4439 
4440         mxBackgroundImage.reset();
4441 
4442         if (rImage.is())
4443         {
4444             mxBackgroundImage.reset(new utl::TempFile());
4445             mxBackgroundImage->EnableKillingFile(true);
4446 
4447             Image aImage(rImage);
4448 
4449             vcl::PNGWriter aPNGWriter(aImage.GetBitmapEx());
4450             SvStream* pStream = mxBackgroundImage->GetStream(StreamMode::WRITE);
4451             aPNGWriter.Write(*pStream);
4452             mxBackgroundImage->CloseStream();
4453 
4454             m_pCssProvider = gtk_css_provider_new();
4455             OUString aBuffer = "* { background-image: url(\"" + mxBackgroundImage->GetURL() + "\"); }";
4456             OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
4457             gtk_css_provider_load_from_data(m_pCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
4458             gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pCssProvider),
4459                                            GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
4460         }
4461     }
4462 
~GtkInstanceAboutDialog()4463     virtual ~GtkInstanceAboutDialog() override
4464     {
4465         set_background(nullptr);
4466     }
4467 };
4468 
4469 class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
4470 {
4471 private:
4472     GtkAssistant* m_pAssistant;
4473     GtkWidget* m_pSidebar;
4474     GtkWidget* m_pSidebarEventBox;
4475     GtkButtonBox* m_pButtonBox;
4476     GtkButton* m_pHelp;
4477     GtkButton* m_pBack;
4478     GtkButton* m_pNext;
4479     GtkButton* m_pFinish;
4480     GtkButton* m_pCancel;
4481     gulong m_nButtonPressSignalId;
4482     std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
4483     std::map<OString, bool> m_aNotClickable;
4484 
find_page(const OString & rIdent) const4485     int find_page(const OString& rIdent) const
4486     {
4487         int nPages = gtk_assistant_get_n_pages(m_pAssistant);
4488         for (int i = 0; i < nPages; ++i)
4489         {
4490             GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, i);
4491             const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pPage));
4492             if (g_strcmp0(pStr, rIdent.getStr()) == 0)
4493                 return i;
4494         }
4495         return -1;
4496     }
4497 
wrap_sidebar_label(GtkWidget * pWidget,gpointer)4498     static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
4499     {
4500         if (GTK_IS_LABEL(pWidget))
4501         {
4502             gtk_label_set_line_wrap(GTK_LABEL(pWidget), true);
4503             gtk_label_set_width_chars(GTK_LABEL(pWidget), 22);
4504             gtk_label_set_max_width_chars(GTK_LABEL(pWidget), 22);
4505         }
4506     }
4507 
find_sidebar(GtkWidget * pWidget,gpointer user_data)4508     static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
4509     {
4510         if (g_strcmp0(gtk_buildable_get_name(GTK_BUILDABLE(pWidget)), "sidebar") == 0)
4511         {
4512             GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
4513             *ppSidebar = pWidget;
4514         }
4515         if (GTK_IS_CONTAINER(pWidget))
4516             gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
4517     }
4518 
signalHelpClicked(GtkButton *,gpointer widget)4519     static void signalHelpClicked(GtkButton*, gpointer widget)
4520     {
4521         GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
4522         pThis->signal_help_clicked();
4523     }
4524 
signal_help_clicked()4525     void signal_help_clicked()
4526     {
4527         help();
4528     }
4529 
signalButton(GtkWidget *,GdkEventButton * pEvent,gpointer widget)4530     static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
4531     {
4532         GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
4533         SolarMutexGuard aGuard;
4534         return pThis->signal_button(pEvent);
4535     }
4536 
signal_button(GdkEventButton * pEvent)4537     bool signal_button(GdkEventButton* pEvent)
4538     {
4539         int nNewCurrentPage = -1;
4540 
4541         GtkAllocation allocation;
4542 
4543         int nPageIndex = 0;
4544         GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pSidebar));
4545         for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
4546         {
4547             GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
4548             if (!gtk_widget_get_visible(pWidget))
4549                 continue;
4550 
4551             gtk_widget_get_allocation(pWidget, &allocation);
4552 
4553             gint dest_x1, dest_y1;
4554             gtk_widget_translate_coordinates(pWidget,
4555                                              m_pSidebarEventBox,
4556                                              0,
4557                                              0,
4558                                              &dest_x1,
4559                                              &dest_y1);
4560 
4561             gint dest_x2, dest_y2;
4562             gtk_widget_translate_coordinates(pWidget,
4563                                              m_pSidebarEventBox,
4564                                              allocation.width,
4565                                              allocation.height,
4566                                              &dest_x2,
4567                                              &dest_y2);
4568 
4569 
4570             if (pEvent->x >= dest_x1 && pEvent->x <= dest_x2 && pEvent->y >= dest_y1 && pEvent->y <= dest_y2)
4571             {
4572                 nNewCurrentPage = nPageIndex;
4573                 break;
4574             }
4575 
4576             ++nPageIndex;
4577         }
4578         g_list_free(pChildren);
4579 
4580         if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
4581         {
4582             OString sIdent = get_page_ident(nNewCurrentPage);
4583             if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
4584                 set_current_page(nNewCurrentPage);
4585         }
4586 
4587         return false;
4588     }
4589 
4590 public:
GtkInstanceAssistant(GtkAssistant * pAssistant,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)4591     GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4592         : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
4593         , m_pAssistant(pAssistant)
4594         , m_pSidebar(nullptr)
4595     {
4596         m_pButtonBox = GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
4597         gtk_button_box_set_layout(m_pButtonBox, GTK_BUTTONBOX_END);
4598         gtk_box_set_spacing(GTK_BOX(m_pButtonBox), 6);
4599 
4600         m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
4601         gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
4602         gtk_buildable_set_name(GTK_BUILDABLE(m_pBack), "previous");
4603         gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
4604 
4605         m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
4606         gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
4607         gtk_buildable_set_name(GTK_BUILDABLE(m_pNext), "next");
4608         gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
4609 
4610         m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
4611         gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
4612         gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
4613 
4614         m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
4615         gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
4616         gtk_buildable_set_name(GTK_BUILDABLE(m_pFinish), "finish");
4617         gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
4618 
4619         m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
4620         gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
4621         g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
4622         gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
4623 
4624         gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
4625         gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
4626         gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
4627 
4628         GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
4629         gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
4630         gtk_widget_set_halign(pParent, GTK_ALIGN_FILL);
4631 
4632         // Hide the built-in ones early so we get a nice optimal size for the width without
4633         // including the unused contents
4634         GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pParent));
4635         for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
4636         {
4637             GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
4638             gtk_widget_hide(pWidget);
4639         }
4640         g_list_free(pChildren);
4641 
4642         gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
4643 
4644         find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
4645 
4646         m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
4647         m_nButtonPressSignalId = m_pSidebarEventBox ? g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this) : 0;
4648     }
4649 
get_current_page() const4650     virtual int get_current_page() const override
4651     {
4652         return gtk_assistant_get_current_page(m_pAssistant);
4653     }
4654 
get_n_pages() const4655     virtual int get_n_pages() const override
4656     {
4657         return gtk_assistant_get_n_pages(m_pAssistant);
4658     }
4659 
get_page_ident(int nPage) const4660     virtual OString get_page_ident(int nPage) const override
4661     {
4662         const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
4663         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
4664         return OString(pStr, pStr ? strlen(pStr) : 0);
4665     }
4666 
get_current_page_ident() const4667     virtual OString get_current_page_ident() const override
4668     {
4669         return get_page_ident(get_current_page());
4670     }
4671 
set_current_page(int nPage)4672     virtual void set_current_page(int nPage) override
4673     {
4674         OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
4675 
4676         gtk_assistant_set_current_page(m_pAssistant, nPage);
4677 
4678         // if the page doesn't have a title, then the dialog will now have no
4679         // title, so restore the original title as a fallback
4680         GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nPage);
4681         if (!gtk_assistant_get_page_title(m_pAssistant, pPage))
4682             gtk_window_set_title(GTK_WINDOW(m_pAssistant), sDialogTitle.getStr());
4683     }
4684 
set_current_page(const OString & rIdent)4685     virtual void set_current_page(const OString& rIdent) override
4686     {
4687         int nPage = find_page(rIdent);
4688         if (nPage == -1)
4689             return;
4690         set_current_page(nPage);
4691     }
4692 
set_page_title(const OString & rIdent,const OUString & rTitle)4693     virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
4694     {
4695         int nIndex = find_page(rIdent);
4696         if (nIndex == -1)
4697             return;
4698         GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
4699         gtk_assistant_set_page_title(m_pAssistant, pPage,
4700                                      OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
4701         gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
4702     }
4703 
get_page_title(const OString & rIdent) const4704     virtual OUString get_page_title(const OString& rIdent) const override
4705     {
4706         int nIndex = find_page(rIdent);
4707         if (nIndex == -1)
4708             return OUString();
4709         GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
4710         const gchar* pStr = gtk_assistant_get_page_title(m_pAssistant, pPage);
4711         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4712     }
4713 
set_page_sensitive(const OString & rIdent,bool bSensitive)4714     virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
4715     {
4716         m_aNotClickable[rIdent] = !bSensitive;
4717     }
4718 
set_page_index(const OString & rIdent,int nNewIndex)4719     virtual void set_page_index(const OString& rIdent, int nNewIndex) override
4720     {
4721         int nOldIndex = find_page(rIdent);
4722         if (nOldIndex == -1)
4723             return;
4724 
4725         if (nOldIndex == nNewIndex)
4726             return;
4727 
4728         GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
4729 
4730         g_object_ref(pPage);
4731         OString sTitle(gtk_assistant_get_page_title(m_pAssistant, pPage));
4732         gtk_assistant_remove_page(m_pAssistant, nOldIndex);
4733         gtk_assistant_insert_page(m_pAssistant, pPage, nNewIndex);
4734         gtk_assistant_set_page_type(m_pAssistant, pPage, GTK_ASSISTANT_PAGE_CUSTOM);
4735         gtk_assistant_set_page_title(m_pAssistant, pPage, sTitle.getStr());
4736         gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
4737         g_object_unref(pPage);
4738     }
4739 
append_page(const OString & rIdent)4740     virtual weld::Container* append_page(const OString& rIdent) override
4741     {
4742         disable_notify_events();
4743 
4744         GtkWidget *pChild = gtk_grid_new();
4745         gtk_buildable_set_name(GTK_BUILDABLE(pChild), rIdent.getStr());
4746         gtk_assistant_append_page(m_pAssistant, pChild);
4747         gtk_assistant_set_page_type(m_pAssistant, pChild, GTK_ASSISTANT_PAGE_CUSTOM);
4748         gtk_widget_show(pChild);
4749 
4750         enable_notify_events();
4751 
4752         m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
4753 
4754         return m_aPages.back().get();
4755     }
4756 
set_page_side_help_id(const OString & rHelpId)4757     virtual void set_page_side_help_id(const OString& rHelpId) override
4758     {
4759         if (!m_pSidebar)
4760             return;
4761         ::set_help_id(m_pSidebar, rHelpId);
4762     }
4763 
get_widget_for_response(int nGtkResponse)4764     virtual GtkButton* get_widget_for_response(int nGtkResponse) override
4765     {
4766         GtkButton* pButton = nullptr;
4767         if (nGtkResponse == GTK_RESPONSE_YES)
4768             pButton = m_pNext;
4769         else if (nGtkResponse == GTK_RESPONSE_NO)
4770             pButton = m_pBack;
4771         else if (nGtkResponse == GTK_RESPONSE_OK)
4772             pButton = m_pFinish;
4773         else if (nGtkResponse == GTK_RESPONSE_CANCEL)
4774             pButton = m_pCancel;
4775         else if (nGtkResponse == GTK_RESPONSE_HELP)
4776             pButton = m_pHelp;
4777         return pButton;
4778     }
4779 
~GtkInstanceAssistant()4780     virtual ~GtkInstanceAssistant() override
4781     {
4782         if (m_nButtonPressSignalId)
4783             g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
4784     }
4785 };
4786 
4787 class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
4788 {
4789 private:
4790     GtkFrame* m_pFrame;
4791 public:
GtkInstanceFrame(GtkFrame * pFrame,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)4792     GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4793         : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
4794         , m_pFrame(pFrame)
4795     {
4796     }
4797 
set_label(const OUString & rText)4798     virtual void set_label(const OUString& rText) override
4799     {
4800         gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
4801     }
4802 
get_label() const4803     virtual OUString get_label() const override
4804     {
4805         const gchar* pStr = gtk_frame_get_label(m_pFrame);
4806         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4807     }
4808 
4809     virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
4810 };
4811 
4812 static GType crippled_viewport_get_type();
4813 
4814 #define CRIPPLED_TYPE_VIEWPORT            (crippled_viewport_get_type ())
4815 #define CRIPPLED_VIEWPORT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CRIPPLED_TYPE_VIEWPORT, CrippledViewport))
4816 #ifndef NDEBUG
4817 #   define CRIPPLED_IS_VIEWPORT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CRIPPLED_TYPE_VIEWPORT))
4818 #endif
4819 
4820 struct CrippledViewport
4821 {
4822     GtkViewport viewport;
4823 
4824     GtkAdjustment  *hadjustment;
4825     GtkAdjustment  *vadjustment;
4826 };
4827 
4828 enum
4829 {
4830     PROP_0,
4831     PROP_HADJUSTMENT,
4832     PROP_VADJUSTMENT,
4833     PROP_HSCROLL_POLICY,
4834     PROP_VSCROLL_POLICY,
4835     PROP_SHADOW_TYPE
4836 };
4837 
viewport_set_adjustment(CrippledViewport * viewport,GtkOrientation orientation,GtkAdjustment * adjustment)4838 static void viewport_set_adjustment(CrippledViewport *viewport,
4839                                     GtkOrientation  orientation,
4840                                     GtkAdjustment  *adjustment)
4841 {
4842     if (!adjustment)
4843         adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
4844 
4845     if (orientation == GTK_ORIENTATION_HORIZONTAL)
4846     {
4847         if (viewport->hadjustment)
4848             g_object_unref(viewport->hadjustment);
4849         viewport->hadjustment = adjustment;
4850     }
4851     else
4852     {
4853         if (viewport->vadjustment)
4854             g_object_unref(viewport->vadjustment);
4855         viewport->vadjustment = adjustment;
4856     }
4857 
4858     g_object_ref_sink(adjustment);
4859 }
4860 
4861 static void
crippled_viewport_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec *)4862 crippled_viewport_set_property(GObject* object,
4863                                guint prop_id,
4864                                const GValue* value,
4865                                GParamSpec* /*pspec*/)
4866 {
4867     CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
4868 
4869     switch (prop_id)
4870     {
4871         case PROP_HADJUSTMENT:
4872             viewport_set_adjustment(viewport, GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(g_value_get_object(value)));
4873             break;
4874         case PROP_VADJUSTMENT:
4875             viewport_set_adjustment(viewport, GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(g_value_get_object(value)));
4876             break;
4877         case PROP_HSCROLL_POLICY:
4878         case PROP_VSCROLL_POLICY:
4879             break;
4880         default:
4881             SAL_WARN( "vcl.gtk", "unknown property\n");
4882             break;
4883     }
4884 }
4885 
4886 static void
crippled_viewport_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec *)4887 crippled_viewport_get_property(GObject* object,
4888                                guint prop_id,
4889                                GValue* value,
4890                                GParamSpec* /*pspec*/)
4891 {
4892     CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
4893 
4894     switch (prop_id)
4895     {
4896         case PROP_HADJUSTMENT:
4897             g_value_set_object(value, viewport->hadjustment);
4898             break;
4899         case PROP_VADJUSTMENT:
4900             g_value_set_object(value, viewport->vadjustment);
4901             break;
4902         case PROP_HSCROLL_POLICY:
4903             g_value_set_enum(value, GTK_SCROLL_MINIMUM);
4904             break;
4905         case PROP_VSCROLL_POLICY:
4906             g_value_set_enum(value, GTK_SCROLL_MINIMUM);
4907             break;
4908         default:
4909             SAL_WARN( "vcl.gtk", "unknown property\n");
4910             break;
4911     }
4912 }
4913 
crippled_viewport_class_init(GtkViewportClass * klass)4914 static void crippled_viewport_class_init(GtkViewportClass *klass)
4915 {
4916     GObjectClass* o_class = G_OBJECT_CLASS(klass);
4917 
4918     /* GObject signals */
4919     o_class->set_property = crippled_viewport_set_property;
4920     o_class->get_property = crippled_viewport_get_property;
4921 
4922     /* Properties */
4923     g_object_class_override_property(o_class, PROP_HADJUSTMENT,    "hadjustment");
4924     g_object_class_override_property(o_class, PROP_VADJUSTMENT,    "vadjustment");
4925     g_object_class_override_property(o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
4926     g_object_class_override_property(o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
4927 }
4928 
crippled_viewport_get_type()4929 GType crippled_viewport_get_type()
4930 {
4931     static GType type = 0;
4932 
4933     if (!type)
4934     {
4935         static const GTypeInfo tinfo =
4936         {
4937             sizeof (GtkViewportClass),
4938             nullptr,  /* base init */
4939             nullptr,  /* base finalize */
4940             reinterpret_cast<GClassInitFunc>(crippled_viewport_class_init), /* class init */
4941             nullptr, /* class finalize */
4942             nullptr,                   /* class data */
4943             sizeof (CrippledViewport), /* instance size */
4944             0,                         /* nb preallocs */
4945             nullptr,  /* instance init */
4946             nullptr                    /* value table */
4947         };
4948 
4949         type = g_type_register_static( GTK_TYPE_VIEWPORT, "CrippledViewport",
4950                                        &tinfo, GTypeFlags(0));
4951     }
4952 
4953     return type;
4954 }
4955 
GtkToVcl(GtkPolicyType eType)4956 static VclPolicyType GtkToVcl(GtkPolicyType eType)
4957 {
4958     VclPolicyType eRet(VclPolicyType::NEVER);
4959     switch (eType)
4960     {
4961         case GTK_POLICY_ALWAYS:
4962             eRet = VclPolicyType::ALWAYS;
4963             break;
4964         case GTK_POLICY_AUTOMATIC:
4965             eRet = VclPolicyType::AUTOMATIC;
4966             break;
4967         case GTK_POLICY_EXTERNAL:
4968         case GTK_POLICY_NEVER:
4969             eRet = VclPolicyType::NEVER;
4970             break;
4971     }
4972     return eRet;
4973 }
4974 
VclToGtk(VclPolicyType eType)4975 static GtkPolicyType VclToGtk(VclPolicyType eType)
4976 {
4977     GtkPolicyType eRet(GTK_POLICY_ALWAYS);
4978     switch (eType)
4979     {
4980         case VclPolicyType::ALWAYS:
4981             eRet = GTK_POLICY_ALWAYS;
4982             break;
4983         case VclPolicyType::AUTOMATIC:
4984             eRet = GTK_POLICY_AUTOMATIC;
4985             break;
4986         case VclPolicyType::NEVER:
4987             eRet = GTK_POLICY_NEVER;
4988             break;
4989     }
4990     return eRet;
4991 }
4992 
VclToGtk(VclMessageType eType)4993 static GtkMessageType VclToGtk(VclMessageType eType)
4994 {
4995     GtkMessageType eRet(GTK_MESSAGE_INFO);
4996     switch (eType)
4997     {
4998         case VclMessageType::Info:
4999             eRet = GTK_MESSAGE_INFO;
5000             break;
5001         case VclMessageType::Warning:
5002             eRet = GTK_MESSAGE_WARNING;
5003             break;
5004         case VclMessageType::Question:
5005             eRet = GTK_MESSAGE_QUESTION;
5006             break;
5007         case VclMessageType::Error:
5008             eRet = GTK_MESSAGE_ERROR;
5009             break;
5010         case VclMessageType::Other:
5011             eRet = GTK_MESSAGE_OTHER;
5012             break;
5013     }
5014     return eRet;
5015 }
5016 
VclToGtk(VclButtonsType eType)5017 static GtkButtonsType VclToGtk(VclButtonsType eType)
5018 {
5019     GtkButtonsType eRet(GTK_BUTTONS_NONE);
5020     switch (eType)
5021     {
5022         case VclButtonsType::NONE:
5023             eRet = GTK_BUTTONS_NONE;
5024             break;
5025         case VclButtonsType::Ok:
5026             eRet = GTK_BUTTONS_OK;
5027             break;
5028         case VclButtonsType::Close:
5029             eRet = GTK_BUTTONS_CLOSE;
5030             break;
5031         case VclButtonsType::Cancel:
5032             eRet = GTK_BUTTONS_CANCEL;
5033             break;
5034         case VclButtonsType::YesNo:
5035             eRet = GTK_BUTTONS_YES_NO;
5036             break;
5037         case VclButtonsType::OkCancel:
5038             eRet = GTK_BUTTONS_OK_CANCEL;
5039             break;
5040     }
5041     return eRet;
5042 }
5043 
VclToGtk(SelectionMode eType)5044 static GtkSelectionMode VclToGtk(SelectionMode eType)
5045 {
5046     GtkSelectionMode eRet(GTK_SELECTION_NONE);
5047     switch (eType)
5048     {
5049         case SelectionMode::NONE:
5050             eRet = GTK_SELECTION_NONE;
5051             break;
5052         case SelectionMode::Single:
5053             eRet = GTK_SELECTION_SINGLE;
5054             break;
5055         case SelectionMode::Range:
5056             eRet = GTK_SELECTION_BROWSE;
5057             break;
5058         case SelectionMode::Multiple:
5059             eRet = GTK_SELECTION_MULTIPLE;
5060             break;
5061     }
5062     return eRet;
5063 }
5064 
5065 class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
5066 {
5067 private:
5068     GtkScrolledWindow* m_pScrolledWindow;
5069     GtkWidget *m_pOrigViewport;
5070     GtkAdjustment* m_pVAdjustment;
5071     GtkAdjustment* m_pHAdjustment;
5072     gulong m_nVAdjustChangedSignalId;
5073     gulong m_nHAdjustChangedSignalId;
5074 
signalVAdjustValueChanged(GtkAdjustment *,gpointer widget)5075     static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
5076     {
5077         GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
5078         SolarMutexGuard aGuard;
5079         pThis->signal_vadjustment_changed();
5080     }
5081 
signalHAdjustValueChanged(GtkAdjustment *,gpointer widget)5082     static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
5083     {
5084         GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
5085         SolarMutexGuard aGuard;
5086         pThis->signal_hadjustment_changed();
5087     }
5088 
5089 public:
GtkInstanceScrolledWindow(GtkScrolledWindow * pScrolledWindow,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)5090     GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5091         : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
5092         , m_pScrolledWindow(pScrolledWindow)
5093         , m_pOrigViewport(nullptr)
5094         , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
5095         , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
5096         , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
5097         , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
5098     {
5099     }
5100 
set_user_managed_scrolling()5101     virtual void set_user_managed_scrolling() override
5102     {
5103         disable_notify_events();
5104         //remove the original viewport and replace it with our bodged one which
5105         //doesn't do any scrolling and expects its child to figure it out somehow
5106         assert(!m_pOrigViewport);
5107         GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
5108         assert(GTK_IS_VIEWPORT(pViewport));
5109         GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
5110         g_object_ref(pChild);
5111         gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
5112         g_object_ref(pViewport);
5113         gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
5114         GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(crippled_viewport_get_type(), nullptr));
5115         gtk_widget_show(pNewViewport);
5116         gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
5117         gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
5118         g_object_unref(pChild);
5119         m_pOrigViewport = pViewport;
5120         enable_notify_events();
5121     }
5122 
hadjustment_configure(int value,int lower,int upper,int step_increment,int page_increment,int page_size)5123     virtual void hadjustment_configure(int value, int lower, int upper,
5124                                        int step_increment, int page_increment,
5125                                        int page_size) override
5126     {
5127         disable_notify_events();
5128         if (SwapForRTL())
5129             value = upper - (value - lower + page_size);
5130         gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
5131         enable_notify_events();
5132     }
5133 
hadjustment_get_value() const5134     virtual int hadjustment_get_value() const override
5135     {
5136         int value = gtk_adjustment_get_value(m_pHAdjustment);
5137 
5138         if (SwapForRTL())
5139         {
5140             int upper = gtk_adjustment_get_upper(m_pHAdjustment);
5141             int lower = gtk_adjustment_get_lower(m_pHAdjustment);
5142             int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
5143             value = lower + (upper - value - page_size);
5144         }
5145 
5146         return value;
5147     }
5148 
hadjustment_set_value(int value)5149     virtual void hadjustment_set_value(int value) override
5150     {
5151         disable_notify_events();
5152 
5153         if (SwapForRTL())
5154         {
5155             int upper = gtk_adjustment_get_upper(m_pHAdjustment);
5156             int lower = gtk_adjustment_get_lower(m_pHAdjustment);
5157             int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
5158             value = upper - (value - lower + page_size);
5159         }
5160 
5161         gtk_adjustment_set_value(m_pHAdjustment, value);
5162         enable_notify_events();
5163     }
5164 
hadjustment_get_upper() const5165     virtual int hadjustment_get_upper() const override
5166     {
5167          return gtk_adjustment_get_upper(m_pHAdjustment);
5168     }
5169 
hadjustment_set_upper(int upper)5170     virtual void hadjustment_set_upper(int upper) override
5171     {
5172         disable_notify_events();
5173         gtk_adjustment_set_upper(m_pHAdjustment, upper);
5174         enable_notify_events();
5175     }
5176 
hadjustment_get_page_size() const5177     virtual int hadjustment_get_page_size() const override
5178     {
5179         return gtk_adjustment_get_page_size(m_pHAdjustment);
5180     }
5181 
hadjustment_set_page_size(int size)5182     virtual void hadjustment_set_page_size(int size) override
5183     {
5184         gtk_adjustment_set_page_size(m_pHAdjustment, size);
5185     }
5186 
hadjustment_set_page_increment(int size)5187     virtual void hadjustment_set_page_increment(int size) override
5188     {
5189         gtk_adjustment_set_page_increment(m_pHAdjustment, size);
5190     }
5191 
hadjustment_set_step_increment(int size)5192     virtual void hadjustment_set_step_increment(int size) override
5193     {
5194         gtk_adjustment_set_step_increment(m_pHAdjustment, size);
5195     }
5196 
set_hpolicy(VclPolicyType eHPolicy)5197     virtual void set_hpolicy(VclPolicyType eHPolicy) override
5198     {
5199         GtkPolicyType eGtkVPolicy;
5200         gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
5201         gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkVPolicy, VclToGtk(eHPolicy));
5202     }
5203 
get_hpolicy() const5204     virtual VclPolicyType get_hpolicy() const override
5205     {
5206         GtkPolicyType eGtkHPolicy;
5207         gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
5208         return GtkToVcl(eGtkHPolicy);
5209     }
5210 
get_hscroll_height() const5211     virtual int get_hscroll_height() const override
5212     {
5213         if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
5214             return 0;
5215         return gtk_widget_get_allocated_height(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
5216     }
5217 
vadjustment_configure(int value,int lower,int upper,int step_increment,int page_increment,int page_size)5218     virtual void vadjustment_configure(int value, int lower, int upper,
5219                                        int step_increment, int page_increment,
5220                                        int page_size) override
5221     {
5222         disable_notify_events();
5223         gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
5224         enable_notify_events();
5225     }
5226 
vadjustment_get_value() const5227     virtual int vadjustment_get_value() const override
5228     {
5229         return gtk_adjustment_get_value(m_pVAdjustment);
5230     }
5231 
vadjustment_set_value(int value)5232     virtual void vadjustment_set_value(int value) override
5233     {
5234         disable_notify_events();
5235         gtk_adjustment_set_value(m_pVAdjustment, value);
5236         enable_notify_events();
5237     }
5238 
vadjustment_get_upper() const5239     virtual int vadjustment_get_upper() const override
5240     {
5241          return gtk_adjustment_get_upper(m_pVAdjustment);
5242     }
5243 
vadjustment_set_upper(int upper)5244     virtual void vadjustment_set_upper(int upper) override
5245     {
5246         disable_notify_events();
5247         gtk_adjustment_set_upper(m_pVAdjustment, upper);
5248         enable_notify_events();
5249     }
5250 
vadjustment_get_lower() const5251     virtual int vadjustment_get_lower() const override
5252     {
5253          return gtk_adjustment_get_lower(m_pVAdjustment);
5254     }
5255 
vadjustment_set_lower(int lower)5256     virtual void vadjustment_set_lower(int lower) override
5257     {
5258         disable_notify_events();
5259         gtk_adjustment_set_lower(m_pVAdjustment, lower);
5260         enable_notify_events();
5261     }
5262 
vadjustment_get_page_size() const5263     virtual int vadjustment_get_page_size() const override
5264     {
5265         return gtk_adjustment_get_page_size(m_pVAdjustment);
5266     }
5267 
vadjustment_set_page_size(int size)5268     virtual void vadjustment_set_page_size(int size) override
5269     {
5270         gtk_adjustment_set_page_size(m_pVAdjustment, size);
5271     }
5272 
vadjustment_set_page_increment(int size)5273     virtual void vadjustment_set_page_increment(int size) override
5274     {
5275         gtk_adjustment_set_page_increment(m_pVAdjustment, size);
5276     }
5277 
vadjustment_set_step_increment(int size)5278     virtual void vadjustment_set_step_increment(int size) override
5279     {
5280         gtk_adjustment_set_step_increment(m_pVAdjustment, size);
5281     }
5282 
set_vpolicy(VclPolicyType eVPolicy)5283     virtual void set_vpolicy(VclPolicyType eVPolicy) override
5284     {
5285         GtkPolicyType eGtkHPolicy;
5286         gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
5287         gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
5288     }
5289 
get_vpolicy() const5290     virtual VclPolicyType get_vpolicy() const override
5291     {
5292         GtkPolicyType eGtkVPolicy;
5293         gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
5294         return GtkToVcl(eGtkVPolicy);
5295     }
5296 
get_vscroll_width() const5297     virtual int get_vscroll_width() const override
5298     {
5299         if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
5300             return 0;
5301         return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
5302     }
5303 
disable_notify_events()5304     virtual void disable_notify_events() override
5305     {
5306         g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
5307         g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
5308         GtkInstanceContainer::disable_notify_events();
5309     }
5310 
enable_notify_events()5311     virtual void enable_notify_events() override
5312     {
5313         GtkInstanceContainer::enable_notify_events();
5314         g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
5315         g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
5316     }
5317 
~GtkInstanceScrolledWindow()5318     virtual ~GtkInstanceScrolledWindow() override
5319     {
5320         //put it back the way it was
5321         if (m_pOrigViewport)
5322         {
5323             disable_notify_events();
5324             GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
5325             assert(CRIPPLED_IS_VIEWPORT(pViewport));
5326             GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
5327             g_object_ref(pChild);
5328             gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
5329             g_object_ref(pViewport);
5330             gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
5331             gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
5332             g_object_unref(m_pOrigViewport);
5333             gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
5334             g_object_unref(pChild);
5335             gtk_widget_destroy(pViewport);
5336             g_object_unref(pViewport);
5337             m_pOrigViewport = nullptr;
5338             enable_notify_events();
5339         }
5340         g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
5341         g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
5342     }
5343 };
5344 
5345 class GtkInstanceNotebook : public GtkInstanceContainer, public virtual weld::Notebook
5346 {
5347 private:
5348     GtkNotebook* m_pNotebook;
5349     GtkBox* m_pOverFlowBox;
5350     GtkNotebook* m_pOverFlowNotebook;
5351     gulong m_nSwitchPageSignalId;
5352     gulong m_nOverFlowSwitchPageSignalId;
5353     gulong m_nSizeAllocateSignalId;
5354     gulong m_nFocusSignalId;
5355     gulong m_nChangeCurrentPageId;
5356     guint m_nLaunchSplitTimeoutId;
5357     bool m_bOverFlowBoxActive;
5358     bool m_bOverFlowBoxIsStart;
5359     int m_nStartTabCount;
5360     int m_nEndTabCount;
5361     mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
5362 
signalSwitchPage(GtkNotebook *,GtkWidget *,guint nNewPage,gpointer widget)5363     static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
5364     {
5365         GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5366         SolarMutexGuard aGuard;
5367         pThis->signal_switch_page(nNewPage);
5368     }
5369 
launch_overflow_switch_page(GtkInstanceNotebook * pThis)5370     static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
5371     {
5372         SolarMutexGuard aGuard;
5373         pThis->signal_overflow_switch_page();
5374         return false;
5375     }
5376 
signalOverFlowSwitchPage(GtkNotebook *,GtkWidget *,guint,gpointer widget)5377     static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
5378     {
5379         g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
5380     }
5381 
signal_switch_page(int nNewPage)5382     void signal_switch_page(int nNewPage)
5383     {
5384         if (m_bOverFlowBoxIsStart)
5385         {
5386             auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5387             // add count of overflow pages, minus the extra tab
5388             nNewPage += nOverFlowLen;
5389         }
5390 
5391         bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
5392         if (!bAllow)
5393         {
5394             g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
5395             return;
5396         }
5397         if (m_bOverFlowBoxActive)
5398             gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
5399         OString sNewIdent(get_page_ident(nNewPage));
5400         m_aEnterPageHdl.Call(sNewIdent);
5401     }
5402 
unsplit_notebooks()5403     void unsplit_notebooks()
5404     {
5405         int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5406         int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
5407         int nPageIndex = 0;
5408         if (!m_bOverFlowBoxIsStart)
5409             nPageIndex += nMainPages;
5410 
5411         // take the overflow pages, and put them back at the end of the normal one
5412         int i = nMainPages;
5413         while (nOverFlowPages)
5414         {
5415             OString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
5416             OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
5417             remove_page(m_pOverFlowNotebook, sIdent);
5418 
5419             GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
5420             append_page(m_pNotebook, sIdent, sLabel, pPage);
5421 
5422             GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
5423                                                                gtk_notebook_get_nth_page(m_pNotebook, i));
5424             gtk_widget_set_hexpand(pTabWidget, true);
5425             --nOverFlowPages;
5426             ++i;
5427             ++nPageIndex;
5428         }
5429 
5430         // remove the dangling placeholder tab page
5431         remove_page(m_pOverFlowNotebook, "useless");
5432     }
5433 
5434     // a tab has been selected on the overflow notebook
signal_overflow_switch_page()5435     void signal_overflow_switch_page()
5436     {
5437         int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
5438         int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5439         if (nNewPage == nOverFlowPages)
5440         {
5441             // the useless tab which is there because there has to be an active tab
5442             return;
5443         }
5444 
5445         // check if we are allowed leave before attempting to resplit the notebooks
5446         bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
5447         if (!bAllow)
5448             return;
5449 
5450         disable_notify_events();
5451 
5452         // take the overflow pages, and put them back at the end of the normal one
5453         unsplit_notebooks();
5454 
5455         // now redo the split, the pages will be split the other way around this time
5456         std::swap(m_nStartTabCount, m_nEndTabCount);
5457         split_notebooks();
5458 
5459         gtk_notebook_set_current_page(m_pNotebook, nNewPage);
5460 
5461         enable_notify_events();
5462 
5463         // trigger main notebook switch-page callback
5464         OString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
5465         m_aEnterPageHdl.Call(sNewIdent);
5466     }
5467 
get_page_ident(GtkNotebook * pNotebook,guint nPage)5468     static OString get_page_ident(GtkNotebook *pNotebook, guint nPage)
5469     {
5470         const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
5471         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
5472         return OString(pStr, pStr ? strlen(pStr) : 0);
5473     }
5474 
get_page_number(GtkNotebook * pNotebook,const OString & rIdent)5475     static gint get_page_number(GtkNotebook *pNotebook, const OString& rIdent)
5476     {
5477         gint nPages = gtk_notebook_get_n_pages(pNotebook);
5478         for (gint i = 0; i < nPages; ++i)
5479         {
5480             const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
5481             const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
5482             if (pStr && strcmp(pStr, rIdent.getStr()) == 0)
5483                 return i;
5484         }
5485         return -1;
5486     }
5487 
remove_page(GtkNotebook * pNotebook,const OString & rIdent)5488     int remove_page(GtkNotebook *pNotebook, const OString& rIdent)
5489     {
5490         disable_notify_events();
5491         int nPageNumber = get_page_number(pNotebook, rIdent);
5492         gtk_notebook_remove_page(pNotebook, nPageNumber);
5493         enable_notify_events();
5494         return nPageNumber;
5495     }
5496 
get_tab_label_text(GtkNotebook * pNotebook,guint nPage)5497     static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
5498     {
5499         const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
5500         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
5501     }
5502 
set_tab_label_text(GtkNotebook * pNotebook,guint nPage,const OUString & rText)5503     static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
5504     {
5505         OString sUtf8(rText.toUtf8());
5506 
5507         GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
5508 
5509         // tdf#128241 if there's already a label here, reuse it so the buildable
5510         // name remains the same, gtk_notebook_set_tab_label_text will replace
5511         // the label widget with a new one
5512         GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
5513         if (pTabWidget && GTK_IS_LABEL(pTabWidget))
5514         {
5515             gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
5516             return;
5517         }
5518 
5519         gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
5520     }
5521 
append_useless_page(GtkNotebook * pNotebook)5522     void append_useless_page(GtkNotebook *pNotebook)
5523     {
5524         disable_notify_events();
5525 
5526         GtkWidget *pTabWidget = gtk_fixed_new();
5527         gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), "useless");
5528 
5529         GtkWidget *pChild = gtk_grid_new();
5530         gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
5531         gtk_widget_show(pChild);
5532         gtk_widget_show(pTabWidget);
5533 
5534         enable_notify_events();
5535     }
5536 
append_page(GtkNotebook * pNotebook,const OString & rIdent,const OUString & rLabel,GtkWidget * pChild)5537     void append_page(GtkNotebook *pNotebook, const OString& rIdent, const OUString& rLabel, GtkWidget *pChild)
5538     {
5539         disable_notify_events();
5540 
5541         GtkWidget *pTabWidget = gtk_label_new(MapToGtkAccelerator(rLabel).getStr());
5542         gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), rIdent.getStr());
5543 
5544         gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
5545         gtk_widget_show(pChild);
5546         gtk_widget_show(pTabWidget);
5547 
5548         enable_notify_events();
5549     }
5550 
get_page_number(const OString & rIdent) const5551     gint get_page_number(const OString& rIdent) const
5552     {
5553         auto nMainIndex = get_page_number(m_pNotebook, rIdent);
5554         auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
5555 
5556         if (nMainIndex == -1 && nOverFlowIndex == -1)
5557             return -1;
5558 
5559         if (m_bOverFlowBoxIsStart)
5560         {
5561             if (nOverFlowIndex != -1)
5562                 return nOverFlowIndex;
5563             else
5564             {
5565                 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5566                 return nMainIndex + nOverFlowLen;
5567             }
5568         }
5569         else
5570         {
5571             if (nMainIndex != -1)
5572                 return nMainIndex;
5573             else
5574             {
5575                 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5576                 return nOverFlowIndex + nMainLen;
5577             }
5578         }
5579     }
5580 
make_overflow_boxes()5581     void make_overflow_boxes()
5582     {
5583         m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
5584         GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
5585         gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pOverFlowBox));
5586         gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
5587         g_object_ref(m_pNotebook);
5588         gtk_container_remove(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
5589         gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
5590         g_object_unref(m_pNotebook);
5591         gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
5592     }
5593 
split_notebooks()5594     void split_notebooks()
5595     {
5596         // get the original preferred size for the notebook, the sane width
5597         // expected here depends on the notebooks all initially having
5598         // scrollable tabs enabled
5599         GtkAllocation alloc;
5600         gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
5601 
5602         // toggle the direction of the split since the last time
5603         m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
5604         if (!m_pOverFlowBox)
5605              make_overflow_boxes();
5606 
5607         // don't scroll the tabs anymore
5608         gtk_notebook_set_scrollable(m_pNotebook, false);
5609 
5610         gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
5611         gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5612 
5613         gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
5614 
5615         gint nPages;
5616 
5617         GtkRequisition size1, size2;
5618 
5619         if (!m_nStartTabCount && !m_nEndTabCount)
5620         {
5621             nPages = gtk_notebook_get_n_pages(m_pNotebook);
5622 
5623             std::vector<int> aLabelWidths;
5624             //move tabs to the overflow notebook
5625             for (int i = 0; i < nPages; ++i)
5626             {
5627                 OUString sLabel(get_tab_label_text(m_pNotebook, i));
5628                 aLabelWidths.push_back(get_pixel_size(sLabel).Width());
5629             }
5630             int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
5631             int count = 0;
5632             for (int i = 0; i < nPages; ++i)
5633             {
5634                 count += aLabelWidths[i];
5635                 if (count >= row_width)
5636                 {
5637                     m_nStartTabCount = i;
5638                     break;
5639                 }
5640             }
5641 
5642             m_nEndTabCount = nPages - m_nStartTabCount;
5643         }
5644 
5645         //move the tabs to the overflow notebook
5646         int i = 0;
5647         int nOverFlowPages = m_nStartTabCount;
5648         while (nOverFlowPages)
5649         {
5650             OString sIdent(get_page_ident(m_pNotebook, 0));
5651             OUString sLabel(get_tab_label_text(m_pNotebook, 0));
5652             remove_page(m_pNotebook, sIdent);
5653             append_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new());
5654             GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
5655                                                                gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
5656             gtk_widget_set_hexpand(pTabWidget, true);
5657 
5658             --nOverFlowPages;
5659             ++i;
5660         }
5661 
5662         for (i = 0; i < m_nEndTabCount; ++i)
5663         {
5664             GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
5665                                                                gtk_notebook_get_nth_page(m_pNotebook, i));
5666             gtk_widget_set_hexpand(pTabWidget, true);
5667         }
5668 
5669         // have to have some tab as the active tab of the overflow notebook
5670         append_useless_page(m_pOverFlowNotebook);
5671         gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
5672         if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
5673             gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
5674 
5675         // add this temporarily to the normal notebook to measure how wide
5676         // the row would be if switched to the other notebook
5677         append_useless_page(m_pNotebook);
5678 
5679         gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
5680         gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
5681 
5682         auto nWidth = std::max(size1.width, size2.width);
5683         gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
5684         gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
5685 
5686         // remove it once we've measured it
5687         remove_page(m_pNotebook, "useless");
5688 
5689         gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5690         gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
5691 
5692         m_bOverFlowBoxActive = true;
5693     }
5694 
launch_split_notebooks(GtkInstanceNotebook * pThis)5695     static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
5696     {
5697         int nCurrentPage = pThis->get_current_page();
5698         pThis->split_notebooks();
5699         pThis->set_current_page(nCurrentPage);
5700         pThis->m_nLaunchSplitTimeoutId = 0;
5701         return false;
5702     }
5703 
5704     // tdf#120371
5705     // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
5706     // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
5707     // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
5708     // tabs in a single row when they would fit
signal_notebook_size_allocate()5709     void signal_notebook_size_allocate()
5710     {
5711         if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
5712             return;
5713         disable_notify_events();
5714         gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
5715         if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
5716         {
5717             for (gint i = 0; i < nPages; ++i)
5718             {
5719                 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
5720                 if (!gtk_widget_get_child_visible(pTabWidget))
5721                 {
5722                     m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
5723                     break;
5724                 }
5725             }
5726         }
5727         enable_notify_events();
5728     }
5729 
signalSizeAllocate(GtkWidget *,GdkRectangle *,gpointer widget)5730     static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
5731     {
5732         GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5733         pThis->signal_notebook_size_allocate();
5734     }
5735 
signal_focus(GtkDirectionType direction)5736     bool signal_focus(GtkDirectionType direction)
5737     {
5738         if (!m_bOverFlowBoxActive)
5739             return false;
5740 
5741         int nPage = gtk_notebook_get_current_page(m_pNotebook);
5742         if (direction == GTK_DIR_LEFT && nPage == 0)
5743         {
5744             auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5745             gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
5746             return true;
5747         }
5748         else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
5749         {
5750             gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
5751             return true;
5752         }
5753 
5754         return false;
5755     }
5756 
signalFocus(GtkNotebook * notebook,GtkDirectionType direction,gpointer widget)5757     static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
5758     {
5759         // if the notebook widget itself has focus
5760         if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
5761         {
5762             GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5763             return pThis->signal_focus(direction);
5764         }
5765         return false;
5766     }
5767 
5768     // ctrl + page_up/ page_down
signal_change_current_page(gint arg1)5769     bool signal_change_current_page(gint arg1)
5770     {
5771         bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
5772         if (bHandled)
5773             g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
5774         return false;
5775     }
5776 
signalChangeCurrentPage(GtkNotebook *,gint arg1,gpointer widget)5777     static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
5778     {
5779         if (arg1 == 0)
5780             return true;
5781         GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5782         return pThis->signal_change_current_page(arg1);
5783     }
5784 
5785 public:
GtkInstanceNotebook(GtkNotebook * pNotebook,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)5786     GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5787         : GtkInstanceContainer(GTK_CONTAINER(pNotebook), pBuilder, bTakeOwnership)
5788         , m_pNotebook(pNotebook)
5789         , m_pOverFlowBox(nullptr)
5790         , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
5791         , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
5792         , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
5793         , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
5794         , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
5795         , m_nLaunchSplitTimeoutId(0)
5796         , m_bOverFlowBoxActive(false)
5797         , m_bOverFlowBoxIsStart(false)
5798         , m_nStartTabCount(0)
5799         , m_nEndTabCount(0)
5800     {
5801         gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
5802         if (get_n_pages() > 6)
5803             m_nSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
5804         else
5805             m_nSizeAllocateSignalId = 0;
5806         gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
5807 
5808         // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
5809         // the unwanted tab into invisibility
5810         GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
5811         GtkCssProvider *pProvider = gtk_css_provider_new();
5812         static const gchar data[] = "header.top > tabs > tab:checked { 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; }";
5813         static const gchar olddata[] = "tab.top:active { 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; }";
5814         gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
5815         gtk_style_context_add_provider(pNotebookContext, GTK_STYLE_PROVIDER(pProvider),
5816                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
5817     }
5818 
get_current_page() const5819     virtual int get_current_page() const override
5820     {
5821         int nPage = gtk_notebook_get_current_page(m_pNotebook);
5822         if (nPage == -1)
5823             return nPage;
5824         if (m_bOverFlowBoxIsStart)
5825         {
5826             auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5827             // add count of overflow pages, minus the extra tab
5828             nPage += nOverFlowLen;
5829         }
5830         return nPage;
5831     }
5832 
get_page_ident(int nPage) const5833     virtual OString get_page_ident(int nPage) const override
5834     {
5835         auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5836         auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5837         if (m_bOverFlowBoxIsStart)
5838         {
5839             if (nPage < nOverFlowLen)
5840                 return get_page_ident(m_pOverFlowNotebook, nPage);
5841             nPage -= nOverFlowLen;
5842             return get_page_ident(m_pNotebook, nPage);
5843         }
5844         else
5845         {
5846             if (nPage < nMainLen)
5847                 return get_page_ident(m_pNotebook, nPage);
5848             nPage -= nMainLen;
5849             return get_page_ident(m_pOverFlowNotebook, nPage);
5850         }
5851     }
5852 
get_current_page_ident() const5853     virtual OString get_current_page_ident() const override
5854     {
5855         return get_page_ident(get_current_page());
5856     }
5857 
get_page(const OString & rIdent) const5858     virtual weld::Container* get_page(const OString& rIdent) const override
5859     {
5860         int nPage = get_page_number(rIdent);
5861         if (nPage < 0)
5862             return nullptr;
5863 
5864         GtkContainer* pChild;
5865         if (m_bOverFlowBoxIsStart)
5866         {
5867             auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5868             if (nPage < nOverFlowLen)
5869                 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
5870             else
5871             {
5872                 nPage -= nOverFlowLen;
5873                 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
5874             }
5875         }
5876         else
5877         {
5878             auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5879             if (nPage < nMainLen)
5880                 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
5881             else
5882             {
5883                 nPage -= nMainLen;
5884                 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
5885             }
5886         }
5887 
5888         unsigned int nPageIndex = static_cast<unsigned int>(nPage);
5889         if (m_aPages.size() < nPageIndex + 1)
5890             m_aPages.resize(nPageIndex + 1);
5891         if (!m_aPages[nPageIndex])
5892             m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
5893         return m_aPages[nPageIndex].get();
5894     }
5895 
set_current_page(int nPage)5896     virtual void set_current_page(int nPage) override
5897     {
5898         if (m_bOverFlowBoxIsStart)
5899         {
5900             auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5901             if (nPage < nOverFlowLen)
5902                 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
5903             else
5904             {
5905                 nPage -= nOverFlowLen;
5906                 gtk_notebook_set_current_page(m_pNotebook, nPage);
5907             }
5908         }
5909         else
5910         {
5911             auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5912             if (nPage < nMainLen)
5913                 gtk_notebook_set_current_page(m_pNotebook, nPage);
5914             else
5915             {
5916                 nPage -= nMainLen;
5917                 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
5918             }
5919         }
5920     }
5921 
set_current_page(const OString & rIdent)5922     virtual void set_current_page(const OString& rIdent) override
5923     {
5924         gint nPage = get_page_number(rIdent);
5925         set_current_page(nPage);
5926     }
5927 
get_n_pages() const5928     virtual int get_n_pages() const override
5929     {
5930         int nLen = gtk_notebook_get_n_pages(m_pNotebook);
5931         if (m_bOverFlowBoxActive)
5932             nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5933         return nLen;
5934     }
5935 
get_tab_label_text(const OString & rIdent) const5936     virtual OUString get_tab_label_text(const OString& rIdent) const override
5937     {
5938         gint nPageNum = get_page_number(m_pNotebook, rIdent);
5939         if (nPageNum != -1)
5940             return get_tab_label_text(m_pNotebook, nPageNum);
5941         nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
5942         if (nPageNum != -1)
5943             return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
5944         return OUString();
5945     }
5946 
set_tab_label_text(const OString & rIdent,const OUString & rText)5947     virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
5948     {
5949         gint nPageNum = get_page_number(m_pNotebook, rIdent);
5950         if (nPageNum != -1)
5951         {
5952             set_tab_label_text(m_pNotebook, nPageNum, rText);
5953             return;
5954         }
5955         nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
5956         if (nPageNum != -1)
5957         {
5958             set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
5959         }
5960     }
5961 
disable_notify_events()5962     virtual void disable_notify_events() override
5963     {
5964         g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
5965         g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
5966         g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
5967         g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
5968         gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5969         GtkInstanceContainer::disable_notify_events();
5970     }
5971 
enable_notify_events()5972     virtual void enable_notify_events() override
5973     {
5974         GtkInstanceContainer::enable_notify_events();
5975         gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5976         g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
5977         g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
5978         g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
5979         g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
5980     }
5981 
reset_split_data()5982     void reset_split_data()
5983     {
5984         // reset overflow and allow it to be recalculated if necessary
5985         gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
5986         m_bOverFlowBoxActive = false;
5987         m_nStartTabCount = 0;
5988         m_nEndTabCount = 0;
5989     }
5990 
remove_page(const OString & rIdent)5991     virtual void remove_page(const OString& rIdent) override
5992     {
5993         if (m_bOverFlowBoxActive)
5994         {
5995             unsplit_notebooks();
5996             reset_split_data();
5997         }
5998 
5999         unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
6000         if (nPageIndex < m_aPages.size())
6001             m_aPages.erase(m_aPages.begin() + nPageIndex);
6002     }
6003 
append_page(const OString & rIdent,const OUString & rLabel)6004     virtual void append_page(const OString& rIdent, const OUString& rLabel) override
6005     {
6006         if (m_bOverFlowBoxActive)
6007         {
6008             unsplit_notebooks();
6009             reset_split_data();
6010         }
6011 
6012         // reset overflow and allow it to be recalculated if necessary
6013         gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
6014         m_bOverFlowBoxActive = false;
6015 
6016         append_page(m_pNotebook, rIdent, rLabel, gtk_grid_new());
6017     }
6018 
~GtkInstanceNotebook()6019     virtual ~GtkInstanceNotebook() override
6020     {
6021         if (m_nLaunchSplitTimeoutId)
6022             g_source_remove(m_nLaunchSplitTimeoutId);
6023         if (m_nSizeAllocateSignalId)
6024             g_signal_handler_disconnect(m_pNotebook, m_nSizeAllocateSignalId);
6025         g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
6026         g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
6027         g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
6028         g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
6029         gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
6030         if (m_pOverFlowBox)
6031             gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
6032     }
6033 };
6034 
6035 class GtkInstanceButton : public GtkInstanceContainer, public virtual weld::Button
6036 {
6037 private:
6038     GtkButton* m_pButton;
6039     gulong m_nSignalId;
6040 
signalClicked(GtkButton *,gpointer widget)6041     static void signalClicked(GtkButton*, gpointer widget)
6042     {
6043         GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
6044         SolarMutexGuard aGuard;
6045         pThis->signal_clicked();
6046     }
6047 
6048 public:
GtkInstanceButton(GtkButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)6049     GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6050         : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
6051         , m_pButton(pButton)
6052         , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
6053     {
6054         g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
6055     }
6056 
set_label(const OUString & rText)6057     virtual void set_label(const OUString& rText) override
6058     {
6059         ::set_label(m_pButton, rText);
6060     }
6061 
set_image(VirtualDevice * pDevice)6062     virtual void set_image(VirtualDevice* pDevice) override
6063     {
6064         gtk_button_set_always_show_image(m_pButton, true);
6065         gtk_button_set_image_position(m_pButton, GTK_POS_LEFT);
6066         if (pDevice)
6067             gtk_button_set_image(m_pButton, image_new_from_virtual_device(*pDevice));
6068         else
6069             gtk_button_set_image(m_pButton, nullptr);
6070     }
6071 
set_from_icon_name(const OUString & rIconName)6072     virtual void set_from_icon_name(const OUString& rIconName) override
6073     {
6074         GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
6075         if (!pixbuf)
6076             gtk_button_set_image(m_pButton, nullptr);
6077         else
6078         {
6079             gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
6080             g_object_unref(pixbuf);
6081         }
6082     }
6083 
set_image(const css::uno::Reference<css::graphic::XGraphic> & rImage)6084     virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
6085     {
6086         GdkPixbuf* pixbuf = getPixbuf(rImage);
6087         if (!pixbuf)
6088             gtk_button_set_image(m_pButton, nullptr);
6089         else
6090         {
6091             gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
6092             g_object_unref(pixbuf);
6093         }
6094     }
6095 
get_label() const6096     virtual OUString get_label() const override
6097     {
6098         return ::get_label(m_pButton);
6099     }
6100 
set_label_line_wrap(bool wrap)6101     virtual void set_label_line_wrap(bool wrap) override
6102     {
6103         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pButton));
6104         gtk_label_set_line_wrap(GTK_LABEL(pChild), wrap);
6105     }
6106 
6107     // allow us to block buttons with click handlers making dialogs return a response
has_click_handler() const6108     bool has_click_handler() const
6109     {
6110         return m_aClickHdl.IsSet();
6111     }
6112 
clear_click_handler()6113     void clear_click_handler()
6114     {
6115         m_aClickHdl = Link<Button&, void>();
6116     }
6117 
disable_notify_events()6118     virtual void disable_notify_events() override
6119     {
6120         g_signal_handler_block(m_pButton, m_nSignalId);
6121         GtkInstanceContainer::disable_notify_events();
6122     }
6123 
enable_notify_events()6124     virtual void enable_notify_events() override
6125     {
6126         GtkInstanceContainer::enable_notify_events();
6127         g_signal_handler_unblock(m_pButton, m_nSignalId);
6128     }
6129 
~GtkInstanceButton()6130     virtual ~GtkInstanceButton() override
6131     {
6132         g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
6133         g_signal_handler_disconnect(m_pButton, m_nSignalId);
6134     }
6135 };
6136 
asyncresponse(gint ret)6137 void GtkInstanceDialog::asyncresponse(gint ret)
6138 {
6139     if (ret == GTK_RESPONSE_HELP)
6140     {
6141         help();
6142         return;
6143     }
6144 
6145     GtkInstanceButton* pClickHandler = has_click_handler(ret);
6146     if (pClickHandler)
6147     {
6148         // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
6149         if (ret == GTK_RESPONSE_DELETE_EVENT)
6150             close(false);
6151         return;
6152     }
6153 
6154     if (get_modal())
6155         m_aDialogRun.dec_modal_count();
6156     hide();
6157 
6158     // move the self pointer, otherwise it might be de-allocated by time we try to reset it
6159     auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
6160     auto xDialogController = std::move(m_xDialogController);
6161     auto aFunc = std::move(m_aFunc);
6162 
6163     auto nResponseSignalId = m_nResponseSignalId;
6164     auto nCancelSignalId = m_nCancelSignalId;
6165     auto nSignalDeleteId = m_nSignalDeleteId;
6166     m_nResponseSignalId = 0;
6167     m_nCancelSignalId = 0;
6168     m_nSignalDeleteId = 0;
6169 
6170     aFunc(GtkToVcl(ret));
6171 
6172     if (nResponseSignalId)
6173         g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
6174     if (nCancelSignalId)
6175         g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
6176     if (nSignalDeleteId)
6177         g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
6178 
6179     xDialogController.reset();
6180     xRunAsyncSelf.reset();
6181 }
6182 
run()6183 int GtkInstanceDialog::run()
6184 {
6185     if (GTK_IS_DIALOG(m_pDialog))
6186         sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
6187     int ret;
6188     while (true)
6189     {
6190         ret = m_aDialogRun.run();
6191         if (ret == GTK_RESPONSE_HELP)
6192         {
6193             help();
6194             continue;
6195         }
6196         else if (has_click_handler(ret))
6197             continue;
6198         break;
6199     }
6200     hide();
6201     return GtkToVcl(ret);
6202 }
6203 
weld_widget_for_response(int nVclResponse)6204 weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
6205 {
6206     GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
6207     if (!pButton)
6208         return nullptr;
6209     return new GtkInstanceButton(pButton, m_pBuilder, false);
6210 }
6211 
response(int nResponse)6212 void GtkInstanceDialog::response(int nResponse)
6213 {
6214     int nGtkResponse = VclToGtk(nResponse);
6215     //unblock this response now when activated through code
6216     if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
6217     {
6218         void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
6219         GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
6220         if (pButton)
6221             pButton->clear_click_handler();
6222     }
6223     if (GTK_IS_DIALOG(m_pDialog))
6224         gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
6225     else if (GTK_IS_ASSISTANT(m_pDialog))
6226     {
6227         if (!m_aDialogRun.loop_is_running())
6228             asyncresponse(nGtkResponse);
6229         else
6230         {
6231             m_aDialogRun.m_nResponseId = nGtkResponse;
6232             m_aDialogRun.loop_quit();
6233         }
6234     }
6235 }
6236 
close(bool bCloseSignal)6237 void GtkInstanceDialog::close(bool bCloseSignal)
6238 {
6239     GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
6240     if (pClickHandler)
6241     {
6242         if (bCloseSignal)
6243             g_signal_stop_emission_by_name(m_pDialog, "close");
6244         // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
6245         // act as if cancel button was pressed
6246         pClickHandler->clicked();
6247         return;
6248     }
6249     response(RET_CANCEL);
6250 }
6251 
has_click_handler(int nResponse)6252 GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
6253 {
6254     GtkInstanceButton* pButton = nullptr;
6255     // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
6256     nResponse = VclToGtk(GtkToVcl(nResponse));
6257     if (GtkButton* pWidget = get_widget_for_response(nResponse))
6258     {
6259         void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
6260         pButton = static_cast<GtkInstanceButton*>(pData);
6261         if (pButton && !pButton->has_click_handler())
6262             pButton = nullptr;
6263     }
6264     return pButton;
6265 }
6266 
6267 class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
6268 {
6269 private:
6270     GtkToggleButton* m_pToggleButton;
6271     gulong m_nSignalId;
6272 
signalToggled(GtkToggleButton *,gpointer widget)6273     static void signalToggled(GtkToggleButton*, gpointer widget)
6274     {
6275         GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
6276         SolarMutexGuard aGuard;
6277         pThis->signal_toggled();
6278     }
6279 public:
GtkInstanceToggleButton(GtkToggleButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)6280     GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6281         : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
6282         , m_pToggleButton(pButton)
6283         , m_nSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
6284     {
6285     }
6286 
set_active(bool active)6287     virtual void set_active(bool active) override
6288     {
6289         disable_notify_events();
6290         gtk_toggle_button_set_inconsistent(m_pToggleButton, false);
6291         gtk_toggle_button_set_active(m_pToggleButton, active);
6292         enable_notify_events();
6293     }
6294 
get_active() const6295     virtual bool get_active() const override
6296     {
6297         return gtk_toggle_button_get_active(m_pToggleButton);
6298     }
6299 
set_inconsistent(bool inconsistent)6300     virtual void set_inconsistent(bool inconsistent) override
6301     {
6302         gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
6303     }
6304 
get_inconsistent() const6305     virtual bool get_inconsistent() const override
6306     {
6307         return gtk_toggle_button_get_inconsistent(m_pToggleButton);
6308     }
6309 
disable_notify_events()6310     virtual void disable_notify_events() override
6311     {
6312         g_signal_handler_block(m_pToggleButton, m_nSignalId);
6313         GtkInstanceButton::disable_notify_events();
6314     }
6315 
enable_notify_events()6316     virtual void enable_notify_events() override
6317     {
6318         GtkInstanceButton::enable_notify_events();
6319         g_signal_handler_unblock(m_pToggleButton, m_nSignalId);
6320     }
6321 
~GtkInstanceToggleButton()6322     virtual ~GtkInstanceToggleButton() override
6323     {
6324         g_signal_handler_disconnect(m_pToggleButton, m_nSignalId);
6325     }
6326 };
6327 
6328 class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
6329 {
6330 private:
6331     GtkMenuButton* m_pMenuButton;
6332     GtkBox* m_pBox;
6333     GtkImage* m_pImage;
6334     GtkWidget* m_pLabel;
6335     //popover cannot escape dialog under X so stick up own window instead
6336     GtkWindow* m_pMenuHack;
6337     GtkWidget* m_pPopover;
6338     gulong m_nSignalId;
6339 
signalToggled(GtkWidget *,gpointer widget)6340     static void signalToggled(GtkWidget*, gpointer widget)
6341     {
6342         GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6343         SolarMutexGuard aGuard;
6344         pThis->toggle_menu();
6345     }
6346 
do_grab()6347     void do_grab()
6348     {
6349         GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pMenuHack));
6350 #if GTK_CHECK_VERSION(3, 20, 0)
6351         if (gtk_check_version(3, 20, 0) == nullptr)
6352         {
6353             GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
6354             gdk_seat_grab(pSeat, gtk_widget_get_window(GTK_WIDGET(m_pMenuHack)),
6355                           GDK_SEAT_CAPABILITY_ALL, true, nullptr, nullptr, nullptr, nullptr);
6356             return;
6357         }
6358 #endif
6359         //else older gtk3
6360         const int nMask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
6361 
6362         GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
6363         GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
6364         gdk_device_grab(pPointer, gtk_widget_get_window(GTK_WIDGET(m_pMenuHack)), GDK_OWNERSHIP_NONE,
6365                         true, GdkEventMask(nMask), nullptr, gtk_get_current_event_time());
6366     }
6367 
do_ungrab()6368     void do_ungrab()
6369     {
6370         GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pMenuHack));
6371 #if GTK_CHECK_VERSION(3, 20, 0)
6372         if (gtk_check_version(3, 20, 0) == nullptr)
6373         {
6374             GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
6375             gdk_seat_ungrab(pSeat);
6376             return;
6377         }
6378 #endif
6379         //else older gtk3
6380         GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
6381         GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
6382         gdk_device_ungrab(pPointer, gtk_get_current_event_time());
6383     }
6384 
toggle_menu()6385     void toggle_menu()
6386     {
6387         if (!m_pMenuHack)
6388             return;
6389         if (!get_active())
6390         {
6391             do_ungrab();
6392 
6393             gtk_widget_hide(GTK_WIDGET(m_pMenuHack));
6394             //put contents back from where the came from
6395             GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pMenuHack));
6396             g_object_ref(pChild);
6397             gtk_container_remove(GTK_CONTAINER(m_pMenuHack), pChild);
6398             gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
6399             g_object_unref(pChild);
6400         }
6401         else
6402         {
6403             //set border width
6404             gtk_container_set_border_width(GTK_CONTAINER(m_pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(m_pPopover)));
6405 
6406             //steal popover contents and smuggle into toplevel display window
6407             GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pPopover));
6408             g_object_ref(pChild);
6409             gtk_container_remove(GTK_CONTAINER(m_pPopover), pChild);
6410             gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
6411             g_object_unref(pChild);
6412 
6413             //place the toplevel just below its launcher button
6414             GtkWidget* pToplevel = gtk_widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
6415             gint x, y, absx, absy;
6416             gtk_widget_translate_coordinates(GTK_WIDGET(m_pMenuButton), pToplevel, 0, 0, &x, &y);
6417             GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
6418             gdk_window_get_position(pWindow, &absx, &absy);
6419 
6420             gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), m_pMenuHack);
6421             gtk_window_set_transient_for(m_pMenuHack, GTK_WINDOW(pToplevel));
6422 
6423             gtk_widget_show_all(GTK_WIDGET(m_pMenuHack));
6424             gtk_window_move(m_pMenuHack, x + absx, y + absy + gtk_widget_get_allocated_height(GTK_WIDGET(m_pMenuButton)));
6425 
6426             gtk_widget_grab_focus(GTK_WIDGET(m_pMenuHack));
6427 
6428             do_grab();
6429         }
6430     }
6431 
signalGrabBroken(GtkWidget *,GdkEventGrabBroken * pEvent,gpointer widget)6432     static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
6433     {
6434         GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6435         pThis->grab_broken(pEvent);
6436     }
6437 
grab_broken(const GdkEventGrabBroken * event)6438     void grab_broken(const GdkEventGrabBroken *event)
6439     {
6440         if (event->grab_window == nullptr)
6441         {
6442             set_active(false);
6443         }
6444         else
6445         {
6446             //try and regrab, so when we lose the grab to the menu of the color palette
6447             //combobox we regain it so the color palette doesn't itself disappear on next
6448             //click on the color palette combobox
6449             do_grab();
6450         }
6451     }
6452 
signalButtonRelease(GtkWidget * pWidget,GdkEventButton * pEvent,gpointer widget)6453     static gboolean signalButtonRelease(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
6454     {
6455         GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6456         return pThis->button_release(pWidget, pEvent);
6457     }
6458 
button_release(GtkWidget * pWidget,GdkEventButton * pEvent)6459     bool button_release(GtkWidget* pWidget, GdkEventButton* pEvent)
6460     {
6461         //we want to pop down if the button was released outside our popup
6462         gdouble x = pEvent->x_root;
6463         gdouble y = pEvent->y_root;
6464         gint xoffset, yoffset;
6465         gdk_window_get_root_origin(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
6466 
6467         GtkAllocation alloc;
6468         gtk_widget_get_allocation(pWidget, &alloc);
6469         xoffset += alloc.x;
6470         yoffset += alloc.y;
6471 
6472         gtk_widget_get_allocation(GTK_WIDGET(m_pMenuHack), &alloc);
6473         gint x1 = alloc.x + xoffset;
6474         gint y1 = alloc.y + yoffset;
6475         gint x2 = x1 + alloc.width;
6476         gint y2 = y1 + alloc.height;
6477 
6478         if (x > x1 && x < x2 && y > y1 && y < y2)
6479             return false;
6480 
6481         set_active(false);
6482 
6483         return false;
6484     }
6485 
keyPress(GtkWidget *,GdkEventKey * pEvent,gpointer widget)6486     static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
6487     {
6488         GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6489         return pThis->key_press(pEvent);
6490     }
6491 
key_press(const GdkEventKey * pEvent)6492     bool key_press(const GdkEventKey* pEvent)
6493     {
6494         if (pEvent->keyval == GDK_KEY_Escape)
6495         {
6496             set_active(false);
6497             return true;
6498         }
6499         return false;
6500     }
6501 
ensure_image_widget()6502     void ensure_image_widget()
6503     {
6504         if (!m_pImage)
6505         {
6506             m_pImage = GTK_IMAGE(gtk_image_new());
6507             gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
6508             gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
6509             gtk_widget_show(GTK_WIDGET(m_pImage));
6510         }
6511     }
6512 
6513 public:
GtkInstanceMenuButton(GtkMenuButton * pMenuButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)6514     GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6515         : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
6516         , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
6517         , m_pMenuButton(pMenuButton)
6518         , m_pImage(nullptr)
6519         , m_pMenuHack(nullptr)
6520         , m_pPopover(nullptr)
6521         , m_nSignalId(0)
6522     {
6523         m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
6524         //do it "manually" so we can have the dropdown image in GtkMenuButtons shown
6525         //on the right at the same time as this image is shown on the left
6526         g_object_ref(m_pLabel);
6527         gtk_container_remove(GTK_CONTAINER(m_pMenuButton), m_pLabel);
6528 
6529         gint nImageSpacing(2);
6530         GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pMenuButton));
6531         gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
6532         m_pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
6533 
6534         gtk_box_pack_start(m_pBox, m_pLabel, false, false, 0);
6535         g_object_unref(m_pLabel);
6536 
6537         if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(m_pMenuButton)))
6538             gtk_box_pack_end(m_pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
6539 
6540         gtk_container_add(GTK_CONTAINER(m_pMenuButton), GTK_WIDGET(m_pBox));
6541         gtk_widget_show_all(GTK_WIDGET(m_pBox));
6542     }
6543 
set_size_request(int nWidth,int nHeight)6544     virtual void set_size_request(int nWidth, int nHeight) override
6545     {
6546         // tweak the label to get a narrower size to stick
6547         if (GTK_IS_LABEL(m_pLabel))
6548             gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
6549         gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
6550     }
6551 
set_label(const OUString & rText)6552     virtual void set_label(const OUString& rText) override
6553     {
6554         ::set_label(GTK_LABEL(m_pLabel), rText);
6555     }
6556 
get_label() const6557     virtual OUString get_label() const override
6558     {
6559         return ::get_label(GTK_LABEL(m_pLabel));
6560     }
6561 
set_image(VirtualDevice * pDevice)6562     virtual void set_image(VirtualDevice* pDevice) override
6563     {
6564         ensure_image_widget();
6565         if (pDevice)
6566         {
6567             if (gtk_check_version(3, 20, 0) == nullptr)
6568                 gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
6569             else
6570             {
6571                 GdkPixbuf* pixbuf = getPixbuf(*pDevice);
6572                 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
6573                 g_object_unref(pixbuf);
6574             }
6575         }
6576         else
6577             gtk_image_set_from_surface(m_pImage, nullptr);
6578     }
6579 
set_image(const css::uno::Reference<css::graphic::XGraphic> & rImage)6580     virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
6581     {
6582         ensure_image_widget();
6583         GdkPixbuf* pixbuf = getPixbuf(rImage);
6584         if (pixbuf)
6585         {
6586             gtk_image_set_from_pixbuf(m_pImage, pixbuf);
6587             g_object_unref(pixbuf);
6588         }
6589         else
6590             gtk_image_set_from_surface(m_pImage, nullptr);
6591     }
6592 
insert_item(int pos,const OUString & rId,const OUString & rStr,const OUString * pIconName,VirtualDevice * pImageSurface,bool bCheck)6593     virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
6594                         const OUString* pIconName, VirtualDevice* pImageSurface, bool bCheck) override
6595     {
6596         MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, bCheck);
6597     }
6598 
insert_separator(int pos,const OUString & rId)6599     virtual void insert_separator(int pos, const OUString& rId) override
6600     {
6601         MenuHelper::insert_separator(pos, rId);
6602     }
6603 
remove_item(const OString & rId)6604     virtual void remove_item(const OString& rId) override
6605     {
6606         MenuHelper::remove_item(rId);
6607     }
6608 
clear()6609     virtual void clear() override
6610     {
6611         clear_items();
6612     }
6613 
set_item_active(const OString & rIdent,bool bActive)6614     virtual void set_item_active(const OString& rIdent, bool bActive) override
6615     {
6616         MenuHelper::set_item_active(rIdent, bActive);
6617     }
6618 
set_item_sensitive(const OString & rIdent,bool bSensitive)6619     virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
6620     {
6621         MenuHelper::set_item_sensitive(rIdent, bSensitive);
6622     }
6623 
set_item_label(const OString & rIdent,const OUString & rLabel)6624     virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
6625     {
6626         MenuHelper::set_item_label(rIdent, rLabel);
6627     }
6628 
get_item_label(const OString & rIdent) const6629     virtual OUString get_item_label(const OString& rIdent) const override
6630     {
6631         return MenuHelper::get_item_label(rIdent);
6632     }
6633 
set_item_visible(const OString & rIdent,bool bVisible)6634     virtual void set_item_visible(const OString& rIdent, bool bVisible) override
6635     {
6636         MenuHelper::set_item_visible(rIdent, bVisible);
6637     }
6638 
set_item_help_id(const OString & rIdent,const OString & rHelpId)6639     virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
6640     {
6641         MenuHelper::set_item_help_id(rIdent, rHelpId);
6642     }
6643 
get_item_help_id(const OString & rIdent) const6644     virtual OString get_item_help_id(const OString& rIdent) const override
6645     {
6646         return MenuHelper::get_item_help_id(rIdent);
6647     }
6648 
signal_activate(GtkMenuItem * pItem)6649     virtual void signal_activate(GtkMenuItem* pItem) override
6650     {
6651         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
6652         signal_selected(OString(pStr, pStr ? strlen(pStr) : 0));
6653     }
6654 
set_popover(weld::Widget * pPopover)6655     virtual void set_popover(weld::Widget* pPopover) override
6656     {
6657         GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
6658         m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
6659 
6660 #if defined(GDK_WINDOWING_X11)
6661         if (!m_pMenuHack)
6662         {
6663             //under wayland a Popover will work to "escape" the parent dialog, not
6664             //so under X, so come up with this hack to use a raw GtkWindow
6665             GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
6666             if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
6667             {
6668                 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
6669                 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
6670                 gtk_window_set_modal(m_pMenuHack, true);
6671                 gtk_window_set_resizable(m_pMenuHack, false);
6672                 m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalToggled), this);
6673                 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
6674                 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
6675                 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
6676             }
6677         }
6678 #endif
6679 
6680         if (m_pMenuHack)
6681         {
6682             gtk_menu_button_set_popover(m_pMenuButton, gtk_popover_new(GTK_WIDGET(m_pMenuButton)));
6683         }
6684         else
6685         {
6686             gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
6687             if (m_pPopover)
6688                 gtk_widget_show_all(m_pPopover);
6689         }
6690     }
6691 
6692     void set_menu(weld::Menu* pMenu);
6693 
~GtkInstanceMenuButton()6694     virtual ~GtkInstanceMenuButton() override
6695     {
6696         if (m_pMenuHack)
6697         {
6698             g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
6699             gtk_menu_button_set_popover(m_pMenuButton, nullptr);
6700             gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
6701         }
6702     }
6703 };
6704 
6705 class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
6706 {
6707 protected:
6708     std::vector<GtkMenuItem*> m_aExtraItems;
6709     OString m_sActivated;
6710     GtkInstanceMenuButton* m_pTopLevelMenuButton;
6711 
6712 private:
signal_activate(GtkMenuItem * pItem)6713     virtual void signal_activate(GtkMenuItem* pItem) override
6714     {
6715         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
6716         m_sActivated = OString(pStr, pStr ? strlen(pStr) : 0);
6717         weld::Menu::signal_activate(m_sActivated);
6718     }
6719 
clear_extras()6720     void clear_extras()
6721     {
6722         if (m_aExtraItems.empty())
6723             return;
6724         if (m_pTopLevelMenuButton)
6725         {
6726             for (auto a : m_aExtraItems)
6727                 m_pTopLevelMenuButton->remove_from_map(a);
6728         }
6729         m_aExtraItems.clear();
6730     }
6731 
6732 public:
GtkInstanceMenu(GtkMenu * pMenu,bool bTakeOwnership)6733     GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
6734         : MenuHelper(pMenu, bTakeOwnership)
6735         , m_pTopLevelMenuButton(nullptr)
6736     {
6737         // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
6738         // then find that MenuButton parent so that when adding items to this
6739         // menu we can inform the MenuButton of their addition
6740         GtkMenu* pTopLevelMenu = pMenu;
6741         while (true)
6742         {
6743             GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
6744             if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
6745                 break;
6746             GtkWidget* pParent = gtk_widget_get_parent(pAttached);
6747             if (!pParent || !GTK_IS_MENU(pParent))
6748                 break;
6749             pTopLevelMenu = GTK_MENU(pParent);
6750         }
6751         if (pTopLevelMenu != pMenu)
6752         {
6753             GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
6754             if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
6755             {
6756                 void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
6757                 m_pTopLevelMenuButton = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
6758             }
6759         }
6760     }
6761 
popup_at_rect(weld::Widget * pParent,const tools::Rectangle & rRect)6762     virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle &rRect) override
6763     {
6764         m_sActivated.clear();
6765 
6766         GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
6767         assert(pGtkWidget);
6768 
6769         GtkWidget* pWidget = pGtkWidget->getWidget();
6770         gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
6771 
6772         //run in a sub main loop because we need to keep vcl PopupMenu alive to use
6773         //it during DispatchCommand, returning now to the outer loop causes the
6774         //launching PopupMenu to be destroyed, instead run the subloop here
6775         //until the gtk menu is destroyed
6776         GMainLoop* pLoop = g_main_loop_new(nullptr, true);
6777         gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
6778 
6779 #if GTK_CHECK_VERSION(3,22,0)
6780         if (gtk_check_version(3, 22, 0) == nullptr)
6781         {
6782             GdkRectangle aRect{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
6783                                static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
6784             if (SwapForRTL(pWidget))
6785                 aRect.x = gtk_widget_get_allocated_width(pWidget) - aRect.width - 1 - aRect.x;
6786 
6787             // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
6788             // before trying to launch the menu
6789             // https://gitlab.gnome.org/GNOME/gtk/issues/1785
6790             GdkEvent *event = GtkSalFrame::makeFakeKeyPress(pWidget);
6791             gtk_main_do_event(event);
6792             gdk_event_free(event);
6793 
6794             gtk_menu_popup_at_rect(m_pMenu, gtk_widget_get_window(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, nullptr);
6795         }
6796         else
6797 #else
6798         (void) rRect;
6799 #endif
6800         {
6801             guint nButton;
6802             guint32 nTime;
6803 
6804             //typically there is an event, and we can then distinguish if this was
6805             //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
6806             //doesn't)
6807             GdkEvent *pEvent = gtk_get_current_event();
6808             if (pEvent)
6809             {
6810                 gdk_event_get_button(pEvent, &nButton);
6811                 nTime = gdk_event_get_time(pEvent);
6812             }
6813             else
6814             {
6815                 nButton = 0;
6816                 nTime = GtkSalFrame::GetLastInputEventTime();
6817             }
6818 
6819             gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
6820         }
6821 
6822         if (g_main_loop_is_running(pLoop))
6823         {
6824             gdk_threads_leave();
6825             g_main_loop_run(pLoop);
6826             gdk_threads_enter();
6827         }
6828         g_main_loop_unref(pLoop);
6829         g_signal_handler_disconnect(m_pMenu, nSignalId);
6830         gtk_menu_detach(m_pMenu);
6831 
6832         return m_sActivated;
6833     }
6834 
set_sensitive(const OString & rIdent,bool bSensitive)6835     virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
6836     {
6837         set_item_sensitive(rIdent, bSensitive);
6838     }
6839 
set_active(const OString & rIdent,bool bActive)6840     virtual void set_active(const OString& rIdent, bool bActive) override
6841     {
6842         set_item_active(rIdent, bActive);
6843     }
6844 
get_active(const OString & rIdent) const6845     virtual bool get_active(const OString& rIdent) const override
6846     {
6847         return get_item_active(rIdent);
6848     }
6849 
set_visible(const OString & rIdent,bool bShow)6850     virtual void set_visible(const OString& rIdent, bool bShow) override
6851     {
6852         set_item_visible(rIdent, bShow);
6853     }
6854 
set_label(const OString & rIdent,const OUString & rLabel)6855     virtual void set_label(const OString& rIdent, const OUString& rLabel) override
6856     {
6857         set_item_label(rIdent, rLabel);
6858     }
6859 
insert_separator(int pos,const OUString & rId)6860     virtual void insert_separator(int pos, const OUString& rId) override
6861     {
6862         MenuHelper::insert_separator(pos, rId);
6863     }
6864 
clear()6865     virtual void clear() override
6866     {
6867         clear_extras();
6868         clear_items();
6869     }
6870 
insert(int pos,const OUString & rId,const OUString & rStr,const OUString * pIconName,VirtualDevice * pImageSurface,bool bCheck)6871     virtual void insert(int pos, const OUString& rId, const OUString& rStr,
6872                         const OUString* pIconName, VirtualDevice* pImageSurface,
6873                         bool bCheck) override
6874     {
6875         GtkWidget* pImage = nullptr;
6876         if (pIconName)
6877         {
6878             if (GdkPixbuf* pixbuf = load_icon_by_name(*pIconName))
6879             {
6880                 pImage = gtk_image_new_from_pixbuf(pixbuf);
6881                 g_object_unref(pixbuf);
6882             }
6883         }
6884         else if (pImageSurface)
6885         {
6886             pImage = image_new_from_virtual_device(*pImageSurface);
6887         }
6888 
6889         GtkWidget *pItem;
6890         if (pImage)
6891         {
6892             GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
6893             GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
6894             pItem = bCheck ? gtk_check_menu_item_new() : gtk_menu_item_new();
6895             gtk_container_add(GTK_CONTAINER(pBox), pImage);
6896             gtk_container_add(GTK_CONTAINER(pBox), pLabel);
6897             gtk_container_add(GTK_CONTAINER(pItem), pBox);
6898             gtk_widget_show_all(pItem);
6899         }
6900         else
6901         {
6902             pItem = bCheck ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
6903                            : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
6904         }
6905         gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
6906         gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
6907         gtk_widget_show(pItem);
6908         GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
6909         m_aExtraItems.push_back(pMenuItem);
6910         add_to_map(pMenuItem);
6911         if (m_pTopLevelMenuButton)
6912             m_pTopLevelMenuButton->add_to_map(pMenuItem);
6913         if (pos != -1)
6914             gtk_menu_reorder_child(m_pMenu, pItem, pos);
6915     }
6916 
~GtkInstanceMenu()6917     virtual ~GtkInstanceMenu() override
6918     {
6919         clear_extras();
6920     }
6921 };
6922 
6923 namespace
6924 {
GtkToVcl(GtkIconSize eSize)6925     vcl::ImageType GtkToVcl(GtkIconSize eSize)
6926     {
6927         vcl::ImageType eRet;
6928         switch (eSize)
6929         {
6930             case GTK_ICON_SIZE_MENU:
6931             case GTK_ICON_SIZE_SMALL_TOOLBAR:
6932             case GTK_ICON_SIZE_BUTTON:
6933                 eRet = vcl::ImageType::Size16;
6934                 break;
6935             case GTK_ICON_SIZE_LARGE_TOOLBAR:
6936                 eRet = vcl::ImageType::Size26;
6937                 break;
6938             case GTK_ICON_SIZE_DND:
6939             case GTK_ICON_SIZE_DIALOG:
6940                 eRet = vcl::ImageType::Size32;
6941                 break;
6942             default:
6943             case GTK_ICON_SIZE_INVALID:
6944                 eRet = vcl::ImageType::Small;
6945                 break;
6946         }
6947         return eRet;
6948     }
6949 }
6950 
set_menu(weld::Menu * pMenu)6951 void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
6952 {
6953     GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
6954     m_pPopover = nullptr;
6955     GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
6956     gtk_menu_button_set_popup(m_pMenuButton, pMenuWidget);
6957 }
6958 
6959 class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
6960 {
6961 private:
6962     GtkToolbar* m_pToolbar;
6963 
6964     std::map<OString, GtkToolButton*> m_aMap;
6965     std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
6966 
6967     // at the time of writing there is no gtk_menu_tool_button_set_popover available
6968     // though there will be in the future
6969     // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
find_menu_button(GtkWidget * pWidget,gpointer user_data)6970     static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
6971     {
6972         if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
6973         {
6974             GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
6975             *ppToggleButton = pWidget;
6976         }
6977         else if (GTK_IS_CONTAINER(pWidget))
6978             gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
6979     }
6980 
collect(GtkWidget * pItem,gpointer widget)6981     static void collect(GtkWidget* pItem, gpointer widget)
6982     {
6983         if (GTK_IS_TOOL_BUTTON(pItem))
6984         {
6985             GtkToolButton* pToolItem = GTK_TOOL_BUTTON(pItem);
6986             GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
6987 
6988             GtkMenuButton* pMenuButton = nullptr;
6989             if (GTK_IS_MENU_TOOL_BUTTON(pItem))
6990                 find_menu_button(pItem, &pMenuButton);
6991 
6992             pThis->add_to_map(pToolItem, pMenuButton);
6993         }
6994     }
6995 
add_to_map(GtkToolButton * pToolItem,GtkMenuButton * pMenuButton)6996     void add_to_map(GtkToolButton* pToolItem, GtkMenuButton* pMenuButton)
6997     {
6998         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pToolItem));
6999         OString id(pStr, pStr ? strlen(pStr) : 0);
7000         m_aMap[id] = pToolItem;
7001         if (pMenuButton)
7002             m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, m_pBuilder, false);
7003         g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
7004     }
7005 
signalItemClicked(GtkToolButton * pItem,gpointer widget)7006     static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
7007     {
7008         GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
7009         SolarMutexGuard aGuard;
7010         pThis->signal_item_clicked(pItem);
7011     }
7012 
signal_item_clicked(GtkToolButton * pItem)7013     void signal_item_clicked(GtkToolButton* pItem)
7014     {
7015         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7016         signal_clicked(OString(pStr, pStr ? strlen(pStr) : 0));
7017     }
7018 
7019 public:
GtkInstanceToolbar(GtkToolbar * pToolbar,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7020     GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7021         : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
7022         , m_pToolbar(pToolbar)
7023     {
7024         gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
7025     }
7026 
disable_item_notify_events()7027     void disable_item_notify_events()
7028     {
7029         for (auto& a : m_aMap)
7030             g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
7031     }
7032 
enable_item_notify_events()7033     void enable_item_notify_events()
7034     {
7035         for (auto& a : m_aMap)
7036             g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
7037     }
7038 
set_item_sensitive(const OString & rIdent,bool bSensitive)7039     virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
7040     {
7041         disable_item_notify_events();
7042         gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
7043         enable_item_notify_events();
7044     }
7045 
get_item_sensitive(const OString & rIdent) const7046     virtual bool get_item_sensitive(const OString& rIdent) const override
7047     {
7048         return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
7049     }
7050 
set_item_active(const OString & rIdent,bool bActive)7051     virtual void set_item_active(const OString& rIdent, bool bActive) override
7052     {
7053         disable_item_notify_events();
7054 
7055         auto aFind = m_aMenuButtonMap.find(rIdent);
7056         if (aFind != m_aMenuButtonMap.end())
7057             aFind->second->set_active(bActive);
7058         else
7059         {
7060             GtkToolButton* pToolButton = m_aMap.find(rIdent)->second;
7061             gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
7062         }
7063 
7064         enable_item_notify_events();
7065     }
7066 
get_item_active(const OString & rIdent) const7067     virtual bool get_item_active(const OString& rIdent) const override
7068     {
7069         auto aFind = m_aMenuButtonMap.find(rIdent);
7070         if (aFind != m_aMenuButtonMap.end())
7071             return aFind->second->get_active();
7072 
7073         GtkToolButton* pToolButton = m_aMap.find(rIdent)->second;
7074         return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
7075     }
7076 
insert_separator(int pos,const OUString & rId)7077     virtual void insert_separator(int pos, const OUString& rId) override
7078     {
7079         GtkToolItem* pItem = gtk_separator_tool_item_new();
7080         gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
7081         gtk_toolbar_insert(m_pToolbar, pItem, pos);
7082         gtk_widget_show(GTK_WIDGET(pItem));
7083     }
7084 
set_item_popover(const OString & rIdent,weld::Widget * pPopover)7085     virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
7086     {
7087         m_aMenuButtonMap[rIdent]->set_popover(pPopover);
7088     }
7089 
set_item_menu(const OString & rIdent,weld::Menu * pMenu)7090     virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
7091     {
7092         m_aMenuButtonMap[rIdent]->set_menu(pMenu);
7093     }
7094 
get_n_items() const7095     virtual int get_n_items() const override
7096     {
7097         return gtk_toolbar_get_n_items(m_pToolbar);
7098     }
7099 
get_item_ident(int nIndex) const7100     virtual OString get_item_ident(int nIndex) const override
7101     {
7102         GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7103         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7104         return OString(pStr, pStr ? strlen(pStr) : 0);
7105     }
7106 
set_item_label(int nIndex,const OUString & rLabel)7107     virtual void set_item_label(int nIndex, const OUString& rLabel) override
7108     {
7109         GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7110         gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
7111     }
7112 
set_item_icon(int nIndex,const css::uno::Reference<css::graphic::XGraphic> & rIcon)7113     virtual void set_item_icon(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
7114     {
7115         GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7116 
7117         GtkWidget* pImage = nullptr;
7118 
7119         if (GdkPixbuf* pixbuf = getPixbuf(rIcon))
7120         {
7121             pImage = gtk_image_new_from_pixbuf(pixbuf);
7122             g_object_unref(pixbuf);
7123             gtk_widget_show(pImage);
7124         }
7125 
7126         gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
7127     }
7128 
set_item_tooltip_text(int nIndex,const OUString & rTip)7129     virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
7130     {
7131         GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7132         gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
7133     }
7134 
get_icon_size() const7135     virtual vcl::ImageType get_icon_size() const override
7136     {
7137         return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
7138     }
7139 
~GtkInstanceToolbar()7140     virtual ~GtkInstanceToolbar() override
7141     {
7142         for (auto& a : m_aMap)
7143             g_signal_handlers_disconnect_by_data(a.second, this);
7144     }
7145 };
7146 
7147 class GtkInstanceLinkButton : public GtkInstanceContainer, public virtual weld::LinkButton
7148 {
7149 private:
7150     GtkLinkButton* m_pButton;
7151     gulong m_nSignalId;
7152 
signalActivateLink(GtkButton *,gpointer widget)7153     static bool signalActivateLink(GtkButton*, gpointer widget)
7154     {
7155         GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
7156         SolarMutexGuard aGuard;
7157         return pThis->signal_activate_link();
7158     }
7159 
7160 public:
GtkInstanceLinkButton(GtkLinkButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7161     GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7162         : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
7163         , m_pButton(pButton)
7164         , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
7165     {
7166     }
7167 
set_label(const OUString & rText)7168     virtual void set_label(const OUString& rText) override
7169     {
7170         ::set_label(GTK_BUTTON(m_pButton), rText);
7171     }
7172 
get_label() const7173     virtual OUString get_label() const override
7174     {
7175         return ::get_label(GTK_BUTTON(m_pButton));
7176     }
7177 
set_uri(const OUString & rText)7178     virtual void set_uri(const OUString& rText) override
7179     {
7180         gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
7181     }
7182 
get_uri() const7183     virtual OUString get_uri() const override
7184     {
7185         const gchar* pStr = gtk_link_button_get_uri(m_pButton);
7186         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7187     }
7188 
disable_notify_events()7189     virtual void disable_notify_events() override
7190     {
7191         g_signal_handler_block(m_pButton, m_nSignalId);
7192         GtkInstanceContainer::disable_notify_events();
7193     }
7194 
enable_notify_events()7195     virtual void enable_notify_events() override
7196     {
7197         GtkInstanceContainer::enable_notify_events();
7198         g_signal_handler_unblock(m_pButton, m_nSignalId);
7199     }
7200 
~GtkInstanceLinkButton()7201     virtual ~GtkInstanceLinkButton() override
7202     {
7203         g_signal_handler_disconnect(m_pButton, m_nSignalId);
7204     }
7205 };
7206 
7207 class GtkInstanceRadioButton : public GtkInstanceToggleButton, public virtual weld::RadioButton
7208 {
7209 public:
GtkInstanceRadioButton(GtkRadioButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7210     GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7211         : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
7212     {
7213     }
7214 };
7215 
7216 class GtkInstanceCheckButton : public GtkInstanceToggleButton, public virtual weld::CheckButton
7217 {
7218 public:
GtkInstanceCheckButton(GtkCheckButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7219     GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7220         : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
7221     {
7222     }
7223 };
7224 
7225 class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
7226 {
7227 private:
7228     GtkScale* m_pScale;
7229     gulong m_nValueChangedSignalId;
7230 
signalValueChanged(GtkScale *,gpointer widget)7231     static void signalValueChanged(GtkScale*, gpointer widget)
7232     {
7233         GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
7234         SolarMutexGuard aGuard;
7235         pThis->signal_value_changed();
7236     }
7237 
7238 public:
GtkInstanceScale(GtkScale * pScale,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7239     GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7240         : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
7241         , m_pScale(pScale)
7242         , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
7243     {
7244     }
7245 
disable_notify_events()7246     virtual void disable_notify_events() override
7247     {
7248         g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
7249         GtkInstanceWidget::disable_notify_events();
7250     }
7251 
enable_notify_events()7252     virtual void enable_notify_events() override
7253     {
7254         GtkInstanceWidget::enable_notify_events();
7255         g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
7256     }
7257 
set_value(int value)7258     virtual void set_value(int value) override
7259     {
7260         disable_notify_events();
7261         gtk_range_set_value(GTK_RANGE(m_pScale), value);
7262         enable_notify_events();
7263     }
7264 
set_range(int min,int max)7265     virtual void set_range(int min, int max) override
7266     {
7267         disable_notify_events();
7268         gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
7269         enable_notify_events();
7270     }
7271 
get_value() const7272     virtual int get_value() const override
7273     {
7274         return gtk_range_get_value(GTK_RANGE(m_pScale));
7275     }
7276 
~GtkInstanceScale()7277     virtual ~GtkInstanceScale() override
7278     {
7279         g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
7280     }
7281 };
7282 
7283 class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
7284 {
7285 private:
7286     GtkProgressBar* m_pProgressBar;
7287 
7288 public:
GtkInstanceProgressBar(GtkProgressBar * pProgressBar,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7289     GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7290         : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
7291         , m_pProgressBar(pProgressBar)
7292     {
7293     }
7294 
set_percentage(int value)7295     virtual void set_percentage(int value) override
7296     {
7297         gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
7298     }
7299 
get_text() const7300     virtual OUString get_text() const override
7301     {
7302         const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
7303         OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
7304         return sRet;
7305     }
7306 
set_text(const OUString & rText)7307     virtual void set_text(const OUString& rText) override
7308     {
7309         gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
7310     }
7311 };
7312 
7313 class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
7314 {
7315 private:
7316     GtkSpinner* m_pSpinner;
7317 
7318 public:
GtkInstanceSpinner(GtkSpinner * pSpinner,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7319     GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7320         : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
7321         , m_pSpinner(pSpinner)
7322     {
7323     }
7324 
start()7325     virtual void start() override
7326     {
7327         gtk_spinner_start(m_pSpinner);
7328     }
7329 
stop()7330     virtual void stop() override
7331     {
7332         gtk_spinner_stop(m_pSpinner);
7333     }
7334 };
7335 
7336 class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
7337 {
7338 private:
7339     GtkImage* m_pImage;
7340 
7341 public:
GtkInstanceImage(GtkImage * pImage,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7342     GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7343         : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
7344         , m_pImage(pImage)
7345     {
7346     }
7347 
set_from_icon_name(const OUString & rIconName)7348     virtual void set_from_icon_name(const OUString& rIconName) override
7349     {
7350         GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
7351         if (!pixbuf)
7352             return;
7353         gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7354         g_object_unref(pixbuf);
7355     }
7356 
set_image(VirtualDevice * pDevice)7357     virtual void set_image(VirtualDevice* pDevice) override
7358     {
7359         if (gtk_check_version(3, 20, 0) == nullptr)
7360         {
7361             if (pDevice)
7362                 gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
7363             else
7364                 gtk_image_set_from_surface(m_pImage, nullptr);
7365             return;
7366         }
7367 
7368         GdkPixbuf* pixbuf = pDevice ? getPixbuf(*pDevice) : nullptr;
7369         gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7370         if (pixbuf)
7371             g_object_unref(pixbuf);
7372     }
7373 
set_image(const css::uno::Reference<css::graphic::XGraphic> & rImage)7374     virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
7375     {
7376         GdkPixbuf* pixbuf = getPixbuf(rImage);
7377         gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7378         if (pixbuf)
7379             g_object_unref(pixbuf);
7380     }
7381 };
7382 
7383 class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
7384 {
7385 private:
7386     GtkCalendar* m_pCalendar;
7387     gulong m_nDaySelectedSignalId;
7388     gulong m_nDaySelectedDoubleClickSignalId;
7389     gulong m_nKeyPressEventSignalId;
7390 
signalDaySelected(GtkCalendar *,gpointer widget)7391     static void signalDaySelected(GtkCalendar*, gpointer widget)
7392     {
7393         GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
7394         pThis->signal_selected();
7395     }
7396 
signalDaySelectedDoubleClick(GtkCalendar *,gpointer widget)7397     static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
7398     {
7399         GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
7400         pThis->signal_activated();
7401     }
7402 
signal_key_press(GdkEventKey * pEvent)7403     bool signal_key_press(GdkEventKey* pEvent)
7404     {
7405         if (pEvent->keyval == GDK_KEY_Return)
7406         {
7407             signal_activated();
7408             return true;
7409         }
7410         return false;
7411     }
7412 
signalKeyPress(GtkWidget *,GdkEventKey * pEvent,gpointer widget)7413     static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
7414     {
7415         GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
7416         return pThis->signal_key_press(pEvent);
7417     }
7418 
7419 public:
GtkInstanceCalendar(GtkCalendar * pCalendar,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7420     GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7421         : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
7422         , m_pCalendar(pCalendar)
7423         , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
7424         , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
7425         , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
7426     {
7427     }
7428 
set_date(const Date & rDate)7429     virtual void set_date(const Date& rDate) override
7430     {
7431         disable_notify_events();
7432         gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
7433         gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
7434         enable_notify_events();
7435     }
7436 
get_date() const7437     virtual Date get_date() const override
7438     {
7439         guint year, month, day;
7440         gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
7441         return Date(day, month + 1, year);
7442     }
7443 
disable_notify_events()7444     virtual void disable_notify_events() override
7445     {
7446         g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
7447         g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
7448         GtkInstanceWidget::disable_notify_events();
7449     }
7450 
enable_notify_events()7451     virtual void enable_notify_events() override
7452     {
7453         GtkInstanceWidget::enable_notify_events();
7454         g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
7455         g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
7456     }
7457 
~GtkInstanceCalendar()7458     virtual ~GtkInstanceCalendar() override
7459     {
7460         g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
7461         g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
7462         g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
7463     }
7464 };
7465 
7466 namespace
7467 {
create_attr_list(const vcl::Font & rFont)7468     PangoAttrList* create_attr_list(const vcl::Font& rFont)
7469     {
7470         PangoAttrList* pAttrList = pango_attr_list_new();
7471         pango_attr_list_insert(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
7472         pango_attr_list_insert(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
7473         switch (rFont.GetItalic())
7474         {
7475             case ITALIC_NONE:
7476                 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
7477                 break;
7478             case ITALIC_NORMAL:
7479                 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
7480                 break;
7481             case ITALIC_OBLIQUE:
7482                 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
7483                 break;
7484             default:
7485                 break;
7486         }
7487         switch (rFont.GetWeight())
7488         {
7489             case WEIGHT_ULTRALIGHT:
7490                 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
7491                 break;
7492             case WEIGHT_LIGHT:
7493                 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
7494                 break;
7495             case WEIGHT_NORMAL:
7496                 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
7497                 break;
7498             case WEIGHT_BOLD:
7499                 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
7500                 break;
7501             case WEIGHT_ULTRABOLD:
7502                 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
7503                 break;
7504             default:
7505                 break;
7506         }
7507         switch (rFont.GetWidthType())
7508         {
7509             case WIDTH_ULTRA_CONDENSED:
7510                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
7511                 break;
7512             case WIDTH_EXTRA_CONDENSED:
7513                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
7514                 break;
7515             case WIDTH_CONDENSED:
7516                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
7517                 break;
7518             case WIDTH_SEMI_CONDENSED:
7519                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
7520                 break;
7521             case WIDTH_NORMAL:
7522                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
7523                 break;
7524             case WIDTH_SEMI_EXPANDED:
7525                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
7526                 break;
7527             case WIDTH_EXPANDED:
7528                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
7529                 break;
7530             case WIDTH_EXTRA_EXPANDED:
7531                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
7532                 break;
7533             case WIDTH_ULTRA_EXPANDED:
7534                 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
7535                 break;
7536             default:
7537                 break;
7538         }
7539         return pAttrList;
7540     }
7541 }
7542 
7543 namespace
7544 {
set_entry_message_type(GtkEntry * pEntry,weld::EntryMessageType eType)7545     void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
7546     {
7547         if (eType == weld::EntryMessageType::Error)
7548             gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
7549         else if (eType == weld::EntryMessageType::Warning)
7550             gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
7551         else
7552             gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
7553     }
7554 }
7555 
7556 class GtkInstanceEntry : public GtkInstanceWidget, public virtual weld::Entry
7557 {
7558 private:
7559     GtkEntry* m_pEntry;
7560     gulong m_nChangedSignalId;
7561     gulong m_nInsertTextSignalId;
7562     gulong m_nCursorPosSignalId;
7563     gulong m_nSelectionPosSignalId;
7564     gulong m_nActivateSignalId;
7565 
signalChanged(GtkEntry *,gpointer widget)7566     static void signalChanged(GtkEntry*, gpointer widget)
7567     {
7568         GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7569         SolarMutexGuard aGuard;
7570         pThis->signal_changed();
7571     }
7572 
signalInsertText(GtkEntry * pEntry,const gchar * pNewText,gint nNewTextLength,gint * position,gpointer widget)7573     static void signalInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
7574                                  gint* position, gpointer widget)
7575     {
7576         GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7577         SolarMutexGuard aGuard;
7578         pThis->signal_insert_text(pEntry, pNewText, nNewTextLength, position);
7579     }
7580 
signal_insert_text(GtkEntry * pEntry,const gchar * pNewText,gint nNewTextLength,gint * position)7581     void signal_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
7582     {
7583         if (!m_aInsertTextHdl.IsSet())
7584             return;
7585         OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
7586         const bool bContinue = m_aInsertTextHdl.Call(sText);
7587         if (bContinue && !sText.isEmpty())
7588         {
7589             OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
7590             g_signal_handlers_block_by_func(pEntry, gpointer(signalInsertText), this);
7591             gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
7592             g_signal_handlers_unblock_by_func(pEntry, gpointer(signalInsertText), this);
7593         }
7594         g_signal_stop_emission_by_name(pEntry, "insert-text");
7595     }
7596 
signalCursorPosition(GtkEntry *,GParamSpec *,gpointer widget)7597     static void signalCursorPosition(GtkEntry*, GParamSpec*, gpointer widget)
7598     {
7599         GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7600         pThis->signal_cursor_position();
7601     }
7602 
signalActivate(GtkEntry *,gpointer widget)7603     static void signalActivate(GtkEntry*, gpointer widget)
7604     {
7605         GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7606         pThis->signal_activate();
7607     }
7608 
signal_activate()7609     void signal_activate()
7610     {
7611         if (m_aActivateHdl.IsSet())
7612         {
7613             SolarMutexGuard aGuard;
7614             if (m_aActivateHdl.Call(*this))
7615                 g_signal_stop_emission_by_name(m_pEntry, "activate");
7616         }
7617     }
7618 
7619 public:
GtkInstanceEntry(GtkEntry * pEntry,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)7620     GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7621         : GtkInstanceWidget(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
7622         , m_pEntry(pEntry)
7623         , m_nChangedSignalId(g_signal_connect(pEntry, "changed", G_CALLBACK(signalChanged), this))
7624         , m_nInsertTextSignalId(g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalInsertText), this))
7625         , m_nCursorPosSignalId(g_signal_connect(pEntry, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
7626         , m_nSelectionPosSignalId(g_signal_connect(pEntry, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
7627         , m_nActivateSignalId(g_signal_connect(pEntry, "activate", G_CALLBACK(signalActivate), this))
7628     {
7629     }
7630 
set_text(const OUString & rText)7631     virtual void set_text(const OUString& rText) override
7632     {
7633         disable_notify_events();
7634         gtk_entry_set_text(m_pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
7635         enable_notify_events();
7636     }
7637 
get_text() const7638     virtual OUString get_text() const override
7639     {
7640         const gchar* pText = gtk_entry_get_text(m_pEntry);
7641         OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
7642         return sRet;
7643     }
7644 
set_width_chars(int nChars)7645     virtual void set_width_chars(int nChars) override
7646     {
7647         disable_notify_events();
7648         gtk_entry_set_width_chars(m_pEntry, nChars);
7649         gtk_entry_set_max_width_chars(m_pEntry, nChars);
7650         enable_notify_events();
7651     }
7652 
get_width_chars() const7653     virtual int get_width_chars() const override
7654     {
7655         return gtk_entry_get_width_chars(m_pEntry);
7656     }
7657 
set_max_length(int nChars)7658     virtual void set_max_length(int nChars) override
7659     {
7660         disable_notify_events();
7661         gtk_entry_set_max_length(m_pEntry, nChars);
7662         enable_notify_events();
7663     }
7664 
select_region(int nStartPos,int nEndPos)7665     virtual void select_region(int nStartPos, int nEndPos) override
7666     {
7667         disable_notify_events();
7668         gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
7669         enable_notify_events();
7670     }
7671 
get_selection_bounds(int & rStartPos,int & rEndPos)7672     bool get_selection_bounds(int& rStartPos, int& rEndPos) override
7673     {
7674         return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
7675     }
7676 
replace_selection(const OUString & rText)7677     virtual void replace_selection(const OUString& rText) override
7678     {
7679         disable_notify_events();
7680         gtk_editable_delete_selection(GTK_EDITABLE(m_pEntry));
7681         OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
7682         gint position = gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
7683         gtk_editable_insert_text(GTK_EDITABLE(m_pEntry), sText.getStr(), sText.getLength(),
7684                                  &position);
7685         enable_notify_events();
7686     }
7687 
set_position(int nCursorPos)7688     virtual void set_position(int nCursorPos) override
7689     {
7690         disable_notify_events();
7691         gtk_editable_set_position(GTK_EDITABLE(m_pEntry), nCursorPos);
7692         enable_notify_events();
7693     }
7694 
get_position() const7695     virtual int get_position() const override
7696     {
7697         return gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
7698     }
7699 
set_editable(bool bEditable)7700     virtual void set_editable(bool bEditable) override
7701     {
7702         gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
7703     }
7704 
get_editable() const7705     virtual bool get_editable() const override
7706     {
7707         return gtk_editable_get_editable(GTK_EDITABLE(m_pEntry));
7708     }
7709 
set_message_type(weld::EntryMessageType eType)7710     virtual void set_message_type(weld::EntryMessageType eType) override
7711     {
7712         ::set_entry_message_type(m_pEntry, eType);
7713     }
7714 
disable_notify_events()7715     virtual void disable_notify_events() override
7716     {
7717         g_signal_handler_block(m_pEntry, m_nActivateSignalId);
7718         g_signal_handler_block(m_pEntry, m_nSelectionPosSignalId);
7719         g_signal_handler_block(m_pEntry, m_nCursorPosSignalId);
7720         g_signal_handler_block(m_pEntry, m_nInsertTextSignalId);
7721         g_signal_handler_block(m_pEntry, m_nChangedSignalId);
7722         GtkInstanceWidget::disable_notify_events();
7723     }
7724 
enable_notify_events()7725     virtual void enable_notify_events() override
7726     {
7727         GtkInstanceWidget::enable_notify_events();
7728         g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
7729         g_signal_handler_unblock(m_pEntry, m_nInsertTextSignalId);
7730         g_signal_handler_unblock(m_pEntry, m_nCursorPosSignalId);
7731         g_signal_handler_unblock(m_pEntry, m_nSelectionPosSignalId);
7732         g_signal_handler_unblock(m_pEntry, m_nActivateSignalId);
7733     }
7734 
set_font(const vcl::Font & rFont)7735     virtual void set_font(const vcl::Font& rFont) override
7736     {
7737         PangoAttrList* pAttrList = create_attr_list(rFont);
7738         gtk_entry_set_attributes(m_pEntry, pAttrList);
7739         pango_attr_list_unref(pAttrList);
7740     }
7741 
fire_signal_changed()7742     void fire_signal_changed()
7743     {
7744         signal_changed();
7745     }
7746 
cut_clipboard()7747     virtual void cut_clipboard() override
7748     {
7749         gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
7750     }
7751 
copy_clipboard()7752     virtual void copy_clipboard() override
7753     {
7754         gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
7755     }
7756 
paste_clipboard()7757     virtual void paste_clipboard() override
7758     {
7759         gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
7760     }
7761 
~GtkInstanceEntry()7762     virtual ~GtkInstanceEntry() override
7763     {
7764         g_signal_handler_disconnect(m_pEntry, m_nActivateSignalId);
7765         g_signal_handler_disconnect(m_pEntry, m_nSelectionPosSignalId);
7766         g_signal_handler_disconnect(m_pEntry, m_nCursorPosSignalId);
7767         g_signal_handler_disconnect(m_pEntry, m_nInsertTextSignalId);
7768         g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
7769     }
7770 };
7771 
7772 namespace
7773 {
7774     struct Search
7775     {
7776         OString str;
7777         int index;
7778         int col;
Search__anon0db309491111::Search7779         Search(const OUString& rText, int nCol)
7780             : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
7781             , index(-1)
7782             , col(nCol)
7783         {
7784         }
7785     };
7786 
foreach_find(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)7787     gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
7788     {
7789         Search* search = static_cast<Search*>(data);
7790         gchar *pStr = nullptr;
7791         gtk_tree_model_get(model, iter, search->col, &pStr, -1);
7792         bool found = strcmp(pStr, search->str.getStr()) == 0;
7793         if (found)
7794         {
7795             gint depth;
7796             gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
7797             search->index = indices[depth-1];
7798         }
7799         g_free(pStr);
7800         return found;
7801     }
7802 
getPixbuf(const OUString & rIconName)7803     GdkPixbuf* getPixbuf(const OUString& rIconName)
7804     {
7805         if (rIconName.isEmpty())
7806             return nullptr;
7807 
7808         GdkPixbuf* pixbuf = nullptr;
7809 
7810         if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
7811         {
7812             assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
7813                    "unknown stock image");
7814 
7815             GError *error = nullptr;
7816             GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
7817             pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
7818                                               16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
7819         }
7820         else
7821         {
7822             const AllSettings& rSettings = Application::GetSettings();
7823             pixbuf = load_icon_by_name(rIconName,
7824                                        rSettings.GetStyleSettings().DetermineIconTheme(),
7825                                        rSettings.GetUILanguageTag().getBcp47());
7826         }
7827 
7828         return pixbuf;
7829     }
7830 
insert_row(GtkListStore * pListStore,GtkTreeIter & iter,int pos,const OUString * pId,const OUString & rText,const OUString * pIconName,const VirtualDevice * pDevice)7831     void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, const OUString& rText, const OUString* pIconName, const VirtualDevice* pDevice)
7832     {
7833         if (!pIconName && !pDevice)
7834         {
7835             gtk_list_store_insert_with_values(pListStore, &iter, pos,
7836                                               0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
7837                                               1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
7838                                               -1);
7839         }
7840         else
7841         {
7842             if (pIconName)
7843             {
7844                 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
7845 
7846                 gtk_list_store_insert_with_values(pListStore, &iter, pos,
7847                                                   0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
7848                                                   1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
7849                                                   2, pixbuf,
7850                                                   -1);
7851 
7852                 if (pixbuf)
7853                     g_object_unref(pixbuf);
7854             }
7855             else
7856             {
7857                 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
7858 
7859                 Size aSize(pDevice->GetOutputSizePixel());
7860                 cairo_surface_t* target = cairo_surface_create_similar(surface,
7861                                                                         cairo_surface_get_content(surface),
7862                                                                         aSize.Width(),
7863                                                                         aSize.Height());
7864 
7865                 cairo_t* cr = cairo_create(target);
7866                 cairo_set_source_surface(cr, surface, 0, 0);
7867                 cairo_paint(cr);
7868                 cairo_destroy(cr);
7869 
7870                 gtk_list_store_insert_with_values(pListStore, &iter, pos,
7871                                                   0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
7872                                                   1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
7873                                                   3, target,
7874                                                   -1);
7875                 cairo_surface_destroy(target);
7876             }
7877         }
7878     }
7879 }
7880 
7881 namespace
7882 {
default_sort_func(GtkTreeModel * pModel,GtkTreeIter * a,GtkTreeIter * b,gpointer data)7883     gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
7884     {
7885         comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
7886         gchar* pName1;
7887         gchar* pName2;
7888         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
7889         gint sort_column_id(0);
7890         gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
7891         gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
7892         gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
7893         gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
7894                                     OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
7895         g_free(pName1);
7896         g_free(pName2);
7897         return ret;
7898     }
7899 
starts_with(GtkTreeModel * pTreeModel,const OUString & rStr,int col,int nStartRow,bool bCaseSensitive)7900     int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
7901     {
7902         GtkTreeIter iter;
7903         if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
7904             return -1;
7905 
7906         const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
7907         int nRet = nStartRow;
7908         do
7909         {
7910             gchar* pStr;
7911             gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
7912             OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7913             g_free(pStr);
7914             const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
7915             if (bMatch)
7916                 return nRet;
7917             ++nRet;
7918         } while (gtk_tree_model_iter_next(pTreeModel, &iter));
7919 
7920         return -1;
7921     }
7922 }
7923 
7924 struct GtkInstanceTreeIter : public weld::TreeIter
7925 {
GtkInstanceTreeIterGtkInstanceTreeIter7926     GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
7927     {
7928         if (pOrig)
7929             iter = pOrig->iter;
7930         else
7931             memset(&iter, 0, sizeof(iter));
7932     }
GtkInstanceTreeIterGtkInstanceTreeIter7933     GtkInstanceTreeIter(const GtkTreeIter& rOrig)
7934     {
7935         memcpy(&iter, &rOrig, sizeof(iter));
7936     }
equalGtkInstanceTreeIter7937     virtual bool equal(const TreeIter& rOther) const override
7938     {
7939         return memcmp(&iter,  &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
7940     }
7941     GtkTreeIter iter;
7942 };
7943 
7944 class GtkInstanceTreeView;
7945 
7946 static GtkInstanceTreeView* g_DragSource;
7947 
7948 class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView
7949 {
7950 private:
7951     GtkTreeView* m_pTreeView;
7952     GtkTreeStore* m_pTreeStore;
7953     std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
7954     GList *m_pColumns;
7955     std::vector<gulong> m_aColumnSignalIds;
7956     // map from toggle column to toggle visibility column
7957     std::map<int, int> m_aToggleVisMap;
7958     // map from toggle column to tristate column
7959     std::map<int, int> m_aToggleTriStateMap;
7960     // map from text column to text weight column
7961     std::map<int, int> m_aWeightMap;
7962     // map from text column to sensitive column
7963     std::map<int, int> m_aSensitiveMap;
7964     std::vector<GtkSortType> m_aSavedSortTypes;
7965     std::vector<int> m_aSavedSortColumns;
7966     std::vector<int> m_aViewColToModelCol;
7967     std::vector<int> m_aModelColToViewCol;
7968     bool m_bWorkAroundBadDragRegion;
7969     bool m_bInDrag;
7970     gint m_nTextCol;
7971     gint m_nImageCol;
7972     gint m_nExpanderImageCol;
7973     gint m_nIdCol;
7974     gulong m_nChangedSignalId;
7975     gulong m_nRowActivatedSignalId;
7976     gulong m_nTestExpandRowSignalId;
7977     gulong m_nVAdjustmentChangedSignalId;
7978     gulong m_nRowDeletedSignalId;
7979     gulong m_nRowInsertedSignalId;
7980     gulong m_nPopupMenuSignalId;
7981     gulong m_nDragBeginSignalId;
7982     gulong m_nDragEndSignalId;
7983     gulong m_nKeyPressSignalId;
7984     ImplSVEvent* m_pChangeEvent;
7985 
7986     DECL_LINK(async_signal_changed, void*, void);
7987 
launch_signal_changed()7988     void launch_signal_changed()
7989     {
7990         //tdf#117991 selection change is sent before the focus change, and focus change
7991         //is what will cause a spinbutton that currently has the focus to set its contents
7992         //as the spin button value. So any LibreOffice callbacks on
7993         //signal-change would happen before the spinbutton value-change occurs.
7994         //To avoid this, send the signal-change to LibreOffice to occur after focus-change
7995         //has been processed
7996         if (m_pChangeEvent)
7997             Application::RemoveUserEvent(m_pChangeEvent);
7998         m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
7999     }
8000 
signalChanged(GtkTreeView *,gpointer widget)8001     static void signalChanged(GtkTreeView*, gpointer widget)
8002     {
8003         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8004         pThis->launch_signal_changed();
8005     }
8006 
handle_row_activated()8007     void handle_row_activated()
8008     {
8009         if (signal_row_activated())
8010             return;
8011         GtkInstanceTreeIter aIter(nullptr);
8012         if (!get_cursor(&aIter))
8013             return;
8014         if (iter_has_child(aIter))
8015             get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
8016     }
8017 
signalRowActivated(GtkTreeView *,GtkTreePath *,GtkTreeViewColumn *,gpointer widget)8018     static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
8019     {
8020         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8021         SolarMutexGuard aGuard;
8022         pThis->handle_row_activated();
8023     }
8024 
signal_popup_menu(const CommandEvent & rCEvt)8025     virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
8026     {
8027         return m_aPopupMenuHdl.Call(rCEvt);
8028     }
8029 
insert_row(GtkTreeIter & iter,const GtkTreeIter * parent,int pos,const OUString * pId,const OUString * pText,const OUString * pIconName,const VirtualDevice * pDevice,const OUString * pExpanderName)8030     void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
8031                     const OUString* pIconName, const VirtualDevice* pDevice, const OUString* pExpanderName)
8032     {
8033         gtk_tree_store_insert_with_values(m_pTreeStore, &iter, const_cast<GtkTreeIter*>(parent), pos,
8034                                           m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
8035                                           m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
8036                                           -1);
8037         if (pIconName)
8038         {
8039             GdkPixbuf* pixbuf = getPixbuf(*pIconName);
8040             gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
8041             if (pixbuf)
8042                 g_object_unref(pixbuf);
8043         }
8044         else if (pDevice)
8045         {
8046             cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
8047 
8048             Size aSize(pDevice->GetOutputSizePixel());
8049             cairo_surface_t* target = cairo_surface_create_similar(surface,
8050                                                                     cairo_surface_get_content(surface),
8051                                                                     aSize.Width(),
8052                                                                     aSize.Height());
8053 
8054             cairo_t* cr = cairo_create(target);
8055             cairo_set_source_surface(cr, surface, 0, 0);
8056             cairo_paint(cr);
8057             cairo_destroy(cr);
8058 
8059             gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, target, -1);
8060             cairo_surface_destroy(target);
8061         }
8062 
8063         if (pExpanderName)
8064         {
8065             GdkPixbuf* pixbuf = getPixbuf(*pExpanderName);
8066             gtk_tree_store_set(m_pTreeStore, &iter, m_nExpanderImageCol, pixbuf, -1);
8067             if (pixbuf)
8068                 g_object_unref(pixbuf);
8069         }
8070     }
8071 
get(const GtkTreeIter & iter,int col) const8072     OUString get(const GtkTreeIter& iter, int col) const
8073     {
8074         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8075         gchar* pStr;
8076         gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
8077         OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
8078         g_free(pStr);
8079         return sRet;
8080     }
8081 
get(int pos,int col) const8082     OUString get(int pos, int col) const
8083     {
8084         OUString sRet;
8085         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8086         GtkTreeIter iter;
8087         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8088             sRet = get(iter, col);
8089         return sRet;
8090     }
8091 
get_int(const GtkTreeIter & iter,int col) const8092     gint get_int(const GtkTreeIter& iter, int col) const
8093     {
8094         gint nRet(-1);
8095         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8096         gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
8097         return nRet;
8098     }
8099 
get_int(int pos,int col) const8100     gint get_int(int pos, int col) const
8101     {
8102         gint nRet(-1);
8103         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8104         GtkTreeIter iter;
8105         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8106             nRet = get_int(iter, col);
8107         gtk_tree_model_get(pModel, &iter, col, &nRet, -1);
8108         return nRet;
8109     }
8110 
get_bool(const GtkTreeIter & iter,int col) const8111     bool get_bool(const GtkTreeIter& iter, int col) const
8112     {
8113         gboolean bRet(false);
8114         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8115         gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
8116         return bRet;
8117     }
8118 
get_bool(int pos,int col) const8119     bool get_bool(int pos, int col) const
8120     {
8121         bool bRet(false);
8122         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8123         GtkTreeIter iter;
8124         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8125             bRet = get_bool(iter, col);
8126         return bRet;
8127     }
8128 
set(const GtkTreeIter & iter,int col,const OUString & rText)8129     void set(const GtkTreeIter& iter, int col, const OUString& rText)
8130     {
8131         OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
8132         gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
8133     }
8134 
set(int pos,int col,const OUString & rText)8135     void set(int pos, int col, const OUString& rText)
8136     {
8137         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8138         GtkTreeIter iter;
8139         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8140             set(iter, col, rText);
8141     }
8142 
set(const GtkTreeIter & iter,int col,bool bOn)8143     void set(const GtkTreeIter& iter, int col, bool bOn)
8144     {
8145         gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bOn, -1);
8146     }
8147 
set(int pos,int col,bool bOn)8148     void set(int pos, int col, bool bOn)
8149     {
8150         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8151         GtkTreeIter iter;
8152         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8153             set(iter, col, bOn);
8154     }
8155 
set(const GtkTreeIter & iter,int col,gint bInt)8156     void set(const GtkTreeIter& iter, int col, gint bInt)
8157     {
8158         gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
8159     }
8160 
set(int pos,int col,gint bInt)8161     void set(int pos, int col, gint bInt)
8162     {
8163         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8164         GtkTreeIter iter;
8165         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8166             set(iter, col, bInt);
8167     }
8168 
signalTestExpandRow(GtkTreeView *,GtkTreeIter * iter,GtkTreePath *,gpointer widget)8169     static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
8170     {
8171         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8172         return !pThis->signal_test_expand_row(*iter);
8173     }
8174 
signal_test_expand_row(GtkTreeIter & iter)8175     bool signal_test_expand_row(GtkTreeIter& iter)
8176     {
8177         disable_notify_events();
8178         GtkInstanceTreeIter aIter(nullptr);
8179 
8180         // if there's a preexisting placeholder child, required to make this
8181         // potentially expandable in the first place, now we remove it
8182         bool bPlaceHolder = false;
8183         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8184         GtkTreeIter tmp;
8185         if (gtk_tree_model_iter_children(pModel, &tmp, &iter))
8186         {
8187             aIter.iter = tmp;
8188             if (get_text(aIter, -1) == "<dummy>")
8189             {
8190                 gtk_tree_store_remove(m_pTreeStore, &tmp);
8191                 bPlaceHolder = true;
8192             }
8193         }
8194 
8195         aIter.iter = iter;
8196         bool bRet = signal_expanding(aIter);
8197 
8198         //expand disallowed, restore placeholder
8199         if (!bRet && bPlaceHolder)
8200         {
8201             GtkTreeIter subiter;
8202             OUString sDummy("<dummy>");
8203             insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr, nullptr);
8204         }
8205 
8206         enable_notify_events();
8207         return bRet;
8208     }
8209 
signalCellToggled(GtkCellRendererToggle * pCell,const gchar * path,gpointer widget)8210     static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
8211     {
8212         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8213         void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
8214         pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
8215     }
8216 
signal_cell_toggled(const gchar * path,int nCol)8217     void signal_cell_toggled(const gchar *path, int nCol)
8218     {
8219         GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
8220 
8221         // toggled signal handlers can query get_cursor to get which
8222         // node was clicked
8223         gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
8224 
8225         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8226         GtkTreeIter iter;
8227         gtk_tree_model_get_iter(pModel, &iter, tree_path);
8228 
8229         gboolean bRet(false);
8230         gtk_tree_model_get(pModel, &iter, nCol, &bRet, -1);
8231         bRet = !bRet;
8232         gtk_tree_store_set(m_pTreeStore, &iter, nCol, bRet, -1);
8233 
8234         gint depth;
8235         gint* indices = gtk_tree_path_get_indices_with_depth(tree_path, &depth);
8236         int nRow = indices[depth-1];
8237 
8238         set(iter, m_aToggleTriStateMap[nCol], false);
8239 
8240         signal_toggled(std::make_pair(nRow, nCol));
8241 
8242         gtk_tree_path_free(tree_path);
8243     }
8244 
8245     DECL_LINK(async_stop_cell_editing, void*, void);
8246 
signalCellEditingStarted(GtkCellRenderer *,GtkCellEditable *,const gchar * path,gpointer widget)8247     static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
8248     {
8249         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8250         if (!pThis->signal_cell_editing_started(path))
8251             Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
8252     }
8253 
signal_cell_editing_started(const gchar * path)8254     bool signal_cell_editing_started(const gchar *path)
8255     {
8256         GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
8257 
8258         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8259         GtkInstanceTreeIter aGtkIter(nullptr);
8260         gtk_tree_model_get_iter(pModel, &aGtkIter.iter, tree_path);
8261         gtk_tree_path_free(tree_path);
8262 
8263         return signal_editing_started(aGtkIter);
8264     }
8265 
signalCellEdited(GtkCellRendererText * pCell,const gchar * path,const gchar * pNewText,gpointer widget)8266     static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
8267     {
8268         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8269         pThis->signal_cell_edited(pCell, path, pNewText);
8270     }
8271 
restoreNonEditable(GObject * pCell)8272     static void restoreNonEditable(GObject* pCell)
8273     {
8274         if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
8275         {
8276             g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
8277             g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
8278         }
8279     }
8280 
signal_cell_edited(GtkCellRendererText * pCell,const gchar * path,const gchar * pNewText)8281     void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
8282     {
8283         GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
8284 
8285         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8286         GtkInstanceTreeIter aGtkIter(nullptr);
8287         gtk_tree_model_get_iter(pModel, &aGtkIter.iter, tree_path);
8288         gtk_tree_path_free(tree_path);
8289 
8290         OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
8291         if (signal_editing_done(std::pair<const weld::TreeIter&, OUString>(aGtkIter, sText)))
8292         {
8293             void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
8294             set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
8295         }
8296 
8297         restoreNonEditable(G_OBJECT(pCell));
8298     }
8299 
signalCellEditingCanceled(GtkCellRenderer * pCell,gpointer)8300     static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
8301     {
8302         restoreNonEditable(G_OBJECT(pCell));
8303     }
8304 
signal_column_clicked(GtkTreeViewColumn * pClickedColumn)8305     void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
8306     {
8307         int nIndex(0);
8308         for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
8309         {
8310             GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8311             if (pColumn == pClickedColumn)
8312             {
8313                 TreeView::signal_column_clicked(nIndex);
8314                 break;
8315             }
8316             ++nIndex;
8317         }
8318     }
8319 
signalColumnClicked(GtkTreeViewColumn * pColumn,gpointer widget)8320     static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
8321     {
8322         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8323         pThis->signal_column_clicked(pColumn);
8324     }
8325 
signalVAdjustmentChanged(GtkAdjustment *,gpointer widget)8326     static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
8327     {
8328         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8329         pThis->signal_visible_range_changed();
8330     }
8331 
get_model_col(int viewcol) const8332     int get_model_col(int viewcol) const
8333     {
8334         return m_aViewColToModelCol[viewcol];
8335     }
8336 
get_view_col(int modelcol) const8337     int get_view_col(int modelcol) const
8338     {
8339         return m_aModelColToViewCol[modelcol];
8340     }
8341 
signalRowDeleted(GtkTreeModel *,GtkTreePath *,gpointer widget)8342     static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
8343     {
8344         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8345         pThis->signal_model_changed();
8346     }
8347 
signalRowInserted(GtkTreeModel *,GtkTreePath *,GtkTreeIter *,gpointer widget)8348     static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
8349     {
8350         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8351         pThis->signal_model_changed();
8352     }
8353 
sortFunc(GtkTreeModel * pModel,GtkTreeIter * a,GtkTreeIter * b,gpointer widget)8354     static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
8355     {
8356         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8357         return pThis->sort_func(pModel, a, b);
8358     }
8359 
sort_func(GtkTreeModel * pModel,GtkTreeIter * a,GtkTreeIter * b)8360     gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
8361     {
8362         if (m_aCustomSort)
8363             return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
8364         return default_sort_func(pModel, a, b, m_xSorter.get());
8365     }
8366 
signalDragBegin(GtkWidget *,GdkDragContext *,gpointer widget)8367     static void signalDragBegin(GtkWidget*, GdkDragContext*, gpointer widget)
8368     {
8369         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8370         g_DragSource = pThis;
8371     }
8372 
signalDragEnd(GtkWidget *,GdkDragContext *,gpointer)8373     static void signalDragEnd(GtkWidget*, GdkDragContext*, gpointer)
8374     {
8375         g_DragSource = nullptr;
8376     }
8377 
signal_key_press(GdkEventKey * pEvent)8378     bool signal_key_press(GdkEventKey* pEvent)
8379     {
8380         if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
8381             return false;
8382 
8383         GtkInstanceTreeIter aIter(nullptr);
8384         if (!get_cursor(&aIter))
8385             return false;
8386 
8387         if (pEvent->keyval == GDK_KEY_Right)
8388         {
8389             if (iter_has_child(aIter) && !get_row_expanded(aIter))
8390             {
8391                 expand_row(aIter);
8392                 return true;
8393             }
8394             return false;
8395         }
8396 
8397         if (iter_has_child(aIter) && get_row_expanded(aIter))
8398         {
8399             collapse_row(aIter);
8400             return true;
8401         }
8402 
8403         if (iter_parent(aIter))
8404         {
8405             unselect_all();
8406             set_cursor(aIter);
8407             select(aIter);
8408             return true;
8409         }
8410 
8411         return false;
8412     }
8413 
signalKeyPress(GtkWidget *,GdkEventKey * pEvent,gpointer widget)8414     static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
8415     {
8416         GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8417         return pThis->signal_key_press(pEvent);
8418     }
8419 
8420 public:
GtkInstanceTreeView(GtkTreeView * pTreeView,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)8421     GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8422         : GtkInstanceContainer(GTK_CONTAINER(pTreeView), pBuilder, bTakeOwnership)
8423         , m_pTreeView(pTreeView)
8424         , m_pTreeStore(GTK_TREE_STORE(gtk_tree_view_get_model(m_pTreeView)))
8425         , m_bWorkAroundBadDragRegion(false)
8426         , m_bInDrag(false)
8427         , m_nTextCol(-1)
8428         , m_nImageCol(-1)
8429         , m_nExpanderImageCol(-1)
8430         , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
8431                              G_CALLBACK(signalChanged), this))
8432         , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
8433         , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
8434         , m_nVAdjustmentChangedSignalId(0)
8435         , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
8436         , m_nDragBeginSignalId(g_signal_connect(pTreeView, "drag-begin", G_CALLBACK(signalDragBegin), this))
8437         , m_nDragEndSignalId(g_signal_connect(pTreeView, "drag-end", G_CALLBACK(signalDragEnd), this))
8438         , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
8439         , m_pChangeEvent(nullptr)
8440     {
8441         m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
8442         int nIndex(0);
8443         for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
8444         {
8445             GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8446             m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
8447             GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
8448             for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
8449             {
8450                 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
8451                 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
8452                 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
8453                 {
8454                     if (m_nTextCol == -1)
8455                         m_nTextCol = nIndex;
8456                     m_aWeightMap[nIndex] = -1;
8457                     m_aSensitiveMap[nIndex] = -1;
8458                     g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
8459                     g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
8460                     g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
8461                 }
8462                 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
8463                 {
8464                     g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
8465                     m_aToggleVisMap[nIndex] = -1;
8466                     m_aToggleTriStateMap[nIndex] = -1;
8467                 }
8468                 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
8469                 {
8470                     const bool bExpander = g_list_next(pRenderer) != nullptr;
8471                     if (bExpander && m_nExpanderImageCol == -1)
8472                         m_nExpanderImageCol = nIndex;
8473                     else if (m_nImageCol == -1)
8474                         m_nImageCol = nIndex;
8475                 }
8476                 m_aModelColToViewCol.push_back(m_aViewColToModelCol.size());
8477                 ++nIndex;
8478             }
8479             g_list_free(pRenderers);
8480             m_aViewColToModelCol.push_back(nIndex - 1);
8481         }
8482 
8483         m_nIdCol = nIndex++;
8484 
8485         for (auto& a : m_aToggleVisMap)
8486             a.second = nIndex++;
8487         for (auto& a : m_aToggleTriStateMap)
8488             a.second = nIndex++;
8489         for (auto& a : m_aWeightMap)
8490             a.second = nIndex++;
8491         for (auto& a : m_aSensitiveMap)
8492             a.second = nIndex++;
8493 
8494         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8495         m_nRowDeletedSignalId = g_signal_connect(pModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
8496         m_nRowInsertedSignalId = g_signal_connect(pModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
8497     }
8498 
columns_autosize()8499     virtual void columns_autosize() override
8500     {
8501         gtk_tree_view_columns_autosize(m_pTreeView);
8502     }
8503 
set_column_fixed_widths(const std::vector<int> & rWidths)8504     virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
8505     {
8506         GList* pEntry = g_list_first(m_pColumns);
8507         for (auto nWidth : rWidths)
8508         {
8509             assert(pEntry && "wrong count");
8510             GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8511             gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
8512             pEntry = g_list_next(pEntry);
8513         }
8514     }
8515 
set_centered_column(int nCol)8516     virtual void set_centered_column(int nCol) override
8517     {
8518         for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
8519         {
8520             GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8521             GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
8522             for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
8523             {
8524                 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
8525                 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
8526                 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
8527                 {
8528                     g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
8529                     break;
8530                 }
8531             }
8532             g_list_free(pRenderers);
8533         }
8534     }
8535 
get_column_width(int nColumn) const8536     virtual int get_column_width(int nColumn) const override
8537     {
8538         GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
8539         assert(pColumn && "wrong count");
8540         int nWidth = gtk_tree_view_column_get_width(pColumn);
8541         // https://github.com/exaile/exaile/issues/580
8542         // after setting fixed_width on a column and requesting width before
8543         // gtk has a chance to do its layout of the column means that the width
8544         // request hasn't come into effect
8545         if (!nWidth)
8546             nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
8547         return nWidth;
8548     }
8549 
get_column_title(int nColumn) const8550     virtual OUString get_column_title(int nColumn) const override
8551     {
8552         GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
8553         assert(pColumn && "wrong count");
8554         const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
8555         OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
8556         return sRet;
8557     }
8558 
set_column_title(int nColumn,const OUString & rTitle)8559     virtual void set_column_title(int nColumn, const OUString& rTitle) override
8560     {
8561         GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
8562         assert(pColumn && "wrong count");
8563         gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
8564     }
8565 
insert(const weld::TreeIter * pParent,int pos,const OUString * pText,const OUString * pId,const OUString * pIconName,VirtualDevice * pImageSurface,const OUString * pExpanderName,bool bChildrenOnDemand,weld::TreeIter * pRet)8566     virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
8567                         VirtualDevice* pImageSurface, const OUString* pExpanderName,
8568                         bool bChildrenOnDemand, weld::TreeIter* pRet) override
8569     {
8570         disable_notify_events();
8571         GtkTreeIter iter;
8572         const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
8573         insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface, pExpanderName);
8574         if (bChildrenOnDemand)
8575         {
8576             GtkTreeIter subiter;
8577             OUString sDummy("<dummy>");
8578             insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr, nullptr);
8579         }
8580         if (pRet)
8581         {
8582             GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
8583             pGtkRetIter->iter = iter;
8584         }
8585         enable_notify_events();
8586     }
8587 
set_font_color(const GtkTreeIter & iter,const Color & rColor) const8588     void set_font_color(const GtkTreeIter& iter, const Color& rColor) const
8589     {
8590         GdkRGBA aColor{rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0, 0};
8591         gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
8592     }
8593 
set_font_color(int pos,const Color & rColor) const8594     virtual void set_font_color(int pos, const Color& rColor) const override
8595     {
8596         GtkTreeIter iter;
8597         gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
8598         set_font_color(iter, rColor);
8599     }
8600 
set_font_color(const weld::TreeIter & rIter,const Color & rColor) const8601     virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) const override
8602     {
8603         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8604         set_font_color(rGtkIter.iter, rColor);
8605     }
8606 
remove(int pos)8607     virtual void remove(int pos) override
8608     {
8609         disable_notify_events();
8610         GtkTreeIter iter;
8611         gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
8612         gtk_tree_store_remove(m_pTreeStore, &iter);
8613         enable_notify_events();
8614     }
8615 
find_text(const OUString & rText) const8616     virtual int find_text(const OUString& rText) const override
8617     {
8618         Search aSearch(rText, m_nTextCol);
8619         gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
8620         return aSearch.index;
8621     }
8622 
find_id(const OUString & rId) const8623     virtual int find_id(const OUString& rId) const override
8624     {
8625         Search aSearch(rId, m_nIdCol);
8626         gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
8627         return aSearch.index;
8628     }
8629 
bulk_insert_for_each(int nSourceCount,const std::function<void (weld::TreeIter &,int nSourceIndex)> & func,const std::vector<int> * pFixedWidths)8630     virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
8631                                       const std::vector<int>* pFixedWidths) override
8632     {
8633         freeze();
8634         clear();
8635         GtkInstanceTreeIter aGtkIter(nullptr);
8636 
8637         if (pFixedWidths)
8638             set_column_fixed_widths(*pFixedWidths);
8639 
8640         while (nSourceCount)
8641         {
8642             // tdf#125241 inserting backwards is massively faster
8643             gtk_tree_store_prepend(m_pTreeStore, &aGtkIter.iter, nullptr);
8644             func(aGtkIter, --nSourceCount);
8645         }
8646 
8647         thaw();
8648     }
8649 
swap(int pos1,int pos2)8650     virtual void swap(int pos1, int pos2) override
8651     {
8652         disable_notify_events();
8653 
8654         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8655 
8656         GtkTreeIter iter1;
8657         gtk_tree_model_iter_nth_child(pModel, &iter1, nullptr, pos1);
8658 
8659         GtkTreeIter iter2;
8660         gtk_tree_model_iter_nth_child(pModel, &iter2, nullptr, pos2);
8661 
8662         gtk_tree_store_swap(m_pTreeStore, &iter1, &iter2);
8663 
8664         enable_notify_events();
8665     }
8666 
clear()8667     virtual void clear() override
8668     {
8669         disable_notify_events();
8670         gtk_tree_store_clear(m_pTreeStore);
8671         enable_notify_events();
8672     }
8673 
make_sorted()8674     virtual void make_sorted() override
8675     {
8676         // thaw wants to restore sort state of freeze
8677         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8678         m_xSorter.reset(new comphelper::string::NaturalStringSorter(
8679                             ::comphelper::getProcessComponentContext(),
8680                             Application::GetSettings().GetUILanguageTag().getLocale()));
8681         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8682         gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
8683         gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
8684     }
8685 
make_unsorted()8686     virtual void make_unsorted() override
8687     {
8688         m_xSorter.reset();
8689         int nSortColumn;
8690         GtkSortType eSortType;
8691         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8692         gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
8693         gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
8694     }
8695 
set_sort_order(bool bAscending)8696     virtual void set_sort_order(bool bAscending) override
8697     {
8698         GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
8699 
8700         gint sort_column_id(0);
8701         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8702         gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
8703         gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
8704     }
8705 
get_sort_order() const8706     virtual bool get_sort_order() const override
8707     {
8708         GtkSortType eSortType;
8709 
8710         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8711         gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
8712         return eSortType == GTK_SORT_ASCENDING;
8713     }
8714 
set_sort_indicator(TriState eState,int col)8715     virtual void set_sort_indicator(TriState eState, int col) override
8716     {
8717         if (col == -1)
8718             col = get_view_col(m_nTextCol);
8719 
8720         GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
8721         assert(pColumn && "wrong count");
8722         if (eState == TRISTATE_INDET)
8723             gtk_tree_view_column_set_sort_indicator(pColumn, false);
8724         else
8725         {
8726             gtk_tree_view_column_set_sort_indicator(pColumn, true);
8727             GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
8728             gtk_tree_view_column_set_sort_order(pColumn, eSortType);
8729         }
8730     }
8731 
get_sort_indicator(int col) const8732     virtual TriState get_sort_indicator(int col) const override
8733     {
8734         if (col == -1)
8735             col = get_view_col(m_nTextCol);
8736 
8737         GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
8738         if (!gtk_tree_view_column_get_sort_indicator(pColumn))
8739             return TRISTATE_INDET;
8740         return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
8741     }
8742 
get_sort_column() const8743     virtual int get_sort_column() const override
8744     {
8745         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8746         gint sort_column_id(0);
8747         if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
8748             return -1;
8749         return get_view_col(sort_column_id);
8750     }
8751 
set_sort_column(int nColumn)8752     virtual void set_sort_column(int nColumn) override
8753     {
8754         if (nColumn == -1)
8755         {
8756             make_unsorted();
8757             return;
8758         }
8759         GtkSortType eSortType;
8760         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8761         gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
8762         int nSortCol = get_model_col(nColumn);
8763         gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
8764         gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
8765     }
8766 
set_sort_func(const std::function<int (const weld::TreeIter &,const weld::TreeIter &)> & func)8767     virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
8768     {
8769         weld::TreeView::set_sort_func(func);
8770         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8771         gtk_tree_sortable_sort_column_changed(pSortable);
8772     }
8773 
n_children() const8774     virtual int n_children() const override
8775     {
8776         return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
8777     }
8778 
select(int pos)8779     virtual void select(int pos) override
8780     {
8781         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8782         disable_notify_events();
8783         if (pos == -1 || (pos == 0 && n_children() == 0))
8784         {
8785             gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
8786         }
8787         else
8788         {
8789             GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8790             gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
8791             gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
8792             gtk_tree_path_free(path);
8793         }
8794         enable_notify_events();
8795     }
8796 
set_cursor(int pos)8797     virtual void set_cursor(int pos) override
8798     {
8799         GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8800         gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
8801         gtk_tree_path_free(path);
8802     }
8803 
scroll_to_row(int pos)8804     virtual void scroll_to_row(int pos) override
8805     {
8806         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8807         disable_notify_events();
8808         GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8809         gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
8810         gtk_tree_path_free(path);
8811         enable_notify_events();
8812     }
8813 
is_selected(int pos) const8814     virtual bool is_selected(int pos) const override
8815     {
8816         GtkTreeIter iter;
8817         gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
8818         return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
8819     }
8820 
unselect(int pos)8821     virtual void unselect(int pos) override
8822     {
8823         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8824         disable_notify_events();
8825         if (pos == -1 || (pos == 0 && n_children() == 0))
8826         {
8827             gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
8828         }
8829         else
8830         {
8831             GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8832             gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
8833             gtk_tree_path_free(path);
8834         }
8835         enable_notify_events();
8836     }
8837 
get_selected_rows() const8838     virtual std::vector<int> get_selected_rows() const override
8839     {
8840         std::vector<int> aRows;
8841 
8842         GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
8843         for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
8844         {
8845             GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
8846 
8847             gint depth;
8848             gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
8849             int nRow = indices[depth-1];
8850 
8851             aRows.push_back(nRow);
8852         }
8853         g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
8854 
8855         return aRows;
8856     }
8857 
all_foreach(const std::function<bool (weld::TreeIter &)> & func)8858     virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
8859     {
8860         GtkInstanceTreeIter aGtkIter(nullptr);
8861         if (get_iter_first(aGtkIter))
8862         {
8863             do
8864             {
8865                 if (func(aGtkIter))
8866                     break;
8867             } while (iter_next(aGtkIter));
8868         }
8869     }
8870 
selected_foreach(const std::function<bool (weld::TreeIter &)> & func)8871     virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
8872     {
8873         GtkInstanceTreeIter aGtkIter(nullptr);
8874 
8875         GtkTreeModel* pModel;
8876         GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
8877         for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
8878         {
8879             GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
8880             gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
8881             if (func(aGtkIter))
8882                 break;
8883         }
8884         g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
8885     }
8886 
visible_foreach(const std::function<bool (weld::TreeIter &)> & func)8887     virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
8888     {
8889         GtkTreePath* start_path;
8890         GtkTreePath* end_path;
8891 
8892         if (gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
8893         {
8894             GtkInstanceTreeIter aGtkIter(nullptr);
8895             GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8896             gtk_tree_model_get_iter(pModel, &aGtkIter.iter, start_path);
8897 
8898             do
8899             {
8900                 if (func(aGtkIter))
8901                     break;
8902                 GtkTreePath* path = gtk_tree_model_get_path(pModel, &aGtkIter.iter);
8903                 bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
8904                 gtk_tree_path_free(path);
8905                 if (!bContinue)
8906                     break;
8907                 if (!iter_next(aGtkIter))
8908                     break;
8909             } while(true);
8910 
8911             gtk_tree_path_free(start_path);
8912             gtk_tree_path_free(end_path);
8913         }
8914     }
8915 
connect_visible_range_changed(const Link<weld::TreeView &,void> & rLink)8916     virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
8917     {
8918         weld::TreeView::connect_visible_range_changed(rLink);
8919         if (!m_nVAdjustmentChangedSignalId)
8920         {
8921             GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
8922             m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
8923         }
8924     }
8925 
is_selected(const weld::TreeIter & rIter) const8926     virtual bool is_selected(const weld::TreeIter& rIter) const override
8927     {
8928         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8929         return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
8930     }
8931 
get_text(int pos,int col) const8932     virtual OUString get_text(int pos, int col) const override
8933     {
8934         if (col == -1)
8935             return get(pos, m_nTextCol);
8936         return get(pos, get_model_col(col));
8937     }
8938 
set_text(int pos,const OUString & rText,int col)8939     virtual void set_text(int pos, const OUString& rText, int col) override
8940     {
8941         if (col == -1)
8942             col = m_nTextCol;
8943         else
8944             col = get_model_col(col);
8945         set(pos, col, rText);
8946     }
8947 
get_toggle(int pos,int col) const8948     virtual TriState get_toggle(int pos, int col) const override
8949     {
8950         col = get_model_col(col);
8951         if (get_bool(pos, m_aToggleTriStateMap.find(col)->second))
8952             return TRISTATE_INDET;
8953         return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
8954     }
8955 
get_toggle(const weld::TreeIter & rIter,int col) const8956     virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
8957     {
8958         col = get_model_col(col);
8959         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8960         if (get_bool(rGtkIter.iter, m_aToggleTriStateMap.find(col)->second))
8961             return TRISTATE_INDET;
8962         return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
8963     }
8964 
set_toggle(int pos,TriState eState,int col)8965     virtual void set_toggle(int pos, TriState eState, int col) override
8966     {
8967         col = get_model_col(col);
8968         // checkbuttons are invisible until toggled on or off
8969         set(pos, m_aToggleVisMap[col], true);
8970         if (eState == TRISTATE_INDET)
8971             set(pos, m_aToggleTriStateMap[col], true);
8972         else
8973         {
8974             set(pos, m_aToggleTriStateMap[col], false);
8975             set(pos, col, eState == TRISTATE_TRUE);
8976         }
8977     }
8978 
set_toggle(const weld::TreeIter & rIter,TriState eState,int col)8979     virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
8980     {
8981         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8982         col = get_model_col(col);
8983         // checkbuttons are invisible until toggled on or off
8984         set(rGtkIter.iter, m_aToggleVisMap[col], true);
8985         if (eState == TRISTATE_INDET)
8986             set(rGtkIter.iter, m_aToggleTriStateMap[col], true);
8987         else
8988         {
8989             set(rGtkIter.iter, m_aToggleTriStateMap[col], false);
8990             set(rGtkIter.iter, col, eState == TRISTATE_TRUE);
8991         }
8992     }
8993 
set_text_emphasis(const weld::TreeIter & rIter,bool bOn,int col)8994     virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
8995     {
8996         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8997         col = get_model_col(col);
8998         set(rGtkIter.iter, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
8999     }
9000 
set_text_emphasis(int pos,bool bOn,int col)9001     virtual void set_text_emphasis(int pos, bool bOn, int col) override
9002     {
9003         col = get_model_col(col);
9004         set(pos, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
9005     }
9006 
get_text_emphasis(const weld::TreeIter & rIter,int col) const9007     virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
9008     {
9009         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9010         col = get_model_col(col);
9011         return get_int(rGtkIter.iter, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
9012     }
9013 
get_text_emphasis(int pos,int col) const9014     virtual bool get_text_emphasis(int pos, int col) const override
9015     {
9016         col = get_model_col(col);
9017         return get_int(pos, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
9018     }
9019 
9020     using GtkInstanceWidget::set_sensitive;
9021 
set_sensitive(int pos,bool bSensitive,int col)9022     virtual void set_sensitive(int pos, bool bSensitive, int col) override
9023     {
9024         if (col == -1)
9025             col = m_nTextCol;
9026         else
9027             col = get_model_col(col);
9028         set(pos, m_aSensitiveMap[col], bSensitive);
9029     }
9030 
set_sensitive(const weld::TreeIter & rIter,bool bSensitive,int col)9031     virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
9032     {
9033         if (col == -1)
9034             col = m_nTextCol;
9035         else
9036             col = get_model_col(col);
9037         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9038         set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
9039     }
9040 
set_image(const GtkTreeIter & iter,int col,GdkPixbuf * pixbuf)9041     void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
9042     {
9043         if (col == -1)
9044             col = m_nExpanderImageCol;
9045         else
9046             col = get_model_col(col);
9047         gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
9048         if (pixbuf)
9049             g_object_unref(pixbuf);
9050     }
9051 
set_image(int pos,GdkPixbuf * pixbuf,int col)9052     void set_image(int pos, GdkPixbuf* pixbuf, int col)
9053     {
9054         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9055         GtkTreeIter iter;
9056         if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
9057         {
9058             set_image(iter, col, pixbuf);
9059         }
9060     }
9061 
set_image(int pos,const css::uno::Reference<css::graphic::XGraphic> & rImage,int col)9062     virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
9063     {
9064         set_image(pos, getPixbuf(rImage), col);
9065     }
9066 
set_image(int pos,const OUString & rImage,int col)9067     virtual void set_image(int pos, const OUString& rImage, int col) override
9068     {
9069         set_image(pos, getPixbuf(rImage), col);
9070     }
9071 
set_image(int pos,VirtualDevice & rImage,int col)9072     virtual void set_image(int pos, VirtualDevice& rImage, int col) override
9073     {
9074         set_image(pos, getPixbuf(rImage), col);
9075     }
9076 
set_image(const weld::TreeIter & rIter,const css::uno::Reference<css::graphic::XGraphic> & rImage,int col)9077     virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
9078     {
9079         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9080         set_image(rGtkIter.iter, col, getPixbuf(rImage));
9081     }
9082 
set_image(const weld::TreeIter & rIter,const OUString & rImage,int col)9083     virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
9084     {
9085         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9086         set_image(rGtkIter.iter, col, getPixbuf(rImage));
9087     }
9088 
set_image(const weld::TreeIter & rIter,VirtualDevice & rImage,int col)9089     virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
9090     {
9091         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9092         set_image(rGtkIter.iter, col, getPixbuf(rImage));
9093     }
9094 
get_id(int pos) const9095     virtual OUString get_id(int pos) const override
9096     {
9097         return get(pos, m_nIdCol);
9098     }
9099 
set_id(int pos,const OUString & rId)9100     virtual void set_id(int pos, const OUString& rId) override
9101     {
9102         return set(pos, m_nIdCol, rId);
9103     }
9104 
get_iter_index_in_parent(const weld::TreeIter & rIter) const9105     virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
9106     {
9107         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9108 
9109         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9110         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9111 
9112         gint depth;
9113         gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9114         int nRet = indices[depth-1];
9115 
9116         gtk_tree_path_free(path);
9117 
9118         return nRet;
9119     }
9120 
iter_compare(const weld::TreeIter & a,const weld::TreeIter & b) const9121     virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
9122     {
9123         const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
9124         const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
9125 
9126         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9127         GtkTreePath* pathA = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
9128         GtkTreePath* pathB = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
9129 
9130         int nRet = gtk_tree_path_compare(pathA, pathB);
9131 
9132         gtk_tree_path_free(pathB);
9133         gtk_tree_path_free(pathA);
9134 
9135         return nRet;
9136     }
9137 
9138     // by copy and delete of old copy
move_subtree(GtkTreeIter & rFromIter,GtkTreeIter * pGtkParentIter,int nIndexInNewParent)9139     void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
9140     {
9141         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9142 
9143         int nCols = gtk_tree_model_get_n_columns(pModel);
9144         GValue value;
9145 
9146         GtkTreeIter toiter;
9147         gtk_tree_store_insert(m_pTreeStore, &toiter, pGtkParentIter, nIndexInNewParent);
9148 
9149         for (int i = 0; i < nCols; ++i)
9150         {
9151             memset(&value,  0, sizeof(GValue));
9152             gtk_tree_model_get_value(pModel, &rFromIter, i, &value);
9153             gtk_tree_store_set_value(m_pTreeStore, &toiter, i, &value);
9154             g_value_unset(&value);
9155         }
9156 
9157         GtkTreeIter tmpfromiter;
9158         if (gtk_tree_model_iter_children(pModel, &tmpfromiter, &rFromIter))
9159         {
9160             int j = 0;
9161             do
9162             {
9163                 move_subtree(tmpfromiter, &toiter, j++);
9164             } while (gtk_tree_model_iter_next(pModel, &tmpfromiter));
9165         }
9166 
9167         gtk_tree_store_remove(m_pTreeStore, &rFromIter);
9168     }
9169 
move_subtree(weld::TreeIter & rNode,const weld::TreeIter * pNewParent,int nIndexInNewParent)9170     virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
9171     {
9172         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
9173         const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
9174         move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
9175     }
9176 
get_selected_index() const9177     virtual int get_selected_index() const override
9178     {
9179         assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9180         int nRet = -1;
9181         GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
9182         if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
9183         {
9184             GtkTreeIter iter;
9185             GtkTreeModel* pModel;
9186             if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
9187             {
9188                 GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
9189 
9190                 gint depth;
9191                 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9192                 nRet = indices[depth-1];
9193 
9194                 gtk_tree_path_free(path);
9195             }
9196         }
9197         else
9198         {
9199             auto vec = get_selected_rows();
9200             return vec.empty() ? -1 : vec[0];
9201         }
9202         return nRet;
9203     }
9204 
get_selected_iterator(GtkTreeIter * pIter) const9205     bool get_selected_iterator(GtkTreeIter* pIter) const
9206     {
9207         assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9208         bool bRet = false;
9209         GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
9210         if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
9211             bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
9212         else
9213         {
9214             GtkTreeModel* pModel;
9215             GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
9216             for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
9217             {
9218                 if (pIter)
9219                 {
9220                     GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
9221                     gtk_tree_model_get_iter(pModel, pIter, path);
9222                 }
9223                 bRet = true;
9224                 break;
9225             }
9226             g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
9227         }
9228         return bRet;
9229     }
9230 
get_selected_text() const9231     virtual OUString get_selected_text() const override
9232     {
9233         assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9234         GtkTreeIter iter;
9235         if (get_selected_iterator(&iter))
9236             return get(iter, m_nTextCol);
9237         return OUString();
9238     }
9239 
get_selected_id() const9240     virtual OUString get_selected_id() const override
9241     {
9242         assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9243         GtkTreeIter iter;
9244         if (get_selected_iterator(&iter))
9245             return get(iter, m_nIdCol);
9246         return OUString();
9247     }
9248 
make_iterator(const weld::TreeIter * pOrig) const9249     virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
9250     {
9251         return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
9252     }
9253 
copy_iterator(const weld::TreeIter & rSource,weld::TreeIter & rDest) const9254     virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
9255     {
9256         const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
9257         GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
9258         rGtkDest.iter = rGtkSource.iter;
9259     }
9260 
get_selected(weld::TreeIter * pIter) const9261     virtual bool get_selected(weld::TreeIter* pIter) const override
9262     {
9263         GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
9264         return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
9265     }
9266 
get_cursor(weld::TreeIter * pIter) const9267     virtual bool get_cursor(weld::TreeIter* pIter) const override
9268     {
9269         GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
9270         GtkTreePath* path;
9271         gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
9272         if (pGtkIter && path)
9273         {
9274             GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9275             gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
9276         }
9277         if (!path)
9278             return false;
9279         gtk_tree_path_free(path);
9280         return true;
9281     }
9282 
get_cursor_index() const9283     virtual int get_cursor_index() const override
9284     {
9285         int nRet = -1;
9286 
9287         GtkTreePath* path;
9288         gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
9289         if (path)
9290         {
9291             gint depth;
9292             gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9293             nRet = indices[depth-1];
9294             gtk_tree_path_free(path);
9295         }
9296 
9297         return nRet;
9298     }
9299 
set_cursor(const weld::TreeIter & rIter)9300     virtual void set_cursor(const weld::TreeIter& rIter) override
9301     {
9302         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9303         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9304         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9305         gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
9306         gtk_tree_path_free(path);
9307     }
9308 
get_iter_first(weld::TreeIter & rIter) const9309     virtual bool get_iter_first(weld::TreeIter& rIter) const override
9310     {
9311         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9312         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9313         return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
9314     }
9315 
iter_next_sibling(weld::TreeIter & rIter) const9316     virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
9317     {
9318         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9319         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9320         return gtk_tree_model_iter_next(pModel, &rGtkIter.iter);
9321     }
9322 
iter_next(weld::TreeIter & rIter) const9323     virtual bool iter_next(weld::TreeIter& rIter) const override
9324     {
9325         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9326         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9327         GtkTreeIter iter = rGtkIter.iter;
9328         if (iter_children(rGtkIter))
9329             return true;
9330         GtkTreeIter tmp = iter;
9331         if (gtk_tree_model_iter_next(pModel, &tmp))
9332         {
9333             rGtkIter.iter = tmp;
9334             return true;
9335         }
9336         // Move up level(s) until we find the level where the next sibling exists.
9337         while (gtk_tree_model_iter_parent(pModel, &tmp, &iter))
9338         {
9339             iter = tmp;
9340             if (gtk_tree_model_iter_next(pModel, &tmp))
9341             {
9342                 rGtkIter.iter = tmp;
9343                 return true;
9344             }
9345         }
9346         return false;
9347     }
9348 
iter_children(weld::TreeIter & rIter) const9349     virtual bool iter_children(weld::TreeIter& rIter) const override
9350     {
9351         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9352         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9353         GtkTreeIter tmp;
9354         bool ret = gtk_tree_model_iter_children(pModel, &tmp, &rGtkIter.iter);
9355         rGtkIter.iter = tmp;
9356         if (ret)
9357         {
9358             //on-demand dummy entry doesn't count
9359             return get_text(rGtkIter, -1) != "<dummy>";
9360         }
9361         return ret;
9362     }
9363 
iter_parent(weld::TreeIter & rIter) const9364     virtual bool iter_parent(weld::TreeIter& rIter) const override
9365     {
9366         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9367         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9368         GtkTreeIter tmp;
9369         auto ret = gtk_tree_model_iter_parent(pModel, &tmp, &rGtkIter.iter);
9370         rGtkIter.iter = tmp;
9371         return ret;
9372     }
9373 
remove(const weld::TreeIter & rIter)9374     virtual void remove(const weld::TreeIter& rIter) override
9375     {
9376         disable_notify_events();
9377         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9378         gtk_tree_store_remove(m_pTreeStore, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9379         enable_notify_events();
9380     }
9381 
remove_selection()9382     virtual void remove_selection() override
9383     {
9384         disable_notify_events();
9385 
9386         std::vector<GtkTreeIter> aIters;
9387         GtkTreeModel* pModel;
9388         GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
9389         for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
9390         {
9391             GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
9392             aIters.emplace_back();
9393             gtk_tree_model_get_iter(pModel, &aIters.back(), path);
9394         }
9395         g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
9396 
9397         for (auto& iter : aIters)
9398             gtk_tree_store_remove(m_pTreeStore, &iter);
9399 
9400         enable_notify_events();
9401     }
9402 
select(const weld::TreeIter & rIter)9403     virtual void select(const weld::TreeIter& rIter) override
9404     {
9405         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
9406         disable_notify_events();
9407         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9408         gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
9409         enable_notify_events();
9410     }
9411 
scroll_to_row(const weld::TreeIter & rIter)9412     virtual void scroll_to_row(const weld::TreeIter& rIter) override
9413     {
9414         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
9415         disable_notify_events();
9416         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9417         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9418         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9419         gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
9420         gtk_tree_path_free(path);
9421         enable_notify_events();
9422     }
9423 
unselect(const weld::TreeIter & rIter)9424     virtual void unselect(const weld::TreeIter& rIter) override
9425     {
9426         assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
9427         disable_notify_events();
9428         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9429         gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
9430         enable_notify_events();
9431     }
9432 
get_iter_depth(const weld::TreeIter & rIter) const9433     virtual int get_iter_depth(const weld::TreeIter& rIter) const override
9434     {
9435         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9436         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9437         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9438         int ret = gtk_tree_path_get_depth(path) - 1;
9439         gtk_tree_path_free(path);
9440         return ret;
9441     }
9442 
iter_has_child(const weld::TreeIter & rIter) const9443     virtual bool iter_has_child(const weld::TreeIter& rIter) const override
9444     {
9445         weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
9446         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNonConstIter);
9447         GtkTreeIter restore(rGtkIter.iter);
9448         bool ret = iter_children(rNonConstIter);
9449         rGtkIter.iter = restore;
9450         return ret;
9451     }
9452 
get_row_expanded(const weld::TreeIter & rIter) const9453     virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
9454     {
9455         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9456         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9457         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9458         bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
9459         gtk_tree_path_free(path);
9460         return ret;
9461     }
9462 
expand_row(const weld::TreeIter & rIter)9463     virtual void expand_row(const weld::TreeIter& rIter) override
9464     {
9465         assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
9466 
9467         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9468         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9469         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9470         if (!gtk_tree_view_row_expanded(m_pTreeView, path))
9471             gtk_tree_view_expand_to_path(m_pTreeView, path);
9472         gtk_tree_path_free(path);
9473     }
9474 
collapse_row(const weld::TreeIter & rIter)9475     virtual void collapse_row(const weld::TreeIter& rIter) override
9476     {
9477         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9478         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9479         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9480         if (gtk_tree_view_row_expanded(m_pTreeView, path))
9481             gtk_tree_view_collapse_row(m_pTreeView, path);
9482         gtk_tree_path_free(path);
9483     }
9484 
get_text(const weld::TreeIter & rIter,int col) const9485     virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
9486     {
9487         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9488         if (col == -1)
9489             col = m_nTextCol;
9490         else
9491             col = get_model_col(col);
9492         return get(rGtkIter.iter, col);
9493     }
9494 
set_text(const weld::TreeIter & rIter,const OUString & rText,int col)9495     virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
9496     {
9497         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9498         if (col == -1)
9499             col = m_nTextCol;
9500         else
9501             col = get_model_col(col);
9502         set(rGtkIter.iter, col, rText);
9503     }
9504 
get_id(const weld::TreeIter & rIter) const9505     virtual OUString get_id(const weld::TreeIter& rIter) const override
9506     {
9507         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9508         return get(rGtkIter.iter, m_nIdCol);
9509     }
9510 
set_id(const weld::TreeIter & rIter,const OUString & rId)9511     virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
9512     {
9513         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9514         set(rGtkIter.iter, m_nIdCol, rId);
9515     }
9516 
freeze()9517     virtual void freeze() override
9518     {
9519         disable_notify_events();
9520         g_object_ref(m_pTreeStore);
9521         GtkInstanceContainer::freeze();
9522         gtk_tree_view_set_model(m_pTreeView, nullptr);
9523         if (m_xSorter)
9524         {
9525             int nSortColumn;
9526             GtkSortType eSortType;
9527             GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
9528             gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
9529             gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
9530 
9531             m_aSavedSortColumns.push_back(nSortColumn);
9532             m_aSavedSortTypes.push_back(eSortType);
9533         }
9534         enable_notify_events();
9535     }
9536 
thaw()9537     virtual void thaw() override
9538     {
9539         disable_notify_events();
9540         if (m_xSorter)
9541         {
9542             GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
9543             gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
9544             m_aSavedSortTypes.pop_back();
9545             m_aSavedSortColumns.pop_back();
9546         }
9547         gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeStore));
9548         GtkInstanceContainer::thaw();
9549         g_object_unref(m_pTreeStore);
9550         enable_notify_events();
9551     }
9552 
get_height_rows(int nRows) const9553     virtual int get_height_rows(int nRows) const override
9554     {
9555         gint nMaxRowHeight = 0;
9556         for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
9557         {
9558             GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
9559             GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
9560             for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
9561             {
9562                 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
9563                 gint nRowHeight;
9564                 gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(m_pTreeView), nullptr, &nRowHeight);
9565                 nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
9566             }
9567             g_list_free(pRenderers);
9568         }
9569 
9570         gint nVerticalSeparator;
9571         gtk_widget_style_get(GTK_WIDGET(m_pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
9572 
9573         return (nMaxRowHeight * nRows) + (nVerticalSeparator * (nRows + 1));
9574     }
9575 
get_size_request() const9576     virtual Size get_size_request() const override
9577     {
9578         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9579         if (GTK_IS_SCROLLED_WINDOW(pParent))
9580         {
9581             return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
9582                         gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
9583         }
9584         int nWidth, nHeight;
9585         gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
9586         return Size(nWidth, nHeight);
9587     }
9588 
get_preferred_size() const9589     virtual Size get_preferred_size() const override
9590     {
9591         Size aRet(-1, -1);
9592         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9593         if (GTK_IS_SCROLLED_WINDOW(pParent))
9594         {
9595             aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
9596                         gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
9597         }
9598         GtkRequisition size;
9599         gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
9600         if (aRet.Width() == -1)
9601             aRet.setWidth(size.width);
9602         if (aRet.Height() == -1)
9603             aRet.setHeight(size.height);
9604         return aRet;
9605     }
9606 
show()9607     virtual void show() override
9608     {
9609         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9610         if (GTK_IS_SCROLLED_WINDOW(pParent))
9611             gtk_widget_show(pParent);
9612         gtk_widget_show(m_pWidget);
9613     }
9614 
hide()9615     virtual void hide() override
9616     {
9617         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9618         if (GTK_IS_SCROLLED_WINDOW(pParent))
9619             gtk_widget_hide(pParent);
9620         gtk_widget_hide(m_pWidget);
9621     }
9622 
set_selection_mode(SelectionMode eMode)9623     virtual void set_selection_mode(SelectionMode eMode) override
9624     {
9625         disable_notify_events();
9626         gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
9627         enable_notify_events();
9628     }
9629 
count_selected_rows() const9630     virtual int count_selected_rows() const override
9631     {
9632         return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
9633     }
9634 
starts_with(const OUString & rStr,int col,int nStartRow,bool bCaseSensitive)9635     int starts_with(const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
9636     {
9637         return ::starts_with(GTK_TREE_MODEL(m_pTreeStore), rStr, get_model_col(col), nStartRow, bCaseSensitive);
9638     }
9639 
disable_notify_events()9640     virtual void disable_notify_events() override
9641     {
9642         g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
9643         g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
9644 
9645         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9646         g_signal_handler_block(pModel, m_nRowDeletedSignalId);
9647         g_signal_handler_block(pModel, m_nRowInsertedSignalId);
9648 
9649         GtkInstanceContainer::disable_notify_events();
9650     }
9651 
enable_notify_events()9652     virtual void enable_notify_events() override
9653     {
9654         GtkInstanceContainer::enable_notify_events();
9655 
9656         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9657         g_signal_handler_unblock(pModel, m_nRowDeletedSignalId);
9658         g_signal_handler_unblock(pModel, m_nRowInsertedSignalId);
9659 
9660         g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
9661         g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
9662     }
9663 
connect_popup_menu(const Link<const CommandEvent &,bool> & rLink)9664     virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
9665     {
9666         ensureButtonPressSignal();
9667         weld::TreeView::connect_popup_menu(rLink);
9668     }
9669 
get_dest_row_at_pos(const Point & rPos,weld::TreeIter * pResult)9670     virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult) override
9671     {
9672         const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
9673 
9674         // to keep it simple we'll default to always drop before the current row
9675         // except for the special edge cases
9676         GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
9677 
9678         // unhighlight current highlighted row
9679         gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
9680 
9681         if (m_bWorkAroundBadDragRegion)
9682             gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
9683 
9684         GtkTreePath *path = nullptr;
9685         GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
9686         bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
9687                                                      &path, &gtkpos);
9688 
9689         // find the last entry in the model for comparison
9690         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9691         int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
9692         GtkTreePath *lastpath;
9693         if (nChildren)
9694             lastpath = gtk_tree_path_new_from_indices(nChildren - 1, -1);
9695         else
9696             lastpath = gtk_tree_path_new_from_indices(0, -1);
9697 
9698         if (!ret)
9699         {
9700             // empty space, draw an indicator at the last entry
9701             assert(!path);
9702             path = gtk_tree_path_copy(lastpath);
9703             pos = GTK_TREE_VIEW_DROP_AFTER;
9704         }
9705         else if (gtk_tree_path_compare(path, lastpath) == 0)
9706         {
9707             // if we're on the last entry, see if gtk thinks
9708             // the drop should be before or after it, and if
9709             // its after, treat it like a drop into empty
9710             // space, i.e. append it
9711             if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
9712                 gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
9713             {
9714                 ret = false;
9715                 pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
9716             }
9717         }
9718 
9719         if (ret && pResult)
9720         {
9721             GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
9722             gtk_tree_model_get_iter(pModel, &rGtkIter.iter, path);
9723         }
9724 
9725         if (m_bInDrag)
9726         {
9727             // highlight the row
9728             gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
9729         }
9730 
9731         assert(path);
9732         gtk_tree_path_free(path);
9733         gtk_tree_path_free(lastpath);
9734 
9735         // auto scroll if we're close to the edges
9736         GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
9737         double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
9738         if (rPos.Y() < fStep)
9739         {
9740             double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
9741             if (fValue < 0)
9742                 fValue = 0.0;
9743             gtk_adjustment_set_value(pVAdjustment, fValue);
9744         }
9745         else
9746         {
9747             GdkRectangle aRect;
9748             gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
9749             if (rPos.Y() > aRect.height - fStep)
9750             {
9751                 double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
9752                 double fMax = gtk_adjustment_get_upper(pVAdjustment);
9753                 if (fValue > fMax)
9754                     fValue = fMax;
9755                 gtk_adjustment_set_value(pVAdjustment, fValue);
9756             }
9757         }
9758 
9759         return ret;
9760     }
9761 
start_editing(const weld::TreeIter & rIter)9762     virtual void start_editing(const weld::TreeIter& rIter) override
9763     {
9764         int col = get_view_col(m_nTextCol);
9765         GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
9766         assert(pColumn && "wrong column");
9767 
9768         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9769         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9770         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9771 
9772         // allow editing of cells which are not usually editable, so we can have double click
9773         // do its usual row-activate but if we explicitly want to edit (remote files dialog)
9774         // we can still do that
9775         GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
9776         for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
9777         {
9778             GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
9779             if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
9780             {
9781                 gboolean is_editable(false);
9782                 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
9783                 if (!is_editable)
9784                 {
9785                     g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
9786                     g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
9787                     break;
9788                 }
9789             }
9790         }
9791         g_list_free(pRenderers);
9792 
9793         gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
9794 
9795         gtk_tree_path_free(path);
9796     }
9797 
end_editing()9798     virtual void end_editing() override
9799     {
9800         GtkTreeViewColumn *focus_column = nullptr;
9801         gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
9802         if (focus_column)
9803             gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
9804     }
9805 
get_drag_source() const9806     virtual TreeView* get_drag_source() const override
9807     {
9808         return g_DragSource;
9809     }
9810 
9811     // Under gtk 3.24.8 dragging into the TreeView is not highlighting
9812     // entire TreeView widget, just the rectangle which has no entries
9813     // in it, so as a workaround highlight the parent container
9814     // on drag start, and undo it on drag end, and trigger removal
9815     // of the treeview's highlight effort
drag_started()9816     virtual void drag_started() override
9817     {
9818         m_bInDrag = true;
9819         GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
9820         GtkWidget* pParent = gtk_widget_get_parent(pWidget);
9821         if (GTK_IS_SCROLLED_WINDOW(pParent))
9822         {
9823             gtk_drag_unhighlight(pWidget);
9824             gtk_drag_highlight(pParent);
9825             m_bWorkAroundBadDragRegion = true;
9826         }
9827     }
9828 
drag_ended()9829     virtual void drag_ended() override
9830     {
9831         m_bInDrag = false;
9832         if (m_bWorkAroundBadDragRegion)
9833         {
9834             GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
9835             GtkWidget* pParent = gtk_widget_get_parent(pWidget);
9836             gtk_drag_unhighlight(pParent);
9837             m_bWorkAroundBadDragRegion = false;
9838         }
9839         // unhighlight the row
9840         gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
9841     }
9842 
~GtkInstanceTreeView()9843     virtual ~GtkInstanceTreeView() override
9844     {
9845         if (m_pChangeEvent)
9846             Application::RemoveUserEvent(m_pChangeEvent);
9847         g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
9848         g_signal_handler_disconnect(m_pTreeView, m_nDragEndSignalId);
9849         g_signal_handler_disconnect(m_pTreeView, m_nDragBeginSignalId);
9850         g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
9851         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9852         g_signal_handler_disconnect(pModel, m_nRowDeletedSignalId);
9853         g_signal_handler_disconnect(pModel, m_nRowInsertedSignalId);
9854 
9855         if (m_nVAdjustmentChangedSignalId)
9856         {
9857             GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
9858             g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
9859         }
9860 
9861         g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
9862         g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
9863         g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
9864 
9865         for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
9866         {
9867             GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
9868             g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
9869             m_aColumnSignalIds.pop_back();
9870         }
9871         g_list_free(m_pColumns);
9872     }
9873 };
9874 
IMPL_LINK_NOARG(GtkInstanceTreeView,async_signal_changed,void *,void)9875 IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
9876 {
9877     m_pChangeEvent = nullptr;
9878     signal_changed();
9879 }
9880 
IMPL_LINK_NOARG(GtkInstanceTreeView,async_stop_cell_editing,void *,void)9881 IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
9882 {
9883     end_editing();
9884 }
9885 
9886 class GtkInstanceIconView : public GtkInstanceContainer, public virtual weld::IconView
9887 {
9888 private:
9889     GtkIconView* m_pIconView;
9890     GtkTreeStore* m_pTreeStore;
9891     gint m_nTextCol;
9892     gint m_nImageCol;
9893     gint m_nIdCol;
9894     gulong m_nSelectionChangedSignalId;
9895     gulong m_nItemActivatedSignalId;
9896     ImplSVEvent* m_pSelectionChangeEvent;
9897 
9898     DECL_LINK(async_signal_selection_changed, void*, void);
9899 
launch_signal_selection_changed()9900     void launch_signal_selection_changed()
9901     {
9902         //tdf#117991 selection change is sent before the focus change, and focus change
9903         //is what will cause a spinbutton that currently has the focus to set its contents
9904         //as the spin button value. So any LibreOffice callbacks on
9905         //signal-change would happen before the spinbutton value-change occurs.
9906         //To avoid this, send the signal-change to LibreOffice to occur after focus-change
9907         //has been processed
9908         if (m_pSelectionChangeEvent)
9909             Application::RemoveUserEvent(m_pSelectionChangeEvent);
9910         m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
9911     }
9912 
signalSelectionChanged(GtkIconView *,gpointer widget)9913     static void signalSelectionChanged(GtkIconView*, gpointer widget)
9914     {
9915         GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
9916         pThis->launch_signal_selection_changed();
9917     }
9918 
handle_item_activated()9919     void handle_item_activated()
9920     {
9921         if (signal_item_activated())
9922             return;
9923     }
9924 
signalItemActivated(GtkIconView *,GtkTreePath *,gpointer widget)9925     static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
9926     {
9927         GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
9928         SolarMutexGuard aGuard;
9929         pThis->handle_item_activated();
9930     }
9931 
insert_item(GtkTreeIter & iter,int pos,const OUString * pId,const OUString * pText,const OUString * pIconName)9932     void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
9933     {
9934         gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
9935                                           m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
9936                                           m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
9937                                           -1);
9938         if (pIconName)
9939         {
9940             GdkPixbuf* pixbuf = getPixbuf(*pIconName);
9941             gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
9942             if (pixbuf)
9943                 g_object_unref(pixbuf);
9944         }
9945     }
9946 
get(const GtkTreeIter & iter,int col) const9947     OUString get(const GtkTreeIter& iter, int col) const
9948     {
9949         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9950         gchar* pStr;
9951         gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
9952         OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
9953         g_free(pStr);
9954         return sRet;
9955     }
9956 
get_selected_iterator(GtkTreeIter * pIter) const9957     bool get_selected_iterator(GtkTreeIter* pIter) const
9958     {
9959         assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
9960         bool bRet = false;
9961         {
9962             GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
9963             GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
9964             for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
9965             {
9966                 if (pIter)
9967                 {
9968                     GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
9969                     gtk_tree_model_get_iter(pModel, pIter, path);
9970                 }
9971                 bRet = true;
9972                 break;
9973             }
9974             g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
9975         }
9976         return bRet;
9977     }
9978 
9979 public:
GtkInstanceIconView(GtkIconView * pIconView,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)9980     GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9981         : GtkInstanceContainer(GTK_CONTAINER(pIconView), pBuilder, bTakeOwnership)
9982         , m_pIconView(pIconView)
9983         , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
9984         , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView))
9985         , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
9986         , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
9987                                       G_CALLBACK(signalSelectionChanged), this))
9988         , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
9989         , m_pSelectionChangeEvent(nullptr)
9990     {
9991         m_nIdCol = m_nTextCol + 1;
9992     }
9993 
insert(int pos,const OUString * pText,const OUString * pId,const OUString * pIconName,weld::TreeIter * pRet)9994     virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
9995     {
9996         disable_notify_events();
9997         GtkTreeIter iter;
9998         insert_item(iter, pos, pId, pText, pIconName);
9999         if (pRet)
10000         {
10001             GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
10002             pGtkRetIter->iter = iter;
10003         }
10004         enable_notify_events();
10005     }
10006 
get_selected_id() const10007     virtual OUString get_selected_id() const override
10008     {
10009         assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
10010         GtkTreeIter iter;
10011         if (get_selected_iterator(&iter))
10012             return get(iter, m_nIdCol);
10013         return OUString();
10014     }
10015 
clear()10016     virtual void clear() override
10017     {
10018         disable_notify_events();
10019         gtk_tree_store_clear(m_pTreeStore);
10020         enable_notify_events();
10021     }
10022 
freeze()10023     virtual void freeze() override
10024     {
10025         disable_notify_events();
10026         g_object_ref(m_pTreeStore);
10027         GtkInstanceContainer::freeze();
10028         gtk_icon_view_set_model(m_pIconView, nullptr);
10029         enable_notify_events();
10030     }
10031 
thaw()10032     virtual void thaw() override
10033     {
10034         disable_notify_events();
10035         gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
10036         GtkInstanceContainer::thaw();
10037         g_object_unref(m_pTreeStore);
10038         enable_notify_events();
10039     }
10040 
get_size_request() const10041     virtual Size get_size_request() const override
10042     {
10043         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10044         if (GTK_IS_SCROLLED_WINDOW(pParent))
10045         {
10046             return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
10047                         gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
10048         }
10049         int nWidth, nHeight;
10050         gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
10051         return Size(nWidth, nHeight);
10052     }
10053 
get_preferred_size() const10054     virtual Size get_preferred_size() const override
10055     {
10056         Size aRet(-1, -1);
10057         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10058         if (GTK_IS_SCROLLED_WINDOW(pParent))
10059         {
10060             aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
10061                         gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
10062         }
10063         GtkRequisition size;
10064         gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
10065         if (aRet.Width() == -1)
10066             aRet.setWidth(size.width);
10067         if (aRet.Height() == -1)
10068             aRet.setHeight(size.height);
10069         return aRet;
10070     }
10071 
show()10072     virtual void show() override
10073     {
10074         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10075         if (GTK_IS_SCROLLED_WINDOW(pParent))
10076             gtk_widget_show(pParent);
10077         gtk_widget_show(m_pWidget);
10078     }
10079 
hide()10080     virtual void hide() override
10081     {
10082         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10083         if (GTK_IS_SCROLLED_WINDOW(pParent))
10084             gtk_widget_hide(pParent);
10085         gtk_widget_hide(m_pWidget);
10086     }
10087 
get_selected_text() const10088     virtual OUString get_selected_text() const override
10089     {
10090         assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
10091         GtkTreeIter iter;
10092         if (get_selected_iterator(&iter))
10093             return get(iter, m_nTextCol);
10094         return OUString();
10095     }
10096 
count_selected_items() const10097     virtual int count_selected_items() const override
10098     {
10099         GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
10100         int nRet = g_list_length(pList);
10101         g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
10102         return nRet;
10103     }
10104 
select(int pos)10105     virtual void select(int pos) override
10106     {
10107         assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen");
10108         disable_notify_events();
10109         if (pos == -1 || (pos == 0 && n_children() == 0))
10110         {
10111             gtk_icon_view_unselect_all(m_pIconView);
10112         }
10113         else
10114         {
10115             GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
10116             gtk_icon_view_select_path(m_pIconView, path);
10117             gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
10118             gtk_tree_path_free(path);
10119         }
10120         enable_notify_events();
10121     }
10122 
unselect(int pos)10123     virtual void unselect(int pos) override
10124     {
10125         assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen");
10126         disable_notify_events();
10127         if (pos == -1 || (pos == 0 && n_children() == 0))
10128         {
10129             gtk_icon_view_select_all(m_pIconView);
10130         }
10131         else
10132         {
10133             GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
10134             gtk_icon_view_select_path(m_pIconView, path);
10135             gtk_tree_path_free(path);
10136         }
10137         enable_notify_events();
10138     }
10139 
get_selected(weld::TreeIter * pIter) const10140     virtual bool get_selected(weld::TreeIter* pIter) const override
10141     {
10142         GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
10143         return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
10144     }
10145 
get_cursor(weld::TreeIter * pIter) const10146     virtual bool get_cursor(weld::TreeIter* pIter) const override
10147     {
10148         GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
10149         GtkTreePath* path;
10150         gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
10151         if (pGtkIter && path)
10152         {
10153             GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10154             gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
10155         }
10156         return path != nullptr;
10157     }
10158 
set_cursor(const weld::TreeIter & rIter)10159     virtual void set_cursor(const weld::TreeIter& rIter) override
10160     {
10161         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10162         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10163         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
10164         gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
10165         gtk_tree_path_free(path);
10166     }
10167 
get_iter_first(weld::TreeIter & rIter) const10168     virtual bool get_iter_first(weld::TreeIter& rIter) const override
10169     {
10170         GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
10171         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10172         return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
10173     }
10174 
scroll_to_item(const weld::TreeIter & rIter)10175     virtual void scroll_to_item(const weld::TreeIter& rIter) override
10176     {
10177         assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen");
10178         disable_notify_events();
10179         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10180         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10181         GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
10182         gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
10183         gtk_tree_path_free(path);
10184         enable_notify_events();
10185     }
10186 
make_iterator(const weld::TreeIter * pOrig) const10187     virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
10188     {
10189         return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
10190     }
10191 
selected_foreach(const std::function<bool (weld::TreeIter &)> & func)10192     virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
10193     {
10194         GtkInstanceTreeIter aGtkIter(nullptr);
10195 
10196         GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10197         GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
10198         for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
10199         {
10200             GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
10201             gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
10202             if (func(aGtkIter))
10203                 break;
10204         }
10205         g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
10206     }
10207 
n_children() const10208     virtual int n_children() const override
10209     {
10210         return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
10211     }
10212 
get_id(const weld::TreeIter & rIter) const10213     virtual OUString get_id(const weld::TreeIter& rIter) const override
10214     {
10215         const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10216         return get(rGtkIter.iter, m_nIdCol);
10217     }
10218 
disable_notify_events()10219     virtual void disable_notify_events() override
10220     {
10221         g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
10222         g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
10223 
10224         GtkInstanceContainer::disable_notify_events();
10225     }
10226 
enable_notify_events()10227     virtual void enable_notify_events() override
10228     {
10229         GtkInstanceContainer::enable_notify_events();
10230 
10231         g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
10232         g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
10233     }
10234 
~GtkInstanceIconView()10235     virtual ~GtkInstanceIconView() override
10236     {
10237         if (m_pSelectionChangeEvent)
10238             Application::RemoveUserEvent(m_pSelectionChangeEvent);
10239 
10240         g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
10241         g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
10242     }
10243 };
10244 
IMPL_LINK_NOARG(GtkInstanceIconView,async_signal_selection_changed,void *,void)10245 IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
10246 {
10247     m_pSelectionChangeEvent = nullptr;
10248     signal_selection_changed();
10249 }
10250 
10251 class GtkInstanceSpinButton : public GtkInstanceEntry, public virtual weld::SpinButton
10252 {
10253 private:
10254     GtkSpinButton* m_pButton;
10255     gulong m_nValueChangedSignalId;
10256     gulong m_nOutputSignalId;
10257     gulong m_nInputSignalId;
10258     bool m_bFormatting;
10259     bool m_bBlockOutput;
10260     bool m_bBlank;
10261 
signalValueChanged(GtkSpinButton *,gpointer widget)10262     static void signalValueChanged(GtkSpinButton*, gpointer widget)
10263     {
10264         GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
10265         SolarMutexGuard aGuard;
10266         pThis->m_bBlank = false;
10267         pThis->signal_value_changed();
10268     }
10269 
guarded_signal_output()10270     bool guarded_signal_output()
10271     {
10272         if (m_bBlockOutput)
10273             return true;
10274         m_bFormatting = true;
10275         bool bRet = signal_output();
10276         m_bFormatting = false;
10277         return bRet;
10278     }
10279 
signalOutput(GtkSpinButton *,gpointer widget)10280     static gboolean signalOutput(GtkSpinButton*, gpointer widget)
10281     {
10282         GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
10283         SolarMutexGuard aGuard;
10284         return pThis->guarded_signal_output();
10285     }
10286 
signalInput(GtkSpinButton *,gdouble * new_value,gpointer widget)10287     static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
10288     {
10289         GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
10290         SolarMutexGuard aGuard;
10291         int result;
10292         TriState eHandled = pThis->signal_input(&result);
10293         if (eHandled == TRISTATE_INDET)
10294             return 0;
10295         if (eHandled == TRISTATE_TRUE)
10296         {
10297             *new_value = pThis->toGtk(result);
10298             return 1;
10299         }
10300         return GTK_INPUT_ERROR;
10301     }
10302 
toGtk(int nValue) const10303     double toGtk(int nValue) const
10304     {
10305         return static_cast<double>(nValue) / Power10(get_digits());
10306     }
10307 
fromGtk(double fValue) const10308     int fromGtk(double fValue) const
10309     {
10310         return FRound(fValue * Power10(get_digits()));
10311     }
10312 
10313 public:
GtkInstanceSpinButton(GtkSpinButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)10314     GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10315         : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
10316         , m_pButton(pButton)
10317         , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
10318         , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
10319         , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
10320         , m_bFormatting(false)
10321         , m_bBlockOutput(false)
10322         , m_bBlank(false)
10323     {
10324         localizeDecimalSeparator();
10325     }
10326 
get_value() const10327     virtual int get_value() const override
10328     {
10329         return fromGtk(gtk_spin_button_get_value(m_pButton));
10330     }
10331 
set_value(int value)10332     virtual void set_value(int value) override
10333     {
10334         disable_notify_events();
10335         m_bBlank = false;
10336         gtk_spin_button_set_value(m_pButton, toGtk(value));
10337         enable_notify_events();
10338     }
10339 
set_text(const OUString & rText)10340     virtual void set_text(const OUString& rText) override
10341     {
10342         disable_notify_events();
10343         // tdf#122786 if we're just formatting a value, then we're done,
10344         // however if set_text has been called directly we want to update our
10345         // value from this new text, but don't want to reformat with that value
10346         if (!m_bFormatting)
10347         {
10348             gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
10349 
10350             m_bBlockOutput = true;
10351             gtk_spin_button_update(m_pButton);
10352             m_bBlank = rText.isEmpty();
10353             m_bBlockOutput = false;
10354         }
10355         else
10356         {
10357             bool bKeepBlank = m_bBlank && get_value() == 0;
10358             if (!bKeepBlank)
10359             {
10360                 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
10361                 m_bBlank = false;
10362             }
10363         }
10364         enable_notify_events();
10365     }
10366 
set_range(int min,int max)10367     virtual void set_range(int min, int max) override
10368     {
10369         disable_notify_events();
10370         gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
10371         enable_notify_events();
10372     }
10373 
get_range(int & min,int & max) const10374     virtual void get_range(int& min, int& max) const override
10375     {
10376         double gtkmin, gtkmax;
10377         gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
10378         min = fromGtk(gtkmin);
10379         max = fromGtk(gtkmax);
10380     }
10381 
set_increments(int step,int page)10382     virtual void set_increments(int step, int page) override
10383     {
10384         disable_notify_events();
10385         gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
10386         enable_notify_events();
10387     }
10388 
get_increments(int & step,int & page) const10389     virtual void get_increments(int& step, int& page) const override
10390     {
10391         double gtkstep, gtkpage;
10392         gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
10393         step = fromGtk(gtkstep);
10394         page = fromGtk(gtkpage);
10395     }
10396 
set_digits(unsigned int digits)10397     virtual void set_digits(unsigned int digits) override
10398     {
10399         disable_notify_events();
10400         gtk_spin_button_set_digits(m_pButton, digits);
10401         enable_notify_events();
10402     }
10403 
get_digits() const10404     virtual unsigned int get_digits() const override
10405     {
10406         return gtk_spin_button_get_digits(m_pButton);
10407     }
10408 
disable_notify_events()10409     virtual void disable_notify_events() override
10410     {
10411         g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
10412         GtkInstanceEntry::disable_notify_events();
10413     }
10414 
enable_notify_events()10415     virtual void enable_notify_events() override
10416     {
10417         GtkInstanceEntry::enable_notify_events();
10418         g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
10419     }
10420 
~GtkInstanceSpinButton()10421     virtual ~GtkInstanceSpinButton() override
10422     {
10423         g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
10424         g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
10425         g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
10426     }
10427 };
10428 
10429 class GtkInstanceFormattedSpinButton : public GtkInstanceEntry, public virtual weld::FormattedSpinButton
10430 {
10431 private:
10432     GtkSpinButton* m_pButton;
10433     SvNumberFormatter* m_pFormatter;
10434     Color* m_pLastOutputColor;
10435     sal_uInt32 m_nFormatKey;
10436     gulong m_nValueChangedSignalId;
10437     gulong m_nOutputSignalId;
10438     gulong m_nInputSignalId;
10439 
signal_output()10440     bool signal_output()
10441     {
10442         if (!m_pFormatter)
10443             return false;
10444         double dVal = get_value();
10445         OUString sNewText;
10446         if (m_pFormatter->IsTextFormat(m_nFormatKey))
10447         {
10448             // first convert the number as string in standard format
10449             OUString sTemp;
10450             m_pFormatter->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
10451             // then encode the string in the corresponding text format
10452             m_pFormatter->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
10453         }
10454         else
10455         {
10456             m_pFormatter->GetInputLineString(dVal, m_nFormatKey, sNewText);
10457         }
10458         set_text(sNewText);
10459         return true;
10460     }
10461 
signalOutput(GtkSpinButton *,gpointer widget)10462     static gboolean signalOutput(GtkSpinButton*, gpointer widget)
10463     {
10464         GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
10465         SolarMutexGuard aGuard;
10466         return pThis->signal_output();
10467     }
10468 
signal_input(double * value)10469     gint signal_input(double* value)
10470     {
10471         if (!m_pFormatter)
10472             return 0;
10473 
10474         sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
10475 
10476         if (m_pFormatter->IsTextFormat(nFormatKey))
10477             // for detection of values like "1,1" in fields that are formatted as text
10478             nFormatKey = 0;
10479 
10480         OUString sText(get_text());
10481 
10482         // special treatment for percentage formatting
10483         if (m_pFormatter->GetType(m_nFormatKey) == SvNumFormatType::PERCENT)
10484         {
10485             // the language of our format
10486             LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
10487             // the default number format for this language
10488             sal_uLong nStandardNumericFormat = m_pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, eLanguage);
10489 
10490             sal_uInt32 nTempFormat = nStandardNumericFormat;
10491             double dTemp;
10492             if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
10493                 SvNumFormatType::NUMBER == m_pFormatter->GetType(nTempFormat))
10494                 // the string is equivalent to a number formatted one (has no % sign) -> append it
10495                 sText += "%";
10496             // (with this, an input of '3' becomes '3%', which then by the formatter is translated
10497             // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
10498             // which equals 300 percent.
10499         }
10500         if (!m_pFormatter->IsNumberFormat(sText, nFormatKey, *value))
10501             return GTK_INPUT_ERROR;
10502 
10503         return 1;
10504     }
10505 
signalInput(GtkSpinButton *,gdouble * new_value,gpointer widget)10506     static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
10507     {
10508         GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
10509         SolarMutexGuard aGuard;
10510         return pThis->signal_input(new_value);
10511     }
10512 
signalValueChanged(GtkSpinButton *,gpointer widget)10513     static void signalValueChanged(GtkSpinButton*, gpointer widget)
10514     {
10515         GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
10516         SolarMutexGuard aGuard;
10517         pThis->signal_value_changed();
10518     }
10519 
10520 public:
GtkInstanceFormattedSpinButton(GtkSpinButton * pButton,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)10521     GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10522         : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
10523         , m_pButton(pButton)
10524         , m_pFormatter(nullptr)
10525         , m_pLastOutputColor(nullptr)
10526         , m_nFormatKey(0)
10527         , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
10528         , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
10529         , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
10530     {
10531     }
10532 
get_value() const10533     virtual double get_value() const override
10534     {
10535         return gtk_spin_button_get_value(m_pButton);
10536     }
10537 
set_value(double value)10538     virtual void set_value(double value) override
10539     {
10540         disable_notify_events();
10541         gtk_spin_button_set_value(m_pButton, value);
10542         enable_notify_events();
10543     }
10544 
set_range(double min,double max)10545     virtual void set_range(double min, double max) override
10546     {
10547         disable_notify_events();
10548         gtk_spin_button_set_range(m_pButton, min, max);
10549         enable_notify_events();
10550     }
10551 
get_range(double & min,double & max) const10552     virtual void get_range(double& min, double& max) const override
10553     {
10554         gtk_spin_button_get_range(m_pButton, &min, &max);
10555     }
10556 
set_formatter(SvNumberFormatter * pFormatter)10557     virtual void set_formatter(SvNumberFormatter* pFormatter) override
10558     {
10559         m_pFormatter = pFormatter;
10560 
10561         // calc the default format key from the Office's UI locale
10562         if (m_pFormatter)
10563         {
10564             // get the Office's locale and translate
10565             LanguageType eSysLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType( false);
10566             // get the standard numeric format for this language
10567             m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
10568         }
10569         else
10570             m_nFormatKey = 0;
10571         signal_output();
10572     }
10573 
get_format_key() const10574     virtual sal_Int32 get_format_key() const override
10575     {
10576         return m_nFormatKey;
10577     }
10578 
set_format_key(sal_Int32 nFormatKey)10579     virtual void set_format_key(sal_Int32 nFormatKey) override
10580     {
10581         m_nFormatKey = nFormatKey;
10582     }
10583 
~GtkInstanceFormattedSpinButton()10584     virtual ~GtkInstanceFormattedSpinButton() override
10585     {
10586         g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
10587         g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
10588         g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
10589     }
10590 };
10591 
10592 class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
10593 {
10594 private:
10595     GtkLabel* m_pLabel;
10596 
set_text_color(const Color & rColor)10597     void set_text_color(const Color& rColor)
10598     {
10599         guint16 nRed = rColor.GetRed() << 8;
10600         guint16 nGreen = rColor.GetRed() << 8;
10601         guint16 nBlue = rColor.GetBlue() << 8;
10602 
10603         PangoAttrList* pAttrs = pango_attr_list_new();
10604         pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
10605         gtk_label_set_attributes(m_pLabel, pAttrs);
10606         pango_attr_list_unref(pAttrs);
10607     }
10608 
10609 public:
GtkInstanceLabel(GtkLabel * pLabel,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)10610     GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10611         : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
10612         , m_pLabel(pLabel)
10613     {
10614     }
10615 
set_label(const OUString & rText)10616     virtual void set_label(const OUString& rText) override
10617     {
10618         ::set_label(m_pLabel, rText);
10619     }
10620 
get_label() const10621     virtual OUString get_label() const override
10622     {
10623         return ::get_label(m_pLabel);
10624     }
10625 
set_mnemonic_widget(Widget * pTarget)10626     virtual void set_mnemonic_widget(Widget* pTarget) override
10627     {
10628         assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
10629         GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
10630         gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
10631     }
10632 
set_message_type(weld::EntryMessageType eType)10633     virtual void set_message_type(weld::EntryMessageType eType) override
10634     {
10635         if (eType == weld::EntryMessageType::Error)
10636             set_text_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
10637         else if (eType == weld::EntryMessageType::Warning)
10638             set_text_color(COL_YELLOW);
10639         else
10640             gtk_label_set_attributes(m_pLabel, nullptr);
10641     }
10642 
set_font(const vcl::Font & rFont)10643     virtual void set_font(const vcl::Font& rFont) override
10644     {
10645         PangoAttrList* pAttrList = create_attr_list(rFont);
10646         gtk_label_set_attributes(m_pLabel, pAttrList);
10647         pango_attr_list_unref(pAttrList);
10648     }
10649 };
10650 
weld_label_widget() const10651 std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
10652 {
10653     GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
10654     if (!pLabel || !GTK_IS_LABEL(pLabel))
10655         return nullptr;
10656     return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
10657 }
10658 
10659 class GtkInstanceTextView : public GtkInstanceContainer, public virtual weld::TextView
10660 {
10661 private:
10662     GtkTextView* m_pTextView;
10663     GtkTextBuffer* m_pTextBuffer;
10664     GtkAdjustment* m_pVAdjustment;
10665     gulong m_nChangedSignalId;
10666     gulong m_nCursorPosSignalId;
10667     gulong m_nVAdjustChangedSignalId;
10668 
signalChanged(GtkTextView *,gpointer widget)10669     static void signalChanged(GtkTextView*, gpointer widget)
10670     {
10671         GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
10672         SolarMutexGuard aGuard;
10673         pThis->signal_changed();
10674     }
10675 
signalCursorPosition(GtkTextView *,GParamSpec *,gpointer widget)10676     static void signalCursorPosition(GtkTextView*, GParamSpec*, gpointer widget)
10677     {
10678         GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
10679         pThis->signal_cursor_position();
10680     }
10681 
signalVAdjustValueChanged(GtkAdjustment *,gpointer widget)10682     static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
10683     {
10684         GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
10685         SolarMutexGuard aGuard;
10686         pThis->signal_vadjustment_changed();
10687     }
10688 
10689 public:
GtkInstanceTextView(GtkTextView * pTextView,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)10690     GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10691         : GtkInstanceContainer(GTK_CONTAINER(pTextView), pBuilder, bTakeOwnership)
10692         , m_pTextView(pTextView)
10693         , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
10694         , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
10695         , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
10696         , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
10697         , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
10698     {
10699     }
10700 
set_size_request(int nWidth,int nHeight)10701     virtual void set_size_request(int nWidth, int nHeight) override
10702     {
10703         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10704         if (GTK_IS_SCROLLED_WINDOW(pParent))
10705         {
10706             gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
10707             gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
10708             return;
10709         }
10710         gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
10711     }
10712 
set_text(const OUString & rText)10713     virtual void set_text(const OUString& rText) override
10714     {
10715         disable_notify_events();
10716         GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10717         OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
10718         gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
10719         enable_notify_events();
10720     }
10721 
get_text() const10722     virtual OUString get_text() const override
10723     {
10724         GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10725         GtkTextIter start, end;
10726         gtk_text_buffer_get_bounds(pBuffer, &start, &end);
10727         char* pStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
10728         OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
10729         g_free(pStr);
10730         return sRet;
10731     }
10732 
replace_selection(const OUString & rText)10733     virtual void replace_selection(const OUString& rText) override
10734     {
10735         disable_notify_events();
10736         GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10737         gtk_text_buffer_delete_selection(pBuffer, false, gtk_text_view_get_editable(m_pTextView));
10738         OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
10739         gtk_text_buffer_insert_at_cursor(pBuffer, sText.getStr(), sText.getLength());
10740         enable_notify_events();
10741     }
10742 
get_selection_bounds(int & rStartPos,int & rEndPos)10743     virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
10744     {
10745         GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10746         GtkTextIter start, end;
10747         gtk_text_buffer_get_selection_bounds(pBuffer, &start, &end);
10748         rStartPos = gtk_text_iter_get_offset(&start);
10749         rEndPos = gtk_text_iter_get_offset(&end);
10750         return rStartPos != rEndPos;
10751     }
10752 
select_region(int nStartPos,int nEndPos)10753     virtual void select_region(int nStartPos, int nEndPos) override
10754     {
10755         disable_notify_events();
10756         GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10757         GtkTextIter start, end;
10758         gtk_text_buffer_get_iter_at_offset(pBuffer, &start, nStartPos);
10759         gtk_text_buffer_get_iter_at_offset(pBuffer, &end, nEndPos);
10760         gtk_text_buffer_select_range(pBuffer, &start, &end);
10761         GtkTextMark* mark = gtk_text_buffer_create_mark(pBuffer, "scroll", &end, true);
10762         gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
10763         enable_notify_events();
10764     }
10765 
set_editable(bool bEditable)10766     virtual void set_editable(bool bEditable) override
10767     {
10768         gtk_text_view_set_editable(m_pTextView, bEditable);
10769     }
10770 
set_monospace(bool bMonospace)10771     virtual void set_monospace(bool bMonospace) override
10772     {
10773         gtk_text_view_set_monospace(m_pTextView, bMonospace);
10774     }
10775 
disable_notify_events()10776     virtual void disable_notify_events() override
10777     {
10778         g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
10779         g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
10780         g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
10781         GtkInstanceContainer::disable_notify_events();
10782     }
10783 
enable_notify_events()10784     virtual void enable_notify_events() override
10785     {
10786         GtkInstanceContainer::enable_notify_events();
10787         g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
10788         g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
10789         g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
10790     }
10791 
vadjustment_get_value() const10792     virtual int vadjustment_get_value() const override
10793     {
10794         return gtk_adjustment_get_value(m_pVAdjustment);
10795     }
10796 
vadjustment_set_value(int value)10797     virtual void vadjustment_set_value(int value) override
10798     {
10799         disable_notify_events();
10800         gtk_adjustment_set_value(m_pVAdjustment, value);
10801         enable_notify_events();
10802     }
10803 
vadjustment_get_upper() const10804     virtual int vadjustment_get_upper() const override
10805     {
10806          return gtk_adjustment_get_upper(m_pVAdjustment);
10807     }
10808 
vadjustment_get_lower() const10809     virtual int vadjustment_get_lower() const override
10810     {
10811          return gtk_adjustment_get_lower(m_pVAdjustment);
10812     }
10813 
vadjustment_get_page_size() const10814     virtual int vadjustment_get_page_size() const override
10815     {
10816         return gtk_adjustment_get_page_size(m_pVAdjustment);
10817     }
10818 
show()10819     virtual void show() override
10820     {
10821         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10822         if (GTK_IS_SCROLLED_WINDOW(pParent))
10823             gtk_widget_show(pParent);
10824         gtk_widget_show(m_pWidget);
10825     }
10826 
hide()10827     virtual void hide() override
10828     {
10829         GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10830         if (GTK_IS_SCROLLED_WINDOW(pParent))
10831             gtk_widget_hide(pParent);
10832         gtk_widget_hide(m_pWidget);
10833     }
10834 
~GtkInstanceTextView()10835     virtual ~GtkInstanceTextView() override
10836     {
10837         g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
10838         g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
10839         g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
10840     }
10841 };
10842 
10843 namespace
10844 {
10845     AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
10846 }
10847 
10848 class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
10849 {
10850 private:
10851     GtkDrawingArea* m_pDrawingArea;
10852     a11yref m_xAccessible;
10853     AtkObject *m_pAccessible;
10854     ScopedVclPtrInstance<VirtualDevice> m_xDevice;
10855     cairo_surface_t* m_pSurface;
10856     gulong m_nDrawSignalId;
10857     gulong m_nStyleUpdatedSignalId;
10858     gulong m_nQueryTooltip;
10859     gulong m_nPopupMenu;
10860     gulong m_nScrollEvent;
10861 
signalDraw(GtkWidget *,cairo_t * cr,gpointer widget)10862     static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
10863     {
10864         GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10865         SolarMutexGuard aGuard;
10866         pThis->signal_draw(cr);
10867         return false;
10868     }
signal_draw(cairo_t * cr)10869     void signal_draw(cairo_t* cr)
10870     {
10871         GdkRectangle rect;
10872         if (!m_pSurface || !gdk_cairo_get_clip_rectangle(cr, &rect))
10873             return;
10874         tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
10875         aRect = m_xDevice->PixelToLogic(aRect);
10876         m_xDevice->Erase(aRect);
10877         m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
10878         cairo_surface_mark_dirty(m_pSurface);
10879 
10880         cairo_set_source_surface(cr, m_pSurface, 0, 0);
10881         cairo_paint(cr);
10882 
10883         tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
10884         if (!aFocusRect.IsEmpty())
10885         {
10886             gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
10887                              aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
10888         }
10889     }
signal_size_allocate(guint nWidth,guint nHeight)10890     virtual void signal_size_allocate(guint nWidth, guint nHeight) override
10891     {
10892         m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
10893         m_pSurface = get_underlying_cairo_surface(*m_xDevice);
10894         GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
10895     }
signalStyleUpdated(GtkWidget *,gpointer widget)10896     static void signalStyleUpdated(GtkWidget*, gpointer widget)
10897     {
10898         GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10899         SolarMutexGuard aGuard;
10900         return pThis->signal_style_updated();
10901     }
signal_style_updated()10902     void signal_style_updated()
10903     {
10904         m_aStyleUpdatedHdl.Call(*this);
10905     }
signalQueryTooltip(GtkWidget * pGtkWidget,gint x,gint y,gboolean,GtkTooltip * tooltip,gpointer widget)10906     static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
10907                                          gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
10908                                          gpointer widget)
10909     {
10910         GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10911         tools::Rectangle aHelpArea(x, y);
10912         OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
10913         if (aTooltip.isEmpty())
10914             return false;
10915         gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
10916         GdkRectangle aGdkHelpArea;
10917         aGdkHelpArea.x = aHelpArea.Left();
10918         aGdkHelpArea.y = aHelpArea.Top();
10919         aGdkHelpArea.width = aHelpArea.GetWidth();
10920         aGdkHelpArea.height = aHelpArea.GetHeight();
10921         if (pThis->SwapForRTL())
10922             aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
10923         gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
10924         return true;
10925     }
signal_popup_menu(const CommandEvent & rCEvt)10926     virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
10927     {
10928         return m_aCommandHdl.Call(rCEvt);
10929     }
signal_scroll(GdkEventScroll * pEvent)10930     bool signal_scroll(GdkEventScroll* pEvent)
10931     {
10932         SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
10933 
10934         if (SwapForRTL())
10935             aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
10936 
10937         CommandWheelMode nMode;
10938         sal_uInt16 nCode = aEvt.mnCode;
10939         bool bHorz = aEvt.mbHorz;
10940         if (nCode & KEY_MOD1)
10941             nMode = CommandWheelMode::ZOOM;
10942         else if (nCode & KEY_MOD2)
10943             nMode = CommandWheelMode::DATAZOOM;
10944         else
10945         {
10946             nMode = CommandWheelMode::SCROLL;
10947             // #i85450# interpret shift-wheel as horizontal wheel action
10948             if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
10949                 bHorz = true;
10950         }
10951 
10952         CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
10953                                     nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
10954         CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
10955         return m_aCommandHdl.Call(aCEvt);
10956     }
signalScroll(GtkWidget *,GdkEventScroll * pEvent,gpointer widget)10957     static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
10958     {
10959         GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10960         return pThis->signal_scroll(pEvent);
10961     }
10962 public:
GtkInstanceDrawingArea(GtkDrawingArea * pDrawingArea,GtkInstanceBuilder * pBuilder,const a11yref & rA11y,bool bTakeOwnership)10963     GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
10964         : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
10965         , m_pDrawingArea(pDrawingArea)
10966         , m_xAccessible(rA11y)
10967         , m_pAccessible(nullptr)
10968         , m_xDevice(DeviceFormat::DEFAULT)
10969         , m_pSurface(nullptr)
10970         , m_nDrawSignalId(g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this))
10971         , m_nStyleUpdatedSignalId(g_signal_connect(m_pDrawingArea,"style-updated", G_CALLBACK(signalStyleUpdated), this))
10972         , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
10973         , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
10974         , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
10975     {
10976         gtk_widget_set_has_tooltip(m_pWidget, true);
10977         g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
10978         m_xDevice->EnableRTL(get_direction());
10979     }
10980 
GetAtkObject(AtkObject * pDefaultAccessible)10981     AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
10982     {
10983         if (!m_pAccessible && m_xAccessible.is())
10984         {
10985             GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10986             m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
10987             g_object_ref(m_pAccessible);
10988         }
10989         return m_pAccessible;
10990     }
10991 
set_direction(bool bRTL)10992     virtual void set_direction(bool bRTL) override
10993     {
10994         GtkInstanceWidget::set_direction(bRTL);
10995         m_xDevice->EnableRTL(bRTL);
10996     }
10997 
set_cursor(PointerStyle ePointerStyle)10998     virtual void set_cursor(PointerStyle ePointerStyle) override
10999     {
11000         GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
11001         if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
11002             gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
11003         gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(m_pDrawingArea)), pCursor);
11004     }
11005 
queue_draw()11006     virtual void queue_draw() override
11007     {
11008         gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
11009     }
11010 
queue_draw_area(int x,int y,int width,int height)11011     virtual void queue_draw_area(int x, int y, int width, int height) override
11012     {
11013         tools::Rectangle aRect(Point(x, y), Size(width, height));
11014         aRect = m_xDevice->LogicToPixel(aRect);
11015         gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
11016     }
11017 
queue_resize()11018     virtual void queue_resize() override
11019     {
11020         gtk_widget_queue_resize(GTK_WIDGET(m_pDrawingArea));
11021     }
11022 
get_accessible_parent()11023     virtual a11yref get_accessible_parent() override
11024     {
11025         //get_accessible_parent should only be needed for the vcl implementation,
11026         //in the gtk impl the native AtkObject parent set via
11027         //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
11028         //should negate the need.
11029         assert(false && "get_accessible_parent should only be called on a vcl impl");
11030         return uno::Reference<css::accessibility::XAccessible>();
11031     }
11032 
get_accessible_relation_set()11033     virtual a11yrelationset get_accessible_relation_set() override
11034     {
11035         //get_accessible_relation_set should only be needed for the vcl implementation,
11036         //in the gtk impl the native equivalent should negate the need.
11037         assert(false && "get_accessible_parent should only be called on a vcl impl");
11038         return uno::Reference<css::accessibility::XAccessibleRelationSet>();
11039     }
11040 
get_accessible_location()11041     virtual Point get_accessible_location() override
11042     {
11043         AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11044         gint x(0), y(0);
11045         if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
11046             atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_WINDOW);
11047         return Point(x, y);
11048     }
11049 
set_accessible_name(const OUString & rName)11050     virtual void set_accessible_name(const OUString& rName) override
11051     {
11052         AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11053         if (!pAtkObject)
11054             return;
11055         atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
11056     }
11057 
get_accessible_name() const11058     virtual OUString get_accessible_name() const override
11059     {
11060         AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11061         const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
11062         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11063     }
11064 
get_accessible_description() const11065     virtual OUString get_accessible_description() const override
11066     {
11067         AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11068         const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
11069         return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11070     }
11071 
~GtkInstanceDrawingArea()11072     virtual ~GtkInstanceDrawingArea() override
11073     {
11074         g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
11075         if (m_pAccessible)
11076             g_object_unref(m_pAccessible);
11077         css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
11078         if (xComp.is())
11079             xComp->dispose();
11080         g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
11081         g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
11082         g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
11083         g_signal_handler_disconnect(m_pDrawingArea, m_nStyleUpdatedSignalId);
11084         g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
11085     }
11086 
get_ref_device()11087     virtual OutputDevice& get_ref_device() override
11088     {
11089         return *m_xDevice;
11090     }
11091 };
11092 
11093 #define g_signal_handlers_block_by_data(instance, data) \
11094     g_signal_handlers_block_matched ((instance), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, (data))
11095 
11096 /* tdf#125388 on measuring each row, the GtkComboBox GtkTreeMenu will call
11097    its area_apply_attributes_cb function on the row, but that calls
11098    gtk_tree_menu_get_path_item which then loops through each child of the
11099    menu looking for the widget of the row, so performance drops to useless.
11100 
11101    All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
11102    with fragile hackery which assumes that the unwanted callback is the only one with a
11103    user_data of the ComboBox GtkTreeMenu */
disable_area_apply_attributes_cb(GtkWidget * pItem,gpointer userdata)11104 static void disable_area_apply_attributes_cb(GtkWidget* pItem, gpointer userdata)
11105 {
11106     GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
11107     GtkWidget* child = gtk_bin_get_child(GTK_BIN(pMenuItem));
11108     if (!child)
11109         return;
11110     GtkCellView* pCellView = GTK_CELL_VIEW(child);
11111     GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(pCellView);
11112     GtkCellArea* pCellArea = gtk_cell_layout_get_area(pCellLayout);
11113     g_signal_handlers_block_by_data(pCellArea, userdata);
11114 }
11115 
11116 class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
11117 {
11118 private:
11119     GtkComboBox* m_pComboBox;
11120     GtkTreeModel* m_pTreeModel;
11121     GtkCellRenderer* m_pTextRenderer;
11122     GtkMenu* m_pMenu;
11123     GtkWidget* m_pToggleButton;
11124     std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
11125     vcl::QuickSelectionEngine m_aQuickSelectionEngine;
11126     std::vector<int> m_aSeparatorRows;
11127     bool m_bPopupActive;
11128     bool m_bAutoComplete;
11129     bool m_bAutoCompleteCaseSensitive;
11130     gulong m_nToggleFocusInSignalId;
11131     gulong m_nToggleFocusOutSignalId;
11132     gulong m_nChangedSignalId;
11133     gulong m_nPopupShownSignalId;
11134     gulong m_nKeyPressEventSignalId;
11135     gulong m_nEntryInsertTextSignalId;
11136     gulong m_nEntryActivateSignalId;
11137     gulong m_nEntryFocusInSignalId;
11138     gulong m_nEntryFocusOutSignalId;
11139     guint m_nAutoCompleteIdleId;
11140 
idleAutoComplete(gpointer widget)11141     static gboolean idleAutoComplete(gpointer widget)
11142     {
11143         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11144         pThis->auto_complete();
11145         return false;
11146     }
11147 
auto_complete()11148     void auto_complete()
11149     {
11150         m_nAutoCompleteIdleId = 0;
11151         OUString aStartText = get_active_text();
11152         int nStartPos, nEndPos;
11153         get_entry_selection_bounds(nStartPos, nEndPos);
11154         int nMaxSelection = std::max(nStartPos, nEndPos);
11155         if (nMaxSelection != aStartText.getLength())
11156             return;
11157 
11158         disable_notify_events();
11159         int nActive = get_active();
11160         int nStart = nActive;
11161 
11162         if (nStart == -1)
11163             nStart = 0;
11164 
11165         int nPos = -1;
11166 
11167         if (!m_bAutoCompleteCaseSensitive)
11168         {
11169             // Try match case insensitive from current position
11170             nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
11171             if (nPos == -1 && nStart != 0)
11172             {
11173                 // Try match case insensitive, but from start
11174                 nPos = starts_with(m_pTreeModel, aStartText, 0, 0, false);
11175             }
11176         }
11177 
11178         if (nPos == -1)
11179         {
11180             // Try match case sensitive from current position
11181             nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
11182             if (nPos == -1 && nStart != 0)
11183             {
11184                 // Try match case sensitive, but from start
11185                 nPos = starts_with(m_pTreeModel, aStartText, 0, 0, true);
11186             }
11187         }
11188 
11189         if (nPos != -1)
11190         {
11191             OUString aText = get_text(nPos);
11192             if (aText != aStartText)
11193                 set_active_text(aText);
11194             select_entry_region(aText.getLength(), aStartText.getLength());
11195         }
11196         enable_notify_events();
11197     }
11198 
signalEntryInsertText(GtkEntry * pEntry,const gchar * pNewText,gint nNewTextLength,gint * position,gpointer widget)11199     static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
11200                                       gint* position, gpointer widget)
11201     {
11202         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11203         SolarMutexGuard aGuard;
11204         pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
11205     }
11206 
signal_entry_insert_text(GtkEntry * pEntry,const gchar * pNewText,gint nNewTextLength,gint * position)11207     void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
11208     {
11209         // first filter inserted text
11210         if (m_aEntryInsertTextHdl.IsSet())
11211         {
11212             OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
11213             const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
11214             if (bContinue && !sText.isEmpty())
11215             {
11216                 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
11217                 g_signal_handlers_block_by_func(pEntry, gpointer(signalEntryInsertText), this);
11218                 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
11219                 g_signal_handlers_unblock_by_func(pEntry, gpointer(signalEntryInsertText), this);
11220             }
11221             g_signal_stop_emission_by_name(pEntry, "insert-text");
11222         }
11223         if (m_bAutoComplete)
11224         {
11225             // now check for autocompletes
11226             if (m_nAutoCompleteIdleId)
11227                 g_source_remove(m_nAutoCompleteIdleId);
11228             m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
11229         }
11230     }
11231 
signalChanged(GtkComboBox *,gpointer widget)11232     static void signalChanged(GtkComboBox*, gpointer widget)
11233     {
11234         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11235         SolarMutexGuard aGuard;
11236         pThis->signal_changed();
11237     }
11238 
signalPopupToggled(GtkComboBox *,GParamSpec *,gpointer widget)11239     static void signalPopupToggled(GtkComboBox*, GParamSpec*, gpointer widget)
11240     {
11241         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11242         pThis->signal_popup_toggled();
11243     }
11244 
signal_popup_toggled()11245     virtual void signal_popup_toggled() override
11246     {
11247         m_aQuickSelectionEngine.Reset();
11248         gboolean bIsShown(false);
11249         g_object_get(m_pComboBox, "popup-shown", &bIsShown, nullptr);
11250         if (m_bPopupActive != bool(bIsShown))
11251         {
11252             m_bPopupActive = bIsShown;
11253             ComboBox::signal_popup_toggled();
11254             //restore focus to the entry view when the popup is gone, which
11255             //is what the vcl case does, to ease the transition a little
11256             gtk_widget_grab_focus(m_pWidget);
11257         }
11258     }
11259 
signalEntryFocusIn(GtkWidget *,GdkEvent *,gpointer widget)11260     static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
11261     {
11262         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11263         pThis->signal_entry_focus_in();
11264         return false;
11265     }
11266 
signal_entry_focus_in()11267     void signal_entry_focus_in()
11268     {
11269         signal_focus_in();
11270     }
11271 
signalEntryFocusOut(GtkWidget *,GdkEvent *,gpointer widget)11272     static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
11273     {
11274         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11275         pThis->signal_entry_focus_out();
11276         return false;
11277     }
11278 
signal_entry_focus_out()11279     void signal_entry_focus_out()
11280     {
11281         // if we have an untidy selection on losing focus remove the selection
11282         int nStartPos, nEndPos;
11283         if (get_entry_selection_bounds(nStartPos, nEndPos))
11284         {
11285             int nMin = std::min(nStartPos, nEndPos);
11286             int nMax = std::max(nStartPos, nEndPos);
11287             if (nMin != 0 || nMax != get_active_text().getLength())
11288                 select_entry_region(0, 0);
11289         }
11290         signal_focus_out();
11291     }
11292 
signalEntryActivate(GtkEntry *,gpointer widget)11293     static void signalEntryActivate(GtkEntry*, gpointer widget)
11294     {
11295         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11296         pThis->signal_entry_activate();
11297     }
11298 
signal_entry_activate()11299     void signal_entry_activate()
11300     {
11301         if (m_aEntryActivateHdl.IsSet())
11302         {
11303             SolarMutexGuard aGuard;
11304             if (m_aEntryActivateHdl.Call(*this))
11305                 g_signal_stop_emission_by_name(get_entry(), "activate");
11306         }
11307     }
11308 
get(int pos,int col) const11309     OUString get(int pos, int col) const
11310     {
11311         OUString sRet;
11312         GtkTreeIter iter;
11313         if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
11314         {
11315             gchar* pStr;
11316             gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
11317             sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11318             g_free(pStr);
11319         }
11320         return sRet;
11321     }
11322 
set(int pos,int col,const OUString & rText)11323     void set(int pos, int col, const OUString& rText)
11324     {
11325         GtkTreeIter iter;
11326         if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
11327         {
11328             OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
11329             gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
11330         }
11331     }
11332 
find(const OUString & rStr,int col) const11333     int find(const OUString& rStr, int col) const
11334     {
11335         GtkTreeIter iter;
11336         if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
11337             return -1;
11338 
11339         OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
11340         int nRet = 0;
11341         do
11342         {
11343             gchar* pStr;
11344             gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
11345             const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
11346             g_free(pStr);
11347             if (bEqual)
11348                 return nRet;
11349             ++nRet;
11350         } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
11351 
11352         return -1;
11353     }
11354 
get_entry()11355     GtkEntry* get_entry()
11356     {
11357         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11358         if (!GTK_IS_ENTRY(pChild))
11359             return nullptr;
11360         return GTK_ENTRY(pChild);
11361     }
11362 
separator_function(int nIndex)11363     bool separator_function(int nIndex)
11364     {
11365         return std::find(m_aSeparatorRows.begin(), m_aSeparatorRows.end(), nIndex) != m_aSeparatorRows.end();
11366     }
11367 
separatorFunction(GtkTreeModel * pTreeModel,GtkTreeIter * pIter,gpointer widget)11368     static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
11369     {
11370         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11371         GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
11372 
11373         gint depth;
11374         gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
11375         int nIndex = indices[depth-1];
11376 
11377         gtk_tree_path_free(path);
11378         return pThis->separator_function(nIndex);
11379     }
11380 
11381     // https://gitlab.gnome.org/GNOME/gtk/issues/310
11382     //
11383     // in the absence of a built-in solution
11384     // a) support typeahead for the case where there is no entry widget, typing ahead
11385     // into the button itself will select via the vcl selection engine, a matching
11386     // entry
signalKeyPress(GtkWidget *,GdkEventKey * pEvent,gpointer widget)11387     static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
11388     {
11389         GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11390         return pThis->signal_key_press(pEvent);
11391     }
11392 
11393     // tdf#131076 we want return in a GtkComboBox to act like return in a
11394     // GtkEntry and activate the default dialog/assistant button
combobox_activate()11395     bool combobox_activate()
11396     {
11397         GtkWidget *pComboBox = GTK_WIDGET(m_pComboBox);
11398         GtkWidget *pToplevel = gtk_widget_get_toplevel(pComboBox);
11399         GtkWindow *pWindow = GTK_WINDOW(pToplevel);
11400         if (!pWindow)
11401             return false;
11402         if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
11403             return false;
11404         bool bDone = false;
11405         GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
11406         if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget))
11407             bDone = gtk_widget_activate(pDefaultWidget);
11408         return bDone;
11409     }
11410 
signal_key_press(const GdkEventKey * pEvent)11411     bool signal_key_press(const GdkEventKey* pEvent)
11412     {
11413         KeyEvent aKEvt(GtkToVcl(*pEvent));
11414 
11415         vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
11416 
11417         bool bDone = false;
11418 
11419         auto nCode = aKeyCode.GetCode();
11420         switch (nCode)
11421         {
11422             case KEY_DOWN:
11423             case KEY_UP:
11424             case KEY_PAGEUP:
11425             case KEY_PAGEDOWN:
11426             case KEY_HOME:
11427             case KEY_END:
11428             case KEY_LEFT:
11429             case KEY_RIGHT:
11430             case KEY_RETURN:
11431                 m_aQuickSelectionEngine.Reset();
11432                 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactive
11433                 if (nCode == KEY_RETURN && !aKeyCode.GetModifier() && !m_bPopupActive)
11434                     bDone = combobox_activate();
11435                 break;
11436             default:
11437                 // tdf#131076 let base space toggle menu popup when its not already visible
11438                 if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
11439                     bDone = false;
11440                 else
11441                     bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
11442                 break;
11443         }
11444 
11445         return bDone;
11446     }
11447 
typeahead_getEntry(int nPos,OUString & out_entryText) const11448     vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
11449     {
11450         int nEntryCount(get_count());
11451         if (nPos >= nEntryCount)
11452             nPos = 0;
11453         out_entryText = get_text(nPos);
11454 
11455         // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
11456         // => normalize
11457         return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
11458     }
11459 
typeahead_getEntryPos(vcl::StringEntryIdentifier entry)11460     static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
11461     {
11462         // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
11463         return reinterpret_cast<sal_Int64>(entry) - 1;
11464     }
11465 
get_selected_entry() const11466     int get_selected_entry() const
11467     {
11468         if (m_bPopupActive && m_pMenu)
11469         {
11470             GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
11471             auto nRet = g_list_index(pChildren, gtk_menu_shell_get_selected_item(GTK_MENU_SHELL(m_pMenu)));
11472             g_list_free(pChildren);
11473             return nRet;
11474         }
11475         else
11476             return get_active();
11477     }
11478 
set_selected_entry(int nSelect)11479     void set_selected_entry(int nSelect)
11480     {
11481         if (m_bPopupActive && m_pMenu)
11482         {
11483             GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
11484             gtk_menu_shell_select_item(GTK_MENU_SHELL(m_pMenu), GTK_WIDGET(g_list_nth_data(pChildren, nSelect)));
11485             g_list_free(pChildren);
11486         }
11487         else
11488             set_active(nSelect);
11489     }
11490 
CurrentEntry(OUString & out_entryText) const11491     virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
11492     {
11493         int nCurrentPos = get_selected_entry();
11494         return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
11495     }
11496 
NextEntry(vcl::StringEntryIdentifier currentEntry,OUString & out_entryText) const11497     virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
11498     {
11499         int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
11500         return typeahead_getEntry(nNextPos, out_entryText);
11501     }
11502 
SelectEntry(vcl::StringEntryIdentifier entry)11503     virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
11504     {
11505         int nSelect = typeahead_getEntryPos(entry);
11506         if (nSelect == get_selected_entry())
11507         {
11508             // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
11509             // to select the given entry by typing its starting letters. No need to act.
11510             return;
11511         }
11512 
11513         // normalize
11514         int nCount = get_count();
11515         if (nSelect >= nCount)
11516             nSelect = nCount ? nCount-1 : -1;
11517 
11518         set_selected_entry(nSelect);
11519     }
11520 
11521     // https://gitlab.gnome.org/GNOME/gtk/issues/310
11522     //
11523     // in the absence of a built-in solution
11524     // b) support typeahead for the menu itself, typing into the menu will
11525     // select via the vcl selection engine, a matching entry. Clearly
11526     // this is cheating, brittle and not a long term solution.
install_menu_typeahead()11527     void install_menu_typeahead()
11528     {
11529         AtkObject* pAtkObj = gtk_combo_box_get_popup_accessible(m_pComboBox);
11530         if (!pAtkObj)
11531             return;
11532         if (!GTK_IS_ACCESSIBLE(pAtkObj))
11533             return;
11534         GtkWidget* pWidget = gtk_accessible_get_widget(GTK_ACCESSIBLE(pAtkObj));
11535         if (!pWidget)
11536             return;
11537         if (!GTK_IS_MENU(pWidget))
11538             return;
11539         m_pMenu = GTK_MENU(pWidget);
11540 
11541         guint nSignalId = g_signal_lookup("key-press-event", GTK_TYPE_MENU);
11542         gulong nOriginalMenuKeyPressEventId = g_signal_handler_find(m_pMenu, G_SIGNAL_MATCH_DATA, nSignalId, 0,
11543                                                                     nullptr, nullptr, m_pComboBox);
11544 
11545         g_signal_handler_block(m_pMenu, nOriginalMenuKeyPressEventId);
11546         g_signal_connect(m_pMenu, "key-press-event", G_CALLBACK(signalKeyPress), this);
11547     }
11548 
find_toggle_button(GtkWidget * pWidget,gpointer user_data)11549     static void find_toggle_button(GtkWidget *pWidget, gpointer user_data)
11550     {
11551         if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkToggleButton") == 0)
11552         {
11553             GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
11554             *ppToggleButton = pWidget;
11555         }
11556         else if (GTK_IS_CONTAINER(pWidget))
11557             gtk_container_forall(GTK_CONTAINER(pWidget), find_toggle_button, user_data);
11558     }
11559 
11560 public:
GtkInstanceComboBox(GtkComboBox * pComboBox,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)11561     GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
11562         : GtkInstanceContainer(GTK_CONTAINER(pComboBox), pBuilder, bTakeOwnership)
11563         , m_pComboBox(pComboBox)
11564         , m_pTreeModel(gtk_combo_box_get_model(m_pComboBox))
11565         , m_pMenu(nullptr)
11566         , m_pToggleButton(nullptr)
11567         , m_aQuickSelectionEngine(*this)
11568         , m_bPopupActive(false)
11569         , m_bAutoComplete(false)
11570         , m_bAutoCompleteCaseSensitive(false)
11571         , m_nToggleFocusInSignalId(0)
11572         , m_nToggleFocusOutSignalId(0)
11573         , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this))
11574         , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this))
11575         , m_nAutoCompleteIdleId(0)
11576     {
11577         GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
11578         if (!g_list_length(cells))
11579         {
11580             //Always use the same text column renderer layout
11581             m_pTextRenderer = gtk_cell_renderer_text_new();
11582             gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, true);
11583             gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, "text", 0, nullptr);
11584         }
11585         else
11586         {
11587             m_pTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
11588             if (g_list_length(cells) == 2)
11589             {
11590                 //The ComboBox is always going to show the column associated with
11591                 //the entry when there is one, left to its own devices this image
11592                 //column will be after it, but we want it before
11593                 gtk_cell_layout_reorder(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, 1);
11594             }
11595         }
11596         g_list_free(cells);
11597 
11598         if (GtkEntry* pEntry = get_entry())
11599         {
11600             m_bAutoComplete = true;
11601             m_nEntryInsertTextSignalId = g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
11602             m_nEntryActivateSignalId = g_signal_connect(pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
11603             m_nEntryFocusInSignalId = g_signal_connect(pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
11604             m_nEntryFocusOutSignalId = g_signal_connect(pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
11605             m_nKeyPressEventSignalId = 0;
11606         }
11607         else
11608         {
11609             m_nEntryInsertTextSignalId = 0;
11610             m_nEntryActivateSignalId = 0;
11611             m_nEntryFocusInSignalId = 0;
11612             m_nEntryFocusOutSignalId = 0;
11613             m_nKeyPressEventSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
11614         }
11615 
11616         find_toggle_button(GTK_WIDGET(m_pComboBox), &m_pToggleButton);
11617 
11618         install_menu_typeahead();
11619     }
11620 
get_active() const11621     virtual int get_active() const override
11622     {
11623         return gtk_combo_box_get_active(m_pComboBox);
11624     }
11625 
get_active_id() const11626     virtual OUString get_active_id() const override
11627     {
11628         const gchar* pText = gtk_combo_box_get_active_id(m_pComboBox);
11629         return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
11630     }
11631 
set_active_id(const OUString & rStr)11632     virtual void set_active_id(const OUString& rStr) override
11633     {
11634         disable_notify_events();
11635         OString aId(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
11636         gtk_combo_box_set_active_id(m_pComboBox, aId.getStr());
11637         enable_notify_events();
11638     }
11639 
set_size_request(int nWidth,int nHeight)11640     virtual void set_size_request(int nWidth, int nHeight) override
11641     {
11642         // tweak the cell render to get a narrower size to stick
11643         GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
11644         GtkCellRenderer* cell = static_cast<GtkCellRenderer*>(cells->data);
11645 
11646         if (nWidth != -1)
11647         {
11648             // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
11649             // the popup menu render them in full, in the interim ellipse both of them
11650             g_object_set(G_OBJECT(m_pTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
11651 
11652             // to find out how much of the width of the combobox belongs to the cell, set
11653             // the cell and widget to the min cell width and see what the difference is
11654             int min;
11655             gtk_cell_renderer_get_preferred_width(cell, m_pWidget, &min, nullptr);
11656             gtk_cell_renderer_set_fixed_size(cell, min, -1);
11657             gtk_widget_set_size_request(m_pWidget, min, -1);
11658             int nNonCellWidth = get_preferred_size().Width() - min;
11659 
11660             int nCellWidth = nWidth - nNonCellWidth;
11661             if (nCellWidth >= 0)
11662             {
11663                 // now set the cell to the max width which it can be within the
11664                 // requested widget width
11665                 gtk_cell_renderer_set_fixed_size(cell, nWidth - nNonCellWidth, -1);
11666             }
11667         }
11668         else
11669         {
11670             g_object_set(G_OBJECT(m_pTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
11671             gtk_cell_renderer_set_fixed_size(cell, -1, -1);
11672         }
11673 
11674         g_list_free(cells);
11675 
11676         gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
11677     }
11678 
set_active(int pos)11679     virtual void set_active(int pos) override
11680     {
11681         disable_notify_events();
11682         gtk_combo_box_set_active(m_pComboBox, pos);
11683         enable_notify_events();
11684     }
11685 
get_active_text() const11686     virtual OUString get_active_text() const override
11687     {
11688         if (gtk_combo_box_get_has_entry(m_pComboBox))
11689         {
11690             GtkWidget *pEntry = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11691             const gchar* pText = gtk_entry_get_text(GTK_ENTRY(pEntry));
11692             return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
11693         }
11694 
11695         GtkTreeIter iter;
11696         if (!gtk_combo_box_get_active_iter(m_pComboBox, &iter))
11697             return OUString();
11698 
11699         gint col = gtk_combo_box_get_entry_text_column(m_pComboBox);
11700         gchar* pStr = nullptr;
11701         gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
11702         OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11703         g_free(pStr);
11704 
11705         return sRet;
11706     }
11707 
get_text(int pos) const11708     virtual OUString get_text(int pos) const override
11709     {
11710         return get(pos, 0);
11711     }
11712 
get_id(int pos) const11713     virtual OUString get_id(int pos) const override
11714     {
11715         gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
11716         return get(pos, id_column);
11717     }
11718 
set_id(int pos,const OUString & rId)11719     virtual void set_id(int pos, const OUString& rId) override
11720     {
11721         gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
11722         set(pos, id_column, rId);
11723     }
11724 
11725     // https://gitlab.gnome.org/GNOME/gtk/issues/94
11726     // when a super tall combobox menu is activated, and the selected entry is sufficiently
11727     // far down the list, then the menu doesn't appear under wayland
bodge_wayland_menu_not_appearing()11728     void bodge_wayland_menu_not_appearing()
11729     {
11730         if (get_frozen())
11731             return;
11732         if (has_entry())
11733             return;
11734 #if defined(GDK_WINDOWING_WAYLAND)
11735         GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
11736         if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
11737         {
11738             gtk_combo_box_set_wrap_width(m_pComboBox, get_count() > 30 ? 1 : 0);
11739         }
11740 #endif
11741     }
11742 
11743     // https://gitlab.gnome.org/GNOME/gtk/issues/1910
11744     // has_entry long menus take forever to appear (tdf#125388)
bodge_area_apply_attributes_cb()11745     void bodge_area_apply_attributes_cb()
11746     {
11747         gtk_container_foreach(GTK_CONTAINER(m_pMenu), disable_area_apply_attributes_cb, m_pMenu);
11748     }
11749 
insert_vector(const std::vector<weld::ComboBoxEntry> & rItems,bool bKeepExisting)11750     virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
11751     {
11752         freeze();
11753         if (!bKeepExisting)
11754             clear();
11755         GtkTreeIter iter;
11756         for (const auto& rItem : rItems)
11757         {
11758             insert_row(GTK_LIST_STORE(m_pTreeModel), iter, -1, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
11759                        rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
11760         }
11761         thaw();
11762     }
11763 
remove(int pos)11764     virtual void remove(int pos) override
11765     {
11766         disable_notify_events();
11767         GtkTreeIter iter;
11768         gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
11769         gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
11770         m_aSeparatorRows.erase(std::remove(m_aSeparatorRows.begin(), m_aSeparatorRows.end(), pos), m_aSeparatorRows.end());
11771         enable_notify_events();
11772         bodge_wayland_menu_not_appearing();
11773     }
11774 
insert(int pos,const OUString & rText,const OUString * pId,const OUString * pIconName,VirtualDevice * pImageSurface)11775     virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
11776     {
11777         disable_notify_events();
11778         GtkTreeIter iter;
11779         insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
11780         enable_notify_events();
11781         bodge_wayland_menu_not_appearing();
11782     }
11783 
insert_separator(int pos,const OUString & rId)11784     virtual void insert_separator(int pos, const OUString& rId) override
11785     {
11786         disable_notify_events();
11787         GtkTreeIter iter;
11788         pos = pos == -1 ? get_count() : pos;
11789         m_aSeparatorRows.push_back(pos);
11790         if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
11791             gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
11792         insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, "", nullptr, nullptr);
11793         enable_notify_events();
11794         bodge_wayland_menu_not_appearing();
11795     }
11796 
get_count() const11797     virtual int get_count() const override
11798     {
11799         return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
11800     }
11801 
find_text(const OUString & rStr) const11802     virtual int find_text(const OUString& rStr) const override
11803     {
11804         return find(rStr, 0);
11805     }
11806 
find_id(const OUString & rId) const11807     virtual int find_id(const OUString& rId) const override
11808     {
11809         gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
11810         return find(rId, id_column);
11811     }
11812 
clear()11813     virtual void clear() override
11814     {
11815         disable_notify_events();
11816         gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
11817         m_aSeparatorRows.clear();
11818         gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
11819         enable_notify_events();
11820         bodge_wayland_menu_not_appearing();
11821     }
11822 
make_sorted()11823     virtual void make_sorted() override
11824     {
11825         m_xSorter.reset(new comphelper::string::NaturalStringSorter(
11826                             ::comphelper::getProcessComponentContext(),
11827                             Application::GetSettings().GetUILanguageTag().getLocale()));
11828         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11829         gtk_tree_sortable_set_sort_column_id(pSortable, 0, GTK_SORT_ASCENDING);
11830         gtk_tree_sortable_set_sort_func(pSortable, 0, default_sort_func, m_xSorter.get(), nullptr);
11831     }
11832 
has_entry() const11833     virtual bool has_entry() const override
11834     {
11835         return gtk_combo_box_get_has_entry(m_pComboBox);
11836     }
11837 
set_entry_message_type(weld::EntryMessageType eType)11838     virtual void set_entry_message_type(weld::EntryMessageType eType) override
11839     {
11840         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11841         assert(GTK_IS_ENTRY(pChild));
11842         GtkEntry* pEntry = GTK_ENTRY(pChild);
11843         ::set_entry_message_type(pEntry, eType);
11844     }
11845 
set_entry_text(const OUString & rText)11846     virtual void set_entry_text(const OUString& rText) override
11847     {
11848         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11849         assert(pChild && GTK_IS_ENTRY(pChild));
11850         GtkEntry* pEntry = GTK_ENTRY(pChild);
11851         disable_notify_events();
11852         gtk_entry_set_text(pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
11853         enable_notify_events();
11854     }
11855 
set_entry_width_chars(int nChars)11856     virtual void set_entry_width_chars(int nChars) override
11857     {
11858         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11859         assert(pChild && GTK_IS_ENTRY(pChild));
11860         GtkEntry* pEntry = GTK_ENTRY(pChild);
11861         disable_notify_events();
11862         gtk_entry_set_width_chars(pEntry, nChars);
11863         gtk_entry_set_max_width_chars(pEntry, nChars);
11864         enable_notify_events();
11865     }
11866 
set_entry_max_length(int nChars)11867     virtual void set_entry_max_length(int nChars) override
11868     {
11869         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11870         assert(pChild && GTK_IS_ENTRY(pChild));
11871         GtkEntry* pEntry = GTK_ENTRY(pChild);
11872         disable_notify_events();
11873         gtk_entry_set_max_length(pEntry, nChars);
11874         enable_notify_events();
11875     }
11876 
select_entry_region(int nStartPos,int nEndPos)11877     virtual void select_entry_region(int nStartPos, int nEndPos) override
11878     {
11879         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11880         assert(pChild && GTK_IS_ENTRY(pChild));
11881         GtkEntry* pEntry = GTK_ENTRY(pChild);
11882         disable_notify_events();
11883         gtk_editable_select_region(GTK_EDITABLE(pEntry), nStartPos, nEndPos);
11884         enable_notify_events();
11885     }
11886 
get_entry_selection_bounds(int & rStartPos,int & rEndPos)11887     virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
11888     {
11889         GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11890         assert(pChild && GTK_IS_ENTRY(pChild));
11891         GtkEntry* pEntry = GTK_ENTRY(pChild);
11892         return gtk_editable_get_selection_bounds(GTK_EDITABLE(pEntry), &rStartPos, &rEndPos);
11893     }
11894 
set_entry_completion(bool bEnable,bool bCaseSensitive)11895     virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
11896     {
11897         m_bAutoComplete = bEnable;
11898         m_bAutoCompleteCaseSensitive = bCaseSensitive;
11899     }
11900 
disable_notify_events()11901     virtual void disable_notify_events() override
11902     {
11903         if (GtkEntry* pEntry = get_entry())
11904         {
11905             g_signal_handler_block(pEntry, m_nEntryInsertTextSignalId);
11906             g_signal_handler_block(pEntry, m_nEntryActivateSignalId);
11907             g_signal_handler_block(pEntry, m_nEntryFocusInSignalId);
11908             g_signal_handler_block(pEntry, m_nEntryFocusOutSignalId);
11909         }
11910         else
11911             g_signal_handler_block(m_pComboBox, m_nKeyPressEventSignalId);
11912         if (m_nToggleFocusInSignalId)
11913             g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
11914         if (m_nToggleFocusOutSignalId)
11915             g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
11916         g_signal_handler_block(m_pComboBox, m_nChangedSignalId);
11917         g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
11918         GtkInstanceContainer::disable_notify_events();
11919     }
11920 
enable_notify_events()11921     virtual void enable_notify_events() override
11922     {
11923         GtkInstanceContainer::enable_notify_events();
11924         g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId);
11925         g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
11926         if (m_nToggleFocusInSignalId)
11927             g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
11928         if (m_nToggleFocusOutSignalId)
11929             g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
11930         if (GtkEntry* pEntry = get_entry())
11931         {
11932             g_signal_handler_unblock(pEntry, m_nEntryActivateSignalId);
11933             g_signal_handler_unblock(pEntry, m_nEntryFocusInSignalId);
11934             g_signal_handler_unblock(pEntry, m_nEntryFocusOutSignalId);
11935             g_signal_handler_unblock(pEntry, m_nEntryInsertTextSignalId);
11936         }
11937         else
11938             g_signal_handler_unblock(m_pComboBox, m_nKeyPressEventSignalId);
11939     }
11940 
freeze()11941     virtual void freeze() override
11942     {
11943         disable_notify_events();
11944         g_object_ref(m_pTreeModel);
11945         GtkInstanceContainer::freeze();
11946         gtk_combo_box_set_model(m_pComboBox, nullptr);
11947         if (m_xSorter)
11948         {
11949             GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11950             gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
11951         }
11952         enable_notify_events();
11953     }
11954 
thaw()11955     virtual void thaw() override
11956     {
11957         disable_notify_events();
11958         if (m_xSorter)
11959         {
11960             GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11961             gtk_tree_sortable_set_sort_column_id(pSortable, 0, GTK_SORT_ASCENDING);
11962         }
11963         gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
11964         GtkInstanceContainer::thaw();
11965         g_object_unref(m_pTreeModel);
11966         enable_notify_events();
11967 
11968         bodge_wayland_menu_not_appearing();
11969         bodge_area_apply_attributes_cb();
11970     }
11971 
get_popup_shown() const11972     virtual bool get_popup_shown() const override
11973     {
11974         return m_bPopupActive;
11975     }
11976 
connect_focus_in(const Link<Widget &,void> & rLink)11977     virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
11978     {
11979         if (!m_nToggleFocusInSignalId)
11980             m_nToggleFocusInSignalId = g_signal_connect(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
11981         weld::Widget::connect_focus_in(rLink);
11982     }
11983 
connect_focus_out(const Link<Widget &,void> & rLink)11984     virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
11985     {
11986         if (!m_nToggleFocusOutSignalId)
11987             m_nToggleFocusOutSignalId = g_signal_connect(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
11988         weld::Widget::connect_focus_out(rLink);
11989     }
11990 
has_focus() const11991     virtual bool has_focus() const override
11992     {
11993         return gtk_widget_has_focus(m_pToggleButton) || GtkInstanceWidget::has_focus();
11994     }
11995 
~GtkInstanceComboBox()11996     virtual ~GtkInstanceComboBox() override
11997     {
11998         if (m_nAutoCompleteIdleId)
11999             g_source_remove(m_nAutoCompleteIdleId);
12000         if (GtkEntry* pEntry = get_entry())
12001         {
12002             g_signal_handler_disconnect(pEntry, m_nEntryInsertTextSignalId);
12003             g_signal_handler_disconnect(pEntry, m_nEntryActivateSignalId);
12004             g_signal_handler_disconnect(pEntry, m_nEntryFocusInSignalId);
12005             g_signal_handler_disconnect(pEntry, m_nEntryFocusOutSignalId);
12006         }
12007         else
12008             g_signal_handler_disconnect(m_pComboBox, m_nKeyPressEventSignalId);
12009         if (m_nToggleFocusInSignalId)
12010             g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
12011         if (m_nToggleFocusOutSignalId)
12012             g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
12013         g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId);
12014         g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
12015     }
12016 };
12017 
12018 class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
12019 {
12020 private:
12021     GtkInstanceEntry* m_pEntry;
12022     GtkInstanceTreeView* m_pTreeView;
12023     gulong m_nKeyPressSignalId;
12024     gulong m_nEntryInsertTextSignalId;
12025     guint m_nAutoCompleteIdleId;
12026     bool m_bAutoCompleteCaseSensitive;
12027 
signal_key_press(GdkEventKey * pEvent)12028     bool signal_key_press(GdkEventKey* pEvent)
12029     {
12030         if (GtkSalFrame::GetMouseModCode(pEvent->state)) // only with no modifiers held
12031             return false;
12032 
12033         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 ||
12034             pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
12035         {
12036             gboolean ret;
12037             disable_notify_events();
12038             GtkWidget* pWidget = m_pTreeView->getWidget();
12039             if (m_pTreeView->get_selected_index() == -1)
12040             {
12041                 m_pTreeView->set_cursor(0);
12042                 m_pTreeView->select(0);
12043                 m_xEntry->set_text(m_xTreeView->get_selected_text());
12044             }
12045             else
12046             {
12047                 gtk_widget_grab_focus(pWidget);
12048                 g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
12049                 m_xEntry->set_text(m_xTreeView->get_selected_text());
12050                 gtk_widget_grab_focus(m_pEntry->getWidget());
12051             }
12052             m_xEntry->select_region(0, -1);
12053             enable_notify_events();
12054             m_pEntry->fire_signal_changed();
12055             return true;
12056         }
12057         return false;
12058     }
12059 
signalKeyPress(GtkWidget *,GdkEventKey * pEvent,gpointer widget)12060     static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
12061     {
12062         GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
12063         return pThis->signal_key_press(pEvent);
12064     }
12065 
idleAutoComplete(gpointer widget)12066     static gboolean idleAutoComplete(gpointer widget)
12067     {
12068         GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
12069         pThis->auto_complete();
12070         return false;
12071     }
12072 
auto_complete()12073     void auto_complete()
12074     {
12075         m_nAutoCompleteIdleId = 0;
12076         OUString aStartText = get_active_text();
12077         int nStartPos, nEndPos;
12078         get_entry_selection_bounds(nStartPos, nEndPos);
12079         int nMaxSelection = std::max(nStartPos, nEndPos);
12080         if (nMaxSelection != aStartText.getLength())
12081             return;
12082 
12083         disable_notify_events();
12084         int nActive = get_active();
12085         int nStart = nActive;
12086 
12087         if (nStart == -1)
12088             nStart = 0;
12089 
12090         // Try match case insensitive from current position
12091         int nPos = m_pTreeView->starts_with(aStartText, 0, nStart, true);
12092         if (nPos == -1 && nStart != 0)
12093         {
12094             // Try match case insensitive, but from start
12095             nPos = m_pTreeView->starts_with(aStartText, 0, 0, true);
12096         }
12097 
12098         if (!m_bAutoCompleteCaseSensitive)
12099         {
12100             // Try match case insensitive from current position
12101             nPos = m_pTreeView->starts_with(aStartText, 0, nStart, false);
12102             if (nPos == -1 && nStart != 0)
12103             {
12104                 // Try match case insensitive, but from start
12105                 nPos = m_pTreeView->starts_with(aStartText, 0, 0, false);
12106             }
12107         }
12108 
12109         if (nPos == -1)
12110         {
12111             // Try match case sensitive from current position
12112             nPos = m_pTreeView->starts_with(aStartText, 0, nStart, true);
12113             if (nPos == -1 && nStart != 0)
12114             {
12115                 // Try match case sensitive, but from start
12116                 nPos = m_pTreeView->starts_with(aStartText, 0, 0, true);
12117             }
12118         }
12119 
12120         if (nPos != -1)
12121         {
12122             OUString aText = get_text(nPos);
12123             if (aText != aStartText)
12124                 set_active_text(aText);
12125             select_entry_region(aText.getLength(), aStartText.getLength());
12126         }
12127         enable_notify_events();
12128     }
12129 
signal_entry_insert_text(GtkEntry *,const gchar *,gint,gint *)12130     void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
12131     {
12132         // now check for autocompletes
12133         if (m_nAutoCompleteIdleId)
12134             g_source_remove(m_nAutoCompleteIdleId);
12135         m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
12136     }
12137 
signalEntryInsertText(GtkEntry * pEntry,const gchar * pNewText,gint nNewTextLength,gint * position,gpointer widget)12138     static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
12139                                       gint* position, gpointer widget)
12140     {
12141         GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
12142         pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
12143     }
12144 
12145 
12146 public:
GtkInstanceEntryTreeView(GtkContainer * pContainer,GtkInstanceBuilder * pBuilder,bool bTakeOwnership,std::unique_ptr<weld::Entry> xEntry,std::unique_ptr<weld::TreeView> xTreeView)12147     GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
12148                              std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
12149         : EntryTreeView(std::move(xEntry), std::move(xTreeView))
12150         , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
12151         , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
12152         , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
12153         , m_nAutoCompleteIdleId(0)
12154         , m_bAutoCompleteCaseSensitive(false)
12155     {
12156         assert(m_pEntry);
12157         GtkWidget* pWidget = m_pEntry->getWidget();
12158         m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
12159         m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
12160     }
12161 
insert_separator(int,const OUString &)12162     virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
12163     {
12164         assert(false);
12165     }
12166 
make_sorted()12167     virtual void make_sorted() override
12168     {
12169         GtkWidget* pTreeView = m_pTreeView->getWidget();
12170         GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
12171         GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
12172         gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
12173     }
12174 
set_entry_completion(bool bEnable,bool bCaseSensitive)12175     virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
12176     {
12177         assert(!bEnable && "not implemented yet"); (void)bEnable;
12178         m_bAutoCompleteCaseSensitive = bCaseSensitive;
12179     }
12180 
grab_focus()12181     virtual void grab_focus() override { m_xEntry->grab_focus(); }
12182 
connect_focus_in(const Link<Widget &,void> & rLink)12183     virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
12184     {
12185         m_xEntry->connect_focus_in(rLink);
12186     }
12187 
connect_focus_out(const Link<Widget &,void> & rLink)12188     virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
12189     {
12190         m_xEntry->connect_focus_out(rLink);
12191     }
12192 
disable_notify_events()12193     virtual void disable_notify_events() override
12194     {
12195         GtkWidget* pWidget = m_pEntry->getWidget();
12196         g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
12197         g_signal_handler_block(pWidget, m_nKeyPressSignalId);
12198         m_pTreeView->disable_notify_events();
12199         GtkInstanceContainer::disable_notify_events();
12200     }
12201 
enable_notify_events()12202     virtual void enable_notify_events() override
12203     {
12204         GtkWidget* pWidget = m_pEntry->getWidget();
12205         g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
12206         g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
12207         m_pTreeView->enable_notify_events();
12208         GtkInstanceContainer::disable_notify_events();
12209     }
12210 
~GtkInstanceEntryTreeView()12211     virtual ~GtkInstanceEntryTreeView() override
12212     {
12213         if (m_nAutoCompleteIdleId)
12214             g_source_remove(m_nAutoCompleteIdleId);
12215         GtkWidget* pWidget = m_pEntry->getWidget();
12216         g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
12217         g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
12218     }
12219 };
12220 
12221 class GtkInstanceExpander : public GtkInstanceContainer, public virtual weld::Expander
12222 {
12223 private:
12224     GtkExpander* m_pExpander;
12225     gulong m_nSignalId;
12226 
signalExpanded(GtkExpander * pExpander,GParamSpec *,gpointer widget)12227     static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
12228     {
12229         GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
12230         SolarMutexGuard aGuard;
12231 
12232         GtkWidget *pToplevel = gtk_widget_get_toplevel(GTK_WIDGET(pExpander));
12233 
12234         // https://gitlab.gnome.org/GNOME/gtk/issues/70
12235         // I imagine at some point a release with a fix will be available in which
12236         // case this can be avoided depending on version number
12237         if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
12238         {
12239             int nToplevelWidth, nToplevelHeight;
12240             int nChildHeight;
12241 
12242             GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
12243             gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
12244             gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
12245 
12246             if (pThis->get_expanded())
12247                 nToplevelHeight += nChildHeight;
12248             else
12249                 nToplevelHeight -= nChildHeight;
12250 
12251             gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
12252         }
12253 
12254         pThis->signal_expanded();
12255     }
12256 
12257 public:
GtkInstanceExpander(GtkExpander * pExpander,GtkInstanceBuilder * pBuilder,bool bTakeOwnership)12258     GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12259         : GtkInstanceContainer(GTK_CONTAINER(pExpander), pBuilder, bTakeOwnership)
12260         , m_pExpander(pExpander)
12261         , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
12262     {
12263     }
12264 
get_expanded() const12265     virtual bool get_expanded() const override
12266     {
12267         return gtk_expander_get_expanded(m_pExpander);
12268     }
12269 
set_expanded(bool bExpand)12270     virtual void set_expanded(bool bExpand) override
12271     {
12272         gtk_expander_set_expanded(m_pExpander, bExpand);
12273     }
12274 
~GtkInstanceExpander()12275     virtual ~GtkInstanceExpander() override
12276     {
12277         g_signal_handler_disconnect(m_pExpander, m_nSignalId);
12278     }
12279 };
12280 
12281 namespace
12282 {
signalTooltipQuery(GtkWidget * pWidget,gint,gint,gboolean,GtkTooltip * tooltip)12283     gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
12284                                          gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
12285     {
12286         const ImplSVData* pSVData = ImplGetSVData();
12287         if (pSVData->maHelpData.mbBalloonHelp)
12288         {
12289             /*Current mechanism which needs help installed*/
12290             OString sHelpId = ::get_help_id(pWidget);
12291             Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
12292             if (pHelp)
12293             {
12294                 OUString sHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr));
12295                 if (!sHelpText.isEmpty())
12296                 {
12297                     gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
12298                     return true;
12299                 }
12300             }
12301 
12302             /*This is how I would prefer things to be, only a few like this though*/
12303             AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
12304             const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
12305             if (pDesc && pDesc[0])
12306             {
12307                 gtk_tooltip_set_text(tooltip, pDesc);
12308                 return true;
12309             }
12310         }
12311 
12312         const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
12313         if (pDesc && pDesc[0])
12314         {
12315             gtk_tooltip_set_text(tooltip, pDesc);
12316             return true;
12317         }
12318 
12319         return false;
12320     }
12321 }
12322 
12323 namespace
12324 {
12325 
drawing_area_get_accessibity(GtkWidget * pWidget)12326 AtkObject* drawing_area_get_accessibity(GtkWidget *pWidget)
12327 {
12328     AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
12329     void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
12330     GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
12331     AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
12332     if (pAtkObj)
12333         return pAtkObj;
12334     return pDefaultAccessible;
12335 }
12336 
ensure_intercept_drawing_area_accessibility()12337 void ensure_intercept_drawing_area_accessibility()
12338 {
12339     static bool bDone;
12340     if (!bDone)
12341     {
12342         gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
12343         GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
12344         default_drawing_area_get_accessible = pWidgetClass->get_accessible;
12345         pWidgetClass->get_accessible = drawing_area_get_accessibity;
12346         g_type_class_unref(pClass);
12347         bDone = true;
12348     }
12349 }
12350 
ensure_disable_ctrl_page_up_down(GType eType)12351 void ensure_disable_ctrl_page_up_down(GType eType)
12352 {
12353     gpointer pClass = g_type_class_ref(eType);
12354     GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
12355     GtkBindingSet* pBindingSet = gtk_binding_set_by_class(pWidgetClass);
12356     gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, GDK_CONTROL_MASK);
12357     gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
12358     gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, GDK_CONTROL_MASK);
12359     gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
12360     g_type_class_unref(pClass);
12361 }
12362 
12363 // tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
12364 // keystrokes are consumed by the surrounding notebook bindings instead
ensure_disable_ctrl_page_up_down_bindings()12365 void ensure_disable_ctrl_page_up_down_bindings()
12366 {
12367     static bool bDone;
12368     if (!bDone)
12369     {
12370         ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW);
12371         ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON);
12372         bDone = true;
12373     }
12374 }
12375 
12376 }
12377 
12378 class GtkInstanceBuilder : public weld::Builder
12379 {
12380 private:
12381     ResHookProc m_pStringReplace;
12382     OUString m_sHelpRoot;
12383     OString m_aUtf8HelpRoot;
12384     OUString m_aIconTheme;
12385     OUString m_aUILang;
12386     GtkBuilder* m_pBuilder;
12387     GSList* m_pObjectList;
12388     GtkWidget* m_pParentWidget;
12389     gulong m_nNotifySignalId;
12390     std::vector<GtkButton*> m_aMnemonicButtons;
12391     std::vector<GtkLabel*> m_aMnemonicLabels;
12392 
postprocess_widget(GtkWidget * pWidget)12393     void postprocess_widget(GtkWidget* pWidget)
12394     {
12395         const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
12396             officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
12397 
12398         //fixup icons
12399         //wanted: better way to do this, e.g. make gtk use gio for
12400         //loading from a filename and provide gio protocol handler
12401         //for our image in a zip urls
12402         //
12403         //unpack the images and keep them as dirs and just
12404         //add the paths to the gtk icon theme dir
12405         if (GTK_IS_IMAGE(pWidget))
12406         {
12407             GtkImage* pImage = GTK_IMAGE(pWidget);
12408             const gchar* icon_name;
12409             gtk_image_get_icon_name(pImage, &icon_name, nullptr);
12410             if (icon_name)
12411             {
12412                 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
12413                 GdkPixbuf* pixbuf = load_icon_by_name(aIconName, m_aIconTheme, m_aUILang);
12414                 if (pixbuf)
12415                 {
12416                     gtk_image_set_from_pixbuf(pImage, pixbuf);
12417                     g_object_unref(pixbuf);
12418                 }
12419             }
12420         }
12421         else if (GTK_IS_TOOL_BUTTON(pWidget))
12422         {
12423             GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
12424             const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton);
12425             if (icon_name)
12426             {
12427                 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
12428                 GdkPixbuf* pixbuf = load_icon_by_name(aIconName, m_aIconTheme, m_aUILang);
12429                 if (pixbuf)
12430                 {
12431                     GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
12432                     g_object_unref(pixbuf);
12433                     gtk_tool_button_set_icon_widget(pToolButton, pImage);
12434                     gtk_widget_show(pImage);
12435                 }
12436             }
12437 
12438             // if no tooltip reuse the label as default tooltip
12439             if (!gtk_widget_get_tooltip_text(pWidget))
12440             {
12441                 if (const gchar* label = gtk_tool_button_get_label(pToolButton))
12442                     gtk_widget_set_tooltip_text(pWidget, label);
12443             }
12444         }
12445 
12446         //set helpids
12447         const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
12448         size_t nLen = pStr ? strlen(pStr) : 0;
12449         if (nLen)
12450         {
12451             OString sBuildableName(pStr, nLen);
12452             OString sHelpId = m_aUtf8HelpRoot + sBuildableName;
12453             set_help_id(pWidget, sHelpId);
12454             //hook up for extended help
12455             const ImplSVData* pSVData = ImplGetSVData();
12456             if (pSVData->maHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
12457             {
12458                 gtk_widget_set_has_tooltip(pWidget, true);
12459                 g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
12460             }
12461 
12462             if (bHideHelp && sBuildableName == "help")
12463                 gtk_widget_hide(pWidget);
12464         }
12465 
12466         // expand placeholder and collect potentially missing mnemonics
12467         if (GTK_IS_BUTTON(pWidget))
12468         {
12469             GtkButton* pButton = GTK_BUTTON(pWidget);
12470             if (m_pStringReplace != nullptr)
12471             {
12472                 OUString aLabel(get_label(pButton));
12473                 if (!aLabel.isEmpty())
12474                     set_label(pButton, (*m_pStringReplace)(aLabel));
12475             }
12476             if (gtk_button_get_use_underline(pButton) && !gtk_button_get_use_stock(pButton))
12477                 m_aMnemonicButtons.push_back(pButton);
12478         }
12479         else if (GTK_IS_LABEL(pWidget))
12480         {
12481             GtkLabel* pLabel = GTK_LABEL(pWidget);
12482             if (m_pStringReplace != nullptr)
12483             {
12484                 OUString aLabel(get_label(pLabel));
12485                 if (!aLabel.isEmpty())
12486                     set_label(pLabel, (*m_pStringReplace)(aLabel));
12487             }
12488             if (gtk_label_get_use_underline(pLabel))
12489                 m_aMnemonicLabels.push_back(pLabel);
12490         }
12491         else if (GTK_IS_TEXT_VIEW(pWidget))
12492         {
12493             GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
12494             if (m_pStringReplace != nullptr)
12495             {
12496                 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
12497                 GtkTextIter start, end;
12498                 gtk_text_buffer_get_bounds(pBuffer, &start, &end);
12499                 char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
12500                 int nTextLen = pTextStr ? strlen(pTextStr) : 0;
12501                 if (nTextLen)
12502                 {
12503                     OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
12504                     OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
12505                     gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
12506                 }
12507                 g_free(pTextStr);
12508             }
12509         }
12510         else if (GTK_IS_WINDOW(pWidget))
12511         {
12512             if (m_pStringReplace != nullptr) {
12513                 GtkWindow* pWindow = GTK_WINDOW(pWidget);
12514                 set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
12515                 if (GTK_IS_MESSAGE_DIALOG(pWindow))
12516                 {
12517                     GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
12518                     set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
12519                     set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
12520                 }
12521                 else if (GTK_IS_ABOUT_DIALOG(pWindow))
12522                 {
12523                     GtkAboutDialog* pAboutDialog = GTK_ABOUT_DIALOG(pWindow);
12524                     const gchar *pComments = gtk_about_dialog_get_comments(pAboutDialog);
12525                     if (pComments)
12526                     {
12527                         OUString sComments(pComments, strlen(pComments), RTL_TEXTENCODING_UTF8);
12528                         sComments = (*m_pStringReplace)(sComments);
12529                         gtk_about_dialog_set_comments(pAboutDialog, OUStringToOString(sComments, RTL_TEXTENCODING_UTF8).getStr());
12530                     }
12531                     const gchar *pProgramName = gtk_about_dialog_get_program_name(pAboutDialog);
12532                     if (pProgramName)
12533                     {
12534                         OUString sProgramName(pProgramName, strlen(pProgramName), RTL_TEXTENCODING_UTF8);
12535                         sProgramName = (*m_pStringReplace)(sProgramName);
12536                         gtk_about_dialog_set_program_name(pAboutDialog, OUStringToOString(sProgramName, RTL_TEXTENCODING_UTF8).getStr());
12537                     }
12538                 }
12539             }
12540         }
12541     }
12542 
12543     //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
12544     //In order for GtkBuilder to find the translations bindtextdomain has to be called
12545     //for the domain. So here on the first setting of "domain" we call Translate::Create
12546     //to make sure that happens. Without this, if some other part of LibreOffice has
12547     //used the translation machinery for this domain it will still work, but if it
12548     //hasn't, e.g. tdf#119929, then the translation fails
translation_domain_set()12549     void translation_domain_set()
12550     {
12551         Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
12552         g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
12553     }
12554 
signalNotify(GObject *,GParamSpec * pSpec,gpointer pData)12555     static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
12556     {
12557         g_return_if_fail(pSpec != nullptr);
12558         if (strcmp(pSpec->name, "translation-domain") == 0)
12559         {
12560             GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
12561             pBuilder->translation_domain_set();
12562         }
12563     }
12564 
postprocess(gpointer data,gpointer user_data)12565     static void postprocess(gpointer data, gpointer user_data)
12566     {
12567         GObject* pObject = static_cast<GObject*>(data);
12568         if (!GTK_IS_WIDGET(pObject))
12569             return;
12570         GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
12571         pThis->postprocess_widget(GTK_WIDGET(pObject));
12572     }
12573 public:
GtkInstanceBuilder(GtkWidget * pParent,const OUString & rUIRoot,const OUString & rUIFile)12574     GtkInstanceBuilder(GtkWidget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
12575         : weld::Builder(rUIFile)
12576         , m_pStringReplace(Translate::GetReadStringHook())
12577         , m_sHelpRoot(rUIFile)
12578         , m_pParentWidget(pParent)
12579         , m_nNotifySignalId(0)
12580     {
12581         ensure_intercept_drawing_area_accessibility();
12582         ensure_disable_ctrl_page_up_down_bindings();
12583 
12584         sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.');
12585         if (nIdx != -1)
12586             m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
12587         m_sHelpRoot += OUString('/');
12588         m_aUtf8HelpRoot = OUStringToOString(m_sHelpRoot, RTL_TEXTENCODING_UTF8);
12589         m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
12590         m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
12591 
12592         OUString aUri(rUIRoot + rUIFile);
12593         OUString aPath;
12594         osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
12595         m_pBuilder = gtk_builder_new();
12596         m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
12597         auto rc = gtk_builder_add_from_file(m_pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), nullptr);
12598         assert(rc && "could not load UI file");
12599         (void) rc;
12600 
12601         m_pObjectList = gtk_builder_get_objects(m_pBuilder);
12602         g_slist_foreach(m_pObjectList, postprocess, this);
12603 
12604         GenerateMissingMnemonics();
12605     }
12606 
GenerateMissingMnemonics()12607     void GenerateMissingMnemonics()
12608     {
12609         MnemonicGenerator aMnemonicGenerator('_');
12610         for (const auto a : m_aMnemonicButtons)
12611             aMnemonicGenerator.RegisterMnemonic(get_label(a));
12612         for (const auto a : m_aMnemonicLabels)
12613             aMnemonicGenerator.RegisterMnemonic(get_label(a));
12614 
12615         for (const auto a : m_aMnemonicButtons)
12616         {
12617             OUString aLabel(get_label(a));
12618             OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
12619             if (aLabel == aNewLabel)
12620                 continue;
12621             set_label(a, aNewLabel);
12622         }
12623         for (const auto a : m_aMnemonicLabels)
12624         {
12625             OUString aLabel(get_label(a));
12626             OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
12627             if (aLabel == aNewLabel)
12628                 continue;
12629             set_label(a, aNewLabel);
12630         }
12631 
12632         m_aMnemonicLabels.clear();
12633         m_aMnemonicButtons.clear();
12634     }
12635 
get_current_page_help_id()12636     OString get_current_page_help_id()
12637     {
12638         OString sPageHelpId;
12639         // check to see if there is a notebook called tabcontrol and get the
12640         // helpid for the current page of that
12641         std::unique_ptr<weld::Notebook> xNotebook(weld_notebook("tabcontrol", false));
12642         if (xNotebook)
12643         {
12644             if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
12645             {
12646                 GtkWidget* pContainer = pPage->getWidget();
12647                 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
12648                 GList* pChild = g_list_first(pChildren);
12649                 if (pChild)
12650                 {
12651                     GtkWidget* pPageWidget = static_cast<GtkWidget*>(pChild->data);
12652                     sPageHelpId = ::get_help_id(pPageWidget);
12653                 }
12654                 g_list_free(pChildren);
12655             }
12656         }
12657         return sPageHelpId;
12658     }
12659 
~GtkInstanceBuilder()12660     virtual ~GtkInstanceBuilder() override
12661     {
12662         g_slist_free(m_pObjectList);
12663         g_object_unref(m_pBuilder);
12664     }
12665 
12666     //ideally we would have/use weld::Container add and explicitly
12667     //call add when we want to do this, but in the vcl impl the
12668     //parent has to be set when the child is created, so for the
12669     //gtk impl emulate this by doing this implicitly at weld time
auto_add_parentless_widgets_to_container(GtkWidget * pWidget)12670     void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
12671     {
12672         if (gtk_widget_get_toplevel(pWidget) == pWidget && !GTK_IS_POPOVER(pWidget))
12673             gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
12674     }
12675 
weld_message_dialog(const OString & id,bool bTakeOwnership)12676     virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id, bool bTakeOwnership) override
12677     {
12678         GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
12679         if (!pMessageDialog)
12680             return nullptr;
12681         gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12682         return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, bTakeOwnership);
12683     }
12684 
weld_about_dialog(const OString & id,bool bTakeOwnership)12685     virtual std::unique_ptr<weld::AboutDialog> weld_about_dialog(const OString &id, bool bTakeOwnership) override
12686     {
12687         GtkAboutDialog* pAboutDialog = GTK_ABOUT_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
12688         if (!pAboutDialog)
12689             return nullptr;
12690         gtk_window_set_transient_for(GTK_WINDOW(pAboutDialog), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12691         return std::make_unique<GtkInstanceAboutDialog>(pAboutDialog, this, bTakeOwnership);
12692     }
12693 
weld_assistant(const OString & id,bool bTakeOwnership)12694     virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id, bool bTakeOwnership) override
12695     {
12696         GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, id.getStr()));
12697         if (!pAssistant)
12698             return nullptr;
12699         if (m_pParentWidget)
12700             gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12701         return std::make_unique<GtkInstanceAssistant>(pAssistant, this, bTakeOwnership);
12702     }
12703 
weld_dialog(const OString & id,bool bTakeOwnership)12704     virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id, bool bTakeOwnership) override
12705     {
12706         GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12707         if (!pDialog)
12708             return nullptr;
12709         if (m_pParentWidget)
12710             gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12711         return std::make_unique<GtkInstanceDialog>(pDialog, this, bTakeOwnership);
12712     }
12713 
create_screenshot_window()12714     virtual std::unique_ptr<weld::Window> create_screenshot_window() override
12715     {
12716         GtkWidget* pTopLevel = nullptr;
12717 
12718         for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
12719         {
12720             GObject* pObj = static_cast<GObject*>(l->data);
12721 
12722             if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
12723                 continue;
12724 
12725             if (!pTopLevel)
12726                 pTopLevel = GTK_WIDGET(pObj);
12727             else if (GTK_IS_WINDOW(pObj))
12728                 pTopLevel = GTK_WIDGET(pObj);
12729         }
12730 
12731         if (!pTopLevel)
12732             return nullptr;
12733 
12734         GtkWindow* pDialog;
12735         if (GTK_IS_WINDOW(pTopLevel))
12736             pDialog = GTK_WINDOW(pTopLevel);
12737         else
12738         {
12739             pDialog = GTK_WINDOW(gtk_dialog_new());
12740             ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
12741 
12742             GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
12743             gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
12744             gtk_widget_show_all(pTopLevel);
12745         }
12746 
12747         if (m_pParentWidget)
12748             gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12749         return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
12750     }
12751 
weld_window(const OString & id,bool bTakeOwnership)12752     virtual std::unique_ptr<weld::Window> weld_window(const OString &id, bool bTakeOwnership) override
12753     {
12754         GtkWindow* pWindow = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12755         return pWindow ? std::make_unique<GtkInstanceWindow>(pWindow, this, bTakeOwnership) : nullptr;
12756     }
12757 
weld_widget(const OString & id,bool bTakeOwnership)12758     virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id, bool bTakeOwnership) override
12759     {
12760         GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
12761         if (!pWidget)
12762             return nullptr;
12763         auto_add_parentless_widgets_to_container(pWidget);
12764         return std::make_unique<GtkInstanceWidget>(pWidget, this, bTakeOwnership);
12765     }
12766 
weld_container(const OString & id,bool bTakeOwnership)12767     virtual std::unique_ptr<weld::Container> weld_container(const OString &id, bool bTakeOwnership) override
12768     {
12769         GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
12770         if (!pContainer)
12771             return nullptr;
12772         auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
12773         return std::make_unique<GtkInstanceContainer>(pContainer, this, bTakeOwnership);
12774     }
12775 
weld_box(const OString & id,bool bTakeOwnership)12776     virtual std::unique_ptr<weld::Box> weld_box(const OString &id, bool bTakeOwnership) override
12777     {
12778         GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
12779         if (!pBox)
12780             return nullptr;
12781         auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
12782         return std::make_unique<GtkInstanceBox>(pBox, this, bTakeOwnership);
12783     }
12784 
weld_frame(const OString & id,bool bTakeOwnership)12785     virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id, bool bTakeOwnership) override
12786     {
12787         GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, id.getStr()));
12788         if (!pFrame)
12789             return nullptr;
12790         auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
12791         return std::make_unique<GtkInstanceFrame>(pFrame, this, bTakeOwnership);
12792     }
12793 
weld_scrolled_window(const OString & id,bool bTakeOwnership)12794     virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bTakeOwnership) override
12795     {
12796         GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12797         if (!pScrolledWindow)
12798             return nullptr;
12799         auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
12800         return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, bTakeOwnership);
12801     }
12802 
weld_notebook(const OString & id,bool bTakeOwnership)12803     virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id, bool bTakeOwnership) override
12804     {
12805         GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, id.getStr()));
12806         if (!pNotebook)
12807             return nullptr;
12808         auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
12809         return std::make_unique<GtkInstanceNotebook>(pNotebook, this, bTakeOwnership);
12810     }
12811 
weld_button(const OString & id,bool bTakeOwnership)12812     virtual std::unique_ptr<weld::Button> weld_button(const OString &id, bool bTakeOwnership) override
12813     {
12814         GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12815         if (!pButton)
12816             return nullptr;
12817         auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
12818         return std::make_unique<GtkInstanceButton>(pButton, this, bTakeOwnership);
12819     }
12820 
weld_menu_button(const OString & id,bool bTakeOwnership)12821     virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id, bool bTakeOwnership) override
12822     {
12823         GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12824         if (!pButton)
12825             return nullptr;
12826         auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
12827         return std::make_unique<GtkInstanceMenuButton>(pButton, this, bTakeOwnership);
12828     }
12829 
weld_link_button(const OString & id,bool bTakeOwnership)12830     virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id, bool bTakeOwnership) override
12831     {
12832         GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12833         if (!pButton)
12834             return nullptr;
12835         auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
12836         return std::make_unique<GtkInstanceLinkButton>(pButton, this, bTakeOwnership);
12837     }
12838 
weld_toggle_button(const OString & id,bool bTakeOwnership)12839     virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id, bool bTakeOwnership) override
12840     {
12841         GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12842         if (!pToggleButton)
12843             return nullptr;
12844         auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
12845         return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, bTakeOwnership);
12846     }
12847 
weld_radio_button(const OString & id,bool bTakeOwnership)12848     virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id, bool bTakeOwnership) override
12849     {
12850         GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12851         if (!pRadioButton)
12852             return nullptr;
12853         auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
12854         return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, bTakeOwnership);
12855     }
12856 
weld_check_button(const OString & id,bool bTakeOwnership)12857     virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id, bool bTakeOwnership) override
12858     {
12859         GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12860         if (!pCheckButton)
12861             return nullptr;
12862         auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
12863         return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, bTakeOwnership);
12864     }
12865 
weld_scale(const OString & id,bool bTakeOwnership)12866     virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id, bool bTakeOwnership) override
12867     {
12868         GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, id.getStr()));
12869         if (!pScale)
12870             return nullptr;
12871         auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
12872         return std::make_unique<GtkInstanceScale>(pScale, this, bTakeOwnership);
12873     }
12874 
weld_progress_bar(const OString & id,bool bTakeOwnership)12875     virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id, bool bTakeOwnership) override
12876     {
12877         GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
12878         if (!pProgressBar)
12879             return nullptr;
12880         auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
12881         return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, bTakeOwnership);
12882     }
12883 
weld_spinner(const OString & id,bool bTakeOwnership)12884     virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id, bool bTakeOwnership) override
12885     {
12886         GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
12887         if (!pSpinner)
12888             return nullptr;
12889         auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
12890         return std::make_unique<GtkInstanceSpinner>(pSpinner, this, bTakeOwnership);
12891     }
12892 
weld_image(const OString & id,bool bTakeOwnership)12893     virtual std::unique_ptr<weld::Image> weld_image(const OString &id, bool bTakeOwnership) override
12894     {
12895         GtkImage* pImage = GTK_IMAGE(gtk_builder_get_object(m_pBuilder, id.getStr()));
12896         if (!pImage)
12897             return nullptr;
12898         auto_add_parentless_widgets_to_container(GTK_WIDGET(pImage));
12899         return std::make_unique<GtkInstanceImage>(pImage, this, bTakeOwnership);
12900     }
12901 
weld_calendar(const OString & id,bool bTakeOwnership)12902     virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id, bool bTakeOwnership) override
12903     {
12904         GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
12905         if (!pCalendar)
12906             return nullptr;
12907         auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
12908         return std::make_unique<GtkInstanceCalendar>(pCalendar, this, bTakeOwnership);
12909     }
12910 
weld_entry(const OString & id,bool bTakeOwnership)12911     virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id, bool bTakeOwnership) override
12912     {
12913         GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, id.getStr()));
12914         if (!pEntry)
12915             return nullptr;
12916         auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
12917         return std::make_unique<GtkInstanceEntry>(pEntry, this, bTakeOwnership);
12918     }
12919 
weld_spin_button(const OString & id,bool bTakeOwnership)12920     virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id, bool bTakeOwnership) override
12921     {
12922         GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12923         if (!pSpinButton)
12924             return nullptr;
12925         auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
12926         return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, bTakeOwnership);
12927     }
12928 
weld_metric_spin_button(const OString & id,FieldUnit eUnit,bool bTakeOwnership)12929     virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit,
12930                                                                       bool bTakeOwnership) override
12931     {
12932         return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id, bTakeOwnership), eUnit);
12933     }
12934 
weld_formatted_spin_button(const OString & id,bool bTakeOwnership)12935     virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id, bool bTakeOwnership) override
12936     {
12937         GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12938         if (!pSpinButton)
12939             return nullptr;
12940         auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
12941         return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, bTakeOwnership);
12942     }
12943 
weld_time_spin_button(const OString & id,TimeFieldFormat eFormat,bool bTakeOwnership)12944     virtual std::unique_ptr<weld::TimeSpinButton> weld_time_spin_button(const OString& id, TimeFieldFormat eFormat,
12945                                                         bool bTakeOwnership) override
12946     {
12947         return std::make_unique<weld::TimeSpinButton>(weld_spin_button(id, bTakeOwnership), eFormat);
12948     }
12949 
weld_combo_box(const OString & id,bool bTakeOwnership)12950     virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id, bool bTakeOwnership) override
12951     {
12952         GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
12953         if (!pComboBox)
12954             return nullptr;
12955         auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
12956         return std::make_unique<GtkInstanceComboBox>(pComboBox, this, bTakeOwnership);
12957     }
12958 
weld_tree_view(const OString & id,bool bTakeOwnership)12959     virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id, bool bTakeOwnership) override
12960     {
12961         GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12962         if (!pTreeView)
12963             return nullptr;
12964         auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
12965         return std::make_unique<GtkInstanceTreeView>(pTreeView, this, bTakeOwnership);
12966     }
12967 
weld_icon_view(const OString & id,bool bTakeOwnership)12968     virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id, bool bTakeOwnership) override
12969     {
12970         GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12971         if (!pIconView)
12972             return nullptr;
12973         auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
12974         return std::make_unique<GtkInstanceIconView>(pIconView, this, bTakeOwnership);
12975     }
12976 
weld_entry_tree_view(const OString & containerid,const OString & entryid,const OString & treeviewid,bool bTakeOwnership)12977     virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid, bool bTakeOwnership) override
12978     {
12979         GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
12980         if (!pContainer)
12981             return nullptr;
12982         auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
12983         return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, bTakeOwnership,
12984                                                           weld_entry(entryid, bTakeOwnership),
12985                                                           weld_tree_view(treeviewid, bTakeOwnership));
12986     }
12987 
weld_label(const OString & id,bool bTakeOwnership)12988     virtual std::unique_ptr<weld::Label> weld_label(const OString &id, bool bTakeOwnership) override
12989     {
12990         GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, id.getStr()));
12991         if (!pLabel)
12992             return nullptr;
12993         auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
12994         return std::make_unique<GtkInstanceLabel>(pLabel, this, bTakeOwnership);
12995     }
12996 
weld_text_view(const OString & id,bool bTakeOwnership)12997     virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id, bool bTakeOwnership) override
12998     {
12999         GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
13000         if (!pTextView)
13001             return nullptr;
13002         auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
13003         return std::make_unique<GtkInstanceTextView>(pTextView, this, bTakeOwnership);
13004     }
13005 
weld_expander(const OString & id,bool bTakeOwnership)13006     virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id, bool bTakeOwnership) override
13007     {
13008         GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, id.getStr()));
13009         if (!pExpander)
13010             return nullptr;
13011         auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
13012         return std::make_unique<GtkInstanceExpander>(pExpander, this, bTakeOwnership);
13013     }
13014 
weld_drawing_area(const OString & id,const a11yref & rA11y,FactoryFunction,void *,bool bTakeOwnership)13015     virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
13016             FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/, bool bTakeOwnership) override
13017     {
13018         GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, id.getStr()));
13019         if (!pDrawingArea)
13020             return nullptr;
13021         auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
13022         return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, bTakeOwnership);
13023     }
13024 
weld_menu(const OString & id,bool bTakeOwnership)13025     virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id, bool bTakeOwnership) override
13026     {
13027         GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
13028         if (!pMenu)
13029             return nullptr;
13030         return std::make_unique<GtkInstanceMenu>(pMenu, bTakeOwnership);
13031     }
13032 
weld_toolbar(const OString & id,bool bTakeOwnership)13033     virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id, bool bTakeOwnership) override
13034     {
13035         GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
13036         if (!pToolbar)
13037             return nullptr;
13038         auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
13039         return std::make_unique<GtkInstanceToolbar>(pToolbar, this, bTakeOwnership);
13040     }
13041 
create_size_group()13042     virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
13043     {
13044         return std::make_unique<GtkInstanceSizeGroup>();
13045     }
13046 };
13047 
help()13048 void GtkInstanceWindow::help()
13049 {
13050     //show help for widget with keyboard focus
13051     GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
13052     if (!pWidget)
13053         pWidget = GTK_WIDGET(m_pWindow);
13054     OString sHelpId = ::get_help_id(pWidget);
13055     while (sHelpId.isEmpty())
13056     {
13057         pWidget = gtk_widget_get_parent(pWidget);
13058         if (!pWidget)
13059             break;
13060         sHelpId = ::get_help_id(pWidget);
13061     }
13062     std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
13063     weld::Widget* pSource = xTemp ? xTemp.get() : this;
13064     bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
13065     Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
13066     if (pHelp)
13067     {
13068         // tdf#126007, there's a nice fallback route for offline help where
13069         // the current page of a notebook will get checked when the help
13070         // button is pressed and there was no help for the dialog found.
13071         //
13072         // But for online help that route doesn't get taken, so bodge this here
13073         // by using the page help id if available and if the help button itself
13074         // was the original id
13075         if (m_pBuilder && sHelpId.endsWith("/help"))
13076         {
13077             OString sPageId = m_pBuilder->get_current_page_help_id();
13078             if (!sPageId.isEmpty())
13079                 sHelpId = sPageId;
13080             else
13081             {
13082                 // tdf#129068 likewise the help for the wrapping dialog is less
13083                 // helpful than the help for the content area could be
13084                 GtkContainer* pContainer = nullptr;
13085                 if (GTK_IS_DIALOG(m_pWindow))
13086                     pContainer = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow)));
13087                 else if (GTK_IS_ASSISTANT(m_pWindow))
13088                 {
13089                     GtkAssistant* pAssistant = GTK_ASSISTANT(m_pWindow);
13090                     pContainer = GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant, gtk_assistant_get_current_page(pAssistant)));
13091                 }
13092                 if (pContainer)
13093                 {
13094                     GList* pChildren = gtk_container_get_children(pContainer);
13095                     GList* pChild = g_list_first(pChildren);
13096                     if (pChild)
13097                     {
13098                         GtkWidget* pContentWidget = static_cast<GtkWidget*>(pChild->data);
13099                         sHelpId = ::get_help_id(pContentWidget);
13100                     }
13101                     g_list_free(pChildren);
13102                 }
13103             }
13104         }
13105         pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
13106     }
13107 }
13108 
13109 //iterate upwards through the hierarchy from this widgets through its parents
13110 //calling func with their helpid until func returns true or we run out of parents
help_hierarchy_foreach(const std::function<bool (const OString &)> & func)13111 void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
13112 {
13113     GtkWidget* pParent = m_pWidget;
13114     while ((pParent = gtk_widget_get_parent(pParent)))
13115     {
13116         if (func(::get_help_id(pParent)))
13117             return;
13118     }
13119 }
13120 
CreateBuilder(weld::Widget * pParent,const OUString & rUIRoot,const OUString & rUIFile)13121 weld::Builder* GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
13122 {
13123     GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
13124     if (pParent && !pParentWidget) //remove when complete
13125         return SalInstance::CreateBuilder(pParent, rUIRoot, rUIFile);
13126     GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
13127     return new GtkInstanceBuilder(pBuilderParent, rUIRoot, rUIFile);
13128 }
13129 
CreateMessageDialog(weld::Widget * pParent,VclMessageType eMessageType,VclButtonsType eButtonsType,const OUString & rPrimaryMessage)13130 weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
13131 {
13132     GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
13133     GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
13134     GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
13135                                                           VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
13136                                                           OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
13137     return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
13138 }
13139 
GetFrameWeld(const css::uno::Reference<css::awt::XWindow> & rWindow)13140 weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
13141 {
13142     if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
13143         return pGtkXWindow->getFrameWeld();
13144     return SalInstance::GetFrameWeld(rWindow);
13145 }
13146 
GetFrameWeld() const13147 weld::Window* GtkSalFrame::GetFrameWeld() const
13148 {
13149     if (!m_xFrameWeld)
13150         m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(gtk_widget_get_toplevel(getWindow())), nullptr, false));
13151     return m_xFrameWeld.get();
13152 }
13153 
CreateGStreamerSink(const SystemChildWindow * pWindow)13154 void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
13155 {
13156 #if ENABLE_GSTREAMER_1_0
13157     auto aSymbol = gstElementFactoryNameSymbol();
13158     if (!aSymbol)
13159         return nullptr;
13160 
13161     const SystemEnvData* pEnvData = pWindow->GetSystemData();
13162     if (!pEnvData)
13163         return nullptr;
13164 
13165     GstElement* pVideosink = aSymbol("gtksink", "gtksink");
13166     if (!pVideosink)
13167         return nullptr;
13168 
13169     GtkWidget *pGstWidget;
13170     g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
13171     gtk_widget_set_vexpand(pGstWidget, true);
13172     gtk_widget_set_hexpand(pGstWidget, true);
13173 
13174     GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
13175     gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
13176     g_object_unref(pGstWidget);
13177     gtk_widget_show_all(pParent);
13178 
13179     return pVideosink;
13180 #else
13181     (void)pWindow;
13182     return nullptr;
13183 #endif
13184 }
13185 
13186 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
13187