Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / unx / gtk3 / gtk3gtkinst.cxx
blobf3ec6ddfb71efcb2f394263bd070e63e6febf795
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <stack>
11 #include <string.h>
12 #include <osl/process.h>
13 #include <unx/gtk/gtkdata.hxx>
14 #include <unx/gtk/gtkinst.hxx>
15 #include <unx/salobj.h>
16 #include <unx/gtk/gtkgdi.hxx>
17 #include <unx/gtk/gtkframe.hxx>
18 #include <unx/gtk/gtkobject.hxx>
19 #include <unx/gtk/atkbridge.hxx>
20 #include <unx/gtk/gtkprn.hxx>
21 #include <unx/gtk/gtksalmenu.hxx>
22 #include <headless/svpvd.hxx>
23 #include <headless/svpbmp.hxx>
24 #include <vcl/inputtypes.hxx>
25 #include <unx/genpspgraphics.h>
26 #include <rtl/strbuf.hxx>
27 #include <sal/log.hxx>
28 #include <rtl/uri.hxx>
30 #include <vcl/settings.hxx>
32 #include <dlfcn.h>
33 #include <fcntl.h>
34 #include <unistd.h>
36 #include <unx/gtk/gtkprintwrapper.hxx>
38 #include "a11y/atkwrapper.hxx"
39 #include <com/sun/star/lang/IllegalArgumentException.hpp>
40 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
41 #include <com/sun/star/lang/XServiceInfo.hpp>
42 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
43 #include <com/sun/star/lang/XInitialization.hpp>
44 #include <com/sun/star/datatransfer/XTransferable.hpp>
45 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
46 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
47 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
48 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
49 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
50 #include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
51 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
52 #include <comphelper/processfactory.hxx>
53 #include <comphelper/sequence.hxx>
54 #include <cppuhelper/compbase.hxx>
55 #include <comphelper/string.hxx>
56 #include <cppuhelper/implbase.hxx>
57 #include <cppuhelper/supportsservice.hxx>
58 #include <officecfg/Office/Common.hxx>
59 #include <rtl/bootstrap.hxx>
60 #include <svl/zforlist.hxx>
61 #include <svl/zformat.hxx>
62 #include <tools/helpers.hxx>
63 #include <tools/fract.hxx>
64 #include <tools/stream.hxx>
65 #include <unotools/resmgr.hxx>
66 #include <unx/gstsink.hxx>
67 #include <vcl/ImageTree.hxx>
68 #include <vcl/abstdlg.hxx>
69 #include <vcl/button.hxx>
70 #include <vcl/event.hxx>
71 #include <vcl/i18nhelp.hxx>
72 #include <vcl/quickselectionengine.hxx>
73 #include <vcl/mnemonic.hxx>
74 #include <vcl/pngwrite.hxx>
75 #include <vcl/stdtext.hxx>
76 #include <vcl/syswin.hxx>
77 #include <vcl/virdev.hxx>
78 #include <vcl/weld.hxx>
79 #include <vcl/wrkwin.hxx>
80 #include <strings.hrc>
81 #include <window.h>
82 #include <numeric>
84 using namespace com::sun::star;
85 using namespace com::sun::star::uno;
86 using namespace com::sun::star::lang;
88 extern "C"
90 #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
91 static void GdkThreadsEnter()
93 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
94 pYieldMutex->ThreadsEnter();
96 static void GdkThreadsLeave()
98 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
99 pYieldMutex->ThreadsLeave();
102 VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
104 SAL_INFO(
105 "vcl.gtk",
106 "create vcl plugin instance with gtk version " << gtk_major_version
107 << " " << gtk_minor_version << " " << gtk_micro_version);
109 if (gtk_major_version == 3 && gtk_minor_version < 18)
111 g_warning("require gtk >= 3.18 for theme expectations");
112 return nullptr;
115 // for gtk2 it is always built with X support, so this is always called
116 // for gtk3 it is normally built with X and Wayland support, if
117 // X is supported GDK_WINDOWING_X11 is defined and this is always
118 // called, regardless of if we're running under X or Wayland.
119 // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
120 // X, because we need to do it earlier than we have a display
121 #if defined(GDK_WINDOWING_X11)
122 /* #i92121# workaround deadlocks in the X11 implementation
124 static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
125 /* #i90094#
126 from now on we know that an X connection will be
127 established, so protect X against itself
129 if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
130 XInitThreads();
131 #endif
133 // init gdk thread protection
134 bool const sup = g_thread_supported();
135 // extracted from the 'if' to avoid Clang -Wunreachable-code
136 if ( !sup )
137 g_thread_init( nullptr );
139 gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
140 SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
142 auto pYieldMutex = std::make_unique<GtkYieldMutex>();
144 gdk_threads_init();
146 GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
147 SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
149 // Create SalData, this does not leak
150 new GtkSalData( pInstance );
152 return pInstance;
156 static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
158 VclInputFlags nType = VclInputFlags::NONE;
159 switch( pEvent->type )
161 case GDK_MOTION_NOTIFY:
162 case GDK_BUTTON_PRESS:
163 case GDK_2BUTTON_PRESS:
164 case GDK_3BUTTON_PRESS:
165 case GDK_BUTTON_RELEASE:
166 case GDK_ENTER_NOTIFY:
167 case GDK_LEAVE_NOTIFY:
168 case GDK_SCROLL:
169 nType = VclInputFlags::MOUSE;
170 break;
171 case GDK_KEY_PRESS:
172 // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
173 nType = VclInputFlags::KEYBOARD;
174 break;
175 case GDK_EXPOSE:
176 nType = VclInputFlags::PAINT;
177 break;
178 default:
179 nType = VclInputFlags::OTHER;
180 break;
182 return nType;
185 GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
186 : SvpSalInstance( std::move(pMutex) )
187 , m_pTimer(nullptr)
188 , bNeedsInit(true)
189 , m_pLastCairoFontOptions(nullptr)
193 //We want to defer initializing gtk until we are after uno has been
194 //bootstrapped so we can ask the config what the UI language is so that we can
195 //force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
196 //UI in a LTR locale
197 void GtkInstance::AfterAppInit()
199 EnsureInit();
202 void GtkInstance::EnsureInit()
204 if (!bNeedsInit)
205 return;
206 // initialize SalData
207 GtkSalData *pSalData = GetGtkSalData();
208 pSalData->Init();
209 GtkSalData::initNWF();
211 InitAtkBridge();
213 ImplSVData* pSVData = ImplGetSVData();
214 #ifdef GTK_TOOLKIT_NAME
215 pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
216 #else
217 pSVData->maAppData.mxToolkitName = OUString("gtk3");
218 #endif
220 bNeedsInit = false;
223 GtkInstance::~GtkInstance()
225 assert( nullptr == m_pTimer );
226 DeInitAtkBridge();
227 ResetLastSeenCairoFontOptions(nullptr);
230 SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
232 EnsureInit();
233 return new GtkSalFrame( pParent, nStyle );
236 SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
238 EnsureInit();
239 return new GtkSalFrame( pParentData );
242 SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* /*pWindowData*/, bool bShow )
244 EnsureInit();
245 //FIXME: Missing CreateObject functionality ...
246 return new GtkSalObject( static_cast<GtkSalFrame*>(pParent), bShow );
249 extern "C"
251 typedef void*(* getDefaultFnc)();
252 typedef void(* addItemFnc)(void *, const char *);
255 void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
257 EnsureInit();
258 OString sGtkURL;
259 rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
260 if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
261 sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
262 else
264 //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
265 //Decode %XX components
266 OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
267 //Convert back to system locale encoding
268 OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
269 //Encode to an escaped ASCII-encoded URI
270 gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
271 sGtkURL = OString(g_uri);
272 g_free(g_uri);
274 GtkRecentManager *manager = gtk_recent_manager_get_default ();
275 gtk_recent_manager_add_item (manager, sGtkURL.getStr());
278 SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
279 ImplJobSetup* pSetupData )
281 EnsureInit();
282 mbPrinterInit = true;
283 // create and initialize SalInfoPrinter
284 PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
285 configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
286 return pPrinter;
289 std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
291 EnsureInit();
292 mbPrinterInit = true;
293 return std::unique_ptr<SalPrinter>(new GtkSalPrinter( pInfoPrinter ));
297 * These methods always occur in pairs
298 * A ThreadsEnter is followed by a ThreadsLeave
299 * We need to queue up the recursive lock count
300 * for each pair, so we can accurately restore
301 * it later.
303 thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
305 void GtkYieldMutex::ThreadsEnter()
307 acquire();
308 if (!yieldCounts.empty()) {
309 auto n = yieldCounts.top();
310 yieldCounts.pop();
311 assert(n > 0);
312 n--;
313 if (n > 0)
314 acquire(n);
318 void GtkYieldMutex::ThreadsLeave()
320 assert(m_nCount != 0);
321 yieldCounts.push(m_nCount);
322 release(true);
325 std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
326 long &nDX, long &nDY,
327 DeviceFormat eFormat,
328 const SystemGraphicsData* pGd )
330 EnsureInit();
331 SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
332 assert(pSvpSalGraphics);
333 // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
334 cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
335 std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
336 pNew->SetSize( nDX, nDY );
337 return pNew;
340 std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
342 EnsureInit();
343 return SvpSalInstance::CreateSalBitmap();
346 std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
348 EnsureInit();
349 GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
350 pSalMenu->SetMenu( pVCLMenu );
351 return std::unique_ptr<SalMenu>(pSalMenu);
354 std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
356 EnsureInit();
357 return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
360 SalTimer* GtkInstance::CreateSalTimer()
362 EnsureInit();
363 assert( nullptr == m_pTimer );
364 if ( nullptr == m_pTimer )
365 m_pTimer = new GtkSalTimer();
366 return m_pTimer;
369 void GtkInstance::RemoveTimer ()
371 EnsureInit();
372 m_pTimer = nullptr;
375 bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
377 EnsureInit();
378 return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
381 bool GtkInstance::IsTimerExpired()
383 EnsureInit();
384 return (m_pTimer && m_pTimer->Expired());
387 bool GtkInstance::AnyInput( VclInputFlags nType )
389 EnsureInit();
390 if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
391 return true;
392 if (!gdk_events_pending())
393 return false;
395 if (nType == VCL_INPUT_ANY)
396 return true;
398 bool bRet = false;
399 std::stack<GdkEvent*> aEvents;
400 GdkEvent *pEvent = nullptr;
401 while ((pEvent = gdk_event_get()))
403 aEvents.push(pEvent);
404 VclInputFlags nEventType = categorizeEvent(pEvent);
405 if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
407 bRet = true;
408 break;
412 while (!aEvents.empty())
414 pEvent = aEvents.top();
415 gdk_event_put(pEvent);
416 gdk_event_free(pEvent);
417 aEvents.pop();
419 return bRet;
422 std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
424 EnsureInit();
425 return std::make_unique<GenPspGraphics>();
428 std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
429 GtkInstance::getPrintWrapper() const
431 if (!m_xPrintWrapper)
432 m_xPrintWrapper.reset(new vcl::unx::GtkPrintWrapper);
433 return m_xPrintWrapper;
436 const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
438 const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
439 if (!m_pLastCairoFontOptions && pCairoFontOptions)
440 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
441 return pCairoFontOptions;
444 const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
446 return m_pLastCairoFontOptions;
449 void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
451 if (m_pLastCairoFontOptions)
452 cairo_font_options_destroy(m_pLastCairoFontOptions);
453 if (pCairoFontOptions)
454 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
455 else
456 m_pLastCairoFontOptions = nullptr;
460 namespace
462 struct TypeEntry
464 const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
465 const char* pType; // Mime encoding on our side
468 static const TypeEntry aConversionTab[] =
470 { "ISO10646-1", "text/plain;charset=utf-16" },
471 { "UTF8_STRING", "text/plain;charset=utf-8" },
472 { "UTF-8", "text/plain;charset=utf-8" },
473 { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
474 // ISO encodings
475 { "ISO8859-2", "text/plain;charset=iso8859-2" },
476 { "ISO8859-3", "text/plain;charset=iso8859-3" },
477 { "ISO8859-4", "text/plain;charset=iso8859-4" },
478 { "ISO8859-5", "text/plain;charset=iso8859-5" },
479 { "ISO8859-6", "text/plain;charset=iso8859-6" },
480 { "ISO8859-7", "text/plain;charset=iso8859-7" },
481 { "ISO8859-8", "text/plain;charset=iso8859-8" },
482 { "ISO8859-9", "text/plain;charset=iso8859-9" },
483 { "ISO8859-10", "text/plain;charset=iso8859-10" },
484 { "ISO8859-13", "text/plain;charset=iso8859-13" },
485 { "ISO8859-14", "text/plain;charset=iso8859-14" },
486 { "ISO8859-15", "text/plain;charset=iso8859-15" },
487 // asian encodings
488 { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
489 { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
490 { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
491 { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
492 { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
493 { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
494 // eastern european encodings
495 { "KOI8-R", "text/plain;charset=koi8-r" },
496 { "KOI8-U", "text/plain;charset=koi8-u" },
497 // String (== iso8859-1)
498 { "STRING", "text/plain;charset=iso8859-1" },
499 // special for compound text
500 { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
502 // PIXMAP
503 { "PIXMAP", "image/bmp" }
506 class DataFlavorEq
508 private:
509 const css::datatransfer::DataFlavor& m_rData;
510 public:
511 explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
512 bool operator() (const css::datatransfer::DataFlavor& rData) const
514 return rData.MimeType == m_rData.MimeType &&
515 rData.DataType == m_rData.DataType;
520 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
522 std::vector<css::datatransfer::DataFlavor> aVector;
524 bool bHaveText = false, bHaveUTF16 = false;
526 for (gint i = 0; i < n_targets; ++i)
528 gchar* pName = gdk_atom_name(targets[i]);
529 const char* pFinalName = pName;
530 css::datatransfer::DataFlavor aFlavor;
532 // omit text/plain;charset=unicode since it is not well defined
533 if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
535 g_free(pName);
536 continue;
539 for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
541 if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
543 pFinalName = aConversionTab[j].pType;
544 break;
548 // There are more non-MIME-types reported that are not translated by
549 // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
550 // them out for now before they confuse this code's clients:
551 if (rtl_str_indexOfChar(pFinalName, '/') == -1)
553 g_free(pName);
554 continue;
557 aFlavor.MimeType = OUString(pFinalName,
558 strlen(pFinalName),
559 RTL_TEXTENCODING_UTF8);
561 m_aMimeTypeToAtom[aFlavor.MimeType] = targets[i];
563 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
565 sal_Int32 nIndex(0);
566 if (aFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
568 bHaveText = true;
569 OUString aToken(aFlavor.MimeType.getToken(0, ';', nIndex));
570 if (aToken == "charset=utf-16")
572 bHaveUTF16 = true;
573 aFlavor.DataType = cppu::UnoType<OUString>::get();
576 aVector.push_back(aFlavor);
577 g_free(pName);
580 //If we have text, but no UTF-16 format which is basically the only
581 //text-format LibreOffice supports for cnp then claim we do and we
582 //will convert on demand
583 if (bHaveText && !bHaveUTF16)
585 css::datatransfer::DataFlavor aFlavor;
586 aFlavor.MimeType = "text/plain;charset=utf-16";
587 aFlavor.DataType = cppu::UnoType<OUString>::get();
588 aVector.push_back(aFlavor);
591 return aVector;
595 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
597 return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
600 sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
602 const std::vector<css::datatransfer::DataFlavor> aAll =
603 getTransferDataFlavorsAsVector();
605 return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
608 class GtkClipboardTransferable : public GtkTransferable
610 private:
611 GdkAtom m_nSelection;
612 public:
614 explicit GtkClipboardTransferable(GdkAtom nSelection)
615 : m_nSelection(nSelection)
620 * XTransferable
623 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
625 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
626 if (rFlavor.MimeType == "text/plain;charset=utf-16")
628 OUString aStr;
629 gchar *pText = gtk_clipboard_wait_for_text(clipboard);
630 if (pText)
631 aStr = OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
632 g_free(pText);
633 css::uno::Any aRet;
634 aRet <<= aStr.replaceAll("\r\n", "\n");
635 return aRet;
638 auto it = m_aMimeTypeToAtom.find(rFlavor.MimeType);
639 if (it == m_aMimeTypeToAtom.end())
640 return css::uno::Any();
642 css::uno::Any aRet;
643 GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
644 it->second);
645 if (!data)
647 return css::uno::Any();
649 gint length;
650 const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
651 &length);
652 Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
653 gtk_selection_data_free(data);
654 aRet <<= aSeq;
655 return aRet;
658 std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
659 override
661 std::vector<css::datatransfer::DataFlavor> aVector;
663 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
665 GdkAtom *targets;
666 gint n_targets;
667 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
669 aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
670 g_free(targets);
673 return aVector;
677 class VclGtkClipboard :
678 public cppu::WeakComponentImplHelper<
679 datatransfer::clipboard::XSystemClipboard,
680 datatransfer::clipboard::XFlushableClipboard,
681 XServiceInfo>
683 GdkAtom m_nSelection;
684 osl::Mutex m_aMutex;
685 gulong m_nOwnerChangedSignalId;
686 Reference<css::datatransfer::XTransferable> m_aContents;
687 Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
688 std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
689 std::vector<GtkTargetEntry> m_aGtkTargets;
690 VclToGtkHelper m_aConversionHelper;
692 public:
694 explicit VclGtkClipboard(GdkAtom nSelection);
695 virtual ~VclGtkClipboard() override;
698 * XServiceInfo
701 virtual OUString SAL_CALL getImplementationName() override;
702 virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
703 virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
706 * XClipboard
709 virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
711 virtual void SAL_CALL setContents(
712 const Reference< css::datatransfer::XTransferable >& xTrans,
713 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
715 virtual OUString SAL_CALL getName() override;
718 * XClipboardEx
721 virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
724 * XFlushableClipboard
726 virtual void SAL_CALL flushClipboard() override;
729 * XClipboardNotifier
731 virtual void SAL_CALL addClipboardListener(
732 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
734 virtual void SAL_CALL removeClipboardListener(
735 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
737 void ClipboardGet(GtkSelectionData *selection_data, guint info);
738 void ClipboardClear();
739 void OwnerPossiblyChanged(GtkClipboard *clipboard);
742 OUString VclGtkClipboard::getImplementationName()
744 return "com.sun.star.datatransfer.VclGtkClipboard";
747 Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
749 Sequence<OUString> aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
750 return aRet;
753 sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
755 return cppu::supportsService(this, ServiceName);
758 Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
760 if (!m_aContents.is())
762 //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
763 //the owner of the clipboard and have not already fetched it.
764 m_aContents = new GtkClipboardTransferable(m_nSelection);
767 return m_aContents;
770 void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
772 if (!m_aContents.is())
773 return;
774 m_aConversionHelper.setSelectionData(m_aContents, selection_data, info);
777 namespace
779 const OString& getPID()
781 static OString sPID;
782 if (!sPID.getLength())
784 oslProcessIdentifier aProcessId = 0;
785 oslProcessInfo info;
786 info.Size = sizeof (oslProcessInfo);
787 if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
788 aProcessId = info.Ident;
789 sPID = OString::number(aProcessId);
791 return sPID;
795 namespace
797 void ClipboardGetFunc(GtkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
798 guint info,
799 gpointer user_data_or_owner)
801 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
802 pThis->ClipboardGet(selection_data, info);
805 void ClipboardClearFunc(GtkClipboard* /*clipboard*/, gpointer user_data_or_owner)
807 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
808 pThis->ClipboardClear();
811 void handle_owner_change(GtkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
813 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
814 pThis->OwnerPossiblyChanged(clipboard);
818 void VclGtkClipboard::OwnerPossiblyChanged(GtkClipboard* clipboard)
820 if (!m_aContents.is())
821 return;
823 //if gdk_display_supports_selection_notification is not supported, e.g. like
824 //right now under wayland, then you only get owner-changed notifications at
825 //opportune times when the selection might have changed. So here
826 //we see if the selection supports a dummy selection type identifying
827 //our pid, in which case it's us.
828 bool bSelf = false;
830 //disconnect and reconnect after gtk_clipboard_wait_for_targets to
831 //avoid possible recursion
832 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
834 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
835 GdkAtom *targets;
836 gint n_targets;
837 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
839 for (gint i = 0; i < n_targets && !bSelf; ++i)
841 gchar* pName = gdk_atom_name(targets[i]);
842 if (strcmp(pName, sTunnel.getStr()) == 0)
844 bSelf = true;
846 g_free(pName);
849 g_free(targets);
852 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
853 G_CALLBACK(handle_owner_change), this);
855 if (!bSelf)
857 //null out m_aContents to return control to the system-one which
858 //will be retrieved if getContents is called again
859 setContents(Reference<css::datatransfer::XTransferable>(),
860 Reference<css::datatransfer::clipboard::XClipboardOwner>());
864 void VclGtkClipboard::ClipboardClear()
866 for (auto &a : m_aGtkTargets)
867 g_free(a.target);
868 m_aGtkTargets.clear();
871 GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
873 GtkTargetEntry aEntry;
874 aEntry.target =
875 g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
876 aEntry.flags = 0;
877 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
878 DataFlavorEq(rFlavor));
879 if (it != aInfoToFlavor.end())
880 aEntry.info = std::distance(aInfoToFlavor.begin(), it);
881 else
883 aEntry.info = aInfoToFlavor.size();
884 aInfoToFlavor.push_back(rFlavor);
886 return aEntry;
889 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
890 GtkSelectionData *selection_data, guint info)
892 GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
893 RTL_TEXTENCODING_UTF8).getStr(),
894 false));
896 css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
897 if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
898 aFlavor.MimeType = "text/plain;charset=utf-8";
900 Sequence<sal_Int8> aData;
901 Any aValue;
905 aValue = rTrans->getTransferData(aFlavor);
907 catch (...)
911 if (aValue.getValueTypeClass() == TypeClass_STRING)
913 OUString aString;
914 aValue >>= aString;
915 aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
917 else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
919 aValue >>= aData;
921 else if (aFlavor.MimeType == "text/plain;charset=utf-8")
923 //didn't have utf-8, try utf-16 and convert
924 aFlavor.MimeType = "text/plain;charset=utf-16";
925 aFlavor.DataType = cppu::UnoType<OUString>::get();
928 aValue = rTrans->getTransferData(aFlavor);
930 catch (...)
933 OUString aString;
934 aValue >>= aString;
935 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
936 gtk_selection_data_set(selection_data, type, 8,
937 reinterpret_cast<const guchar *>(aUTF8String.getStr()),
938 aUTF8String.getLength());
939 return;
942 gtk_selection_data_set(selection_data, type, 8,
943 reinterpret_cast<const guchar *>(aData.getArray()),
944 aData.getLength());
947 VclGtkClipboard::VclGtkClipboard(GdkAtom nSelection)
948 : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
949 datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
950 (m_aMutex)
951 , m_nSelection(nSelection)
953 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
954 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
955 G_CALLBACK(handle_owner_change), this);
958 void VclGtkClipboard::flushClipboard()
960 SolarMutexGuard aGuard;
962 if (GDK_SELECTION_CLIPBOARD != m_nSelection)
963 return;
965 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
966 gtk_clipboard_store(clipboard);
969 VclGtkClipboard::~VclGtkClipboard()
971 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
972 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
973 if (!m_aGtkTargets.empty())
975 gtk_clipboard_clear(clipboard);
976 ClipboardClear();
978 assert(m_aGtkTargets.empty());
981 std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
983 std::vector<GtkTargetEntry> aGtkTargets;
985 bool bHaveText(false), bHaveUTF8(false);
986 for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
988 sal_Int32 nIndex(0);
989 if (rFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
991 bHaveText = true;
992 OUString aToken(rFlavor.MimeType.getToken(0, ';', nIndex));
993 if (aToken == "charset=utf-8")
995 bHaveUTF8 = true;
998 GtkTargetEntry aEntry(makeGtkTargetEntry(rFlavor));
999 aGtkTargets.push_back(aEntry);
1002 if (bHaveText)
1004 css::datatransfer::DataFlavor aFlavor;
1005 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
1006 if (!bHaveUTF8)
1008 aFlavor.MimeType = "text/plain;charset=utf-8";
1009 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1011 aFlavor.MimeType = "UTF8_STRING";
1012 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1013 aFlavor.MimeType = "STRING";
1014 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1017 return aGtkTargets;
1020 void VclGtkClipboard::setContents(
1021 const Reference< css::datatransfer::XTransferable >& xTrans,
1022 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
1024 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
1025 if (xTrans.is())
1027 aFormats = xTrans->getTransferDataFlavors();
1030 osl::ClearableMutexGuard aGuard( m_aMutex );
1031 Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
1032 Reference< datatransfer::XTransferable > xOldContents( m_aContents );
1033 m_aContents = xTrans;
1034 m_aOwner = xClipboardOwner;
1036 std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
1037 datatransfer::clipboard::ClipboardEvent aEv;
1039 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
1040 if (!m_aGtkTargets.empty())
1042 gtk_clipboard_clear(clipboard);
1043 ClipboardClear();
1045 assert(m_aGtkTargets.empty());
1046 if (m_aContents.is())
1048 std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1049 if (!aGtkTargets.empty())
1051 GtkTargetEntry aEntry;
1052 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1053 aEntry.target = g_strdup(sTunnel.getStr());
1054 aEntry.flags = 0;
1055 aEntry.info = 0;
1056 aGtkTargets.push_back(aEntry);
1058 gtk_clipboard_set_with_data(clipboard, aGtkTargets.data(), aGtkTargets.size(),
1059 ClipboardGetFunc, ClipboardClearFunc, this);
1060 gtk_clipboard_set_can_store(clipboard, aGtkTargets.data(), aGtkTargets.size());
1063 m_aGtkTargets = aGtkTargets;
1066 aEv.Contents = getContents();
1068 aGuard.clear();
1070 if (xOldOwner.is() && xOldOwner != xClipboardOwner)
1071 xOldOwner->lostOwnership( this, xOldContents );
1072 for (auto const& listener : aListeners)
1074 listener->changedContents( aEv );
1078 OUString VclGtkClipboard::getName()
1080 return (m_nSelection == GDK_SELECTION_CLIPBOARD) ? OUString("CLIPBOARD") : OUString("PRIMARY");
1083 sal_Int8 VclGtkClipboard::getRenderingCapabilities()
1085 return 0;
1088 void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1090 osl::ClearableMutexGuard aGuard( m_aMutex );
1092 m_aListeners.push_back( listener );
1095 void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1097 osl::ClearableMutexGuard aGuard( m_aMutex );
1099 m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
1102 Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
1104 OUString sel;
1105 if (!arguments.hasElements()) {
1106 sel = "CLIPBOARD";
1107 } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
1108 throw css::lang::IllegalArgumentException(
1109 "bad GtkInstance::CreateClipboard arguments",
1110 css::uno::Reference<css::uno::XInterface>(), -1);
1113 GdkAtom nSelection = (sel == "CLIPBOARD") ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY;
1115 auto it = m_aClipboards.find(nSelection);
1116 if (it != m_aClipboards.end())
1117 return it->second;
1119 Reference<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(new VclGtkClipboard(nSelection)));
1120 m_aClipboards[nSelection] = xClipboard;
1122 return xClipboard;
1125 GtkDropTarget::GtkDropTarget()
1126 : WeakComponentImplHelper(m_aMutex)
1127 , m_pFrame(nullptr)
1128 , m_pFormatConversionRequest(nullptr)
1129 , m_bActive(false)
1130 , m_bInDrag(false)
1131 , m_nDefaultActions(0)
1135 OUString SAL_CALL GtkDropTarget::getImplementationName()
1137 return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
1140 sal_Bool SAL_CALL GtkDropTarget::supportsService(OUString const & ServiceName)
1142 return cppu::supportsService(this, ServiceName);
1145 css::uno::Sequence<OUString> SAL_CALL GtkDropTarget::getSupportedServiceNames()
1147 Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
1148 return aRet;
1151 GtkDropTarget::~GtkDropTarget()
1153 if (m_pFrame)
1154 m_pFrame->deregisterDropTarget(this);
1157 void GtkDropTarget::deinitialize()
1159 m_pFrame = nullptr;
1160 m_bActive = false;
1163 void GtkDropTarget::initialize(const Sequence<Any>& rArguments)
1165 if (rArguments.getLength() < 2)
1167 throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
1168 static_cast<OWeakObject*>(this));
1171 sal_IntPtr nFrame = 0;
1172 rArguments.getConstArray()[1] >>= nFrame;
1174 if (!nFrame)
1176 throw RuntimeException("DropTarget::initialize: missing SalFrame",
1177 static_cast<OWeakObject*>(this));
1180 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1181 m_pFrame->registerDropTarget(this);
1182 m_bActive = true;
1185 void GtkDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1187 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1189 m_aListeners.push_back( xListener );
1192 void GtkDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1194 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1196 m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end());
1199 void GtkDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
1201 osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
1202 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1203 aGuard.clear();
1205 for (auto const& listener : aListeners)
1207 listener->drop( dtde );
1211 void GtkDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
1213 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1214 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1215 aGuard.clear();
1217 for (auto const& listener : aListeners)
1219 listener->dragEnter( dtde );
1223 void GtkDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
1225 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1226 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1227 aGuard.clear();
1229 for (auto const& listener : aListeners)
1231 listener->dragOver( dtde );
1235 void GtkDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
1237 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1238 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1239 aGuard.clear();
1241 for (auto const& listener : aListeners)
1243 listener->dragExit( dte );
1247 sal_Bool GtkDropTarget::isActive()
1249 return m_bActive;
1252 void GtkDropTarget::setActive(sal_Bool bActive)
1254 m_bActive = bActive;
1257 sal_Int8 GtkDropTarget::getDefaultActions()
1259 return m_nDefaultActions;
1262 void GtkDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
1264 m_nDefaultActions = nDefaultActions;
1267 Reference< XInterface > GtkInstance::CreateDropTarget()
1269 return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new GtkDropTarget));
1272 GtkDragSource::~GtkDragSource()
1274 if (m_pFrame)
1275 m_pFrame->deregisterDragSource(this);
1277 if (GtkDragSource::g_ActiveDragSource == this)
1279 SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkDragSource before dtor");
1280 GtkDragSource::g_ActiveDragSource = nullptr;
1284 void GtkDragSource::deinitialize()
1286 m_pFrame = nullptr;
1289 sal_Bool GtkDragSource::isDragImageSupported()
1291 return true;
1294 sal_Int32 GtkDragSource::getDefaultCursor( sal_Int8 )
1296 return 0;
1299 void GtkDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
1301 if (rArguments.getLength() < 2)
1303 throw RuntimeException("DragSource::initialize: Cannot install window event handler",
1304 static_cast<OWeakObject*>(this));
1307 sal_IntPtr nFrame = 0;
1308 rArguments.getConstArray()[1] >>= nFrame;
1310 if (!nFrame)
1312 throw RuntimeException("DragSource::initialize: missing SalFrame",
1313 static_cast<OWeakObject*>(this));
1316 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1317 m_pFrame->registerDragSource(this);
1320 OUString SAL_CALL GtkDragSource::getImplementationName()
1322 return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
1325 sal_Bool SAL_CALL GtkDragSource::supportsService(OUString const & ServiceName)
1327 return cppu::supportsService(this, ServiceName);
1330 css::uno::Sequence<OUString> SAL_CALL GtkDragSource::getSupportedServiceNames()
1332 Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
1333 return aRet;
1336 Reference< XInterface > GtkInstance::CreateDragSource()
1338 return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new GtkDragSource()) );
1341 class GtkOpenGLContext : public OpenGLContext
1343 GLWindow m_aGLWin;
1344 GtkWidget *m_pGLArea;
1345 GdkGLContext *m_pContext;
1346 guint m_nAreaFrameBuffer;
1347 guint m_nFrameBuffer;
1348 guint m_nRenderBuffer;
1349 guint m_nDepthBuffer;
1350 guint m_nFrameScratchBuffer;
1351 guint m_nRenderScratchBuffer;
1352 guint m_nDepthScratchBuffer;
1354 public:
1355 GtkOpenGLContext()
1356 : OpenGLContext()
1357 , m_pGLArea(nullptr)
1358 , m_pContext(nullptr)
1359 , m_nAreaFrameBuffer(0)
1360 , m_nFrameBuffer(0)
1361 , m_nRenderBuffer(0)
1362 , m_nDepthBuffer(0)
1363 , m_nFrameScratchBuffer(0)
1364 , m_nRenderScratchBuffer(0)
1365 , m_nDepthScratchBuffer(0)
1369 virtual void initWindow() override
1371 if( !m_pChildWindow )
1373 SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
1374 m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
1377 if (m_pChildWindow)
1379 InitChildWindow(m_pChildWindow.get());
1383 private:
1384 virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
1385 virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
1387 static void signalDestroy(GtkWidget*, gpointer context)
1389 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
1390 pThis->m_pGLArea = nullptr;
1393 static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
1395 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
1397 int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
1398 int width = pThis->m_aGLWin.Width * scale;
1399 int height = pThis->m_aGLWin.Height * scale;
1401 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1403 glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
1404 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1406 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1407 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1409 gdk_gl_context_make_current(pThis->m_pContext);
1410 return true;
1413 virtual void adjustToNewSize() override
1415 if (!m_pGLArea)
1416 return;
1418 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1419 int width = m_aGLWin.Width * scale;
1420 int height = m_aGLWin.Height * scale;
1422 // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
1423 int allocwidth = std::max(width, 1);
1424 int allocheight = std::max(height, 1);
1426 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1427 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1429 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1430 return;
1433 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1434 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1435 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1436 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1437 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
1439 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1440 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1441 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1442 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1444 gdk_gl_context_make_current(m_pContext);
1445 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1446 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1447 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
1449 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1450 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1451 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1452 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1453 glViewport(0, 0, width, height);
1455 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1456 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1457 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1458 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1459 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1461 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1462 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1463 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1464 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1466 glViewport(0, 0, width, height);
1469 virtual bool ImplInit() override
1471 const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
1472 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
1473 m_pGLArea = gtk_gl_area_new();
1474 g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
1475 g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
1476 gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
1477 gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
1478 gtk_widget_set_hexpand(m_pGLArea, true);
1479 gtk_widget_set_vexpand(m_pGLArea, true);
1480 gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
1481 gtk_widget_show_all(pParent);
1483 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1484 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1486 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1487 return false;
1490 gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
1491 glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
1493 GdkWindow *pWindow = gtk_widget_get_window(pParent);
1494 m_pContext = gdk_window_create_gl_context(pWindow, nullptr);
1495 if (!m_pContext)
1496 return false;
1498 if (!gdk_gl_context_realize(m_pContext, nullptr))
1499 return false;
1501 gdk_gl_context_make_current(m_pContext);
1502 glGenFramebuffersEXT(1, &m_nFrameBuffer);
1503 glGenRenderbuffersEXT(1, &m_nRenderBuffer);
1504 glGenRenderbuffersEXT(1, &m_nDepthBuffer);
1505 glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
1506 glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
1507 glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
1509 bool bRet = InitGL();
1510 InitGLDebugging();
1511 return bRet;
1514 virtual void restoreDefaultFramebuffer() override
1516 OpenGLContext::restoreDefaultFramebuffer();
1517 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1518 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1519 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1522 virtual void makeCurrent() override
1524 if (isCurrent())
1525 return;
1527 clearCurrent();
1529 if (m_pGLArea)
1531 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1532 int width = m_aGLWin.Width * scale;
1533 int height = m_aGLWin.Height * scale;
1535 gdk_gl_context_make_current(m_pContext);
1537 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1538 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1539 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1540 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1541 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1542 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1543 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1544 glViewport(0, 0, width, height);
1547 registerAsCurrent();
1550 virtual void destroyCurrentContext() override
1552 gdk_gl_context_clear_current();
1555 virtual bool isCurrent() override
1557 return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
1560 virtual void sync() override
1564 virtual void resetCurrent() override
1566 clearCurrent();
1567 gdk_gl_context_clear_current();
1570 virtual void swapBuffers() override
1572 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1573 int width = m_aGLWin.Width * scale;
1574 int height = m_aGLWin.Height * scale;
1576 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
1577 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1579 glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
1580 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1582 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1583 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1585 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
1586 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1588 gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
1589 BuffersSwapped();
1592 virtual ~GtkOpenGLContext() override
1594 if (m_pContext)
1596 g_clear_object(&m_pContext);
1601 OpenGLContext* GtkInstance::CreateOpenGLContext()
1603 return new GtkOpenGLContext;
1606 // tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
1607 bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
1609 auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
1610 if (!get_type)
1611 return false;
1612 return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
1615 bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
1617 auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
1618 if (!get_type)
1619 return false;
1620 return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
1623 class GtkInstanceBuilder;
1625 namespace
1627 void set_help_id(const GtkWidget *pWidget, const OString& rHelpId)
1629 gchar *helpid = g_strdup(rHelpId.getStr());
1630 g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
1633 OString get_help_id(const GtkWidget *pWidget)
1635 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
1636 const gchar* pStr = static_cast<const gchar*>(pData);
1637 return OString(pStr, pStr ? strlen(pStr) : 0);
1640 KeyEvent GtkToVcl(const GdkEventKey& rEvent)
1642 sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(rEvent.keyval);
1643 if (nKeyCode == 0)
1645 guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), rEvent.hardware_keycode, rEvent.group);
1646 nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
1648 nKeyCode |= GtkSalFrame::GetKeyModCode(rEvent.state);
1649 return KeyEvent(gdk_keyval_to_unicode(rEvent.keyval), nKeyCode, 0);
1653 static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
1655 MouseEventModifiers nMode = MouseEventModifiers::NONE;
1656 if ( nButton == MOUSE_LEFT )
1657 nMode |= MouseEventModifiers::SIMPLECLICK;
1658 if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
1659 nMode |= MouseEventModifiers::SELECT;
1660 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
1661 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
1662 nMode |= MouseEventModifiers::MULTISELECT;
1663 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
1664 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
1665 nMode |= MouseEventModifiers::RANGESELECT;
1666 return nMode;
1669 static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
1671 MouseEventModifiers nMode = MouseEventModifiers::NONE;
1672 if ( !nCode )
1673 nMode |= MouseEventModifiers::SIMPLEMOVE;
1674 if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
1675 nMode |= MouseEventModifiers::DRAGMOVE;
1676 if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
1677 nMode |= MouseEventModifiers::DRAGCOPY;
1678 return nMode;
1681 namespace
1683 #if GTK_CHECK_VERSION(3,22,0)
1684 bool SwapForRTL(GtkWidget* pWidget)
1686 GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
1687 if (eDir == GTK_TEXT_DIR_RTL)
1688 return true;
1689 if (eDir == GTK_TEXT_DIR_LTR)
1690 return false;
1691 return AllSettings::GetLayoutRTL();
1693 #endif
1695 GtkWidget* ensureEventWidget(GtkWidget* pWidget)
1697 if (!pWidget)
1698 return nullptr;
1700 GtkWidget* pMouseEventBox;
1701 // not every widget has a GdkWindow and can get any event, so if we
1702 // want an event it doesn't have, insert a GtkEventBox so we can get
1703 // those
1704 if (gtk_widget_get_has_window(pWidget))
1705 pMouseEventBox = pWidget;
1706 else
1708 // remove the widget and replace it with an eventbox and put the old
1709 // widget into it
1710 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
1712 g_object_ref(pWidget);
1714 gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
1715 if (GTK_IS_GRID(pParent))
1717 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
1718 "left-attach", &nTopAttach,
1719 "top-attach", &nLeftAttach,
1720 "width", &nWidth,
1721 "height", &nHeight,
1722 nullptr);
1725 gboolean bExpand(false), bFill(false);
1726 GtkPackType ePackType(GTK_PACK_START);
1727 guint nPadding(0);
1728 gint nPosition(0);
1729 if (GTK_IS_BOX(pParent))
1731 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
1732 "expand", &bExpand,
1733 "fill", &bFill,
1734 "pack-type", &ePackType,
1735 "padding", &nPadding,
1736 "position", &nPosition,
1737 nullptr);
1740 gtk_container_remove(GTK_CONTAINER(pParent), pWidget);
1742 pMouseEventBox = gtk_event_box_new();
1743 gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
1744 gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
1745 gtk_widget_set_visible(pMouseEventBox, gtk_widget_get_visible(pWidget));
1747 gtk_container_add(GTK_CONTAINER(pParent), pMouseEventBox);
1749 if (GTK_IS_GRID(pParent))
1751 gtk_container_child_set(GTK_CONTAINER(pParent), pMouseEventBox,
1752 "left-attach", nTopAttach,
1753 "top-attach", nLeftAttach,
1754 "width", nWidth,
1755 "height", nHeight,
1756 nullptr);
1759 if (GTK_IS_BOX(pParent))
1761 gtk_container_child_set(GTK_CONTAINER(pParent), pMouseEventBox,
1762 "expand", bExpand,
1763 "fill", bFill,
1764 "pack-type", ePackType,
1765 "padding", nPadding,
1766 "position", nPosition,
1767 nullptr);
1770 gtk_container_add(GTK_CONTAINER(pMouseEventBox), pWidget);
1771 g_object_unref(pWidget);
1773 gtk_widget_set_hexpand(pMouseEventBox, gtk_widget_get_hexpand(pWidget));
1774 gtk_widget_set_vexpand(pMouseEventBox, gtk_widget_get_vexpand(pWidget));
1777 return pMouseEventBox;
1781 class GtkInstanceWidget : public virtual weld::Widget
1783 protected:
1784 GtkWidget* m_pWidget;
1785 GtkWidget* m_pMouseEventBox;
1786 GtkInstanceBuilder* m_pBuilder;
1788 DECL_LINK(async_signal_focus_in, void*, void);
1789 DECL_LINK(async_signal_focus_out, void*, void);
1791 void launch_signal_focus_in()
1793 // in e.g. function wizard RefEdits we want to select all when we get focus
1794 // but there are pending gtk handlers which change selection after our handler
1795 // post our focus in event to happen after those finish
1796 if (m_pFocusInEvent)
1797 Application::RemoveUserEvent(m_pFocusInEvent);
1798 m_pFocusInEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_in));
1801 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
1803 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1804 pThis->launch_signal_focus_in();
1805 return false;
1808 void signal_focus_in()
1810 m_aFocusInHdl.Call(*this);
1813 static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
1815 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1816 SolarMutexGuard aGuard;
1817 return pThis->signal_mnemonic_activate();
1820 bool signal_mnemonic_activate()
1822 return m_aMnemonicActivateHdl.Call(*this);
1825 void launch_signal_focus_out()
1827 // tdf#127262 because focus in is async, focus out must not appear out
1828 // of sequence to focus in
1829 if (m_pFocusOutEvent)
1830 Application::RemoveUserEvent(m_pFocusOutEvent);
1831 m_pFocusOutEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_out));
1834 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
1836 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1837 SolarMutexGuard aGuard;
1838 pThis->launch_signal_focus_out();
1839 return false;
1842 void signal_focus_out()
1844 m_aFocusOutHdl.Call(*this);
1847 void ensureEventWidget()
1849 if (!m_pMouseEventBox)
1850 m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
1853 void ensureButtonPressSignal()
1855 if (!m_nButtonPressSignalId)
1857 ensureEventWidget();
1858 m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButton), this);
1862 static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
1864 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1865 SolarMutexGuard aGuard;
1866 //center it when we don't know where else to use
1867 Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
1868 gtk_widget_get_allocated_height(pWidget) / 2);
1869 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
1870 return pThis->signal_popup_menu(aCEvt);
1873 bool SwapForRTL() const
1875 GtkTextDirection eDir = gtk_widget_get_direction(m_pWidget);
1876 if (eDir == GTK_TEXT_DIR_RTL)
1877 return true;
1878 if (eDir == GTK_TEXT_DIR_LTR)
1879 return false;
1880 return AllSettings::GetLayoutRTL();
1883 private:
1884 bool m_bTakeOwnership;
1885 bool m_bFrozen;
1886 bool m_bDraggedOver;
1887 sal_uInt16 m_nLastMouseButton;
1888 sal_uInt16 m_nLastMouseClicks;
1889 ImplSVEvent* m_pFocusInEvent;
1890 ImplSVEvent* m_pFocusOutEvent;
1891 GtkCssProvider* m_pBgCssProvider;
1892 gulong m_nFocusInSignalId;
1893 gulong m_nMnemonicActivateSignalId;
1894 gulong m_nFocusOutSignalId;
1895 gulong m_nKeyPressSignalId;
1896 gulong m_nKeyReleaseSignalId;
1897 gulong m_nSizeAllocateSignalId;
1898 gulong m_nButtonPressSignalId;
1899 gulong m_nMotionSignalId;
1900 gulong m_nLeaveSignalId;
1901 gulong m_nEnterSignalId;
1902 gulong m_nButtonReleaseSignalId;
1903 gulong m_nDragMotionSignalId;
1904 gulong m_nDragDropSignalId;
1905 gulong m_nDragDropReceivedSignalId;
1906 gulong m_nDragLeaveSignalId;
1908 rtl::Reference<GtkDropTarget> m_xDropTarget;
1909 std::vector<AtkRelation*> m_aExtraAtkRelations;
1911 static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
1913 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1914 SolarMutexGuard aGuard;
1915 pThis->signal_size_allocate(allocation->width, allocation->height);
1918 static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
1920 // #i1820# use locale specific decimal separator
1921 if (pEvent->keyval == GDK_KEY_KP_Decimal && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
1923 OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
1924 pEvent->keyval = aSep[0];
1927 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1928 return pThis->signal_key(pEvent);
1931 virtual bool signal_popup_menu(const CommandEvent&)
1933 return false;
1936 static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
1938 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1939 SolarMutexGuard aGuard;
1940 return pThis->signal_button(pEvent);
1943 bool signal_button(GdkEventButton* pEvent)
1945 Point aPos(pEvent->x, pEvent->y);
1946 if (SwapForRTL())
1947 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
1949 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
1951 //if handled for context menu, stop processing
1952 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
1953 if (signal_popup_menu(aCEvt))
1954 return true;
1957 if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
1958 return false;
1960 SalEvent nEventType = SalEvent::NONE;
1961 switch (pEvent->type)
1963 case GDK_BUTTON_PRESS:
1964 if (GdkEvent* pPeekEvent = gdk_event_peek())
1966 bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
1967 pPeekEvent->type == GDK_3BUTTON_PRESS;
1968 gdk_event_free(pPeekEvent);
1969 if (bSkip)
1971 return true;
1974 nEventType = SalEvent::MouseButtonDown;
1975 m_nLastMouseClicks = 1;
1976 break;
1977 case GDK_2BUTTON_PRESS:
1978 m_nLastMouseClicks = 2;
1979 nEventType = SalEvent::MouseButtonDown;
1980 break;
1981 case GDK_3BUTTON_PRESS:
1982 m_nLastMouseClicks = 3;
1983 nEventType = SalEvent::MouseButtonDown;
1984 break;
1985 case GDK_BUTTON_RELEASE:
1986 nEventType = SalEvent::MouseButtonUp;
1987 break;
1988 default:
1989 return false;
1992 switch (pEvent->button)
1994 case 1:
1995 m_nLastMouseButton = MOUSE_LEFT;
1996 break;
1997 case 2:
1998 m_nLastMouseButton = MOUSE_MIDDLE;
1999 break;
2000 case 3:
2001 m_nLastMouseButton = MOUSE_RIGHT;
2002 break;
2003 default:
2004 return false;
2007 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2008 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2009 MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
2011 if (nEventType == SalEvent::MouseButtonDown)
2013 if (!m_aMousePressHdl.IsSet())
2014 return false;
2015 return m_aMousePressHdl.Call(aMEvt);
2018 if (!m_aMouseReleaseHdl.IsSet())
2019 return false;
2020 return m_aMouseReleaseHdl.Call(aMEvt);
2023 static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
2025 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2026 SolarMutexGuard aGuard;
2027 return pThis->signal_motion(pEvent);
2030 bool signal_motion(const GdkEventMotion* pEvent)
2032 if (!m_aMouseMotionHdl.IsSet())
2033 return false;
2035 Point aPos(pEvent->x, pEvent->y);
2036 if (SwapForRTL())
2037 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2038 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2039 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2040 MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nCode, nCode);
2042 m_aMouseMotionHdl.Call(aMEvt);
2043 return true;
2046 static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
2048 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2049 SolarMutexGuard aGuard;
2050 return pThis->signal_crossing(pEvent);
2053 bool signal_crossing(const GdkEventCrossing* pEvent)
2055 if (!m_aMouseMotionHdl.IsSet())
2056 return false;
2058 Point aPos(pEvent->x, pEvent->y);
2059 if (SwapForRTL())
2060 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2061 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2062 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2063 MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
2064 eModifiers = eModifiers | (pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW);
2065 MouseEvent aMEvt(aPos, 0, eModifiers, nCode, nCode);
2067 m_aMouseMotionHdl.Call(aMEvt);
2068 return true;
2071 virtual void drag_started()
2075 static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
2077 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2078 if (!pThis->m_bDraggedOver)
2080 pThis->m_bDraggedOver = true;
2081 pThis->drag_started();
2083 return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
2086 static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
2088 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2089 return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
2092 static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
2094 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2095 pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
2098 virtual void drag_ended()
2102 static void signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer widget)
2104 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2105 pThis->m_xDropTarget->signalDragLeave(pWidget, context, time);
2106 if (pThis->m_bDraggedOver)
2108 pThis->m_bDraggedOver = false;
2109 pThis->drag_ended();
2113 void set_background(const OUString* pColor)
2115 if (!pColor && !m_pBgCssProvider)
2116 return;
2117 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
2118 if (m_pBgCssProvider)
2120 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
2121 m_pBgCssProvider = nullptr;
2123 if (!pColor)
2124 return;
2125 m_pBgCssProvider = gtk_css_provider_new();
2126 OUString aBuffer = "* { background-color: #" + *pColor + "; }";
2127 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
2128 gtk_css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
2129 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
2130 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2133 public:
2134 GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
2135 : m_pWidget(pWidget)
2136 , m_pMouseEventBox(nullptr)
2137 , m_pBuilder(pBuilder)
2138 , m_bTakeOwnership(bTakeOwnership)
2139 , m_bFrozen(false)
2140 , m_bDraggedOver(false)
2141 , m_nLastMouseButton(0)
2142 , m_nLastMouseClicks(0)
2143 , m_pFocusInEvent(nullptr)
2144 , m_pFocusOutEvent(nullptr)
2145 , m_pBgCssProvider(nullptr)
2146 , m_nFocusInSignalId(0)
2147 , m_nMnemonicActivateSignalId(0)
2148 , m_nFocusOutSignalId(0)
2149 , m_nKeyPressSignalId(0)
2150 , m_nKeyReleaseSignalId(0)
2151 , m_nSizeAllocateSignalId(0)
2152 , m_nButtonPressSignalId(0)
2153 , m_nMotionSignalId(0)
2154 , m_nLeaveSignalId(0)
2155 , m_nEnterSignalId(0)
2156 , m_nButtonReleaseSignalId(0)
2157 , m_nDragMotionSignalId(0)
2158 , m_nDragDropSignalId(0)
2159 , m_nDragDropReceivedSignalId(0)
2160 , m_nDragLeaveSignalId(0)
2164 virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
2166 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
2167 weld::Widget::connect_key_press(rLink);
2170 virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
2172 m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
2173 weld::Widget::connect_key_release(rLink);
2176 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
2178 ensureButtonPressSignal();
2179 weld::Widget::connect_mouse_press(rLink);
2182 virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
2184 ensureEventWidget();
2185 m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
2186 m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
2187 m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
2188 weld::Widget::connect_mouse_move(rLink);
2191 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
2193 ensureEventWidget();
2194 m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButton), this);
2195 weld::Widget::connect_mouse_release(rLink);
2198 virtual void set_sensitive(bool sensitive) override
2200 gtk_widget_set_sensitive(m_pWidget, sensitive);
2203 virtual bool get_sensitive() const override
2205 return gtk_widget_get_sensitive(m_pWidget);
2208 virtual bool get_visible() const override
2210 return gtk_widget_get_visible(m_pWidget);
2213 virtual bool is_visible() const override
2215 return gtk_widget_is_visible(m_pWidget);
2218 virtual void set_can_focus(bool bCanFocus) override
2220 gtk_widget_set_can_focus(m_pWidget, bCanFocus);
2223 virtual void grab_focus() override
2225 disable_notify_events();
2226 gtk_widget_grab_focus(m_pWidget);
2227 enable_notify_events();
2230 virtual bool has_focus() const override
2232 return gtk_widget_has_focus(m_pWidget);
2235 virtual bool is_active() const override
2237 GtkWindow* pTopLevel = GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
2238 return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
2241 virtual void set_has_default(bool has_default) override
2243 g_object_set(G_OBJECT(m_pWidget), "has-default", has_default, nullptr);
2246 virtual bool get_has_default() const override
2248 gboolean has_default(false);
2249 g_object_get(G_OBJECT(m_pWidget), "has-default", &has_default, nullptr);
2250 return has_default;
2253 virtual void show() override
2255 gtk_widget_show(m_pWidget);
2258 virtual void hide() override
2260 gtk_widget_hide(m_pWidget);
2263 virtual void set_size_request(int nWidth, int nHeight) override
2265 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
2266 if (GTK_IS_VIEWPORT(pParent))
2267 pParent = gtk_widget_get_parent(pParent);
2268 if (GTK_IS_SCROLLED_WINDOW(pParent))
2270 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
2271 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
2273 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
2276 virtual Size get_size_request() const override
2278 int nWidth, nHeight;
2279 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
2280 return Size(nWidth, nHeight);
2283 virtual Size get_preferred_size() const override
2285 GtkRequisition size;
2286 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
2287 return Size(size.width, size.height);
2290 virtual float get_approximate_digit_width() const override
2292 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2293 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
2294 pango_context_get_font_description(pContext),
2295 pango_context_get_language(pContext));
2296 float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
2297 pango_font_metrics_unref(pMetrics);
2299 return nDigitWidth / PANGO_SCALE;
2302 virtual int get_text_height() const override
2304 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2305 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
2306 pango_context_get_font_description(pContext),
2307 pango_context_get_language(pContext));
2308 int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
2309 pango_font_metrics_unref(pMetrics);
2310 return nLineHeight / PANGO_SCALE;
2313 virtual Size get_pixel_size(const OUString& rText) const override
2315 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
2316 PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
2317 gint nWidth, nHeight;
2318 pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
2319 g_object_unref(pLayout);
2320 return Size(nWidth, nHeight);
2323 virtual vcl::Font get_font() override
2325 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2326 return pango_to_vcl(pango_context_get_font_description(pContext),
2327 Application::GetSettings().GetUILanguageTag().getLocale());
2330 virtual void set_grid_left_attach(int nAttach) override
2332 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2333 gtk_container_child_set(pParent, m_pWidget, "left-attach", nAttach, nullptr);
2336 virtual int get_grid_left_attach() const override
2338 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2339 gint nAttach(0);
2340 gtk_container_child_get(pParent, m_pWidget, "left-attach", &nAttach, nullptr);
2341 return nAttach;
2344 virtual void set_grid_width(int nCols) override
2346 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2347 gtk_container_child_set(pParent, m_pWidget, "width", nCols, nullptr);
2350 virtual void set_grid_top_attach(int nAttach) override
2352 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2353 gtk_container_child_set(pParent, m_pWidget, "top-attach", nAttach, nullptr);
2356 virtual int get_grid_top_attach() const override
2358 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2359 gint nAttach(0);
2360 gtk_container_child_get(pParent, m_pWidget, "top-attach", &nAttach, nullptr);
2361 return nAttach;
2364 virtual void set_hexpand(bool bExpand) override
2366 gtk_widget_set_hexpand(m_pWidget, bExpand);
2369 virtual bool get_hexpand() const override
2371 return gtk_widget_get_hexpand(m_pWidget);
2374 virtual void set_vexpand(bool bExpand) override
2376 gtk_widget_set_vexpand(m_pWidget, bExpand);
2379 virtual bool get_vexpand() const override
2381 return gtk_widget_get_vexpand(m_pWidget);
2384 virtual void set_secondary(bool bSecondary) override
2386 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
2387 if (pParent && GTK_IS_BUTTON_BOX(pParent))
2388 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(pParent), m_pWidget, bSecondary);
2391 virtual void set_margin_top(int nMargin) override
2393 gtk_widget_set_margin_top(m_pWidget, nMargin);
2396 virtual void set_margin_bottom(int nMargin) override
2398 gtk_widget_set_margin_bottom(m_pWidget, nMargin);
2401 virtual void set_margin_left(int nMargin) override
2403 gtk_widget_set_margin_left(m_pWidget, nMargin);
2406 virtual void set_margin_right(int nMargin) override
2408 gtk_widget_set_margin_right(m_pWidget, nMargin);
2411 virtual int get_margin_top() const override
2413 return gtk_widget_get_margin_top(m_pWidget);
2416 virtual int get_margin_bottom() const override
2418 return gtk_widget_get_margin_bottom(m_pWidget);
2421 virtual int get_margin_left() const override
2423 return gtk_widget_get_margin_left(m_pWidget);
2426 virtual int get_margin_right() const override
2428 return gtk_widget_get_margin_right(m_pWidget);
2431 virtual void set_accessible_name(const OUString& rName) override
2433 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2434 if (!pAtkObject)
2435 return;
2436 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
2439 virtual OUString get_accessible_name() const override
2441 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2442 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
2443 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2446 virtual OUString get_accessible_description() const override
2448 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2449 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
2450 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2453 virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
2455 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2456 if (!pAtkObject)
2457 return;
2458 AtkObject *pAtkLabel = pLabel ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget()) : nullptr;
2459 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2460 AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
2461 if (pRelation)
2462 atk_relation_set_remove(pRelationSet, pRelation);
2463 if (pAtkLabel)
2465 AtkObject *obj_array[1];
2466 obj_array[0] = pAtkLabel;
2467 pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABELLED_BY);
2468 atk_relation_set_add(pRelationSet, pRelation);
2470 g_object_unref(pRelationSet);
2473 virtual void set_accessible_relation_label_for(weld::Widget* pLabeled) override
2475 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2476 if (!pAtkObject)
2477 return;
2478 AtkObject *pAtkLabeled = pLabeled ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabeled).getWidget()) : nullptr;
2479 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2480 AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR);
2481 if (pRelation)
2482 atk_relation_set_remove(pRelationSet, pRelation);
2483 if (pAtkLabeled)
2485 AtkObject *obj_array[1];
2486 obj_array[0] = pAtkLabeled;
2487 pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABEL_FOR);
2488 atk_relation_set_add(pRelationSet, pRelation);
2490 g_object_unref(pRelationSet);
2493 virtual void add_extra_accessible_relation(const css::accessibility::AccessibleRelation &rRelation) override
2495 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2496 if (!pAtkObject)
2497 return;
2499 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2500 AtkRelation *pRel = atk_object_wrapper_relation_new(rRelation);
2501 m_aExtraAtkRelations.push_back(pRel);
2502 atk_relation_set_add(pRelationSet, pRel);
2503 g_object_unref(pRel);
2504 g_object_unref(pRelationSet);
2507 virtual void clear_extra_accessible_relations() override
2509 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2510 if (!pAtkObject)
2511 return;
2513 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2514 for (AtkRelation* pRel : m_aExtraAtkRelations)
2515 atk_relation_set_remove(pRelationSet, pRel);
2516 m_aExtraAtkRelations.clear();
2517 g_object_unref(pRelationSet);
2520 virtual bool get_extents_relative_to(weld::Widget& rRelative, int& x, int &y, int& width, int &height) override
2522 //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
2523 //the document underneath to auto-scroll to place content in a visible location
2524 bool ret = gtk_widget_translate_coordinates(m_pWidget,
2525 dynamic_cast<GtkInstanceWidget&>(rRelative).getWidget(),
2526 0, 0, &x, &y);
2527 width = gtk_widget_get_allocated_width(m_pWidget);
2528 height = gtk_widget_get_allocated_height(m_pWidget);
2529 return ret;
2532 virtual void set_tooltip_text(const OUString& rTip) override
2534 gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
2537 virtual OUString get_tooltip_text() const override
2539 const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
2540 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2543 virtual std::unique_ptr<weld::Container> weld_parent() const override;
2545 virtual OString get_buildable_name() const override
2547 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
2548 return OString(pStr, pStr ? strlen(pStr) : 0);
2551 virtual void set_help_id(const OString& rHelpId) override
2553 ::set_help_id(m_pWidget, rHelpId);
2556 virtual OString get_help_id() const override
2558 OString sRet = ::get_help_id(m_pWidget);
2559 if (sRet.isEmpty())
2560 sRet = OString("null");
2561 return sRet;
2564 GtkWidget* getWidget()
2566 return m_pWidget;
2569 GtkWindow* getWindow()
2571 return GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
2574 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
2576 m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
2577 weld::Widget::connect_focus_in(rLink);
2580 virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
2582 m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
2583 weld::Widget::connect_mnemonic_activate(rLink);
2586 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
2588 m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
2589 weld::Widget::connect_focus_out(rLink);
2592 virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
2594 m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size_allocate", G_CALLBACK(signalSizeAllocate), this);
2595 weld::Widget::connect_size_allocate(rLink);
2598 virtual void signal_size_allocate(guint nWidth, guint nHeight)
2600 m_aSizeAllocateHdl.Call(Size(nWidth, nHeight));
2603 gboolean signal_key(const GdkEventKey* pEvent)
2605 if (pEvent->type == GDK_KEY_PRESS && m_aKeyPressHdl.IsSet())
2607 SolarMutexGuard aGuard;
2608 return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
2610 if (pEvent->type == GDK_KEY_RELEASE && m_aKeyReleaseHdl.IsSet())
2612 SolarMutexGuard aGuard;
2613 return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
2615 return false;
2618 virtual void grab_add() override
2620 gtk_grab_add(m_pWidget);
2623 virtual bool has_grab() const override
2625 return gtk_widget_has_grab(m_pWidget);
2628 virtual void grab_remove() override
2630 gtk_grab_remove(m_pWidget);
2633 virtual bool get_direction() const override
2635 return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
2638 virtual void set_direction(bool bRTL) override
2640 gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
2643 virtual void freeze() override
2645 gtk_widget_freeze_child_notify(m_pWidget);
2646 m_bFrozen = true;
2649 virtual void thaw() override
2651 gtk_widget_thaw_child_notify(m_pWidget);
2652 m_bFrozen = false;
2655 bool get_frozen() const { return m_bFrozen; }
2657 virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
2659 if (!m_xDropTarget)
2661 m_xDropTarget.set(new GtkDropTarget);
2662 if (!gtk_drag_dest_get_track_motion(m_pWidget))
2664 gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
2665 gtk_drag_dest_set_track_motion(m_pWidget, true);
2667 m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
2668 m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
2669 m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
2670 m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
2672 return m_xDropTarget.get();
2675 virtual void set_stack_background() override
2677 OUString sColor = Application::GetSettings().GetStyleSettings().GetWindowColor().AsRGBHexString();
2678 set_background(&sColor);
2681 virtual void set_highlight_background() override
2683 OUString sColor = Application::GetSettings().GetStyleSettings().GetHighlightColor().AsRGBHexString();
2684 set_background(&sColor);
2687 virtual ~GtkInstanceWidget() override
2689 if (m_pFocusInEvent)
2690 Application::RemoveUserEvent(m_pFocusInEvent);
2691 if (m_pFocusOutEvent)
2692 Application::RemoveUserEvent(m_pFocusOutEvent);
2693 if (m_nDragMotionSignalId)
2694 g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
2695 if (m_nDragDropSignalId)
2696 g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
2697 if (m_nDragDropReceivedSignalId)
2698 g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
2699 if (m_nDragLeaveSignalId)
2700 g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
2701 if (m_nKeyPressSignalId)
2702 g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
2703 if (m_nKeyReleaseSignalId)
2704 g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
2705 if (m_nButtonPressSignalId)
2706 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
2707 if (m_nMotionSignalId)
2708 g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
2709 if (m_nLeaveSignalId)
2710 g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
2711 if (m_nEnterSignalId)
2712 g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
2713 if (m_nButtonReleaseSignalId)
2714 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
2715 if (m_nFocusInSignalId)
2716 g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
2717 if (m_nMnemonicActivateSignalId)
2718 g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
2719 if (m_nFocusOutSignalId)
2720 g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
2721 if (m_nSizeAllocateSignalId)
2722 g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
2724 set_background(nullptr);
2726 if (m_pMouseEventBox && m_pMouseEventBox != m_pWidget)
2728 // put things back they way we found them
2729 GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
2731 g_object_ref(m_pWidget);
2732 gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
2734 gtk_widget_destroy(m_pMouseEventBox);
2736 gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
2737 g_object_unref(m_pWidget);
2740 if (m_bTakeOwnership)
2741 gtk_widget_destroy(m_pWidget);
2744 virtual void disable_notify_events()
2746 if (m_nFocusInSignalId)
2747 g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
2748 if (m_nMnemonicActivateSignalId)
2749 g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
2750 if (m_nFocusOutSignalId)
2751 g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
2752 if (m_nSizeAllocateSignalId)
2753 g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
2756 virtual void enable_notify_events()
2758 if (m_nSizeAllocateSignalId)
2759 g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
2760 if (m_nFocusOutSignalId)
2761 g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
2762 if (m_nMnemonicActivateSignalId)
2763 g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
2764 if (m_nFocusInSignalId)
2765 g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
2768 virtual void help_hierarchy_foreach(const std::function<bool(const OString&)>& func) override;
2770 virtual OUString strip_mnemonic(const OUString &rLabel) const override
2772 return rLabel.replaceFirst("_", "");
2775 virtual VclPtr<VirtualDevice> create_virtual_device() const override
2777 // create with no separate alpha layer like everything sane does
2778 auto xRet = VclPtr<VirtualDevice>::Create();
2779 xRet->SetBackground(COL_TRANSPARENT);
2780 return xRet;
2784 IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_in, void*, void)
2786 m_pFocusInEvent = nullptr;
2787 signal_focus_in();
2790 IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_out, void*, void)
2792 m_pFocusOutEvent = nullptr;
2793 signal_focus_out();
2796 namespace
2798 OString MapToGtkAccelerator(const OUString &rStr)
2800 return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
2803 OUString get_label(GtkLabel* pLabel)
2805 const gchar* pStr = gtk_label_get_label(pLabel);
2806 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2809 void set_label(GtkLabel* pLabel, const OUString& rText)
2811 gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
2814 OUString get_label(GtkButton* pButton)
2816 const gchar* pStr = gtk_button_get_label(pButton);
2817 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2820 void set_label(GtkButton* pButton, const OUString& rText)
2822 gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
2825 OUString get_title(GtkWindow* pWindow)
2827 const gchar* pStr = gtk_window_get_title(pWindow);
2828 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2831 void set_title(GtkWindow* pWindow, const OUString& rTitle)
2833 gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
2836 OUString get_primary_text(GtkMessageDialog* pMessageDialog)
2838 gchar* pText = nullptr;
2839 g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
2840 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
2843 void set_primary_text(GtkMessageDialog* pMessageDialog, const OUString& rText)
2845 g_object_set(G_OBJECT(pMessageDialog), "text",
2846 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
2847 nullptr);
2850 void set_secondary_text(GtkMessageDialog* pMessageDialog, const OUString& rText)
2852 g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
2853 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
2854 nullptr);
2857 OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
2859 gchar* pText = nullptr;
2860 g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
2861 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
2865 namespace
2867 GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
2869 GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new();
2870 gdk_pixbuf_loader_write(pixbuf_loader, static_cast<const guchar*>(rStream.GetData()),
2871 rStream.TellEnd(), nullptr);
2872 gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
2873 GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
2874 if (pixbuf)
2875 g_object_ref(pixbuf);
2876 g_object_unref(pixbuf_loader);
2877 return pixbuf;
2880 GdkPixbuf* load_icon_by_name(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
2882 auto xMemStm = ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
2883 if (!xMemStm)
2884 return nullptr;
2885 return load_icon_from_stream(*xMemStm);
2889 GdkPixbuf* load_icon_by_name(const OUString& rIconName)
2891 OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
2892 OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
2893 return load_icon_by_name(rIconName, sIconTheme, sUILang);
2896 namespace
2898 GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
2900 Image aImage(rImage);
2902 std::unique_ptr<SvMemoryStream> xMemStm(new SvMemoryStream);
2903 vcl::PNGWriter aWriter(aImage.GetBitmapEx());
2904 aWriter.Write(*xMemStm);
2906 return load_icon_from_stream(*xMemStm);
2909 GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
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);
2916 cairo_surface_t* surface;
2917 if (m_fXScale != 1.0 || m_fYScale != -1)
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);
2928 else
2929 surface = orig_surface;
2931 GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
2933 if (surface != orig_surface)
2934 cairo_surface_destroy(surface);
2936 return pRet;
2939 GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
2941 GtkWidget* pImage = nullptr;
2942 if (gtk_check_version(3, 20, 0) == nullptr)
2944 cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
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());
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);
2959 else
2961 GdkPixbuf* pixbuf = getPixbuf(rImageSurface);
2962 pImage = gtk_image_new_from_pixbuf(pixbuf);
2963 g_object_unref(pixbuf);
2965 return pImage;
2969 class MenuHelper
2971 protected:
2972 GtkMenu* m_pMenu;
2973 bool m_bTakeOwnership;
2974 std::map<OString, GtkMenuItem*> m_aMap;
2975 private:
2977 static void collect(GtkWidget* pItem, gpointer widget)
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);
2986 static void signalActivate(GtkMenuItem* pItem, gpointer widget)
2988 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
2989 SolarMutexGuard aGuard;
2990 pThis->signal_activate(pItem);
2993 virtual void signal_activate(GtkMenuItem* pItem) = 0;
2995 public:
2996 MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
2997 : m_pMenu(pMenu)
2998 , m_bTakeOwnership(bTakeOwnership)
3000 if (!m_pMenu)
3001 return;
3002 gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
3005 void add_to_map(GtkMenuItem* pMenuItem)
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);
3013 void remove_from_map(GtkMenuItem* pMenuItem)
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);
3022 void disable_item_notify_events()
3024 for (auto& a : m_aMap)
3025 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
3028 void enable_item_notify_events()
3030 for (auto& a : m_aMap)
3031 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
3034 void insert_item(int pos, const OUString& rId, const OUString& rStr,
3035 const OUString* pIconName, const VirtualDevice* pImageSurface,
3036 bool bCheck)
3038 GtkWidget* pImage = nullptr;
3039 if (pIconName && !pIconName->isEmpty())
3041 GdkPixbuf* pixbuf = load_icon_by_name(*pIconName);
3042 if (!pixbuf)
3044 pImage = gtk_image_new_from_pixbuf(pixbuf);
3045 g_object_unref(pixbuf);
3048 else if (pImageSurface)
3049 pImage = image_new_from_virtual_device(*pImageSurface);
3051 GtkWidget *pItem;
3052 if (pImage)
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);
3062 else
3064 pItem = bCheck ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
3065 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
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);
3075 void insert_separator(int pos, const OUString& rId)
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);
3086 void remove_item(const OString& rIdent)
3088 GtkMenuItem* pMenuItem = m_aMap[rIdent];
3089 remove_from_map(pMenuItem);
3090 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
3093 void set_item_sensitive(const OString& rIdent, bool bSensitive)
3095 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
3098 void set_item_active(const OString& rIdent, bool bActive)
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();
3105 bool get_item_active(const OString& rIdent) const
3107 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
3110 void set_item_label(const OString& rIdent, const OUString& rText)
3112 gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
3115 OUString get_item_label(const OString& rIdent) const
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);
3121 void set_item_help_id(const OString& rIdent, const OString& rHelpId)
3123 set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
3126 OString get_item_help_id(const OString& rIdent) const
3128 return get_help_id(GTK_WIDGET(m_aMap.find(rIdent)->second));
3131 void set_item_visible(const OString& rIdent, bool bShow)
3133 GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
3134 if (bShow)
3135 gtk_widget_show(pWidget);
3136 else
3137 gtk_widget_hide(pWidget);
3140 void clear_items()
3142 for (const auto& a : m_aMap)
3144 GtkMenuItem* pMenuItem = a.second;
3145 g_signal_handlers_disconnect_by_data(pMenuItem, this);
3146 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
3148 m_aMap.clear();
3151 GtkMenu* getMenu() const
3153 return m_pMenu;
3156 virtual ~MenuHelper()
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));
3165 class GtkInstanceSizeGroup : public weld::SizeGroup
3167 private:
3168 GtkSizeGroup* m_pGroup;
3169 public:
3170 GtkInstanceSizeGroup()
3171 : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
3174 virtual void add_widget(weld::Widget* pWidget) override
3176 GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3177 assert(pVclWidget);
3178 gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
3180 virtual void set_mode(VclSizeGroupMode eVclMode) override
3182 GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
3183 switch (eVclMode)
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;
3198 gtk_size_group_set_mode(m_pGroup, eGtkMode);
3200 virtual ~GtkInstanceSizeGroup() override
3202 g_object_unref(m_pGroup);
3206 namespace
3208 class ChildFrame : public WorkWindow
3210 public:
3211 ChildFrame(vcl::Window* pParent, WinBits nStyle)
3212 : WorkWindow(pParent, nStyle)
3215 virtual void Resize() override
3217 WorkWindow::Resize();
3218 if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
3219 pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
3224 class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
3226 private:
3227 GtkContainer* m_pContainer;
3229 static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
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);
3237 public:
3238 GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3239 : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
3240 , m_pContainer(pContainer)
3244 GtkContainer* getContainer() { return m_pContainer; }
3246 virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
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);
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);
3261 virtual void recursively_unset_default_buttons() override
3263 implResetDefault(GTK_WIDGET(m_pContainer), nullptr);
3266 virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
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);
3275 // relocate that toplevel GtkEventBox into this widget
3276 GtkWidget* pWindow = pGtkFrame->getWindow();
3278 GtkWidget* pParent = gtk_widget_get_parent(pWindow);
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);
3290 xEmbedWindow->Show();
3291 css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
3292 return xWindow;
3296 std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
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);
3304 class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
3306 private:
3307 GtkBox* m_pBox;
3309 public:
3310 GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3311 : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
3312 , m_pBox(pBox)
3316 virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
3318 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3319 assert(pGtkWidget);
3320 GtkWidget* pChild = pGtkWidget->getWidget();
3321 gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
3325 namespace
3327 void set_cursor(GtkWidget* pWidget, const char *pName)
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);
3340 namespace
3342 struct ButtonOrder
3344 const char * m_aType;
3345 int m_nPriority;
3348 int getButtonPriority(const OString &rType)
3350 static const size_t N_TYPES = 7;
3351 static const ButtonOrder aDiscardCancelSave[N_TYPES] =
3353 { "/discard", 0 },
3354 { "/cancel", 1 },
3355 { "/no", 2 },
3356 { "/open", 3 },
3357 { "/save", 3 },
3358 { "/yes", 3 },
3359 { "/ok", 3 }
3362 static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
3364 { "/open", 0 },
3365 { "/save", 0 },
3366 { "/yes", 0 },
3367 { "/ok", 0 },
3368 { "/discard", 1 },
3369 { "/no", 1 },
3370 { "/cancel", 2 }
3373 const ButtonOrder* pOrder = &aDiscardCancelSave[0];
3375 const OUString &rEnv = Application::GetDesktopEnvironment();
3377 if (rEnv.equalsIgnoreAsciiCase("windows") ||
3378 rEnv.equalsIgnoreAsciiCase("tde") ||
3379 rEnv.startsWithIgnoreAsciiCase("kde"))
3381 pOrder = &aSaveDiscardCancel[0];
3384 for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
3386 if (rType.endsWith(pOrder->m_aType))
3387 return pOrder->m_nPriority;
3390 return -1;
3393 bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
3395 //order within groups according to platform rules
3396 return getButtonPriority(::get_help_id(pA)) < getButtonPriority(::get_help_id(pB));
3399 void sort_native_button_order(GtkBox* pContainer)
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);
3407 //sort child order within parent so that we match the platform button order
3408 std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
3410 for (size_t pos = 0; pos < aChildren.size(); ++pos)
3411 gtk_box_reorder_child(pContainer, aChildren[pos], pos);
3414 Point get_csd_offset(GtkWidget* pTopLevel)
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);
3420 int x, y;
3421 gtk_widget_translate_coordinates(GTK_WIDGET(pChild->data),
3422 GTK_WIDGET(pTopLevel),
3423 0, 0, &x, &y);
3425 int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild->data));
3426 g_list_free(pChildren);
3428 int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
3429 int totalborder = outerborder + innerborder;
3430 x -= totalborder;
3431 y -= totalborder;
3433 return Point(x, y);
3436 void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
3438 GtkWidget* pTopLevel = gtk_widget_get_toplevel(pItem);
3440 int x, y;
3441 gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
3443 Point aOffset = get_csd_offset(pTopLevel);
3445 GtkAllocation alloc;
3446 gtk_widget_get_allocation(pItem, &alloc);
3448 const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
3449 const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
3451 if (!aCurrentRange.isEmpty())
3453 weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
3454 pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
3457 if (GTK_IS_CONTAINER(pItem))
3458 gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
3462 class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
3464 private:
3465 GtkWindow* m_pWindow;
3466 rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
3467 gulong m_nToplevelFocusChangedSignalId;
3469 static void help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
3471 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
3472 pThis->help();
3475 static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
3477 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
3478 pThis->signal_toplevel_focus_changed();
3481 protected:
3482 void help();
3483 public:
3484 GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3485 : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
3486 , m_pWindow(pWindow)
3487 , m_nToplevelFocusChangedSignalId(0)
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);
3496 virtual void set_title(const OUString& rTitle) override
3498 ::set_title(m_pWindow, rTitle);
3501 virtual OUString get_title() const override
3503 return ::get_title(m_pWindow);
3506 virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
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());
3513 virtual void set_busy_cursor(bool bBusy) override
3515 set_cursor(m_pWidget, bBusy ? "progress" : nullptr);
3518 virtual void set_modal(bool bModal) override
3520 gtk_window_set_modal(m_pWindow, bModal);
3523 virtual bool get_modal() const override
3525 return gtk_window_get_modal(m_pWindow);
3528 virtual void resize_to_request() override
3530 gtk_window_resize(m_pWindow, 1, 1);
3533 virtual void window_move(int x, int y) override
3535 gtk_window_move(m_pWindow, x, y);
3538 virtual SystemEnvData get_system_data() const override
3540 assert(false && "nothing should call this impl, yet anyway, if ever");
3541 return SystemEnvData();
3544 virtual Size get_size() const override
3546 int current_width, current_height;
3547 gtk_window_get_size(m_pWindow, &current_width, &current_height);
3548 return Size(current_width, current_height);
3551 virtual Point get_position() const override
3553 int current_x, current_y;
3554 gtk_window_get_position(m_pWindow, &current_x, &current_y);
3555 return Point(current_x, current_y);
3558 virtual tools::Rectangle get_monitor_workarea() const override
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);
3567 virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
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);
3575 virtual bool get_resizable() const override
3577 return gtk_window_get_resizable(m_pWindow);
3580 virtual bool has_toplevel_focus() const override
3582 return gtk_window_has_toplevel_focus(m_pWindow);
3585 virtual void present() override
3587 gtk_window_present(m_pWindow);
3590 virtual void set_window_state(const OString& rStr) override
3592 WindowStateData aData;
3593 ImplWindowStateFromStr( aData, rStr );
3595 auto nMask = aData.GetMask();
3596 auto nState = aData.GetState() & WindowStateState::SystemMask;
3598 if (nMask & WindowStateMask::Width && nMask & WindowStateMask::Height)
3600 gtk_window_set_default_size(m_pWindow, aData.GetWidth(), aData.GetHeight());
3602 if (nMask & WindowStateMask::State)
3604 if (nState & WindowStateState::Maximized)
3605 gtk_window_maximize(m_pWindow);
3606 else
3607 gtk_window_unmaximize(m_pWindow);
3611 virtual OString get_window_state(WindowStateMask nMask) const override
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
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);
3627 if (nMask & WindowStateMask::State)
3629 WindowStateState nState = WindowStateState::Normal;
3630 if (gtk_window_is_maximized(m_pWindow))
3631 nState |= WindowStateState::Maximized;
3632 aData.SetState(nState);
3635 if (bPositioningAllowed && (nMask & (WindowStateMask::X | WindowStateMask::Y)))
3637 auto aPos = get_position();
3638 aData.SetX(aPos.X());
3639 aData.SetY(aPos.Y());
3642 if (nMask & (WindowStateMask::Width | WindowStateMask::Height))
3644 auto aSize = get_size();
3645 aData.SetWidth(aSize.Width());
3646 aData.SetHeight(aSize.Height());
3649 return aData.ToStr();
3652 virtual void connect_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink) override
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);
3659 virtual void disable_notify_events() override
3661 if (m_nToplevelFocusChangedSignalId)
3662 g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
3663 GtkInstanceContainer::disable_notify_events();
3666 virtual void enable_notify_events() override
3668 GtkInstanceContainer::enable_notify_events();
3669 if (m_nToplevelFocusChangedSignalId)
3670 g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
3673 virtual void draw(VirtualDevice& rOutput) override
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)
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));
3686 if (!bAlreadyRealized)
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);
3694 rOutput.SetOutputSizePixel(get_size());
3695 cairo_surface_t* pSurface = get_underlying_cairo_surface(rOutput);
3696 cairo_t* cr = cairo_create(pSurface);
3698 Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
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
3706 cairo_translate(cr, -aOffset.X(), -aOffset.Y());
3708 gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
3710 cairo_destroy(cr);
3712 if (!bAlreadyVisible)
3713 gtk_widget_hide(GTK_WIDGET(m_pWindow));
3714 if (!bAlreadyRealized)
3715 gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
3718 virtual weld::ScreenShotCollection collect_screenshot_data() override
3720 weld::ScreenShotCollection aRet;
3722 gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
3724 return aRet;
3727 virtual ~GtkInstanceWindow() override
3729 if (m_nToplevelFocusChangedSignalId)
3730 g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
3731 if (m_xWindow.is())
3732 m_xWindow->clear();
3736 class GtkInstanceDialog;
3738 struct DialogRunner
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;
3747 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)
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;
3759 bool loop_is_running() const
3761 return m_pLoop && g_main_loop_is_running(m_pLoop);
3764 void loop_quit()
3766 if (g_main_loop_is_running(m_pLoop))
3767 g_main_loop_quit(m_pLoop);
3770 static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
3771 static void signal_cancel(GtkAssistant*, gpointer data);
3773 static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
3775 DialogRunner* pThis = static_cast<DialogRunner*>(data);
3776 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
3778 // An assistant isn't a dialog, but we want to treat it like one
3779 signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
3781 else
3782 pThis->loop_quit();
3783 return true; /* Do not destroy */
3786 static void signal_destroy(GtkDialog*, gpointer data)
3788 DialogRunner* pThis = static_cast<DialogRunner*>(data);
3789 pThis->loop_quit();
3792 void inc_modal_count()
3794 if (m_xFrameWindow)
3796 m_xFrameWindow->IncModalCount();
3797 if (m_nModalDepth == 0)
3798 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
3799 ++m_nModalDepth;
3803 void dec_modal_count()
3805 if (m_xFrameWindow)
3807 m_xFrameWindow->DecModalCount();
3808 --m_nModalDepth;
3809 if (m_nModalDepth == 0)
3810 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
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
3817 gint run()
3819 g_object_ref(m_pDialog);
3821 inc_modal_count();
3823 bool bWasModal = gtk_window_get_modal(m_pDialog);
3824 if (!bWasModal)
3825 gtk_window_set_modal(m_pDialog, true);
3827 if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
3828 gtk_widget_show(GTK_WIDGET(m_pDialog));
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);
3835 m_pLoop = g_main_loop_new(nullptr, false);
3836 m_nResponseId = GTK_RESPONSE_NONE;
3838 gdk_threads_leave();
3839 g_main_loop_run(m_pLoop);
3840 gdk_threads_enter();
3842 g_main_loop_unref(m_pLoop);
3844 m_pLoop = nullptr;
3846 if (!bWasModal)
3847 gtk_window_set_modal(m_pDialog, false);
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);
3856 dec_modal_count();
3858 g_object_unref(m_pDialog);
3860 return m_nResponseId;
3863 ~DialogRunner()
3865 if (m_xFrameWindow && m_nModalDepth)
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();
3876 typedef std::set<GtkWidget*> winset;
3878 namespace
3880 void hideUnless(GtkContainer *pTop, const winset& rVisibleWidgets,
3881 std::vector<GtkWidget*> &rWasVisibleWidgets)
3883 GList* pChildren = gtk_container_get_children(pTop);
3884 for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
3886 GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
3887 if (!gtk_widget_get_visible(pChild))
3888 continue;
3889 if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
3891 g_object_ref(pChild);
3892 rWasVisibleWidgets.emplace_back(pChild);
3893 gtk_widget_hide(pChild);
3895 else if (GTK_IS_CONTAINER(pChild))
3897 hideUnless(GTK_CONTAINER(pChild), rVisibleWidgets, rWasVisibleWidgets);
3900 g_list_free(pChildren);
3904 class GtkInstanceButton;
3906 class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
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;
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
3927 void signal_close()
3929 close(true);
3932 static void signalClose(GtkWidget*, gpointer widget)
3934 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3935 pThis->signal_close();
3938 static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
3940 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3941 pThis->asyncresponse(ret);
3944 static void signalAsyncCancel(GtkAssistant*, gpointer widget)
3946 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3947 // make esc in an assistant act as if cancel button was pressed
3948 pThis->close(false);
3951 static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
3953 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
3954 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
3956 // An assistant isn't a dialog, but we want to treat it like one
3957 signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
3959 return true; /* Do not destroy */
3962 static int GtkToVcl(int ret)
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;
3981 static int VclToGtk(int nResponse)
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;
3998 void asyncresponse(gint ret);
4000 static void signalActivate(GtkMenuItem*, gpointer data)
4002 bool* pActivate = static_cast<bool*>(data);
4003 *pActivate = true;
4006 bool signal_screenshot_popup_menu(GdkEventButton* pEvent)
4008 GtkWidget *pMenu = gtk_menu_new();
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);
4016 int button, event_time;
4017 if (pEvent)
4019 button = pEvent->button;
4020 event_time = pEvent->time;
4022 else
4024 button = 0;
4025 event_time = gtk_get_current_event_time();
4028 gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
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);
4033 gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
4035 if (g_main_loop_is_running(pLoop))
4037 gdk_threads_leave();
4038 g_main_loop_run(pLoop);
4039 gdk_threads_enter();
4042 g_main_loop_unref(pLoop);
4043 g_signal_handler_disconnect(pMenu, nSignalId);
4044 gtk_menu_detach(GTK_MENU(pMenu));
4046 if (bActivate)
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();
4055 return false;
4058 static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
4060 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4061 return pThis->signal_screenshot_popup_menu(nullptr);
4064 static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
4066 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4067 SolarMutexGuard aGuard;
4068 return pThis->signal_screenshot_button(pEvent);
4071 bool signal_screenshot_button(GdkEventButton* pEvent)
4073 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
4075 //if handled for context menu, stop processing
4076 return signal_screenshot_popup_menu(pEvent);
4078 return false;
4081 public:
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)
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)
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);
4106 virtual bool runAsync(std::shared_ptr<weld::DialogController> rDialogController, const std::function<void(sal_Int32)>& func) override
4108 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4110 m_xDialogController = rDialogController;
4111 m_aFunc = func;
4113 if (get_modal())
4114 m_aDialogRun.inc_modal_count();
4115 show();
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);
4121 return true;
4124 virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
4126 assert( rxSelf.get() == this );
4127 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
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;
4134 if (get_modal())
4135 m_aDialogRun.inc_modal_count();
4136 show();
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);
4142 return true;
4145 GtkInstanceButton* has_click_handler(int nResponse);
4147 virtual int run() override;
4149 virtual void show() override
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();
4158 virtual void set_modal(bool bModal) override
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
4166 This change modality while running case is for...
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
4172 b) the validity dialog in calc
4174 if (m_aDialogRun.loop_is_running())
4176 if (bModal)
4177 m_aDialogRun.inc_modal_count();
4178 else
4179 m_aDialogRun.dec_modal_count();
4183 virtual void response(int nResponse) override;
4185 virtual void add_button(const OUString& rText, int nResponse, const OString& rHelpId) override
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);
4192 virtual void set_default_response(int nResponse) override
4194 gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
4197 virtual GtkButton* get_widget_for_response(int nGtkResponse)
4199 return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
4202 virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
4204 virtual Container* weld_content_area() override
4206 return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
4209 virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
4211 GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
4212 assert(pVclEdit);
4213 GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
4215 GtkWidget* pRefEdit = pVclEdit->getWidget();
4216 GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
4218 m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
4220 gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
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))
4231 aVisibleWidgets.insert(pCandidate);
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))
4239 if (aVisibleWidgets.insert(pCandidate).second)
4240 break;
4243 //hide everything except the aVisibleWidgets
4244 hideUnless(GTK_CONTAINER(pContentArea), aVisibleWidgets, m_aHiddenWidgets);
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);
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
4261 resize_to_request();
4263 #if defined(GDK_WINDOWING_WAYLAND)
4264 if (bWorkaroundSizeSpringingBack)
4265 gtk_widget_map(GTK_WIDGET(m_pDialog));
4266 #endif
4268 m_pRefEdit = pRefEdit;
4271 virtual void undo_collapse() override
4273 // All others: Show();
4274 for (GtkWidget* pWindow : m_aHiddenWidgets)
4276 gtk_widget_show(pWindow);
4277 g_object_unref(pWindow);
4279 m_aHiddenWidgets.clear();
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();
4290 void close(bool bCloseSignal);
4292 virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
4294 //not implemented for the gtk variant
4297 virtual ~GtkInstanceDialog() override
4299 if (!m_aHiddenWidgets.empty())
4301 for (GtkWidget* pWindow : m_aHiddenWidgets)
4302 g_object_unref(pWindow);
4303 m_aHiddenWidgets.clear();
4306 if (m_nCloseSignalId)
4307 g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
4308 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4312 void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
4314 DialogRunner* pThis = static_cast<DialogRunner*>(data);
4316 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
4317 if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
4319 pThis->m_pInstance->close(false);
4320 return;
4323 pThis->m_nResponseId = nResponseId;
4324 pThis->loop_quit();
4327 void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
4329 DialogRunner* pThis = static_cast<DialogRunner*>(data);
4331 // make esc in an assistant act as if cancel button was pressed
4332 pThis->m_pInstance->close(false);
4335 class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
4337 private:
4338 GtkMessageDialog* m_pMessageDialog;
4339 public:
4340 GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4341 : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
4342 , m_pMessageDialog(pMessageDialog)
4346 virtual void set_primary_text(const OUString& rText) override
4348 ::set_primary_text(m_pMessageDialog, rText);
4351 virtual OUString get_primary_text() const override
4353 return ::get_primary_text(m_pMessageDialog);
4356 virtual void set_secondary_text(const OUString& rText) override
4358 ::set_secondary_text(m_pMessageDialog, rText);
4361 virtual OUString get_secondary_text() const override
4363 return ::get_secondary_text(m_pMessageDialog);
4366 virtual Container* weld_message_area() override
4368 return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
4372 class GtkInstanceAboutDialog final : public GtkInstanceDialog, public virtual weld::AboutDialog
4374 private:
4375 GtkAboutDialog* m_pAboutDialog;
4376 GtkCssProvider* m_pCssProvider;
4377 std::unique_ptr<utl::TempFile> mxBackgroundImage;
4378 public:
4379 GtkInstanceAboutDialog(GtkAboutDialog* pAboutDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4380 : GtkInstanceDialog(GTK_WINDOW(pAboutDialog), pBuilder, bTakeOwnership)
4381 , m_pAboutDialog(pAboutDialog)
4382 , m_pCssProvider(nullptr)
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);
4391 virtual void set_version(const OUString& rVersion) override
4393 gtk_about_dialog_set_version(m_pAboutDialog, OUStringToOString(rVersion, RTL_TEXTENCODING_UTF8).getStr());
4396 virtual void set_copyright(const OUString& rCopyright) override
4398 gtk_about_dialog_set_copyright(m_pAboutDialog, OUStringToOString(rCopyright, RTL_TEXTENCODING_UTF8).getStr());
4401 virtual void set_website(const OUString& rURL) override
4403 OString sURL(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8));
4404 gtk_about_dialog_set_website(m_pAboutDialog, sURL.isEmpty() ? nullptr : sURL.getStr());
4407 virtual void set_website_label(const OUString& rLabel) override
4409 OString sLabel(OUStringToOString(rLabel, RTL_TEXTENCODING_UTF8));
4410 gtk_about_dialog_set_website_label(m_pAboutDialog, sLabel.isEmpty() ? nullptr : sLabel.getStr());
4413 virtual OUString get_website_label() const override
4415 const gchar* pText = gtk_about_dialog_get_website_label(m_pAboutDialog);
4416 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
4419 virtual void set_logo(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
4421 GdkPixbuf* pixbuf = rImage.is() ? getPixbuf(rImage) : nullptr;
4422 if (!pixbuf)
4423 gtk_about_dialog_set_logo(m_pAboutDialog, nullptr);
4424 else
4426 gtk_about_dialog_set_logo(m_pAboutDialog, pixbuf);
4427 g_object_unref(pixbuf);
4431 virtual void set_background(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
4433 GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pAboutDialog));
4434 if (m_pCssProvider)
4436 gtk_style_context_remove_provider(pStyleContext, GTK_STYLE_PROVIDER(m_pCssProvider));
4437 m_pCssProvider= nullptr;
4440 mxBackgroundImage.reset();
4442 if (rImage.is())
4444 mxBackgroundImage.reset(new utl::TempFile());
4445 mxBackgroundImage->EnableKillingFile(true);
4447 Image aImage(rImage);
4449 vcl::PNGWriter aPNGWriter(aImage.GetBitmapEx());
4450 SvStream* pStream = mxBackgroundImage->GetStream(StreamMode::WRITE);
4451 aPNGWriter.Write(*pStream);
4452 mxBackgroundImage->CloseStream();
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);
4463 virtual ~GtkInstanceAboutDialog() override
4465 set_background(nullptr);
4469 class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
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;
4485 int find_page(const OString& rIdent) const
4487 int nPages = gtk_assistant_get_n_pages(m_pAssistant);
4488 for (int i = 0; i < nPages; ++i)
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;
4495 return -1;
4498 static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
4500 if (GTK_IS_LABEL(pWidget))
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);
4508 static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
4510 if (g_strcmp0(gtk_buildable_get_name(GTK_BUILDABLE(pWidget)), "sidebar") == 0)
4512 GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
4513 *ppSidebar = pWidget;
4515 if (GTK_IS_CONTAINER(pWidget))
4516 gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
4519 static void signalHelpClicked(GtkButton*, gpointer widget)
4521 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
4522 pThis->signal_help_clicked();
4525 void signal_help_clicked()
4527 help();
4530 static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
4532 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
4533 SolarMutexGuard aGuard;
4534 return pThis->signal_button(pEvent);
4537 bool signal_button(GdkEventButton* pEvent)
4539 int nNewCurrentPage = -1;
4541 GtkAllocation allocation;
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))
4547 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
4548 if (!gtk_widget_get_visible(pWidget))
4549 continue;
4551 gtk_widget_get_allocation(pWidget, &allocation);
4553 gint dest_x1, dest_y1;
4554 gtk_widget_translate_coordinates(pWidget,
4555 m_pSidebarEventBox,
4558 &dest_x1,
4559 &dest_y1);
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);
4570 if (pEvent->x >= dest_x1 && pEvent->x <= dest_x2 && pEvent->y >= dest_y1 && pEvent->y <= dest_y2)
4572 nNewCurrentPage = nPageIndex;
4573 break;
4576 ++nPageIndex;
4578 g_list_free(pChildren);
4580 if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
4582 OString sIdent = get_page_ident(nNewCurrentPage);
4583 if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
4584 set_current_page(nNewCurrentPage);
4587 return false;
4590 public:
4591 GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4592 : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
4593 , m_pAssistant(pAssistant)
4594 , m_pSidebar(nullptr)
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);
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);
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);
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);
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);
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);
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);
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);
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))
4637 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
4638 gtk_widget_hide(pWidget);
4640 g_list_free(pChildren);
4642 gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
4644 find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
4646 m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
4647 m_nButtonPressSignalId = m_pSidebarEventBox ? g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this) : 0;
4650 virtual int get_current_page() const override
4652 return gtk_assistant_get_current_page(m_pAssistant);
4655 virtual int get_n_pages() const override
4657 return gtk_assistant_get_n_pages(m_pAssistant);
4660 virtual OString get_page_ident(int nPage) const override
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);
4667 virtual OString get_current_page_ident() const override
4669 return get_page_ident(get_current_page());
4672 virtual void set_current_page(int nPage) override
4674 OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
4676 gtk_assistant_set_current_page(m_pAssistant, nPage);
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());
4685 virtual void set_current_page(const OString& rIdent) override
4687 int nPage = find_page(rIdent);
4688 if (nPage == -1)
4689 return;
4690 set_current_page(nPage);
4693 virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
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);
4704 virtual OUString get_page_title(const OString& rIdent) const override
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);
4714 virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
4716 m_aNotClickable[rIdent] = !bSensitive;
4719 virtual void set_page_index(const OString& rIdent, int nNewIndex) override
4721 int nOldIndex = find_page(rIdent);
4722 if (nOldIndex == -1)
4723 return;
4725 if (nOldIndex == nNewIndex)
4726 return;
4728 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
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);
4740 virtual weld::Container* append_page(const OString& rIdent) override
4742 disable_notify_events();
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);
4750 enable_notify_events();
4752 m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
4754 return m_aPages.back().get();
4757 virtual void set_page_side_help_id(const OString& rHelpId) override
4759 if (!m_pSidebar)
4760 return;
4761 ::set_help_id(m_pSidebar, rHelpId);
4764 virtual GtkButton* get_widget_for_response(int nGtkResponse) override
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;
4780 virtual ~GtkInstanceAssistant() override
4782 if (m_nButtonPressSignalId)
4783 g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
4787 class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
4789 private:
4790 GtkFrame* m_pFrame;
4791 public:
4792 GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4793 : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
4794 , m_pFrame(pFrame)
4798 virtual void set_label(const OUString& rText) override
4800 gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
4803 virtual OUString get_label() const override
4805 const gchar* pStr = gtk_frame_get_label(m_pFrame);
4806 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
4809 virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
4812 static GType crippled_viewport_get_type();
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
4820 struct CrippledViewport
4822 GtkViewport viewport;
4824 GtkAdjustment *hadjustment;
4825 GtkAdjustment *vadjustment;
4828 enum
4830 PROP_0,
4831 PROP_HADJUSTMENT,
4832 PROP_VADJUSTMENT,
4833 PROP_HSCROLL_POLICY,
4834 PROP_VSCROLL_POLICY,
4835 PROP_SHADOW_TYPE
4838 static void viewport_set_adjustment(CrippledViewport *viewport,
4839 GtkOrientation orientation,
4840 GtkAdjustment *adjustment)
4842 if (!adjustment)
4843 adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
4845 if (orientation == GTK_ORIENTATION_HORIZONTAL)
4847 if (viewport->hadjustment)
4848 g_object_unref(viewport->hadjustment);
4849 viewport->hadjustment = adjustment;
4851 else
4853 if (viewport->vadjustment)
4854 g_object_unref(viewport->vadjustment);
4855 viewport->vadjustment = adjustment;
4858 g_object_ref_sink(adjustment);
4861 static void
4862 crippled_viewport_set_property(GObject* object,
4863 guint prop_id,
4864 const GValue* value,
4865 GParamSpec* /*pspec*/)
4867 CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
4869 switch (prop_id)
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;
4886 static void
4887 crippled_viewport_get_property(GObject* object,
4888 guint prop_id,
4889 GValue* value,
4890 GParamSpec* /*pspec*/)
4892 CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
4894 switch (prop_id)
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;
4914 static void crippled_viewport_class_init(GtkViewportClass *klass)
4916 GObjectClass* o_class = G_OBJECT_CLASS(klass);
4918 /* GObject signals */
4919 o_class->set_property = crippled_viewport_set_property;
4920 o_class->get_property = crippled_viewport_get_property;
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");
4929 GType crippled_viewport_get_type()
4931 static GType type = 0;
4933 if (!type)
4935 static const GTypeInfo tinfo =
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 */
4949 type = g_type_register_static( GTK_TYPE_VIEWPORT, "CrippledViewport",
4950 &tinfo, GTypeFlags(0));
4953 return type;
4956 static VclPolicyType GtkToVcl(GtkPolicyType eType)
4958 VclPolicyType eRet(VclPolicyType::NEVER);
4959 switch (eType)
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;
4972 return eRet;
4975 static GtkPolicyType VclToGtk(VclPolicyType eType)
4977 GtkPolicyType eRet(GTK_POLICY_ALWAYS);
4978 switch (eType)
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;
4990 return eRet;
4993 static GtkMessageType VclToGtk(VclMessageType eType)
4995 GtkMessageType eRet(GTK_MESSAGE_INFO);
4996 switch (eType)
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;
5011 return eRet;
5014 static GtkButtonsType VclToGtk(VclButtonsType eType)
5016 GtkButtonsType eRet(GTK_BUTTONS_NONE);
5017 switch (eType)
5019 case VclButtonsType::NONE:
5020 eRet = GTK_BUTTONS_NONE;
5021 break;
5022 case VclButtonsType::Ok:
5023 eRet = GTK_BUTTONS_OK;
5024 break;
5025 case VclButtonsType::Close:
5026 eRet = GTK_BUTTONS_CLOSE;
5027 break;
5028 case VclButtonsType::Cancel:
5029 eRet = GTK_BUTTONS_CANCEL;
5030 break;
5031 case VclButtonsType::YesNo:
5032 eRet = GTK_BUTTONS_YES_NO;
5033 break;
5034 case VclButtonsType::OkCancel:
5035 eRet = GTK_BUTTONS_OK_CANCEL;
5036 break;
5038 return eRet;
5041 static GtkSelectionMode VclToGtk(SelectionMode eType)
5043 GtkSelectionMode eRet(GTK_SELECTION_NONE);
5044 switch (eType)
5046 case SelectionMode::NONE:
5047 eRet = GTK_SELECTION_NONE;
5048 break;
5049 case SelectionMode::Single:
5050 eRet = GTK_SELECTION_SINGLE;
5051 break;
5052 case SelectionMode::Range:
5053 eRet = GTK_SELECTION_BROWSE;
5054 break;
5055 case SelectionMode::Multiple:
5056 eRet = GTK_SELECTION_MULTIPLE;
5057 break;
5059 return eRet;
5062 class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
5064 private:
5065 GtkScrolledWindow* m_pScrolledWindow;
5066 GtkWidget *m_pOrigViewport;
5067 GtkAdjustment* m_pVAdjustment;
5068 GtkAdjustment* m_pHAdjustment;
5069 gulong m_nVAdjustChangedSignalId;
5070 gulong m_nHAdjustChangedSignalId;
5072 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
5074 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
5075 SolarMutexGuard aGuard;
5076 pThis->signal_vadjustment_changed();
5079 static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
5081 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
5082 SolarMutexGuard aGuard;
5083 pThis->signal_hadjustment_changed();
5086 public:
5087 GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5088 : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
5089 , m_pScrolledWindow(pScrolledWindow)
5090 , m_pOrigViewport(nullptr)
5091 , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
5092 , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
5093 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
5094 , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
5098 virtual void set_user_managed_scrolling() override
5100 disable_notify_events();
5101 //remove the original viewport and replace it with our bodged one which
5102 //doesn't do any scrolling and expects its child to figure it out somehow
5103 assert(!m_pOrigViewport);
5104 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
5105 assert(GTK_IS_VIEWPORT(pViewport));
5106 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
5107 g_object_ref(pChild);
5108 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
5109 g_object_ref(pViewport);
5110 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
5111 GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(crippled_viewport_get_type(), nullptr));
5112 gtk_widget_show(pNewViewport);
5113 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
5114 gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
5115 g_object_unref(pChild);
5116 m_pOrigViewport = pViewport;
5117 enable_notify_events();
5120 virtual void hadjustment_configure(int value, int lower, int upper,
5121 int step_increment, int page_increment,
5122 int page_size) override
5124 disable_notify_events();
5125 if (SwapForRTL())
5126 value = upper - (value - lower + page_size);
5127 gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
5128 enable_notify_events();
5131 virtual int hadjustment_get_value() const override
5133 int value = gtk_adjustment_get_value(m_pHAdjustment);
5135 if (SwapForRTL())
5137 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
5138 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
5139 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
5140 value = lower + (upper - value - page_size);
5143 return value;
5146 virtual void hadjustment_set_value(int value) override
5148 disable_notify_events();
5150 if (SwapForRTL())
5152 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
5153 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
5154 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
5155 value = upper - (value - lower + page_size);
5158 gtk_adjustment_set_value(m_pHAdjustment, value);
5159 enable_notify_events();
5162 virtual int hadjustment_get_upper() const override
5164 return gtk_adjustment_get_upper(m_pHAdjustment);
5167 virtual void hadjustment_set_upper(int upper) override
5169 disable_notify_events();
5170 gtk_adjustment_set_upper(m_pHAdjustment, upper);
5171 enable_notify_events();
5174 virtual int hadjustment_get_page_size() const override
5176 return gtk_adjustment_get_page_size(m_pHAdjustment);
5179 virtual void hadjustment_set_page_size(int size) override
5181 gtk_adjustment_set_page_size(m_pHAdjustment, size);
5184 virtual void hadjustment_set_page_increment(int size) override
5186 gtk_adjustment_set_page_increment(m_pHAdjustment, size);
5189 virtual void set_hpolicy(VclPolicyType eHPolicy) override
5191 GtkPolicyType eGtkVPolicy;
5192 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
5193 gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkVPolicy, VclToGtk(eHPolicy));
5196 virtual VclPolicyType get_hpolicy() const override
5198 GtkPolicyType eGtkHPolicy;
5199 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
5200 return GtkToVcl(eGtkHPolicy);
5203 virtual int get_hscroll_height() const override
5205 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
5206 return 0;
5207 return gtk_widget_get_allocated_height(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
5210 virtual void vadjustment_configure(int value, int lower, int upper,
5211 int step_increment, int page_increment,
5212 int page_size) override
5214 disable_notify_events();
5215 gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
5216 enable_notify_events();
5219 virtual int vadjustment_get_value() const override
5221 return gtk_adjustment_get_value(m_pVAdjustment);
5224 virtual void vadjustment_set_value(int value) override
5226 disable_notify_events();
5227 gtk_adjustment_set_value(m_pVAdjustment, value);
5228 enable_notify_events();
5231 virtual int vadjustment_get_upper() const override
5233 return gtk_adjustment_get_upper(m_pVAdjustment);
5236 virtual void vadjustment_set_upper(int upper) override
5238 disable_notify_events();
5239 gtk_adjustment_set_upper(m_pVAdjustment, upper);
5240 enable_notify_events();
5243 virtual int vadjustment_get_lower() const override
5245 return gtk_adjustment_get_lower(m_pVAdjustment);
5248 virtual void vadjustment_set_lower(int lower) override
5250 disable_notify_events();
5251 gtk_adjustment_set_lower(m_pVAdjustment, lower);
5252 enable_notify_events();
5255 virtual int vadjustment_get_page_size() const override
5257 return gtk_adjustment_get_page_size(m_pVAdjustment);
5260 virtual void vadjustment_set_page_size(int size) override
5262 gtk_adjustment_set_page_size(m_pVAdjustment, size);
5265 virtual void vadjustment_set_page_increment(int size) override
5267 gtk_adjustment_set_page_increment(m_pVAdjustment, size);
5270 virtual void set_vpolicy(VclPolicyType eVPolicy) override
5272 GtkPolicyType eGtkHPolicy;
5273 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
5274 gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
5277 virtual VclPolicyType get_vpolicy() const override
5279 GtkPolicyType eGtkVPolicy;
5280 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
5281 return GtkToVcl(eGtkVPolicy);
5284 virtual int get_vscroll_width() const override
5286 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
5287 return 0;
5288 return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
5291 virtual void disable_notify_events() override
5293 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
5294 g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
5295 GtkInstanceContainer::disable_notify_events();
5298 virtual void enable_notify_events() override
5300 GtkInstanceContainer::enable_notify_events();
5301 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
5302 g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
5305 virtual ~GtkInstanceScrolledWindow() override
5307 //put it back the way it was
5308 if (m_pOrigViewport)
5310 disable_notify_events();
5311 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
5312 assert(CRIPPLED_IS_VIEWPORT(pViewport));
5313 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
5314 g_object_ref(pChild);
5315 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
5316 g_object_ref(pViewport);
5317 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
5318 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
5319 g_object_unref(m_pOrigViewport);
5320 gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
5321 g_object_unref(pChild);
5322 gtk_widget_destroy(pViewport);
5323 g_object_unref(pViewport);
5324 m_pOrigViewport = nullptr;
5325 enable_notify_events();
5327 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
5328 g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
5332 class GtkInstanceNotebook : public GtkInstanceContainer, public virtual weld::Notebook
5334 private:
5335 GtkNotebook* m_pNotebook;
5336 GtkBox* m_pOverFlowBox;
5337 GtkNotebook* m_pOverFlowNotebook;
5338 gulong m_nSwitchPageSignalId;
5339 gulong m_nOverFlowSwitchPageSignalId;
5340 gulong m_nSizeAllocateSignalId;
5341 gulong m_nFocusSignalId;
5342 gulong m_nChangeCurrentPageId;
5343 guint m_nLaunchSplitTimeoutId;
5344 bool m_bOverFlowBoxActive;
5345 bool m_bOverFlowBoxIsStart;
5346 int m_nStartTabCount;
5347 int m_nEndTabCount;
5348 mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
5350 static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
5352 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5353 SolarMutexGuard aGuard;
5354 pThis->signal_switch_page(nNewPage);
5357 static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
5359 SolarMutexGuard aGuard;
5360 pThis->signal_overflow_switch_page();
5361 return false;
5364 static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
5366 g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
5369 void signal_switch_page(int nNewPage)
5371 if (m_bOverFlowBoxIsStart)
5373 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5374 // add count of overflow pages, minus the extra tab
5375 nNewPage += nOverFlowLen;
5378 bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
5379 if (!bAllow)
5381 g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
5382 return;
5384 if (m_bOverFlowBoxActive)
5385 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
5386 OString sNewIdent(get_page_ident(nNewPage));
5387 m_aEnterPageHdl.Call(sNewIdent);
5390 void unsplit_notebooks()
5392 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5393 int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
5394 int nPageIndex = 0;
5395 if (!m_bOverFlowBoxIsStart)
5396 nPageIndex += nMainPages;
5398 // take the overflow pages, and put them back at the end of the normal one
5399 int i = nMainPages;
5400 while (nOverFlowPages)
5402 OString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
5403 OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
5404 remove_page(m_pOverFlowNotebook, sIdent);
5406 GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
5407 append_page(m_pNotebook, sIdent, sLabel, pPage);
5409 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
5410 gtk_notebook_get_nth_page(m_pNotebook, i));
5411 gtk_widget_set_hexpand(pTabWidget, true);
5412 --nOverFlowPages;
5413 ++i;
5414 ++nPageIndex;
5417 // remove the dangling placeholder tab page
5418 remove_page(m_pOverFlowNotebook, "useless");
5421 // a tab has been selected on the overflow notebook
5422 void signal_overflow_switch_page()
5424 int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
5425 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5426 if (nNewPage == nOverFlowPages)
5428 // the useless tab which is there because there has to be an active tab
5429 return;
5432 // check if we are allowed leave before attempting to resplit the notebooks
5433 bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
5434 if (!bAllow)
5435 return;
5437 disable_notify_events();
5439 // take the overflow pages, and put them back at the end of the normal one
5440 unsplit_notebooks();
5442 // now redo the split, the pages will be split the other way around this time
5443 std::swap(m_nStartTabCount, m_nEndTabCount);
5444 split_notebooks();
5446 gtk_notebook_set_current_page(m_pNotebook, nNewPage);
5448 enable_notify_events();
5450 // trigger main notebook switch-page callback
5451 OString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
5452 m_aEnterPageHdl.Call(sNewIdent);
5455 static OString get_page_ident(GtkNotebook *pNotebook, guint nPage)
5457 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
5458 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
5459 return OString(pStr, pStr ? strlen(pStr) : 0);
5462 static gint get_page_number(GtkNotebook *pNotebook, const OString& rIdent)
5464 gint nPages = gtk_notebook_get_n_pages(pNotebook);
5465 for (gint i = 0; i < nPages; ++i)
5467 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
5468 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
5469 if (pStr && strcmp(pStr, rIdent.getStr()) == 0)
5470 return i;
5472 return -1;
5475 int remove_page(GtkNotebook *pNotebook, const OString& rIdent)
5477 disable_notify_events();
5478 int nPageNumber = get_page_number(pNotebook, rIdent);
5479 gtk_notebook_remove_page(pNotebook, nPageNumber);
5480 enable_notify_events();
5481 return nPageNumber;
5484 static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
5486 const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
5487 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
5490 static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
5492 OString sUtf8(rText.toUtf8());
5494 GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
5496 // tdf#128241 if there's already a label here, reuse it so the buildable
5497 // name remains the same, gtk_notebook_set_tab_label_text will replace
5498 // the label widget with a new one
5499 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
5500 if (pTabWidget && GTK_IS_LABEL(pTabWidget))
5502 gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
5503 return;
5506 gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
5509 void append_useless_page(GtkNotebook *pNotebook)
5511 disable_notify_events();
5513 GtkWidget *pTabWidget = gtk_fixed_new();
5514 gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), "useless");
5516 GtkWidget *pChild = gtk_grid_new();
5517 gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
5518 gtk_widget_show(pChild);
5519 gtk_widget_show(pTabWidget);
5521 enable_notify_events();
5524 void append_page(GtkNotebook *pNotebook, const OString& rIdent, const OUString& rLabel, GtkWidget *pChild)
5526 disable_notify_events();
5528 GtkWidget *pTabWidget = gtk_label_new(MapToGtkAccelerator(rLabel).getStr());
5529 gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), rIdent.getStr());
5531 gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
5532 gtk_widget_show(pChild);
5533 gtk_widget_show(pTabWidget);
5535 enable_notify_events();
5538 gint get_page_number(const OString& rIdent) const
5540 auto nMainIndex = get_page_number(m_pNotebook, rIdent);
5541 auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
5543 if (nMainIndex == -1 && nOverFlowIndex == -1)
5544 return -1;
5546 if (m_bOverFlowBoxIsStart)
5548 if (nOverFlowIndex != -1)
5549 return nOverFlowIndex;
5550 else
5552 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5553 return nMainIndex + nOverFlowLen;
5556 else
5558 if (nMainIndex != -1)
5559 return nMainIndex;
5560 else
5562 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5563 return nOverFlowIndex + nMainLen;
5568 void make_overflow_boxes()
5570 m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
5571 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
5572 gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pOverFlowBox));
5573 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
5574 g_object_ref(m_pNotebook);
5575 gtk_container_remove(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
5576 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
5577 g_object_unref(m_pNotebook);
5578 gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
5581 void split_notebooks()
5583 // get the original preferred size for the notebook, the sane width
5584 // expected here depends on the notebooks all initially having
5585 // scrollable tabs enabled
5586 GtkAllocation alloc;
5587 gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
5589 // toggle the direction of the split since the last time
5590 m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
5591 if (!m_pOverFlowBox)
5592 make_overflow_boxes();
5594 // don't scroll the tabs anymore
5595 gtk_notebook_set_scrollable(m_pNotebook, false);
5597 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
5598 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5600 gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
5602 gint nPages;
5604 GtkRequisition size1, size2;
5606 if (!m_nStartTabCount && !m_nEndTabCount)
5608 nPages = gtk_notebook_get_n_pages(m_pNotebook);
5610 std::vector<int> aLabelWidths;
5611 //move tabs to the overflow notebook
5612 for (int i = 0; i < nPages; ++i)
5614 OUString sLabel(get_tab_label_text(m_pNotebook, i));
5615 aLabelWidths.push_back(get_pixel_size(sLabel).Width());
5617 int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
5618 int count = 0;
5619 for (int i = 0; i < nPages; ++i)
5621 count += aLabelWidths[i];
5622 if (count >= row_width)
5624 m_nStartTabCount = i;
5625 break;
5629 m_nEndTabCount = nPages - m_nStartTabCount;
5632 //move the tabs to the overflow notebook
5633 int i = 0;
5634 int nOverFlowPages = m_nStartTabCount;
5635 while (nOverFlowPages)
5637 OString sIdent(get_page_ident(m_pNotebook, 0));
5638 OUString sLabel(get_tab_label_text(m_pNotebook, 0));
5639 remove_page(m_pNotebook, sIdent);
5640 append_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new());
5641 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
5642 gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
5643 gtk_widget_set_hexpand(pTabWidget, true);
5645 --nOverFlowPages;
5646 ++i;
5649 for (i = 0; i < m_nEndTabCount; ++i)
5651 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
5652 gtk_notebook_get_nth_page(m_pNotebook, i));
5653 gtk_widget_set_hexpand(pTabWidget, true);
5656 // have to have some tab as the active tab of the overflow notebook
5657 append_useless_page(m_pOverFlowNotebook);
5658 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
5659 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
5660 gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
5662 // add this temporarily to the normal notebook to measure how wide
5663 // the row would be if switched to the other notebook
5664 append_useless_page(m_pNotebook);
5666 gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
5667 gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
5669 auto nWidth = std::max(size1.width, size2.width);
5670 gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
5671 gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
5673 // remove it once we've measured it
5674 remove_page(m_pNotebook, "useless");
5676 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5677 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
5679 m_bOverFlowBoxActive = true;
5682 static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
5684 int nCurrentPage = pThis->get_current_page();
5685 pThis->split_notebooks();
5686 pThis->set_current_page(nCurrentPage);
5687 pThis->m_nLaunchSplitTimeoutId = 0;
5688 return false;
5691 // tdf#120371
5692 // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
5693 // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
5694 // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
5695 // tabs in a single row when they would fit
5696 void signal_notebook_size_allocate()
5698 if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
5699 return;
5700 disable_notify_events();
5701 gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
5702 if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
5704 for (gint i = 0; i < nPages; ++i)
5706 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
5707 if (!gtk_widget_get_child_visible(pTabWidget))
5709 m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
5710 break;
5714 enable_notify_events();
5717 static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
5719 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5720 pThis->signal_notebook_size_allocate();
5723 bool signal_focus(GtkDirectionType direction)
5725 if (!m_bOverFlowBoxActive)
5726 return false;
5728 int nPage = gtk_notebook_get_current_page(m_pNotebook);
5729 if (direction == GTK_DIR_LEFT && nPage == 0)
5731 auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5732 gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
5733 return true;
5735 else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
5737 gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
5738 return true;
5741 return false;
5744 static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
5746 // if the notebook widget itself has focus
5747 if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
5749 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5750 return pThis->signal_focus(direction);
5752 return false;
5755 // ctrl + page_up/ page_down
5756 bool signal_change_current_page(gint arg1)
5758 bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
5759 if (bHandled)
5760 g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
5761 return false;
5764 static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
5766 if (arg1 == 0)
5767 return true;
5768 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
5769 return pThis->signal_change_current_page(arg1);
5772 public:
5773 GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5774 : GtkInstanceContainer(GTK_CONTAINER(pNotebook), pBuilder, bTakeOwnership)
5775 , m_pNotebook(pNotebook)
5776 , m_pOverFlowBox(nullptr)
5777 , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
5778 , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
5779 , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
5780 , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
5781 , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
5782 , m_nLaunchSplitTimeoutId(0)
5783 , m_bOverFlowBoxActive(false)
5784 , m_bOverFlowBoxIsStart(false)
5785 , m_nStartTabCount(0)
5786 , m_nEndTabCount(0)
5788 gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
5789 if (get_n_pages() > 6)
5790 m_nSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
5791 else
5792 m_nSizeAllocateSignalId = 0;
5793 gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
5795 // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
5796 // the unwanted tab into invisibility
5797 GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
5798 GtkCssProvider *pProvider = gtk_css_provider_new();
5799 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; }";
5800 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; }";
5801 gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
5802 gtk_style_context_add_provider(pNotebookContext, GTK_STYLE_PROVIDER(pProvider),
5803 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
5806 virtual int get_current_page() const override
5808 int nPage = gtk_notebook_get_current_page(m_pNotebook);
5809 if (nPage == -1)
5810 return nPage;
5811 if (m_bOverFlowBoxIsStart)
5813 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5814 // add count of overflow pages, minus the extra tab
5815 nPage += nOverFlowLen;
5817 return nPage;
5820 virtual OString get_page_ident(int nPage) const override
5822 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5823 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5824 if (m_bOverFlowBoxIsStart)
5826 if (nPage < nOverFlowLen)
5827 return get_page_ident(m_pOverFlowNotebook, nPage);
5828 nPage -= nOverFlowLen;
5829 return get_page_ident(m_pNotebook, nPage);
5831 else
5833 if (nPage < nMainLen)
5834 return get_page_ident(m_pNotebook, nPage);
5835 nPage -= nMainLen;
5836 return get_page_ident(m_pOverFlowNotebook, nPage);
5840 virtual OString get_current_page_ident() const override
5842 return get_page_ident(get_current_page());
5845 virtual weld::Container* get_page(const OString& rIdent) const override
5847 int nPage = get_page_number(rIdent);
5848 if (nPage < 0)
5849 return nullptr;
5851 GtkContainer* pChild;
5852 if (m_bOverFlowBoxIsStart)
5854 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5855 if (nPage < nOverFlowLen)
5856 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
5857 else
5859 nPage -= nOverFlowLen;
5860 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
5863 else
5865 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5866 if (nPage < nMainLen)
5867 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
5868 else
5870 nPage -= nMainLen;
5871 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
5875 unsigned int nPageIndex = static_cast<unsigned int>(nPage);
5876 if (m_aPages.size() < nPageIndex + 1)
5877 m_aPages.resize(nPageIndex + 1);
5878 if (!m_aPages[nPageIndex])
5879 m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
5880 return m_aPages[nPageIndex].get();
5883 virtual void set_current_page(int nPage) override
5885 if (m_bOverFlowBoxIsStart)
5887 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
5888 if (nPage < nOverFlowLen)
5889 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
5890 else
5892 nPage -= nOverFlowLen;
5893 gtk_notebook_set_current_page(m_pNotebook, nPage);
5896 else
5898 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
5899 if (nPage < nMainLen)
5900 gtk_notebook_set_current_page(m_pNotebook, nPage);
5901 else
5903 nPage -= nMainLen;
5904 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
5909 virtual void set_current_page(const OString& rIdent) override
5911 gint nPage = get_page_number(rIdent);
5912 set_current_page(nPage);
5915 virtual int get_n_pages() const override
5917 int nLen = gtk_notebook_get_n_pages(m_pNotebook);
5918 if (m_bOverFlowBoxActive)
5919 nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
5920 return nLen;
5923 virtual OUString get_tab_label_text(const OString& rIdent) const override
5925 gint nPageNum = get_page_number(m_pNotebook, rIdent);
5926 if (nPageNum != -1)
5927 return get_tab_label_text(m_pNotebook, nPageNum);
5928 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
5929 if (nPageNum != -1)
5930 return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
5931 return OUString();
5934 virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
5936 gint nPageNum = get_page_number(m_pNotebook, rIdent);
5937 if (nPageNum != -1)
5939 set_tab_label_text(m_pNotebook, nPageNum, rText);
5940 return;
5942 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
5943 if (nPageNum != -1)
5945 set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
5949 virtual void disable_notify_events() override
5951 g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
5952 g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
5953 g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
5954 g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
5955 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5956 GtkInstanceContainer::disable_notify_events();
5959 virtual void enable_notify_events() override
5961 GtkInstanceContainer::enable_notify_events();
5962 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
5963 g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
5964 g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
5965 g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
5966 g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
5969 void reset_split_data()
5971 // reset overflow and allow it to be recalculated if necessary
5972 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
5973 m_bOverFlowBoxActive = false;
5974 m_nStartTabCount = 0;
5975 m_nEndTabCount = 0;
5978 virtual void remove_page(const OString& rIdent) override
5980 if (m_bOverFlowBoxActive)
5982 unsplit_notebooks();
5983 reset_split_data();
5986 unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
5987 if (nPageIndex < m_aPages.size())
5988 m_aPages.erase(m_aPages.begin() + nPageIndex);
5991 virtual void append_page(const OString& rIdent, const OUString& rLabel) override
5993 if (m_bOverFlowBoxActive)
5995 unsplit_notebooks();
5996 reset_split_data();
5999 // reset overflow and allow it to be recalculated if necessary
6000 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
6001 m_bOverFlowBoxActive = false;
6003 append_page(m_pNotebook, rIdent, rLabel, gtk_grid_new());
6006 virtual ~GtkInstanceNotebook() override
6008 if (m_nLaunchSplitTimeoutId)
6009 g_source_remove(m_nLaunchSplitTimeoutId);
6010 if (m_nSizeAllocateSignalId)
6011 g_signal_handler_disconnect(m_pNotebook, m_nSizeAllocateSignalId);
6012 g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
6013 g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
6014 g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
6015 g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
6016 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
6017 if (m_pOverFlowBox)
6018 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
6022 class GtkInstanceButton : public GtkInstanceContainer, public virtual weld::Button
6024 private:
6025 GtkButton* m_pButton;
6026 gulong m_nSignalId;
6028 static void signalClicked(GtkButton*, gpointer widget)
6030 GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
6031 SolarMutexGuard aGuard;
6032 pThis->signal_clicked();
6035 public:
6036 GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6037 : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
6038 , m_pButton(pButton)
6039 , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
6041 g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
6044 virtual void set_label(const OUString& rText) override
6046 ::set_label(m_pButton, rText);
6049 virtual void set_image(VirtualDevice* pDevice) override
6051 gtk_button_set_always_show_image(m_pButton, true);
6052 gtk_button_set_image_position(m_pButton, GTK_POS_LEFT);
6053 if (pDevice)
6054 gtk_button_set_image(m_pButton, image_new_from_virtual_device(*pDevice));
6055 else
6056 gtk_button_set_image(m_pButton, nullptr);
6059 virtual void set_from_icon_name(const OUString& rIconName) override
6061 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
6062 if (!pixbuf)
6063 gtk_button_set_image(m_pButton, nullptr);
6064 else
6066 gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
6067 g_object_unref(pixbuf);
6071 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
6073 GdkPixbuf* pixbuf = getPixbuf(rImage);
6074 if (!pixbuf)
6075 gtk_button_set_image(m_pButton, nullptr);
6076 else
6078 gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
6079 g_object_unref(pixbuf);
6083 virtual OUString get_label() const override
6085 return ::get_label(m_pButton);
6088 virtual void set_label_line_wrap(bool wrap) override
6090 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pButton));
6091 gtk_label_set_line_wrap(GTK_LABEL(pChild), wrap);
6094 // allow us to block buttons with click handlers making dialogs return a response
6095 bool has_click_handler() const
6097 return m_aClickHdl.IsSet();
6100 void clear_click_handler()
6102 m_aClickHdl = Link<Button&, void>();
6105 virtual void disable_notify_events() override
6107 g_signal_handler_block(m_pButton, m_nSignalId);
6108 GtkInstanceContainer::disable_notify_events();
6111 virtual void enable_notify_events() override
6113 GtkInstanceContainer::enable_notify_events();
6114 g_signal_handler_unblock(m_pButton, m_nSignalId);
6117 virtual ~GtkInstanceButton() override
6119 g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
6120 g_signal_handler_disconnect(m_pButton, m_nSignalId);
6124 void GtkInstanceDialog::asyncresponse(gint ret)
6126 if (ret == GTK_RESPONSE_HELP)
6128 help();
6129 return;
6132 GtkInstanceButton* pClickHandler = has_click_handler(ret);
6133 if (pClickHandler)
6135 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
6136 if (ret == GTK_RESPONSE_DELETE_EVENT)
6137 close(false);
6138 return;
6141 if (get_modal())
6142 m_aDialogRun.dec_modal_count();
6143 hide();
6145 // move the self pointer, otherwise it might be de-allocated by time we try to reset it
6146 auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
6147 auto xDialogController = std::move(m_xDialogController);
6148 auto aFunc = std::move(m_aFunc);
6150 auto nResponseSignalId = m_nResponseSignalId;
6151 auto nCancelSignalId = m_nCancelSignalId;
6152 auto nSignalDeleteId = m_nSignalDeleteId;
6153 m_nResponseSignalId = 0;
6154 m_nCancelSignalId = 0;
6155 m_nSignalDeleteId = 0;
6157 aFunc(GtkToVcl(ret));
6159 if (nResponseSignalId)
6160 g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
6161 if (nCancelSignalId)
6162 g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
6163 if (m_nSignalDeleteId)
6164 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
6166 xDialogController.reset();
6167 xRunAsyncSelf.reset();
6170 int GtkInstanceDialog::run()
6172 if (GTK_IS_DIALOG(m_pDialog))
6173 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
6174 int ret;
6175 while (true)
6177 ret = m_aDialogRun.run();
6178 if (ret == GTK_RESPONSE_HELP)
6180 help();
6181 continue;
6183 else if (has_click_handler(ret))
6184 continue;
6185 break;
6187 hide();
6188 return GtkToVcl(ret);
6191 weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
6193 GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
6194 if (!pButton)
6195 return nullptr;
6196 return new GtkInstanceButton(pButton, m_pBuilder, false);
6199 void GtkInstanceDialog::response(int nResponse)
6201 int nGtkResponse = VclToGtk(nResponse);
6202 //unblock this response now when activated through code
6203 if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
6205 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
6206 GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
6207 if (pButton)
6208 pButton->clear_click_handler();
6210 if (GTK_IS_DIALOG(m_pDialog))
6211 gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
6212 else if (GTK_IS_ASSISTANT(m_pDialog))
6214 if (!m_aDialogRun.loop_is_running())
6215 asyncresponse(nGtkResponse);
6216 else
6218 m_aDialogRun.m_nResponseId = nGtkResponse;
6219 m_aDialogRun.loop_quit();
6224 void GtkInstanceDialog::close(bool bCloseSignal)
6226 GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
6227 if (pClickHandler)
6229 if (bCloseSignal)
6230 g_signal_stop_emission_by_name(m_pDialog, "close");
6231 // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
6232 // act as if cancel button was pressed
6233 pClickHandler->clicked();
6234 return;
6236 response(RET_CANCEL);
6239 GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
6241 GtkInstanceButton* pButton = nullptr;
6242 // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
6243 nResponse = VclToGtk(GtkToVcl(nResponse));
6244 if (GtkButton* pWidget = get_widget_for_response(nResponse))
6246 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
6247 pButton = static_cast<GtkInstanceButton*>(pData);
6248 if (pButton && !pButton->has_click_handler())
6249 pButton = nullptr;
6251 return pButton;
6254 class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
6256 private:
6257 GtkToggleButton* m_pToggleButton;
6258 gulong m_nSignalId;
6260 static void signalToggled(GtkToggleButton*, gpointer widget)
6262 GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
6263 SolarMutexGuard aGuard;
6264 pThis->signal_toggled();
6266 public:
6267 GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6268 : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
6269 , m_pToggleButton(pButton)
6270 , m_nSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
6274 virtual void set_active(bool active) override
6276 disable_notify_events();
6277 gtk_toggle_button_set_inconsistent(m_pToggleButton, false);
6278 gtk_toggle_button_set_active(m_pToggleButton, active);
6279 enable_notify_events();
6282 virtual bool get_active() const override
6284 return gtk_toggle_button_get_active(m_pToggleButton);
6287 virtual void set_inconsistent(bool inconsistent) override
6289 gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
6292 virtual bool get_inconsistent() const override
6294 return gtk_toggle_button_get_inconsistent(m_pToggleButton);
6297 virtual void disable_notify_events() override
6299 g_signal_handler_block(m_pToggleButton, m_nSignalId);
6300 GtkInstanceButton::disable_notify_events();
6303 virtual void enable_notify_events() override
6305 GtkInstanceButton::enable_notify_events();
6306 g_signal_handler_unblock(m_pToggleButton, m_nSignalId);
6309 virtual ~GtkInstanceToggleButton() override
6311 g_signal_handler_disconnect(m_pToggleButton, m_nSignalId);
6315 class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
6317 private:
6318 GtkMenuButton* m_pMenuButton;
6319 GtkBox* m_pBox;
6320 GtkImage* m_pImage;
6321 GtkWidget* m_pLabel;
6322 //popover cannot escape dialog under X so stick up own window instead
6323 GtkWindow* m_pMenuHack;
6324 GtkWidget* m_pPopover;
6325 gulong m_nSignalId;
6327 static void signalToggled(GtkWidget*, gpointer widget)
6329 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6330 SolarMutexGuard aGuard;
6331 pThis->toggle_menu();
6334 void do_grab()
6336 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pMenuHack));
6337 #if GTK_CHECK_VERSION(3, 20, 0)
6338 if (gtk_check_version(3, 20, 0) == nullptr)
6340 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
6341 gdk_seat_grab(pSeat, gtk_widget_get_window(GTK_WIDGET(m_pMenuHack)),
6342 GDK_SEAT_CAPABILITY_ALL, true, nullptr, nullptr, nullptr, nullptr);
6343 return;
6345 #endif
6346 //else older gtk3
6347 const int nMask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
6349 GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
6350 GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
6351 gdk_device_grab(pPointer, gtk_widget_get_window(GTK_WIDGET(m_pMenuHack)), GDK_OWNERSHIP_NONE,
6352 true, GdkEventMask(nMask), nullptr, gtk_get_current_event_time());
6355 void do_ungrab()
6357 GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pMenuHack));
6358 #if GTK_CHECK_VERSION(3, 20, 0)
6359 if (gtk_check_version(3, 20, 0) == nullptr)
6361 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
6362 gdk_seat_ungrab(pSeat);
6363 return;
6365 #endif
6366 //else older gtk3
6367 GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
6368 GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
6369 gdk_device_ungrab(pPointer, gtk_get_current_event_time());
6372 void toggle_menu()
6374 if (!m_pMenuHack)
6375 return;
6376 if (!get_active())
6378 do_ungrab();
6380 gtk_widget_hide(GTK_WIDGET(m_pMenuHack));
6381 //put contents back from where the came from
6382 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pMenuHack));
6383 g_object_ref(pChild);
6384 gtk_container_remove(GTK_CONTAINER(m_pMenuHack), pChild);
6385 gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
6386 g_object_unref(pChild);
6388 else
6390 //set border width
6391 gtk_container_set_border_width(GTK_CONTAINER(m_pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(m_pPopover)));
6393 //steal popover contents and smuggle into toplevel display window
6394 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pPopover));
6395 g_object_ref(pChild);
6396 gtk_container_remove(GTK_CONTAINER(m_pPopover), pChild);
6397 gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
6398 g_object_unref(pChild);
6400 //place the toplevel just below its launcher button
6401 GtkWidget* pToplevel = gtk_widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
6402 gint x, y, absx, absy;
6403 gtk_widget_translate_coordinates(GTK_WIDGET(m_pMenuButton), pToplevel, 0, 0, &x, &y);
6404 GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
6405 gdk_window_get_position(pWindow, &absx, &absy);
6407 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), m_pMenuHack);
6408 gtk_window_set_transient_for(m_pMenuHack, GTK_WINDOW(pToplevel));
6410 gtk_widget_show_all(GTK_WIDGET(m_pMenuHack));
6411 gtk_window_move(m_pMenuHack, x + absx, y + absy + gtk_widget_get_allocated_height(GTK_WIDGET(m_pMenuButton)));
6413 gtk_widget_grab_focus(GTK_WIDGET(m_pMenuHack));
6415 do_grab();
6419 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
6421 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6422 pThis->grab_broken(pEvent);
6425 void grab_broken(const GdkEventGrabBroken *event)
6427 if (event->grab_window == nullptr)
6429 set_active(false);
6431 else
6433 //try and regrab, so when we lose the grab to the menu of the color palette
6434 //combobox we regain it so the color palette doesn't itself disappear on next
6435 //click on the color palette combobox
6436 do_grab();
6440 static gboolean signalButtonRelease(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
6442 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6443 return pThis->button_release(pWidget, pEvent);
6446 bool button_release(GtkWidget* pWidget, GdkEventButton* pEvent)
6448 //we want to pop down if the button was released outside our popup
6449 gdouble x = pEvent->x_root;
6450 gdouble y = pEvent->y_root;
6451 gint xoffset, yoffset;
6452 gdk_window_get_root_origin(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
6454 GtkAllocation alloc;
6455 gtk_widget_get_allocation(pWidget, &alloc);
6456 xoffset += alloc.x;
6457 yoffset += alloc.y;
6459 gtk_widget_get_allocation(GTK_WIDGET(m_pMenuHack), &alloc);
6460 gint x1 = alloc.x + xoffset;
6461 gint y1 = alloc.y + yoffset;
6462 gint x2 = x1 + alloc.width;
6463 gint y2 = y1 + alloc.height;
6465 if (x > x1 && x < x2 && y > y1 && y < y2)
6466 return false;
6468 set_active(false);
6470 return false;
6473 static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
6475 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
6476 return pThis->key_press(pEvent);
6479 bool key_press(const GdkEventKey* pEvent)
6481 if (pEvent->keyval == GDK_KEY_Escape)
6483 set_active(false);
6484 return true;
6486 return false;
6489 void ensure_image_widget()
6491 if (!m_pImage)
6493 m_pImage = GTK_IMAGE(gtk_image_new());
6494 gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
6495 gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
6496 gtk_widget_show(GTK_WIDGET(m_pImage));
6500 public:
6501 GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6502 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
6503 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
6504 , m_pMenuButton(pMenuButton)
6505 , m_pImage(nullptr)
6506 , m_pMenuHack(nullptr)
6507 , m_pPopover(nullptr)
6508 , m_nSignalId(0)
6510 m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
6511 //do it "manually" so we can have the dropdown image in GtkMenuButtons shown
6512 //on the right at the same time as this image is shown on the left
6513 g_object_ref(m_pLabel);
6514 gtk_container_remove(GTK_CONTAINER(m_pMenuButton), m_pLabel);
6516 gint nImageSpacing(2);
6517 GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pMenuButton));
6518 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
6519 m_pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
6521 gtk_box_pack_start(m_pBox, m_pLabel, false, false, 0);
6522 g_object_unref(m_pLabel);
6524 if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(m_pMenuButton)))
6525 gtk_box_pack_end(m_pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
6527 gtk_container_add(GTK_CONTAINER(m_pMenuButton), GTK_WIDGET(m_pBox));
6528 gtk_widget_show_all(GTK_WIDGET(m_pBox));
6531 virtual void set_size_request(int nWidth, int nHeight) override
6533 // tweak the label to get a narrower size to stick
6534 if (GTK_IS_LABEL(m_pLabel))
6535 gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
6536 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
6539 virtual void set_label(const OUString& rText) override
6541 ::set_label(GTK_LABEL(m_pLabel), rText);
6544 virtual OUString get_label() const override
6546 return ::get_label(GTK_LABEL(m_pLabel));
6549 virtual void set_image(VirtualDevice* pDevice) override
6551 ensure_image_widget();
6552 if (pDevice)
6554 if (gtk_check_version(3, 20, 0) == nullptr)
6555 gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
6556 else
6558 GdkPixbuf* pixbuf = getPixbuf(*pDevice);
6559 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
6560 g_object_unref(pixbuf);
6563 else
6564 gtk_image_set_from_surface(m_pImage, nullptr);
6567 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
6569 ensure_image_widget();
6570 GdkPixbuf* pixbuf = getPixbuf(rImage);
6571 if (pixbuf)
6573 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
6574 g_object_unref(pixbuf);
6576 else
6577 gtk_image_set_from_surface(m_pImage, nullptr);
6580 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
6581 const OUString* pIconName, VirtualDevice* pImageSurface, bool bCheck) override
6583 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, bCheck);
6586 virtual void insert_separator(int pos, const OUString& rId) override
6588 MenuHelper::insert_separator(pos, rId);
6591 virtual void remove_item(const OString& rId) override
6593 MenuHelper::remove_item(rId);
6596 virtual void clear() override
6598 clear_items();
6601 virtual void set_item_active(const OString& rIdent, bool bActive) override
6603 MenuHelper::set_item_active(rIdent, bActive);
6606 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
6608 MenuHelper::set_item_sensitive(rIdent, bSensitive);
6611 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
6613 MenuHelper::set_item_label(rIdent, rLabel);
6616 virtual OUString get_item_label(const OString& rIdent) const override
6618 return MenuHelper::get_item_label(rIdent);
6621 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
6623 MenuHelper::set_item_visible(rIdent, bVisible);
6626 virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
6628 MenuHelper::set_item_help_id(rIdent, rHelpId);
6631 virtual OString get_item_help_id(const OString& rIdent) const override
6633 return MenuHelper::get_item_help_id(rIdent);
6636 virtual void signal_activate(GtkMenuItem* pItem) override
6638 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
6639 signal_selected(OString(pStr, pStr ? strlen(pStr) : 0));
6642 virtual void set_popover(weld::Widget* pPopover) override
6644 GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
6645 m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
6647 #if defined(GDK_WINDOWING_X11)
6648 if (!m_pMenuHack)
6650 //under wayland a Popover will work to "escape" the parent dialog, not
6651 //so under X, so come up with this hack to use a raw GtkWindow
6652 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
6653 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
6655 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
6656 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
6657 gtk_window_set_modal(m_pMenuHack, true);
6658 gtk_window_set_resizable(m_pMenuHack, false);
6659 m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalToggled), this);
6660 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
6661 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
6662 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
6665 #endif
6667 if (m_pMenuHack)
6669 gtk_menu_button_set_popover(m_pMenuButton, gtk_popover_new(GTK_WIDGET(m_pMenuButton)));
6671 else
6673 gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
6674 if (m_pPopover)
6675 gtk_widget_show_all(m_pPopover);
6679 void set_menu(weld::Menu* pMenu);
6681 virtual ~GtkInstanceMenuButton() override
6683 if (m_pMenuHack)
6685 g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
6686 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
6687 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
6692 class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
6694 protected:
6695 std::vector<GtkMenuItem*> m_aExtraItems;
6696 OString m_sActivated;
6697 GtkInstanceMenuButton* m_pTopLevelMenuButton;
6699 private:
6700 virtual void signal_activate(GtkMenuItem* pItem) override
6702 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
6703 m_sActivated = OString(pStr, pStr ? strlen(pStr) : 0);
6704 weld::Menu::signal_activate(m_sActivated);
6707 void clear_extras()
6709 if (m_aExtraItems.empty())
6710 return;
6711 if (m_pTopLevelMenuButton)
6713 for (auto a : m_aExtraItems)
6714 m_pTopLevelMenuButton->remove_from_map(a);
6716 m_aExtraItems.clear();
6719 public:
6720 GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
6721 : MenuHelper(pMenu, bTakeOwnership)
6722 , m_pTopLevelMenuButton(nullptr)
6724 // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
6725 // then find that MenuButton parent so that when adding items to this
6726 // menu we can inform the MenuButton of their addition
6727 GtkMenu* pTopLevelMenu = pMenu;
6728 while (true)
6730 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
6731 if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
6732 break;
6733 GtkWidget* pParent = gtk_widget_get_parent(pAttached);
6734 if (!pParent || !GTK_IS_MENU(pParent))
6735 break;
6736 pTopLevelMenu = GTK_MENU(pParent);
6738 if (pTopLevelMenu != pMenu)
6740 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
6741 if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
6743 void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
6744 m_pTopLevelMenuButton = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
6749 virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle &rRect) override
6751 m_sActivated.clear();
6753 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
6754 assert(pGtkWidget);
6756 GtkWidget* pWidget = pGtkWidget->getWidget();
6757 gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
6759 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
6760 //it during DispatchCommand, returning now to the outer loop causes the
6761 //launching PopupMenu to be destroyed, instead run the subloop here
6762 //until the gtk menu is destroyed
6763 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
6764 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
6766 #if GTK_CHECK_VERSION(3,22,0)
6767 if (gtk_check_version(3, 22, 0) == nullptr)
6769 GdkRectangle aRect{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
6770 static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
6771 if (SwapForRTL(pWidget))
6772 aRect.x = gtk_widget_get_allocated_width(pWidget) - aRect.width - 1 - aRect.x;
6774 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
6775 // before trying to launch the menu
6776 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
6777 GdkEvent *event = GtkSalFrame::makeFakeKeyPress(pWidget);
6778 gtk_main_do_event(event);
6779 gdk_event_free(event);
6781 gtk_menu_popup_at_rect(m_pMenu, gtk_widget_get_window(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, nullptr);
6783 else
6784 #else
6785 (void) rRect;
6786 #endif
6788 guint nButton;
6789 guint32 nTime;
6791 //typically there is an event, and we can then distinguish if this was
6792 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
6793 //doesn't)
6794 GdkEvent *pEvent = gtk_get_current_event();
6795 if (pEvent)
6797 gdk_event_get_button(pEvent, &nButton);
6798 nTime = gdk_event_get_time(pEvent);
6800 else
6802 nButton = 0;
6803 nTime = GtkSalFrame::GetLastInputEventTime();
6806 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
6809 if (g_main_loop_is_running(pLoop))
6811 gdk_threads_leave();
6812 g_main_loop_run(pLoop);
6813 gdk_threads_enter();
6815 g_main_loop_unref(pLoop);
6816 g_signal_handler_disconnect(m_pMenu, nSignalId);
6817 gtk_menu_detach(m_pMenu);
6819 return m_sActivated;
6822 virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
6824 set_item_sensitive(rIdent, bSensitive);
6827 virtual void set_active(const OString& rIdent, bool bActive) override
6829 set_item_active(rIdent, bActive);
6832 virtual bool get_active(const OString& rIdent) const override
6834 return get_item_active(rIdent);
6837 virtual void set_visible(const OString& rIdent, bool bShow) override
6839 set_item_visible(rIdent, bShow);
6842 virtual void set_label(const OString& rIdent, const OUString& rLabel) override
6844 set_item_label(rIdent, rLabel);
6847 virtual void insert_separator(int pos, const OUString& rId) override
6849 MenuHelper::insert_separator(pos, rId);
6852 virtual void clear() override
6854 clear_extras();
6855 clear_items();
6858 virtual void insert(int pos, const OUString& rId, const OUString& rStr,
6859 const OUString* pIconName, VirtualDevice* pImageSurface,
6860 bool bCheck) override
6862 GtkWidget* pImage = nullptr;
6863 if (pIconName)
6865 if (GdkPixbuf* pixbuf = load_icon_by_name(*pIconName))
6867 pImage = gtk_image_new_from_pixbuf(pixbuf);
6868 g_object_unref(pixbuf);
6871 else if (pImageSurface)
6873 pImage = image_new_from_virtual_device(*pImageSurface);
6876 GtkWidget *pItem;
6877 if (pImage)
6879 GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
6880 GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
6881 pItem = bCheck ? gtk_check_menu_item_new() : gtk_menu_item_new();
6882 gtk_container_add(GTK_CONTAINER(pBox), pImage);
6883 gtk_container_add(GTK_CONTAINER(pBox), pLabel);
6884 gtk_container_add(GTK_CONTAINER(pItem), pBox);
6885 gtk_widget_show_all(pItem);
6887 else
6889 pItem = bCheck ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
6890 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
6892 gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
6893 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
6894 gtk_widget_show(pItem);
6895 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
6896 m_aExtraItems.push_back(pMenuItem);
6897 add_to_map(pMenuItem);
6898 if (m_pTopLevelMenuButton)
6899 m_pTopLevelMenuButton->add_to_map(pMenuItem);
6900 if (pos != -1)
6901 gtk_menu_reorder_child(m_pMenu, pItem, pos);
6904 virtual ~GtkInstanceMenu() override
6906 clear_extras();
6910 namespace
6912 vcl::ImageType GtkToVcl(GtkIconSize eSize)
6914 vcl::ImageType eRet;
6915 switch (eSize)
6917 case GTK_ICON_SIZE_MENU:
6918 case GTK_ICON_SIZE_SMALL_TOOLBAR:
6919 case GTK_ICON_SIZE_BUTTON:
6920 eRet = vcl::ImageType::Size16;
6921 break;
6922 case GTK_ICON_SIZE_LARGE_TOOLBAR:
6923 eRet = vcl::ImageType::Size26;
6924 break;
6925 case GTK_ICON_SIZE_DND:
6926 case GTK_ICON_SIZE_DIALOG:
6927 eRet = vcl::ImageType::Size32;
6928 break;
6929 default:
6930 case GTK_ICON_SIZE_INVALID:
6931 eRet = vcl::ImageType::Small;
6932 break;
6934 return eRet;
6938 void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
6940 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
6941 m_pPopover = nullptr;
6942 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
6943 gtk_menu_button_set_popup(m_pMenuButton, pMenuWidget);
6946 class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
6948 private:
6949 GtkToolbar* m_pToolbar;
6951 std::map<OString, GtkToolButton*> m_aMap;
6952 std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
6954 // at the time of writing there is no gtk_menu_tool_button_set_popover available
6955 // though there will be in the future
6956 // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
6957 static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
6959 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
6961 GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
6962 *ppToggleButton = pWidget;
6964 else if (GTK_IS_CONTAINER(pWidget))
6965 gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
6968 static void collect(GtkWidget* pItem, gpointer widget)
6970 if (GTK_IS_TOOL_BUTTON(pItem))
6972 GtkToolButton* pToolItem = GTK_TOOL_BUTTON(pItem);
6973 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
6975 GtkMenuButton* pMenuButton = nullptr;
6976 if (GTK_IS_MENU_TOOL_BUTTON(pItem))
6977 find_menu_button(pItem, &pMenuButton);
6979 pThis->add_to_map(pToolItem, pMenuButton);
6983 void add_to_map(GtkToolButton* pToolItem, GtkMenuButton* pMenuButton)
6985 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pToolItem));
6986 OString id(pStr, pStr ? strlen(pStr) : 0);
6987 m_aMap[id] = pToolItem;
6988 if (pMenuButton)
6989 m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, m_pBuilder, false);
6990 g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
6993 static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
6995 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
6996 SolarMutexGuard aGuard;
6997 pThis->signal_item_clicked(pItem);
7000 void signal_item_clicked(GtkToolButton* pItem)
7002 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7003 signal_clicked(OString(pStr, pStr ? strlen(pStr) : 0));
7006 public:
7007 GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7008 : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
7009 , m_pToolbar(pToolbar)
7011 gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
7014 void disable_item_notify_events()
7016 for (auto& a : m_aMap)
7017 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
7020 void enable_item_notify_events()
7022 for (auto& a : m_aMap)
7023 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
7026 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
7028 disable_item_notify_events();
7029 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
7030 enable_item_notify_events();
7033 virtual bool get_item_sensitive(const OString& rIdent) const override
7035 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
7038 virtual void set_item_active(const OString& rIdent, bool bActive) override
7040 disable_item_notify_events();
7042 auto aFind = m_aMenuButtonMap.find(rIdent);
7043 if (aFind != m_aMenuButtonMap.end())
7044 aFind->second->set_active(bActive);
7045 else
7047 GtkToolButton* pToolButton = m_aMap.find(rIdent)->second;
7048 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
7051 enable_item_notify_events();
7054 virtual bool get_item_active(const OString& rIdent) const override
7056 auto aFind = m_aMenuButtonMap.find(rIdent);
7057 if (aFind != m_aMenuButtonMap.end())
7058 return aFind->second->get_active();
7060 GtkToolButton* pToolButton = m_aMap.find(rIdent)->second;
7061 return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
7064 virtual void insert_separator(int pos, const OUString& rId) override
7066 GtkToolItem* pItem = gtk_separator_tool_item_new();
7067 gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
7068 gtk_toolbar_insert(m_pToolbar, pItem, pos);
7069 gtk_widget_show(GTK_WIDGET(pItem));
7072 virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
7074 m_aMenuButtonMap[rIdent]->set_popover(pPopover);
7077 virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
7079 m_aMenuButtonMap[rIdent]->set_menu(pMenu);
7082 virtual int get_n_items() const override
7084 return gtk_toolbar_get_n_items(m_pToolbar);
7087 virtual OString get_item_ident(int nIndex) const override
7089 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7090 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7091 return OString(pStr, pStr ? strlen(pStr) : 0);
7094 virtual void set_item_label(int nIndex, const OUString& rLabel) override
7096 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7097 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
7100 virtual void set_item_icon(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
7102 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7104 GtkWidget* pImage = nullptr;
7105 if (GdkPixbuf* pixbuf = getPixbuf(rIcon))
7107 pImage = gtk_image_new_from_pixbuf(pixbuf);
7108 g_object_unref(pixbuf);
7111 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
7114 virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
7116 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
7117 gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
7120 virtual vcl::ImageType get_icon_size() const override
7122 return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
7125 virtual ~GtkInstanceToolbar() override
7127 for (auto& a : m_aMap)
7128 g_signal_handlers_disconnect_by_data(a.second, this);
7132 class GtkInstanceLinkButton : public GtkInstanceContainer, public virtual weld::LinkButton
7134 private:
7135 GtkLinkButton* m_pButton;
7136 gulong m_nSignalId;
7138 static bool signalActivateLink(GtkButton*, gpointer widget)
7140 GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
7141 SolarMutexGuard aGuard;
7142 return pThis->signal_activate_link();
7145 public:
7146 GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7147 : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
7148 , m_pButton(pButton)
7149 , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
7153 virtual void set_label(const OUString& rText) override
7155 ::set_label(GTK_BUTTON(m_pButton), rText);
7158 virtual OUString get_label() const override
7160 return ::get_label(GTK_BUTTON(m_pButton));
7163 virtual void set_uri(const OUString& rText) override
7165 gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
7168 virtual OUString get_uri() const override
7170 const gchar* pStr = gtk_link_button_get_uri(m_pButton);
7171 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7174 virtual void disable_notify_events() override
7176 g_signal_handler_block(m_pButton, m_nSignalId);
7177 GtkInstanceContainer::disable_notify_events();
7180 virtual void enable_notify_events() override
7182 GtkInstanceContainer::enable_notify_events();
7183 g_signal_handler_unblock(m_pButton, m_nSignalId);
7186 virtual ~GtkInstanceLinkButton() override
7188 g_signal_handler_disconnect(m_pButton, m_nSignalId);
7192 class GtkInstanceRadioButton : public GtkInstanceToggleButton, public virtual weld::RadioButton
7194 public:
7195 GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7196 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
7201 class GtkInstanceCheckButton : public GtkInstanceToggleButton, public virtual weld::CheckButton
7203 public:
7204 GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7205 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
7210 class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
7212 private:
7213 GtkScale* m_pScale;
7214 gulong m_nValueChangedSignalId;
7216 static void signalValueChanged(GtkScale*, gpointer widget)
7218 GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
7219 SolarMutexGuard aGuard;
7220 pThis->signal_value_changed();
7223 public:
7224 GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7225 : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
7226 , m_pScale(pScale)
7227 , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
7231 virtual void disable_notify_events() override
7233 g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
7234 GtkInstanceWidget::disable_notify_events();
7237 virtual void enable_notify_events() override
7239 GtkInstanceWidget::enable_notify_events();
7240 g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
7243 virtual void set_value(int value) override
7245 disable_notify_events();
7246 gtk_range_set_value(GTK_RANGE(m_pScale), value);
7247 enable_notify_events();
7250 virtual void set_range(int min, int max) override
7252 disable_notify_events();
7253 gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
7254 enable_notify_events();
7257 virtual int get_value() const override
7259 return gtk_range_get_value(GTK_RANGE(m_pScale));
7262 virtual ~GtkInstanceScale() override
7264 g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
7268 class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
7270 private:
7271 GtkProgressBar* m_pProgressBar;
7273 public:
7274 GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7275 : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
7276 , m_pProgressBar(pProgressBar)
7280 virtual void set_percentage(int value) override
7282 gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
7285 virtual OUString get_text() const override
7287 const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
7288 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
7289 return sRet;
7292 virtual void set_text(const OUString& rText) override
7294 gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
7298 class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
7300 private:
7301 GtkSpinner* m_pSpinner;
7303 public:
7304 GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7305 : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
7306 , m_pSpinner(pSpinner)
7310 virtual void start() override
7312 gtk_spinner_start(m_pSpinner);
7315 virtual void stop() override
7317 gtk_spinner_stop(m_pSpinner);
7321 class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
7323 private:
7324 GtkImage* m_pImage;
7326 public:
7327 GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7328 : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
7329 , m_pImage(pImage)
7333 virtual void set_from_icon_name(const OUString& rIconName) override
7335 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
7336 if (!pixbuf)
7337 return;
7338 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7339 g_object_unref(pixbuf);
7342 virtual void set_image(VirtualDevice* pDevice) override
7344 if (gtk_check_version(3, 20, 0) == nullptr)
7346 if (pDevice)
7347 gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
7348 else
7349 gtk_image_set_from_surface(m_pImage, nullptr);
7350 return;
7353 GdkPixbuf* pixbuf = pDevice ? getPixbuf(*pDevice) : nullptr;
7354 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7355 if (pixbuf)
7356 g_object_unref(pixbuf);
7359 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
7361 GdkPixbuf* pixbuf = getPixbuf(rImage);
7362 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7363 if (pixbuf)
7364 g_object_unref(pixbuf);
7368 class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
7370 private:
7371 GtkCalendar* m_pCalendar;
7372 gulong m_nDaySelectedSignalId;
7373 gulong m_nDaySelectedDoubleClickSignalId;
7374 gulong m_nKeyPressEventSignalId;
7376 static void signalDaySelected(GtkCalendar*, gpointer widget)
7378 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
7379 pThis->signal_selected();
7382 static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
7384 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
7385 pThis->signal_activated();
7388 bool signal_key_press(GdkEventKey* pEvent)
7390 if (pEvent->keyval == GDK_KEY_Return)
7392 signal_activated();
7393 return true;
7395 return false;
7398 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
7400 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
7401 return pThis->signal_key_press(pEvent);
7404 public:
7405 GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7406 : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
7407 , m_pCalendar(pCalendar)
7408 , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
7409 , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
7410 , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
7414 virtual void set_date(const Date& rDate) override
7416 disable_notify_events();
7417 gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
7418 gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
7419 enable_notify_events();
7422 virtual Date get_date() const override
7424 guint year, month, day;
7425 gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
7426 return Date(day, month + 1, year);
7429 virtual void disable_notify_events() override
7431 g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
7432 g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
7433 GtkInstanceWidget::disable_notify_events();
7436 virtual void enable_notify_events() override
7438 GtkInstanceWidget::enable_notify_events();
7439 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
7440 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
7443 virtual ~GtkInstanceCalendar() override
7445 g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
7446 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
7447 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
7451 namespace
7453 PangoAttrList* create_attr_list(const vcl::Font& rFont)
7455 PangoAttrList* pAttrList = pango_attr_list_new();
7456 pango_attr_list_insert(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
7457 pango_attr_list_insert(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
7458 switch (rFont.GetItalic())
7460 case ITALIC_NONE:
7461 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
7462 break;
7463 case ITALIC_NORMAL:
7464 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
7465 break;
7466 case ITALIC_OBLIQUE:
7467 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
7468 break;
7469 default:
7470 break;
7472 switch (rFont.GetWeight())
7474 case WEIGHT_ULTRALIGHT:
7475 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
7476 break;
7477 case WEIGHT_LIGHT:
7478 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
7479 break;
7480 case WEIGHT_NORMAL:
7481 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
7482 break;
7483 case WEIGHT_BOLD:
7484 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
7485 break;
7486 case WEIGHT_ULTRABOLD:
7487 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
7488 break;
7489 default:
7490 break;
7492 switch (rFont.GetWidthType())
7494 case WIDTH_ULTRA_CONDENSED:
7495 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
7496 break;
7497 case WIDTH_EXTRA_CONDENSED:
7498 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
7499 break;
7500 case WIDTH_CONDENSED:
7501 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
7502 break;
7503 case WIDTH_SEMI_CONDENSED:
7504 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
7505 break;
7506 case WIDTH_NORMAL:
7507 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
7508 break;
7509 case WIDTH_SEMI_EXPANDED:
7510 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
7511 break;
7512 case WIDTH_EXPANDED:
7513 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
7514 break;
7515 case WIDTH_EXTRA_EXPANDED:
7516 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
7517 break;
7518 case WIDTH_ULTRA_EXPANDED:
7519 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
7520 break;
7521 default:
7522 break;
7524 return pAttrList;
7528 namespace
7530 void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
7532 if (eType == weld::EntryMessageType::Error)
7533 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
7534 else if (eType == weld::EntryMessageType::Warning)
7535 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
7536 else
7537 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
7541 class GtkInstanceEntry : public GtkInstanceWidget, public virtual weld::Entry
7543 private:
7544 GtkEntry* m_pEntry;
7545 gulong m_nChangedSignalId;
7546 gulong m_nInsertTextSignalId;
7547 gulong m_nCursorPosSignalId;
7548 gulong m_nSelectionPosSignalId;
7549 gulong m_nActivateSignalId;
7551 static void signalChanged(GtkEntry*, gpointer widget)
7553 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7554 SolarMutexGuard aGuard;
7555 pThis->signal_changed();
7558 static void signalInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
7559 gint* position, gpointer widget)
7561 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7562 SolarMutexGuard aGuard;
7563 pThis->signal_insert_text(pEntry, pNewText, nNewTextLength, position);
7566 void signal_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
7568 if (!m_aInsertTextHdl.IsSet())
7569 return;
7570 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
7571 const bool bContinue = m_aInsertTextHdl.Call(sText);
7572 if (bContinue && !sText.isEmpty())
7574 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
7575 g_signal_handlers_block_by_func(pEntry, gpointer(signalInsertText), this);
7576 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
7577 g_signal_handlers_unblock_by_func(pEntry, gpointer(signalInsertText), this);
7579 g_signal_stop_emission_by_name(pEntry, "insert-text");
7582 static void signalCursorPosition(GtkEntry*, GParamSpec*, gpointer widget)
7584 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7585 pThis->signal_cursor_position();
7588 static void signalActivate(GtkEntry*, gpointer widget)
7590 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
7591 pThis->signal_activate();
7594 void signal_activate()
7596 if (m_aActivateHdl.IsSet())
7598 SolarMutexGuard aGuard;
7599 if (m_aActivateHdl.Call(*this))
7600 g_signal_stop_emission_by_name(m_pEntry, "activate");
7604 public:
7605 GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7606 : GtkInstanceWidget(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
7607 , m_pEntry(pEntry)
7608 , m_nChangedSignalId(g_signal_connect(pEntry, "changed", G_CALLBACK(signalChanged), this))
7609 , m_nInsertTextSignalId(g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalInsertText), this))
7610 , m_nCursorPosSignalId(g_signal_connect(pEntry, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
7611 , m_nSelectionPosSignalId(g_signal_connect(pEntry, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
7612 , m_nActivateSignalId(g_signal_connect(pEntry, "activate", G_CALLBACK(signalActivate), this))
7616 virtual void set_text(const OUString& rText) override
7618 disable_notify_events();
7619 gtk_entry_set_text(m_pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
7620 enable_notify_events();
7623 virtual OUString get_text() const override
7625 const gchar* pText = gtk_entry_get_text(m_pEntry);
7626 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
7627 return sRet;
7630 virtual void set_width_chars(int nChars) override
7632 disable_notify_events();
7633 gtk_entry_set_width_chars(m_pEntry, nChars);
7634 gtk_entry_set_max_width_chars(m_pEntry, nChars);
7635 enable_notify_events();
7638 virtual int get_width_chars() const override
7640 return gtk_entry_get_width_chars(m_pEntry);
7643 virtual void set_max_length(int nChars) override
7645 disable_notify_events();
7646 gtk_entry_set_max_length(m_pEntry, nChars);
7647 enable_notify_events();
7650 virtual void select_region(int nStartPos, int nEndPos) override
7652 disable_notify_events();
7653 gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
7654 enable_notify_events();
7657 bool get_selection_bounds(int& rStartPos, int& rEndPos) override
7659 return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
7662 virtual void replace_selection(const OUString& rText) override
7664 gtk_editable_delete_selection(GTK_EDITABLE(m_pEntry));
7665 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
7666 gint position = gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
7667 gtk_editable_insert_text(GTK_EDITABLE(m_pEntry), sText.getStr(), sText.getLength(),
7668 &position);
7671 virtual void set_position(int nCursorPos) override
7673 disable_notify_events();
7674 gtk_editable_set_position(GTK_EDITABLE(m_pEntry), nCursorPos);
7675 enable_notify_events();
7678 virtual int get_position() const override
7680 return gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
7683 virtual void set_editable(bool bEditable) override
7685 gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
7688 virtual bool get_editable() const override
7690 return gtk_editable_get_editable(GTK_EDITABLE(m_pEntry));
7693 virtual void set_message_type(weld::EntryMessageType eType) override
7695 ::set_entry_message_type(m_pEntry, eType);
7698 virtual void disable_notify_events() override
7700 g_signal_handler_block(m_pEntry, m_nActivateSignalId);
7701 g_signal_handler_block(m_pEntry, m_nSelectionPosSignalId);
7702 g_signal_handler_block(m_pEntry, m_nCursorPosSignalId);
7703 g_signal_handler_block(m_pEntry, m_nInsertTextSignalId);
7704 g_signal_handler_block(m_pEntry, m_nChangedSignalId);
7705 GtkInstanceWidget::disable_notify_events();
7708 virtual void enable_notify_events() override
7710 GtkInstanceWidget::enable_notify_events();
7711 g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
7712 g_signal_handler_unblock(m_pEntry, m_nInsertTextSignalId);
7713 g_signal_handler_unblock(m_pEntry, m_nCursorPosSignalId);
7714 g_signal_handler_unblock(m_pEntry, m_nSelectionPosSignalId);
7715 g_signal_handler_unblock(m_pEntry, m_nActivateSignalId);
7718 virtual void set_font(const vcl::Font& rFont) override
7720 PangoAttrList* pAttrList = create_attr_list(rFont);
7721 gtk_entry_set_attributes(m_pEntry, pAttrList);
7722 pango_attr_list_unref(pAttrList);
7725 void fire_signal_changed()
7727 signal_changed();
7730 virtual void cut_clipboard() override
7732 gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
7735 virtual void copy_clipboard() override
7737 gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
7740 virtual void paste_clipboard() override
7742 gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
7745 virtual ~GtkInstanceEntry() override
7747 g_signal_handler_disconnect(m_pEntry, m_nActivateSignalId);
7748 g_signal_handler_disconnect(m_pEntry, m_nSelectionPosSignalId);
7749 g_signal_handler_disconnect(m_pEntry, m_nCursorPosSignalId);
7750 g_signal_handler_disconnect(m_pEntry, m_nInsertTextSignalId);
7751 g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
7755 namespace
7757 struct Search
7759 OString str;
7760 int index;
7761 int col;
7762 Search(const OUString& rText, int nCol)
7763 : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
7764 , index(-1)
7765 , col(nCol)
7770 gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
7772 Search* search = static_cast<Search*>(data);
7773 gchar *pStr = nullptr;
7774 gtk_tree_model_get(model, iter, search->col, &pStr, -1);
7775 bool found = strcmp(pStr, search->str.getStr()) == 0;
7776 if (found)
7778 gint depth;
7779 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
7780 search->index = indices[depth-1];
7782 g_free(pStr);
7783 return found;
7786 GdkPixbuf* getPixbuf(const OUString& rIconName)
7788 if (rIconName.isEmpty())
7789 return nullptr;
7791 GdkPixbuf* pixbuf = nullptr;
7793 if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
7795 assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
7796 "unknown stock image");
7798 GError *error = nullptr;
7799 GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
7800 pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
7801 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
7803 else
7805 const AllSettings& rSettings = Application::GetSettings();
7806 pixbuf = load_icon_by_name(rIconName,
7807 rSettings.GetStyleSettings().DetermineIconTheme(),
7808 rSettings.GetUILanguageTag().getBcp47());
7811 return pixbuf;
7814 void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, const OUString& rText, const OUString* pIconName, const VirtualDevice* pDevice)
7816 if (!pIconName && !pDevice)
7818 gtk_list_store_insert_with_values(pListStore, &iter, pos,
7819 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
7820 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
7821 -1);
7823 else
7825 if (pIconName)
7827 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
7829 gtk_list_store_insert_with_values(pListStore, &iter, pos,
7830 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
7831 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
7832 2, pixbuf,
7833 -1);
7835 if (pixbuf)
7836 g_object_unref(pixbuf);
7838 else
7840 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
7842 Size aSize(pDevice->GetOutputSizePixel());
7843 cairo_surface_t* target = cairo_surface_create_similar(surface,
7844 cairo_surface_get_content(surface),
7845 aSize.Width(),
7846 aSize.Height());
7848 cairo_t* cr = cairo_create(target);
7849 cairo_set_source_surface(cr, surface, 0, 0);
7850 cairo_paint(cr);
7851 cairo_destroy(cr);
7853 gtk_list_store_insert_with_values(pListStore, &iter, pos,
7854 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
7855 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
7856 3, target,
7857 -1);
7858 cairo_surface_destroy(target);
7864 namespace
7866 gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
7868 comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
7869 gchar* pName1;
7870 gchar* pName2;
7871 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
7872 gint sort_column_id(0);
7873 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
7874 gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
7875 gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
7876 gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
7877 OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
7878 g_free(pName1);
7879 g_free(pName2);
7880 return ret;
7883 int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
7885 GtkTreeIter iter;
7886 if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
7887 return -1;
7889 const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
7890 int nRet = nStartRow;
7893 gchar* pStr;
7894 gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
7895 OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
7896 g_free(pStr);
7897 const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
7898 if (bMatch)
7899 return nRet;
7900 ++nRet;
7901 } while (gtk_tree_model_iter_next(pTreeModel, &iter));
7903 return -1;
7907 struct GtkInstanceTreeIter : public weld::TreeIter
7909 GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
7911 if (pOrig)
7912 iter = pOrig->iter;
7913 else
7914 memset(&iter, 0, sizeof(iter));
7916 GtkInstanceTreeIter(const GtkTreeIter& rOrig)
7918 memcpy(&iter, &rOrig, sizeof(iter));
7920 virtual bool equal(const TreeIter& rOther) const override
7922 return memcmp(&iter, &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
7924 GtkTreeIter iter;
7927 class GtkInstanceTreeView;
7929 static GtkInstanceTreeView* g_DragSource;
7931 class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView
7933 private:
7934 GtkTreeView* m_pTreeView;
7935 GtkTreeStore* m_pTreeStore;
7936 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
7937 GList *m_pColumns;
7938 std::vector<gulong> m_aColumnSignalIds;
7939 // map from toggle column to toggle visibility column
7940 std::map<int, int> m_aToggleVisMap;
7941 // map from toggle column to tristate column
7942 std::map<int, int> m_aToggleTriStateMap;
7943 // map from text column to text weight column
7944 std::map<int, int> m_aWeightMap;
7945 // map from text column to sensitive column
7946 std::map<int, int> m_aSensitiveMap;
7947 std::vector<GtkSortType> m_aSavedSortTypes;
7948 std::vector<int> m_aSavedSortColumns;
7949 std::vector<int> m_aViewColToModelCol;
7950 std::vector<int> m_aModelColToViewCol;
7951 bool m_bWorkAroundBadDragRegion;
7952 bool m_bInDrag;
7953 gint m_nTextCol;
7954 gint m_nImageCol;
7955 gint m_nExpanderImageCol;
7956 gint m_nIdCol;
7957 gulong m_nChangedSignalId;
7958 gulong m_nRowActivatedSignalId;
7959 gulong m_nTestExpandRowSignalId;
7960 gulong m_nVAdjustmentChangedSignalId;
7961 gulong m_nRowDeletedSignalId;
7962 gulong m_nRowInsertedSignalId;
7963 gulong m_nPopupMenuSignalId;
7964 gulong m_nDragBeginSignalId;
7965 gulong m_nDragEndSignalId;
7966 gulong m_nKeyPressSignalId;
7967 ImplSVEvent* m_pChangeEvent;
7969 DECL_LINK(async_signal_changed, void*, void);
7971 void launch_signal_changed()
7973 //tdf#117991 selection change is sent before the focus change, and focus change
7974 //is what will cause a spinbutton that currently has the focus to set its contents
7975 //as the spin button value. So any LibreOffice callbacks on
7976 //signal-change would happen before the spinbutton value-change occurs.
7977 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
7978 //has been processed
7979 if (m_pChangeEvent)
7980 Application::RemoveUserEvent(m_pChangeEvent);
7981 m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
7984 static void signalChanged(GtkTreeView*, gpointer widget)
7986 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
7987 pThis->launch_signal_changed();
7990 void handle_row_activated()
7992 if (signal_row_activated())
7993 return;
7994 GtkInstanceTreeIter aIter(nullptr);
7995 if (!get_cursor(&aIter))
7996 return;
7997 if (iter_has_child(aIter))
7998 get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
8001 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
8003 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8004 SolarMutexGuard aGuard;
8005 pThis->handle_row_activated();
8008 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
8010 return m_aPopupMenuHdl.Call(rCEvt);
8013 void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
8014 const OUString* pIconName, const VirtualDevice* pDevice, const OUString* pExpanderName)
8016 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, const_cast<GtkTreeIter*>(parent), pos,
8017 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
8018 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
8019 -1);
8020 if (pIconName)
8022 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
8023 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
8024 if (pixbuf)
8025 g_object_unref(pixbuf);
8027 else if (pDevice)
8029 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
8031 Size aSize(pDevice->GetOutputSizePixel());
8032 cairo_surface_t* target = cairo_surface_create_similar(surface,
8033 cairo_surface_get_content(surface),
8034 aSize.Width(),
8035 aSize.Height());
8037 cairo_t* cr = cairo_create(target);
8038 cairo_set_source_surface(cr, surface, 0, 0);
8039 cairo_paint(cr);
8040 cairo_destroy(cr);
8042 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, target, -1);
8043 cairo_surface_destroy(target);
8046 if (pExpanderName)
8048 GdkPixbuf* pixbuf = getPixbuf(*pExpanderName);
8049 gtk_tree_store_set(m_pTreeStore, &iter, m_nExpanderImageCol, pixbuf, -1);
8050 if (pixbuf)
8051 g_object_unref(pixbuf);
8055 OUString get(const GtkTreeIter& iter, int col) const
8057 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8058 gchar* pStr;
8059 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
8060 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
8061 g_free(pStr);
8062 return sRet;
8065 OUString get(int pos, int col) const
8067 OUString sRet;
8068 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8069 GtkTreeIter iter;
8070 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8071 sRet = get(iter, col);
8072 return sRet;
8075 gint get_int(const GtkTreeIter& iter, int col) const
8077 gint nRet(-1);
8078 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8079 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
8080 return nRet;
8083 gint get_int(int pos, int col) const
8085 gint nRet(-1);
8086 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8087 GtkTreeIter iter;
8088 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8089 nRet = get_int(iter, col);
8090 gtk_tree_model_get(pModel, &iter, col, &nRet, -1);
8091 return nRet;
8094 bool get_bool(const GtkTreeIter& iter, int col) const
8096 gboolean bRet(false);
8097 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8098 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
8099 return bRet;
8102 bool get_bool(int pos, int col) const
8104 bool bRet(false);
8105 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8106 GtkTreeIter iter;
8107 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8108 bRet = get_bool(iter, col);
8109 return bRet;
8112 void set(const GtkTreeIter& iter, int col, const OUString& rText)
8114 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
8115 gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
8118 void set(int pos, int col, const OUString& rText)
8120 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8121 GtkTreeIter iter;
8122 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8123 set(iter, col, rText);
8126 void set(const GtkTreeIter& iter, int col, bool bOn)
8128 gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bOn, -1);
8131 void set(int pos, int col, bool bOn)
8133 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8134 GtkTreeIter iter;
8135 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8136 set(iter, col, bOn);
8139 void set(const GtkTreeIter& iter, int col, gint bInt)
8141 gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
8144 void set(int pos, int col, gint bInt)
8146 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8147 GtkTreeIter iter;
8148 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
8149 set(iter, col, bInt);
8152 static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
8154 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8155 return !pThis->signal_test_expand_row(*iter);
8158 bool signal_test_expand_row(GtkTreeIter& iter)
8160 disable_notify_events();
8161 GtkInstanceTreeIter aIter(nullptr);
8163 // if there's a preexisting placeholder child, required to make this
8164 // potentially expandable in the first place, now we remove it
8165 bool bPlaceHolder = false;
8166 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8167 GtkTreeIter tmp;
8168 if (gtk_tree_model_iter_children(pModel, &tmp, &iter))
8170 aIter.iter = tmp;
8171 if (get_text(aIter, -1) == "<dummy>")
8173 gtk_tree_store_remove(m_pTreeStore, &tmp);
8174 bPlaceHolder = true;
8178 aIter.iter = iter;
8179 bool bRet = signal_expanding(aIter);
8181 //expand disallowed, restore placeholder
8182 if (!bRet && bPlaceHolder)
8184 GtkTreeIter subiter;
8185 OUString sDummy("<dummy>");
8186 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr, nullptr);
8189 enable_notify_events();
8190 return bRet;
8193 static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
8195 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8196 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
8197 pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
8200 void signal_cell_toggled(const gchar *path, int nCol)
8202 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
8204 // toggled signal handlers can query get_cursor to get which
8205 // node was clicked
8206 gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
8208 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8209 GtkTreeIter iter;
8210 gtk_tree_model_get_iter(pModel, &iter, tree_path);
8212 gboolean bRet(false);
8213 gtk_tree_model_get(pModel, &iter, nCol, &bRet, -1);
8214 bRet = !bRet;
8215 gtk_tree_store_set(m_pTreeStore, &iter, nCol, bRet, -1);
8217 gint depth;
8218 gint* indices = gtk_tree_path_get_indices_with_depth(tree_path, &depth);
8219 int nRow = indices[depth-1];
8221 set(iter, m_aToggleTriStateMap[nCol], false);
8223 signal_toggled(std::make_pair(nRow, nCol));
8225 gtk_tree_path_free(tree_path);
8228 DECL_LINK(async_stop_cell_editing, void*, void);
8230 static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
8232 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8233 if (!pThis->signal_cell_editing_started(path))
8234 Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
8237 bool signal_cell_editing_started(const gchar *path)
8239 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
8241 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8242 GtkInstanceTreeIter aGtkIter(nullptr);
8243 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, tree_path);
8244 gtk_tree_path_free(tree_path);
8246 return signal_editing_started(aGtkIter);
8249 static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
8251 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8252 pThis->signal_cell_edited(pCell, path, pNewText);
8255 static void restoreNonEditable(GObject* pCell)
8257 if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
8259 g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
8260 g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
8264 void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
8266 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
8268 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8269 GtkInstanceTreeIter aGtkIter(nullptr);
8270 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, tree_path);
8271 gtk_tree_path_free(tree_path);
8273 OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
8274 if (signal_editing_done(std::pair<const weld::TreeIter&, OUString>(aGtkIter, sText)))
8276 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
8277 set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
8280 restoreNonEditable(G_OBJECT(pCell));
8283 static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
8285 restoreNonEditable(G_OBJECT(pCell));
8288 void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
8290 int nIndex(0);
8291 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
8293 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8294 if (pColumn == pClickedColumn)
8296 TreeView::signal_column_clicked(nIndex);
8297 break;
8299 ++nIndex;
8303 static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
8305 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8306 pThis->signal_column_clicked(pColumn);
8309 static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
8311 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8312 pThis->signal_visible_range_changed();
8315 int get_model_col(int viewcol) const
8317 return m_aViewColToModelCol[viewcol];
8320 int get_view_col(int modelcol) const
8322 return m_aModelColToViewCol[modelcol];
8325 static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
8327 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8328 pThis->signal_model_changed();
8331 static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
8333 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8334 pThis->signal_model_changed();
8337 static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
8339 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8340 return pThis->sort_func(pModel, a, b);
8343 gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
8345 if (m_aCustomSort)
8346 return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
8347 return default_sort_func(pModel, a, b, m_xSorter.get());
8350 static void signalDragBegin(GtkWidget*, GdkDragContext*, gpointer widget)
8352 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8353 g_DragSource = pThis;
8356 static void signalDragEnd(GtkWidget*, GdkDragContext*, gpointer)
8358 g_DragSource = nullptr;
8361 bool signal_key_press(GdkEventKey* pEvent)
8363 if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
8364 return false;
8366 GtkInstanceTreeIter aIter(nullptr);
8367 if (!get_cursor(&aIter))
8368 return false;
8370 if (pEvent->keyval == GDK_KEY_Right)
8372 if (iter_has_child(aIter) && !get_row_expanded(aIter))
8374 expand_row(aIter);
8375 return true;
8377 return false;
8380 if (iter_has_child(aIter) && get_row_expanded(aIter))
8382 collapse_row(aIter);
8383 return true;
8386 if (iter_parent(aIter))
8388 unselect_all();
8389 set_cursor(aIter);
8390 select(aIter);
8391 return true;
8394 return false;
8397 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
8399 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
8400 return pThis->signal_key_press(pEvent);
8403 public:
8404 GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8405 : GtkInstanceContainer(GTK_CONTAINER(pTreeView), pBuilder, bTakeOwnership)
8406 , m_pTreeView(pTreeView)
8407 , m_pTreeStore(GTK_TREE_STORE(gtk_tree_view_get_model(m_pTreeView)))
8408 , m_bWorkAroundBadDragRegion(false)
8409 , m_bInDrag(false)
8410 , m_nTextCol(-1)
8411 , m_nImageCol(-1)
8412 , m_nExpanderImageCol(-1)
8413 , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
8414 G_CALLBACK(signalChanged), this))
8415 , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
8416 , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
8417 , m_nVAdjustmentChangedSignalId(0)
8418 , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
8419 , m_nDragBeginSignalId(g_signal_connect(pTreeView, "drag-begin", G_CALLBACK(signalDragBegin), this))
8420 , m_nDragEndSignalId(g_signal_connect(pTreeView, "drag-end", G_CALLBACK(signalDragEnd), this))
8421 , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
8422 , m_pChangeEvent(nullptr)
8424 m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
8425 int nIndex(0);
8426 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
8428 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8429 m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
8430 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
8431 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
8433 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
8434 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
8435 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
8437 if (m_nTextCol == -1)
8438 m_nTextCol = nIndex;
8439 m_aWeightMap[nIndex] = -1;
8440 m_aSensitiveMap[nIndex] = -1;
8441 g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
8442 g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
8443 g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
8445 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
8447 g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
8448 m_aToggleVisMap[nIndex] = -1;
8449 m_aToggleTriStateMap[nIndex] = -1;
8451 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
8453 const bool bExpander = g_list_next(pRenderer) != nullptr;
8454 if (bExpander && m_nExpanderImageCol == -1)
8455 m_nExpanderImageCol = nIndex;
8456 else if (m_nImageCol == -1)
8457 m_nImageCol = nIndex;
8459 m_aModelColToViewCol.push_back(m_aViewColToModelCol.size());
8460 ++nIndex;
8462 g_list_free(pRenderers);
8463 m_aViewColToModelCol.push_back(nIndex - 1);
8466 m_nIdCol = nIndex++;
8468 for (auto& a : m_aToggleVisMap)
8469 a.second = nIndex++;
8470 for (auto& a : m_aToggleTriStateMap)
8471 a.second = nIndex++;
8472 for (auto& a : m_aWeightMap)
8473 a.second = nIndex++;
8474 for (auto& a : m_aSensitiveMap)
8475 a.second = nIndex++;
8477 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8478 m_nRowDeletedSignalId = g_signal_connect(pModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
8479 m_nRowInsertedSignalId = g_signal_connect(pModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
8482 virtual void columns_autosize() override
8484 gtk_tree_view_columns_autosize(m_pTreeView);
8487 virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
8489 GList* pEntry = g_list_first(m_pColumns);
8490 for (auto nWidth : rWidths)
8492 assert(pEntry && "wrong count");
8493 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8494 gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
8495 pEntry = g_list_next(pEntry);
8499 virtual void set_centered_column(int nCol) override
8501 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
8503 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
8504 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
8505 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
8507 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
8508 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
8509 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
8511 g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
8512 break;
8515 g_list_free(pRenderers);
8519 virtual int get_column_width(int nColumn) const override
8521 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
8522 assert(pColumn && "wrong count");
8523 int nWidth = gtk_tree_view_column_get_width(pColumn);
8524 // https://github.com/exaile/exaile/issues/580
8525 // after setting fixed_width on a column and requesting width before
8526 // gtk has a chance to do its layout of the column means that the width
8527 // request hasn't come into effect
8528 if (!nWidth)
8529 nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
8530 return nWidth;
8533 virtual OUString get_column_title(int nColumn) const override
8535 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
8536 assert(pColumn && "wrong count");
8537 const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
8538 OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
8539 return sRet;
8542 virtual void set_column_title(int nColumn, const OUString& rTitle) override
8544 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
8545 assert(pColumn && "wrong count");
8546 gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
8549 virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
8550 VirtualDevice* pImageSurface, const OUString* pExpanderName,
8551 bool bChildrenOnDemand, weld::TreeIter* pRet) override
8553 disable_notify_events();
8554 GtkTreeIter iter;
8555 const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
8556 insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface, pExpanderName);
8557 if (bChildrenOnDemand)
8559 GtkTreeIter subiter;
8560 OUString sDummy("<dummy>");
8561 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr, nullptr);
8563 if (pRet)
8565 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
8566 pGtkRetIter->iter = iter;
8568 enable_notify_events();
8571 void set_font_color(const GtkTreeIter& iter, const Color& rColor) const
8573 GdkRGBA aColor{rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0, 0};
8574 gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
8577 virtual void set_font_color(int pos, const Color& rColor) const override
8579 GtkTreeIter iter;
8580 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
8581 set_font_color(iter, rColor);
8584 virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) const override
8586 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8587 set_font_color(rGtkIter.iter, rColor);
8590 virtual void remove(int pos) override
8592 disable_notify_events();
8593 GtkTreeIter iter;
8594 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
8595 gtk_tree_store_remove(m_pTreeStore, &iter);
8596 enable_notify_events();
8599 virtual int find_text(const OUString& rText) const override
8601 Search aSearch(rText, m_nTextCol);
8602 gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
8603 return aSearch.index;
8606 virtual int find_id(const OUString& rId) const override
8608 Search aSearch(rId, m_nIdCol);
8609 gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
8610 return aSearch.index;
8613 virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
8614 const std::vector<int>* pFixedWidths) override
8616 freeze();
8617 clear();
8618 GtkInstanceTreeIter aGtkIter(nullptr);
8620 if (pFixedWidths)
8621 set_column_fixed_widths(*pFixedWidths);
8623 while (nSourceCount)
8625 // tdf#125241 inserting backwards is massively faster
8626 gtk_tree_store_prepend(m_pTreeStore, &aGtkIter.iter, nullptr);
8627 func(aGtkIter, --nSourceCount);
8630 thaw();
8633 virtual void swap(int pos1, int pos2) override
8635 disable_notify_events();
8637 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8639 GtkTreeIter iter1;
8640 gtk_tree_model_iter_nth_child(pModel, &iter1, nullptr, pos1);
8642 GtkTreeIter iter2;
8643 gtk_tree_model_iter_nth_child(pModel, &iter2, nullptr, pos2);
8645 gtk_tree_store_swap(m_pTreeStore, &iter1, &iter2);
8647 enable_notify_events();
8650 virtual void clear() override
8652 disable_notify_events();
8653 gtk_tree_store_clear(m_pTreeStore);
8654 enable_notify_events();
8657 virtual void make_sorted() override
8659 // thaw wants to restore sort state of freeze
8660 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8661 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
8662 ::comphelper::getProcessComponentContext(),
8663 Application::GetSettings().GetUILanguageTag().getLocale()));
8664 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8665 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
8666 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
8669 virtual void make_unsorted() override
8671 m_xSorter.reset();
8672 int nSortColumn;
8673 GtkSortType eSortType;
8674 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8675 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
8676 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
8679 virtual void set_sort_order(bool bAscending) override
8681 GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
8683 gint sort_column_id(0);
8684 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8685 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
8686 gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
8689 virtual bool get_sort_order() const override
8691 GtkSortType eSortType;
8693 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8694 gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
8695 return eSortType == GTK_SORT_ASCENDING;
8698 virtual void set_sort_indicator(TriState eState, int col) override
8700 if (col == -1)
8701 col = get_view_col(m_nTextCol);
8703 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
8704 assert(pColumn && "wrong count");
8705 if (eState == TRISTATE_INDET)
8706 gtk_tree_view_column_set_sort_indicator(pColumn, false);
8707 else
8709 gtk_tree_view_column_set_sort_indicator(pColumn, true);
8710 GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
8711 gtk_tree_view_column_set_sort_order(pColumn, eSortType);
8715 virtual TriState get_sort_indicator(int col) const override
8717 if (col == -1)
8718 col = get_view_col(m_nTextCol);
8720 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
8721 if (!gtk_tree_view_column_get_sort_indicator(pColumn))
8722 return TRISTATE_INDET;
8723 return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
8726 virtual int get_sort_column() const override
8728 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8729 gint sort_column_id(0);
8730 if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
8731 return -1;
8732 return get_view_col(sort_column_id);
8735 virtual void set_sort_column(int nColumn) override
8737 if (nColumn == -1)
8739 make_unsorted();
8740 return;
8742 GtkSortType eSortType;
8743 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8744 gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
8745 int nSortCol = get_model_col(nColumn);
8746 gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
8747 gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
8750 virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
8752 weld::TreeView::set_sort_func(func);
8753 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
8754 gtk_tree_sortable_sort_column_changed(pSortable);
8757 virtual int n_children() const override
8759 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
8762 virtual void select(int pos) override
8764 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8765 disable_notify_events();
8766 if (pos == -1 || (pos == 0 && n_children() == 0))
8768 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
8770 else
8772 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8773 gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
8774 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
8775 gtk_tree_path_free(path);
8777 enable_notify_events();
8780 virtual void set_cursor(int pos) override
8782 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8783 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
8784 gtk_tree_path_free(path);
8787 virtual void scroll_to_row(int pos) override
8789 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8790 disable_notify_events();
8791 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8792 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
8793 gtk_tree_path_free(path);
8794 enable_notify_events();
8797 virtual bool is_selected(int pos) const override
8799 GtkTreeIter iter;
8800 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
8801 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
8804 virtual void unselect(int pos) override
8806 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
8807 disable_notify_events();
8808 if (pos == -1 || (pos == 0 && n_children() == 0))
8810 gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
8812 else
8814 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
8815 gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
8816 gtk_tree_path_free(path);
8818 enable_notify_events();
8821 virtual std::vector<int> get_selected_rows() const override
8823 std::vector<int> aRows;
8825 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
8826 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
8828 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
8830 gint depth;
8831 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
8832 int nRow = indices[depth-1];
8834 aRows.push_back(nRow);
8836 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
8838 return aRows;
8841 virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
8843 GtkInstanceTreeIter aGtkIter(nullptr);
8844 if (get_iter_first(aGtkIter))
8848 if (func(aGtkIter))
8849 break;
8850 } while (iter_next(aGtkIter));
8854 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
8856 GtkInstanceTreeIter aGtkIter(nullptr);
8858 GtkTreeModel* pModel;
8859 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
8860 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
8862 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
8863 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
8864 if (func(aGtkIter))
8865 break;
8867 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
8870 virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
8872 GtkTreePath* start_path;
8873 GtkTreePath* end_path;
8875 if (gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
8877 GtkInstanceTreeIter aGtkIter(nullptr);
8878 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
8879 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, start_path);
8883 if (func(aGtkIter))
8884 break;
8885 GtkTreePath* path = gtk_tree_model_get_path(pModel, &aGtkIter.iter);
8886 bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
8887 gtk_tree_path_free(path);
8888 if (!bContinue)
8889 break;
8890 if (!iter_next(aGtkIter))
8891 break;
8892 } while(true);
8894 gtk_tree_path_free(start_path);
8895 gtk_tree_path_free(end_path);
8899 virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
8901 weld::TreeView::connect_visible_range_changed(rLink);
8902 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
8903 m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
8906 virtual bool is_selected(const weld::TreeIter& rIter) const override
8908 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8909 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
8912 virtual OUString get_text(int pos, int col) const override
8914 if (col == -1)
8915 return get(pos, m_nTextCol);
8916 return get(pos, get_model_col(col));
8919 virtual void set_text(int pos, const OUString& rText, int col) override
8921 if (col == -1)
8922 col = m_nTextCol;
8923 else
8924 col = get_model_col(col);
8925 set(pos, col, rText);
8928 virtual TriState get_toggle(int pos, int col) const override
8930 col = get_model_col(col);
8931 if (get_bool(pos, m_aToggleTriStateMap.find(col)->second))
8932 return TRISTATE_INDET;
8933 return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
8936 virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
8938 col = get_model_col(col);
8939 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8940 if (get_bool(rGtkIter.iter, m_aToggleTriStateMap.find(col)->second))
8941 return TRISTATE_INDET;
8942 return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
8945 virtual void set_toggle(int pos, TriState eState, int col) override
8947 col = get_model_col(col);
8948 // checkbuttons are invisible until toggled on or off
8949 set(pos, m_aToggleVisMap[col], true);
8950 if (eState == TRISTATE_INDET)
8951 set(pos, m_aToggleTriStateMap[col], true);
8952 else
8954 set(pos, m_aToggleTriStateMap[col], false);
8955 set(pos, col, eState == TRISTATE_TRUE);
8959 virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
8961 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8962 col = get_model_col(col);
8963 // checkbuttons are invisible until toggled on or off
8964 set(rGtkIter.iter, m_aToggleVisMap[col], true);
8965 if (eState == TRISTATE_INDET)
8966 set(rGtkIter.iter, m_aToggleTriStateMap[col], true);
8967 else
8969 set(rGtkIter.iter, m_aToggleTriStateMap[col], false);
8970 set(rGtkIter.iter, col, eState == TRISTATE_TRUE);
8974 virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
8976 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8977 col = get_model_col(col);
8978 set(rGtkIter.iter, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
8981 virtual void set_text_emphasis(int pos, bool bOn, int col) override
8983 col = get_model_col(col);
8984 set(pos, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
8987 virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
8989 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
8990 col = get_model_col(col);
8991 return get_int(rGtkIter.iter, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
8994 virtual bool get_text_emphasis(int pos, int col) const override
8996 col = get_model_col(col);
8997 return get_int(pos, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
9000 using GtkInstanceWidget::set_sensitive;
9002 virtual void set_sensitive(int pos, bool bSensitive, int col) override
9004 if (col == -1)
9005 col = m_nTextCol;
9006 else
9007 col = get_model_col(col);
9008 set(pos, m_aSensitiveMap[col], bSensitive);
9011 virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
9013 if (col == -1)
9014 col = m_nTextCol;
9015 else
9016 col = get_model_col(col);
9017 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9018 set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
9021 void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
9023 if (col == -1)
9024 col = m_nExpanderImageCol;
9025 else
9026 col = get_model_col(col);
9027 gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
9028 if (pixbuf)
9029 g_object_unref(pixbuf);
9032 void set_image(int pos, GdkPixbuf* pixbuf, int col)
9034 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9035 GtkTreeIter iter;
9036 if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
9038 set_image(iter, col, pixbuf);
9042 virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
9044 set_image(pos, getPixbuf(rImage), col);
9047 virtual void set_image(int pos, const OUString& rImage, int col) override
9049 set_image(pos, getPixbuf(rImage), col);
9052 virtual void set_image(int pos, VirtualDevice& rImage, int col) override
9054 set_image(pos, getPixbuf(rImage), col);
9057 virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
9059 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9060 set_image(rGtkIter.iter, col, getPixbuf(rImage));
9063 virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
9065 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9066 set_image(rGtkIter.iter, col, getPixbuf(rImage));
9069 virtual OUString get_id(int pos) const override
9071 return get(pos, m_nIdCol);
9074 virtual void set_id(int pos, const OUString& rId) override
9076 return set(pos, m_nIdCol, rId);
9079 virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
9081 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9083 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9084 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9086 gint depth;
9087 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9088 int nRet = indices[depth-1];
9090 gtk_tree_path_free(path);
9092 return nRet;
9095 virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
9097 const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
9098 const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
9100 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9101 GtkTreePath* pathA = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
9102 GtkTreePath* pathB = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
9104 int nRet = gtk_tree_path_compare(pathA, pathB);
9106 gtk_tree_path_free(pathB);
9107 gtk_tree_path_free(pathA);
9109 return nRet;
9112 // by copy and delete of old copy
9113 void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
9115 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9117 int nCols = gtk_tree_model_get_n_columns(pModel);
9118 GValue value;
9120 GtkTreeIter toiter;
9121 gtk_tree_store_insert(m_pTreeStore, &toiter, pGtkParentIter, nIndexInNewParent);
9123 for (int i = 0; i < nCols; ++i)
9125 memset(&value, 0, sizeof(GValue));
9126 gtk_tree_model_get_value(pModel, &rFromIter, i, &value);
9127 gtk_tree_store_set_value(m_pTreeStore, &toiter, i, &value);
9128 g_value_unset(&value);
9131 GtkTreeIter tmpfromiter;
9132 if (gtk_tree_model_iter_children(pModel, &tmpfromiter, &rFromIter))
9134 int j = 0;
9137 move_subtree(tmpfromiter, &toiter, j++);
9138 } while (gtk_tree_model_iter_next(pModel, &tmpfromiter));
9141 gtk_tree_store_remove(m_pTreeStore, &rFromIter);
9144 virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
9146 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
9147 const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
9148 move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
9151 virtual int get_selected_index() const override
9153 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9154 int nRet = -1;
9155 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
9156 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
9158 GtkTreeIter iter;
9159 GtkTreeModel* pModel;
9160 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
9162 GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
9164 gint depth;
9165 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9166 nRet = indices[depth-1];
9168 gtk_tree_path_free(path);
9171 else
9173 auto vec = get_selected_rows();
9174 return vec.empty() ? -1 : vec[0];
9176 return nRet;
9179 bool get_selected_iterator(GtkTreeIter* pIter) const
9181 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9182 bool bRet = false;
9183 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
9184 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
9185 bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
9186 else
9188 GtkTreeModel* pModel;
9189 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
9190 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
9192 if (pIter)
9194 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
9195 gtk_tree_model_get_iter(pModel, pIter, path);
9197 bRet = true;
9198 break;
9200 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
9202 return bRet;
9205 virtual OUString get_selected_text() const override
9207 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9208 GtkTreeIter iter;
9209 if (get_selected_iterator(&iter))
9210 return get(iter, m_nTextCol);
9211 return OUString();
9214 virtual OUString get_selected_id() const override
9216 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
9217 GtkTreeIter iter;
9218 if (get_selected_iterator(&iter))
9219 return get(iter, m_nIdCol);
9220 return OUString();
9223 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
9225 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
9228 virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
9230 const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
9231 GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
9232 rGtkDest.iter = rGtkSource.iter;
9235 virtual bool get_selected(weld::TreeIter* pIter) const override
9237 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
9238 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
9241 virtual bool get_cursor(weld::TreeIter* pIter) const override
9243 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
9244 GtkTreePath* path;
9245 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
9246 if (pGtkIter && path)
9248 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9249 gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
9251 return path != nullptr;
9254 virtual int get_cursor_index() const override
9256 int nRet = -1;
9258 GtkTreePath* path;
9259 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
9260 if (path)
9262 gint depth;
9263 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9264 nRet = indices[depth-1];
9265 gtk_tree_path_free(path);
9268 return nRet;
9271 virtual void set_cursor(const weld::TreeIter& rIter) override
9273 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9274 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9275 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9276 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
9277 gtk_tree_path_free(path);
9280 virtual bool get_iter_first(weld::TreeIter& rIter) const override
9282 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9283 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9284 return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
9287 virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
9289 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9290 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9291 return gtk_tree_model_iter_next(pModel, &rGtkIter.iter);
9294 virtual bool iter_next(weld::TreeIter& rIter) const override
9296 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9297 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9298 GtkTreeIter iter = rGtkIter.iter;
9299 if (iter_children(rGtkIter))
9300 return true;
9301 GtkTreeIter tmp = iter;
9302 if (gtk_tree_model_iter_next(pModel, &tmp))
9304 rGtkIter.iter = tmp;
9305 return true;
9307 // Move up level(s) until we find the level where the next sibling exists.
9308 while (gtk_tree_model_iter_parent(pModel, &tmp, &iter))
9310 iter = tmp;
9311 if (gtk_tree_model_iter_next(pModel, &tmp))
9313 rGtkIter.iter = tmp;
9314 return true;
9317 return false;
9320 virtual bool iter_children(weld::TreeIter& rIter) const override
9322 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9323 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9324 GtkTreeIter tmp;
9325 bool ret = gtk_tree_model_iter_children(pModel, &tmp, &rGtkIter.iter);
9326 rGtkIter.iter = tmp;
9327 if (ret)
9329 //on-demand dummy entry doesn't count
9330 return get_text(rGtkIter, -1) != "<dummy>";
9332 return ret;
9335 virtual bool iter_parent(weld::TreeIter& rIter) const override
9337 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
9338 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9339 GtkTreeIter tmp;
9340 auto ret = gtk_tree_model_iter_parent(pModel, &tmp, &rGtkIter.iter);
9341 rGtkIter.iter = tmp;
9342 return ret;
9345 virtual void remove(const weld::TreeIter& rIter) override
9347 disable_notify_events();
9348 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9349 gtk_tree_store_remove(m_pTreeStore, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9350 enable_notify_events();
9353 virtual void remove_selection() override
9355 disable_notify_events();
9357 std::vector<GtkTreeIter> aIters;
9358 GtkTreeModel* pModel;
9359 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
9360 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
9362 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
9363 aIters.emplace_back();
9364 gtk_tree_model_get_iter(pModel, &aIters.back(), path);
9366 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
9368 for (auto& iter : aIters)
9369 gtk_tree_store_remove(m_pTreeStore, &iter);
9371 enable_notify_events();
9374 virtual void select(const weld::TreeIter& rIter) override
9376 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
9377 disable_notify_events();
9378 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9379 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
9380 enable_notify_events();
9383 virtual void scroll_to_row(const weld::TreeIter& rIter) override
9385 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
9386 disable_notify_events();
9387 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9388 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9389 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9390 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
9391 gtk_tree_path_free(path);
9392 enable_notify_events();
9395 virtual void unselect(const weld::TreeIter& rIter) override
9397 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen");
9398 disable_notify_events();
9399 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9400 gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
9401 enable_notify_events();
9404 virtual int get_iter_depth(const weld::TreeIter& rIter) const override
9406 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9407 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9408 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9409 int ret = gtk_tree_path_get_depth(path) - 1;
9410 gtk_tree_path_free(path);
9411 return ret;
9414 virtual bool iter_has_child(const weld::TreeIter& rIter) const override
9416 weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
9417 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNonConstIter);
9418 GtkTreeIter restore(rGtkIter.iter);
9419 bool ret = iter_children(rNonConstIter);
9420 rGtkIter.iter = restore;
9421 return ret;
9424 virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
9426 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9427 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9428 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9429 bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
9430 gtk_tree_path_free(path);
9431 return ret;
9434 virtual void expand_row(const weld::TreeIter& rIter) override
9436 assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
9438 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9439 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9440 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9441 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
9442 gtk_tree_view_expand_to_path(m_pTreeView, path);
9443 gtk_tree_path_free(path);
9446 virtual void collapse_row(const weld::TreeIter& rIter) override
9448 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9449 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9450 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9451 if (gtk_tree_view_row_expanded(m_pTreeView, path))
9452 gtk_tree_view_collapse_row(m_pTreeView, path);
9453 gtk_tree_path_free(path);
9456 virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
9458 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9459 if (col == -1)
9460 col = m_nTextCol;
9461 else
9462 col = get_model_col(col);
9463 return get(rGtkIter.iter, col);
9466 virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
9468 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9469 if (col == -1)
9470 col = m_nTextCol;
9471 else
9472 col = get_model_col(col);
9473 set(rGtkIter.iter, col, rText);
9476 virtual OUString get_id(const weld::TreeIter& rIter) const override
9478 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9479 return get(rGtkIter.iter, m_nIdCol);
9482 virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
9484 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9485 set(rGtkIter.iter, m_nIdCol, rId);
9488 virtual void freeze() override
9490 disable_notify_events();
9491 g_object_ref(m_pTreeStore);
9492 GtkInstanceContainer::freeze();
9493 gtk_tree_view_set_model(m_pTreeView, nullptr);
9494 if (m_xSorter)
9496 int nSortColumn;
9497 GtkSortType eSortType;
9498 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
9499 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
9500 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
9502 m_aSavedSortColumns.push_back(nSortColumn);
9503 m_aSavedSortTypes.push_back(eSortType);
9505 enable_notify_events();
9508 virtual void thaw() override
9510 disable_notify_events();
9511 if (m_xSorter)
9513 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
9514 gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
9515 m_aSavedSortTypes.pop_back();
9516 m_aSavedSortColumns.pop_back();
9518 gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeStore));
9519 GtkInstanceContainer::thaw();
9520 g_object_unref(m_pTreeStore);
9521 enable_notify_events();
9524 virtual int get_height_rows(int nRows) const override
9526 gint nMaxRowHeight = 0;
9527 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
9529 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
9530 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
9531 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
9533 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
9534 gint nRowHeight;
9535 gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(m_pTreeView), nullptr, &nRowHeight);
9536 nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
9538 g_list_free(pRenderers);
9541 gint nVerticalSeparator;
9542 gtk_widget_style_get(GTK_WIDGET(m_pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
9544 return (nMaxRowHeight * nRows) + (nVerticalSeparator * (nRows + 1));
9547 virtual Size get_size_request() const override
9549 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9550 if (GTK_IS_SCROLLED_WINDOW(pParent))
9552 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
9553 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
9555 int nWidth, nHeight;
9556 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
9557 return Size(nWidth, nHeight);
9560 virtual Size get_preferred_size() const override
9562 Size aRet(-1, -1);
9563 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9564 if (GTK_IS_SCROLLED_WINDOW(pParent))
9566 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
9567 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
9569 GtkRequisition size;
9570 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
9571 if (aRet.Width() == -1)
9572 aRet.setWidth(size.width);
9573 if (aRet.Height() == -1)
9574 aRet.setHeight(size.height);
9575 return aRet;
9578 virtual void show() override
9580 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9581 if (GTK_IS_SCROLLED_WINDOW(pParent))
9582 gtk_widget_show(pParent);
9583 gtk_widget_show(m_pWidget);
9586 virtual void hide() override
9588 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
9589 if (GTK_IS_SCROLLED_WINDOW(pParent))
9590 gtk_widget_hide(pParent);
9591 gtk_widget_hide(m_pWidget);
9594 virtual void set_selection_mode(SelectionMode eMode) override
9596 disable_notify_events();
9597 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
9598 enable_notify_events();
9601 virtual int count_selected_rows() const override
9603 return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
9606 int starts_with(const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
9608 return ::starts_with(GTK_TREE_MODEL(m_pTreeStore), rStr, get_model_col(col), nStartRow, bCaseSensitive);
9611 virtual void disable_notify_events() override
9613 g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
9614 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
9616 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9617 g_signal_handler_block(pModel, m_nRowDeletedSignalId);
9618 g_signal_handler_block(pModel, m_nRowInsertedSignalId);
9620 GtkInstanceContainer::disable_notify_events();
9623 virtual void enable_notify_events() override
9625 GtkInstanceContainer::enable_notify_events();
9627 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9628 g_signal_handler_unblock(pModel, m_nRowDeletedSignalId);
9629 g_signal_handler_unblock(pModel, m_nRowInsertedSignalId);
9631 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
9632 g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
9635 virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
9637 ensureButtonPressSignal();
9638 weld::TreeView::connect_popup_menu(rLink);
9641 virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult) override
9643 const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
9645 // to keep it simple we'll default to always drop before the current row
9646 // except for the special edge cases
9647 GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
9649 // unhighlight current highlighted row
9650 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
9652 if (m_bWorkAroundBadDragRegion)
9653 gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
9655 GtkTreePath *path = nullptr;
9656 GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
9657 bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
9658 &path, &gtkpos);
9660 // find the last entry in the model for comparison
9661 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9662 int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
9663 GtkTreePath *lastpath;
9664 if (nChildren)
9665 lastpath = gtk_tree_path_new_from_indices(nChildren - 1, -1);
9666 else
9667 lastpath = gtk_tree_path_new_from_indices(0, -1);
9669 if (!ret)
9671 // empty space, draw an indicator at the last entry
9672 assert(!path);
9673 path = gtk_tree_path_copy(lastpath);
9674 pos = GTK_TREE_VIEW_DROP_AFTER;
9676 else if (gtk_tree_path_compare(path, lastpath) == 0)
9678 // if we're on the last entry, see if gtk thinks
9679 // the drop should be before or after it, and if
9680 // its after, treat it like a drop into empty
9681 // space, i.e. append it
9682 if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
9683 gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
9685 ret = false;
9686 pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
9690 if (ret && pResult)
9692 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
9693 gtk_tree_model_get_iter(pModel, &rGtkIter.iter, path);
9696 if (m_bInDrag)
9698 // highlight the row
9699 gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
9702 assert(path);
9703 gtk_tree_path_free(path);
9704 gtk_tree_path_free(lastpath);
9706 // auto scroll if we're close to the edges
9707 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
9708 double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
9709 if (rPos.Y() < fStep)
9711 double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
9712 if (fValue < 0)
9713 fValue = 0.0;
9714 gtk_adjustment_set_value(pVAdjustment, fValue);
9716 else
9718 GdkRectangle aRect;
9719 gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
9720 if (rPos.Y() > aRect.height - fStep)
9722 double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
9723 double fMax = gtk_adjustment_get_upper(pVAdjustment);
9724 if (fValue > fMax)
9725 fValue = fMax;
9726 gtk_adjustment_set_value(pVAdjustment, fValue);
9730 return ret;
9733 virtual void start_editing(const weld::TreeIter& rIter) override
9735 int col = get_view_col(m_nTextCol);
9736 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
9737 assert(pColumn && "wrong column");
9739 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
9740 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9741 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
9743 // allow editing of cells which are not usually editable, so we can have double click
9744 // do its usual row-activate but if we explicitly want to edit (remote files dialog)
9745 // we can still do that
9746 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
9747 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
9749 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
9750 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
9752 gboolean is_editable(false);
9753 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
9754 if (!is_editable)
9756 g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
9757 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
9758 break;
9762 g_list_free(pRenderers);
9764 gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
9766 gtk_tree_path_free(path);
9769 virtual void end_editing() override
9771 GtkTreeViewColumn *focus_column = nullptr;
9772 gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
9773 if (focus_column)
9774 gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
9777 virtual TreeView* get_drag_source() const override
9779 return g_DragSource;
9782 // Under gtk 3.24.8 dragging into the TreeView is not highlighting
9783 // entire TreeView widget, just the rectangle which has no entries
9784 // in it, so as a workaround highlight the parent container
9785 // on drag start, and undo it on drag end, and trigger removal
9786 // of the treeview's highlight effort
9787 virtual void drag_started() override
9789 m_bInDrag = true;
9790 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
9791 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
9792 if (GTK_IS_SCROLLED_WINDOW(pParent))
9794 gtk_drag_unhighlight(pWidget);
9795 gtk_drag_highlight(pParent);
9796 m_bWorkAroundBadDragRegion = true;
9800 virtual void drag_ended() override
9802 m_bInDrag = false;
9803 if (m_bWorkAroundBadDragRegion)
9805 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
9806 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
9807 gtk_drag_unhighlight(pParent);
9808 m_bWorkAroundBadDragRegion = false;
9810 // unhighlight the row
9811 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
9814 virtual ~GtkInstanceTreeView() override
9816 if (m_pChangeEvent)
9817 Application::RemoveUserEvent(m_pChangeEvent);
9818 g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
9819 g_signal_handler_disconnect(m_pTreeView, m_nDragEndSignalId);
9820 g_signal_handler_disconnect(m_pTreeView, m_nDragBeginSignalId);
9821 g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
9822 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9823 g_signal_handler_disconnect(pModel, m_nRowDeletedSignalId);
9824 g_signal_handler_disconnect(pModel, m_nRowInsertedSignalId);
9826 if (m_nVAdjustmentChangedSignalId)
9828 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
9829 g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
9832 g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
9833 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
9834 g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
9836 for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
9838 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
9839 g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
9840 m_aColumnSignalIds.pop_back();
9842 g_list_free(m_pColumns);
9846 IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
9848 m_pChangeEvent = nullptr;
9849 signal_changed();
9852 IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
9854 end_editing();
9857 class GtkInstanceIconView : public GtkInstanceContainer, public virtual weld::IconView
9859 private:
9860 GtkIconView* m_pIconView;
9861 GtkTreeStore* m_pTreeStore;
9862 gint m_nTextCol;
9863 gint m_nImageCol;
9864 gint m_nIdCol;
9865 gulong m_nSelectionChangedSignalId;
9866 gulong m_nItemActivatedSignalId;
9867 ImplSVEvent* m_pSelectionChangeEvent;
9869 DECL_LINK(async_signal_selection_changed, void*, void);
9871 void launch_signal_selection_changed()
9873 //tdf#117991 selection change is sent before the focus change, and focus change
9874 //is what will cause a spinbutton that currently has the focus to set its contents
9875 //as the spin button value. So any LibreOffice callbacks on
9876 //signal-change would happen before the spinbutton value-change occurs.
9877 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
9878 //has been processed
9879 if (m_pSelectionChangeEvent)
9880 Application::RemoveUserEvent(m_pSelectionChangeEvent);
9881 m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
9884 static void signalSelectionChanged(GtkIconView*, gpointer widget)
9886 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
9887 pThis->launch_signal_selection_changed();
9890 void handle_item_activated()
9892 if (signal_item_activated())
9893 return;
9896 static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
9898 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
9899 SolarMutexGuard aGuard;
9900 pThis->handle_item_activated();
9903 void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
9905 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
9906 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
9907 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
9908 -1);
9909 if (pIconName)
9911 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
9912 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
9913 if (pixbuf)
9914 g_object_unref(pixbuf);
9918 OUString get(const GtkTreeIter& iter, int col) const
9920 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
9921 gchar* pStr;
9922 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
9923 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
9924 g_free(pStr);
9925 return sRet;
9928 bool get_selected_iterator(GtkTreeIter* pIter) const
9930 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
9931 bool bRet = false;
9933 GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
9934 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
9935 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
9937 if (pIter)
9939 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
9940 gtk_tree_model_get_iter(pModel, pIter, path);
9942 bRet = true;
9943 break;
9945 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
9947 return bRet;
9950 public:
9951 GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9952 : GtkInstanceContainer(GTK_CONTAINER(pIconView), pBuilder, bTakeOwnership)
9953 , m_pIconView(pIconView)
9954 , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
9955 , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView))
9956 , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
9957 , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
9958 G_CALLBACK(signalSelectionChanged), this))
9959 , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
9960 , m_pSelectionChangeEvent(nullptr)
9962 m_nIdCol = m_nTextCol + 1;
9965 virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
9967 disable_notify_events();
9968 GtkTreeIter iter;
9969 insert_item(iter, pos, pId, pText, pIconName);
9970 if (pRet)
9972 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
9973 pGtkRetIter->iter = iter;
9975 enable_notify_events();
9978 virtual OUString get_selected_id() const override
9980 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
9981 GtkTreeIter iter;
9982 if (get_selected_iterator(&iter))
9983 return get(iter, m_nIdCol);
9984 return OUString();
9987 virtual void clear() override
9989 disable_notify_events();
9990 gtk_tree_store_clear(m_pTreeStore);
9991 enable_notify_events();
9994 virtual void freeze() override
9996 disable_notify_events();
9997 g_object_ref(m_pTreeStore);
9998 GtkInstanceContainer::freeze();
9999 gtk_icon_view_set_model(m_pIconView, nullptr);
10000 enable_notify_events();
10003 virtual void thaw() override
10005 disable_notify_events();
10006 gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
10007 GtkInstanceContainer::thaw();
10008 g_object_unref(m_pTreeStore);
10009 enable_notify_events();
10012 virtual Size get_size_request() const override
10014 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10015 if (GTK_IS_SCROLLED_WINDOW(pParent))
10017 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
10018 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
10020 int nWidth, nHeight;
10021 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
10022 return Size(nWidth, nHeight);
10025 virtual Size get_preferred_size() const override
10027 Size aRet(-1, -1);
10028 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10029 if (GTK_IS_SCROLLED_WINDOW(pParent))
10031 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
10032 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
10034 GtkRequisition size;
10035 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
10036 if (aRet.Width() == -1)
10037 aRet.setWidth(size.width);
10038 if (aRet.Height() == -1)
10039 aRet.setHeight(size.height);
10040 return aRet;
10043 virtual void show() override
10045 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10046 if (GTK_IS_SCROLLED_WINDOW(pParent))
10047 gtk_widget_show(pParent);
10048 gtk_widget_show(m_pWidget);
10051 virtual void hide() override
10053 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10054 if (GTK_IS_SCROLLED_WINDOW(pParent))
10055 gtk_widget_hide(pParent);
10056 gtk_widget_hide(m_pWidget);
10059 virtual OUString get_selected_text() const override
10061 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
10062 GtkTreeIter iter;
10063 if (get_selected_iterator(&iter))
10064 return get(iter, m_nTextCol);
10065 return OUString();
10068 virtual int count_selected_items() const override
10070 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
10071 int nRet = g_list_length(pList);
10072 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
10073 return nRet;
10076 virtual void select(int pos) override
10078 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen");
10079 disable_notify_events();
10080 if (pos == -1 || (pos == 0 && n_children() == 0))
10082 gtk_icon_view_unselect_all(m_pIconView);
10084 else
10086 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
10087 gtk_icon_view_select_path(m_pIconView, path);
10088 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
10089 gtk_tree_path_free(path);
10091 enable_notify_events();
10094 virtual void unselect(int pos) override
10096 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen");
10097 disable_notify_events();
10098 if (pos == -1 || (pos == 0 && n_children() == 0))
10100 gtk_icon_view_select_all(m_pIconView);
10102 else
10104 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
10105 gtk_icon_view_select_path(m_pIconView, path);
10106 gtk_tree_path_free(path);
10108 enable_notify_events();
10111 virtual bool get_selected(weld::TreeIter* pIter) const override
10113 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
10114 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
10117 virtual bool get_cursor(weld::TreeIter* pIter) const override
10119 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
10120 GtkTreePath* path;
10121 gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
10122 if (pGtkIter && path)
10124 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10125 gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
10127 return path != nullptr;
10130 virtual void set_cursor(const weld::TreeIter& rIter) override
10132 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10133 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10134 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
10135 gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
10136 gtk_tree_path_free(path);
10139 virtual bool get_iter_first(weld::TreeIter& rIter) const override
10141 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
10142 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10143 return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
10146 virtual void scroll_to_item(const weld::TreeIter& rIter) override
10148 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen");
10149 disable_notify_events();
10150 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10151 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10152 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
10153 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
10154 gtk_tree_path_free(path);
10155 enable_notify_events();
10158 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
10160 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
10163 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
10165 GtkInstanceTreeIter aGtkIter(nullptr);
10167 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
10168 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
10169 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
10171 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
10172 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
10173 if (func(aGtkIter))
10174 break;
10176 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
10179 virtual int n_children() const override
10181 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
10184 virtual OUString get_id(const weld::TreeIter& rIter) const override
10186 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10187 return get(rGtkIter.iter, m_nIdCol);
10190 virtual void disable_notify_events() override
10192 g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
10193 g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
10195 GtkInstanceContainer::disable_notify_events();
10198 virtual void enable_notify_events() override
10200 GtkInstanceContainer::enable_notify_events();
10202 g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
10203 g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
10206 virtual ~GtkInstanceIconView() override
10208 if (m_pSelectionChangeEvent)
10209 Application::RemoveUserEvent(m_pSelectionChangeEvent);
10211 g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
10212 g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
10216 IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
10218 m_pSelectionChangeEvent = nullptr;
10219 signal_selection_changed();
10222 class GtkInstanceSpinButton : public GtkInstanceEntry, public virtual weld::SpinButton
10224 private:
10225 GtkSpinButton* m_pButton;
10226 gulong m_nValueChangedSignalId;
10227 gulong m_nOutputSignalId;
10228 gulong m_nInputSignalId;
10229 bool m_bFormatting;
10230 bool m_bBlockOutput;
10231 bool m_bBlank;
10233 static void signalValueChanged(GtkSpinButton*, gpointer widget)
10235 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
10236 SolarMutexGuard aGuard;
10237 pThis->m_bBlank = false;
10238 pThis->signal_value_changed();
10241 bool guarded_signal_output()
10243 if (m_bBlockOutput)
10244 return true;
10245 m_bFormatting = true;
10246 bool bRet = signal_output();
10247 m_bFormatting = false;
10248 return bRet;
10251 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
10253 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
10254 SolarMutexGuard aGuard;
10255 return pThis->guarded_signal_output();
10258 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
10260 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
10261 SolarMutexGuard aGuard;
10262 int result;
10263 TriState eHandled = pThis->signal_input(&result);
10264 if (eHandled == TRISTATE_INDET)
10265 return 0;
10266 if (eHandled == TRISTATE_TRUE)
10268 *new_value = pThis->toGtk(result);
10269 return 1;
10271 return GTK_INPUT_ERROR;
10274 double toGtk(int nValue) const
10276 return static_cast<double>(nValue) / Power10(get_digits());
10279 int fromGtk(double fValue) const
10281 return FRound(fValue * Power10(get_digits()));
10284 public:
10285 GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10286 : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
10287 , m_pButton(pButton)
10288 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
10289 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
10290 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
10291 , m_bFormatting(false)
10292 , m_bBlockOutput(false)
10293 , m_bBlank(false)
10297 virtual int get_value() const override
10299 return fromGtk(gtk_spin_button_get_value(m_pButton));
10302 virtual void set_value(int value) override
10304 disable_notify_events();
10305 m_bBlank = false;
10306 gtk_spin_button_set_value(m_pButton, toGtk(value));
10307 enable_notify_events();
10310 virtual void set_text(const OUString& rText) override
10312 disable_notify_events();
10313 // tdf#122786 if we're just formatting a value, then we're done,
10314 // however if set_text has been called directly we want to update our
10315 // value from this new text, but don't want to reformat with that value
10316 if (!m_bFormatting)
10318 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
10320 m_bBlockOutput = true;
10321 gtk_spin_button_update(m_pButton);
10322 m_bBlank = rText.isEmpty();
10323 m_bBlockOutput = false;
10325 else
10327 bool bKeepBlank = m_bBlank && get_value() == 0;
10328 if (!bKeepBlank)
10330 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
10331 m_bBlank = false;
10334 enable_notify_events();
10337 virtual void set_range(int min, int max) override
10339 disable_notify_events();
10340 gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
10341 enable_notify_events();
10344 virtual void get_range(int& min, int& max) const override
10346 double gtkmin, gtkmax;
10347 gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
10348 min = fromGtk(gtkmin);
10349 max = fromGtk(gtkmax);
10352 virtual void set_increments(int step, int page) override
10354 disable_notify_events();
10355 gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
10356 enable_notify_events();
10359 virtual void get_increments(int& step, int& page) const override
10361 double gtkstep, gtkpage;
10362 gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
10363 step = fromGtk(gtkstep);
10364 page = fromGtk(gtkpage);
10367 virtual void set_digits(unsigned int digits) override
10369 disable_notify_events();
10370 gtk_spin_button_set_digits(m_pButton, digits);
10371 enable_notify_events();
10374 virtual unsigned int get_digits() const override
10376 return gtk_spin_button_get_digits(m_pButton);
10379 virtual void disable_notify_events() override
10381 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
10382 GtkInstanceEntry::disable_notify_events();
10385 virtual void enable_notify_events() override
10387 GtkInstanceEntry::enable_notify_events();
10388 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
10391 virtual ~GtkInstanceSpinButton() override
10393 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
10394 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
10395 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
10399 class GtkInstanceFormattedSpinButton : public GtkInstanceEntry, public virtual weld::FormattedSpinButton
10401 private:
10402 GtkSpinButton* m_pButton;
10403 SvNumberFormatter* m_pFormatter;
10404 Color* m_pLastOutputColor;
10405 sal_uInt32 m_nFormatKey;
10406 gulong m_nValueChangedSignalId;
10407 gulong m_nOutputSignalId;
10408 gulong m_nInputSignalId;
10410 bool signal_output()
10412 if (!m_pFormatter)
10413 return false;
10414 double dVal = get_value();
10415 OUString sNewText;
10416 if (m_pFormatter->IsTextFormat(m_nFormatKey))
10418 // first convert the number as string in standard format
10419 OUString sTemp;
10420 m_pFormatter->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
10421 // then encode the string in the corresponding text format
10422 m_pFormatter->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
10424 else
10426 m_pFormatter->GetInputLineString(dVal, m_nFormatKey, sNewText);
10428 set_text(sNewText);
10429 return true;
10432 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
10434 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
10435 SolarMutexGuard aGuard;
10436 return pThis->signal_output();
10439 gint signal_input(double* value)
10441 if (!m_pFormatter)
10442 return 0;
10444 sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
10446 if (m_pFormatter->IsTextFormat(nFormatKey))
10447 // for detection of values like "1,1" in fields that are formatted as text
10448 nFormatKey = 0;
10450 OUString sText(get_text());
10452 // special treatment for percentage formatting
10453 if (m_pFormatter->GetType(m_nFormatKey) == SvNumFormatType::PERCENT)
10455 // the language of our format
10456 LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
10457 // the default number format for this language
10458 sal_uLong nStandardNumericFormat = m_pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, eLanguage);
10460 sal_uInt32 nTempFormat = nStandardNumericFormat;
10461 double dTemp;
10462 if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
10463 SvNumFormatType::NUMBER == m_pFormatter->GetType(nTempFormat))
10464 // the string is equivalent to a number formatted one (has no % sign) -> append it
10465 sText += "%";
10466 // (with this, an input of '3' becomes '3%', which then by the formatter is translated
10467 // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
10468 // which equals 300 percent.
10470 if (!m_pFormatter->IsNumberFormat(sText, nFormatKey, *value))
10471 return GTK_INPUT_ERROR;
10473 return 1;
10476 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
10478 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
10479 SolarMutexGuard aGuard;
10480 return pThis->signal_input(new_value);
10483 static void signalValueChanged(GtkSpinButton*, gpointer widget)
10485 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
10486 SolarMutexGuard aGuard;
10487 pThis->signal_value_changed();
10490 public:
10491 GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10492 : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
10493 , m_pButton(pButton)
10494 , m_pFormatter(nullptr)
10495 , m_pLastOutputColor(nullptr)
10496 , m_nFormatKey(0)
10497 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
10498 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
10499 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
10503 virtual double get_value() const override
10505 return gtk_spin_button_get_value(m_pButton);
10508 virtual void set_value(double value) override
10510 disable_notify_events();
10511 gtk_spin_button_set_value(m_pButton, value);
10512 enable_notify_events();
10515 virtual void set_range(double min, double max) override
10517 disable_notify_events();
10518 gtk_spin_button_set_range(m_pButton, min, max);
10519 enable_notify_events();
10522 virtual void get_range(double& min, double& max) const override
10524 gtk_spin_button_get_range(m_pButton, &min, &max);
10527 virtual void set_formatter(SvNumberFormatter* pFormatter) override
10529 m_pFormatter = pFormatter;
10531 // calc the default format key from the Office's UI locale
10532 if (m_pFormatter)
10534 // get the Office's locale and translate
10535 LanguageType eSysLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType( false);
10536 // get the standard numeric format for this language
10537 m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
10539 else
10540 m_nFormatKey = 0;
10541 signal_output();
10544 virtual sal_Int32 get_format_key() const override
10546 return m_nFormatKey;
10549 virtual void set_format_key(sal_Int32 nFormatKey) override
10551 m_nFormatKey = nFormatKey;
10554 virtual ~GtkInstanceFormattedSpinButton() override
10556 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
10557 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
10558 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
10562 class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
10564 private:
10565 GtkLabel* m_pLabel;
10567 void set_text_color(const Color& rColor)
10569 guint16 nRed = rColor.GetRed() << 8;
10570 guint16 nGreen = rColor.GetRed() << 8;
10571 guint16 nBlue = rColor.GetBlue() << 8;
10573 PangoAttrList* pAttrs = pango_attr_list_new();
10574 pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
10575 gtk_label_set_attributes(m_pLabel, pAttrs);
10576 pango_attr_list_unref(pAttrs);
10579 public:
10580 GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10581 : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
10582 , m_pLabel(pLabel)
10586 virtual void set_label(const OUString& rText) override
10588 ::set_label(m_pLabel, rText);
10591 virtual OUString get_label() const override
10593 return ::get_label(m_pLabel);
10596 virtual void set_mnemonic_widget(Widget* pTarget) override
10598 assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
10599 GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
10600 gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
10603 virtual void set_message_type(weld::EntryMessageType eType) override
10605 if (eType == weld::EntryMessageType::Error)
10606 set_text_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
10607 else if (eType == weld::EntryMessageType::Warning)
10608 set_text_color(COL_YELLOW);
10609 else
10610 gtk_label_set_attributes(m_pLabel, nullptr);
10613 virtual void set_font(const vcl::Font& rFont) override
10615 PangoAttrList* pAttrList = create_attr_list(rFont);
10616 gtk_label_set_attributes(m_pLabel, pAttrList);
10617 pango_attr_list_unref(pAttrList);
10621 std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
10623 GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
10624 if (!pLabel || !GTK_IS_LABEL(pLabel))
10625 return nullptr;
10626 return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
10629 class GtkInstanceTextView : public GtkInstanceContainer, public virtual weld::TextView
10631 private:
10632 GtkTextView* m_pTextView;
10633 GtkTextBuffer* m_pTextBuffer;
10634 GtkAdjustment* m_pVAdjustment;
10635 gulong m_nChangedSignalId;
10636 gulong m_nCursorPosSignalId;
10637 gulong m_nVAdjustChangedSignalId;
10639 static void signalChanged(GtkTextView*, gpointer widget)
10641 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
10642 SolarMutexGuard aGuard;
10643 pThis->signal_changed();
10646 static void signalCursorPosition(GtkTextView*, GParamSpec*, gpointer widget)
10648 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
10649 pThis->signal_cursor_position();
10652 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
10654 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
10655 SolarMutexGuard aGuard;
10656 pThis->signal_vadjustment_changed();
10659 public:
10660 GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10661 : GtkInstanceContainer(GTK_CONTAINER(pTextView), pBuilder, bTakeOwnership)
10662 , m_pTextView(pTextView)
10663 , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
10664 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
10665 , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
10666 , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
10667 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
10671 virtual void set_size_request(int nWidth, int nHeight) override
10673 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10674 if (GTK_IS_SCROLLED_WINDOW(pParent))
10676 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
10677 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
10678 return;
10680 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
10683 virtual void set_text(const OUString& rText) override
10685 disable_notify_events();
10686 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10687 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
10688 gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
10689 enable_notify_events();
10692 virtual OUString get_text() const override
10694 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10695 GtkTextIter start, end;
10696 gtk_text_buffer_get_bounds(pBuffer, &start, &end);
10697 char* pStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
10698 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
10699 g_free(pStr);
10700 return sRet;
10703 virtual void replace_selection(const OUString& rText) override
10705 disable_notify_events();
10706 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10707 gtk_text_buffer_delete_selection(pBuffer, false, gtk_text_view_get_editable(m_pTextView));
10708 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
10709 gtk_text_buffer_insert_at_cursor(pBuffer, sText.getStr(), sText.getLength());
10710 enable_notify_events();
10713 virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
10715 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10716 GtkTextIter start, end;
10717 gtk_text_buffer_get_selection_bounds(pBuffer, &start, &end);
10718 rStartPos = gtk_text_iter_get_offset(&start);
10719 rEndPos = gtk_text_iter_get_offset(&end);
10720 return rStartPos != rEndPos;
10723 virtual void select_region(int nStartPos, int nEndPos) override
10725 disable_notify_events();
10726 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
10727 GtkTextIter start, end;
10728 gtk_text_buffer_get_iter_at_offset(pBuffer, &start, nStartPos);
10729 gtk_text_buffer_get_iter_at_offset(pBuffer, &end, nEndPos);
10730 gtk_text_buffer_select_range(pBuffer, &start, &end);
10731 GtkTextMark* mark = gtk_text_buffer_create_mark(pBuffer, "scroll", &end, true);
10732 gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
10733 enable_notify_events();
10736 virtual void set_editable(bool bEditable) override
10738 gtk_text_view_set_editable(m_pTextView, bEditable);
10741 virtual void set_monospace(bool bMonospace) override
10743 gtk_text_view_set_monospace(m_pTextView, bMonospace);
10746 virtual void disable_notify_events() override
10748 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
10749 g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
10750 g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
10751 GtkInstanceContainer::disable_notify_events();
10754 virtual void enable_notify_events() override
10756 GtkInstanceContainer::enable_notify_events();
10757 g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
10758 g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
10759 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
10762 virtual int vadjustment_get_value() const override
10764 return gtk_adjustment_get_value(m_pVAdjustment);
10767 virtual void vadjustment_set_value(int value) override
10769 disable_notify_events();
10770 gtk_adjustment_set_value(m_pVAdjustment, value);
10771 enable_notify_events();
10774 virtual int vadjustment_get_upper() const override
10776 return gtk_adjustment_get_upper(m_pVAdjustment);
10779 virtual int vadjustment_get_lower() const override
10781 return gtk_adjustment_get_lower(m_pVAdjustment);
10784 virtual int vadjustment_get_page_size() const override
10786 return gtk_adjustment_get_page_size(m_pVAdjustment);
10789 virtual void show() override
10791 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10792 if (GTK_IS_SCROLLED_WINDOW(pParent))
10793 gtk_widget_show(pParent);
10794 gtk_widget_show(m_pWidget);
10797 virtual void hide() override
10799 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10800 if (GTK_IS_SCROLLED_WINDOW(pParent))
10801 gtk_widget_hide(pParent);
10802 gtk_widget_hide(m_pWidget);
10805 virtual ~GtkInstanceTextView() override
10807 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
10808 g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
10809 g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
10813 namespace
10815 AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
10818 class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
10820 private:
10821 GtkDrawingArea* m_pDrawingArea;
10822 a11yref m_xAccessible;
10823 AtkObject *m_pAccessible;
10824 ScopedVclPtrInstance<VirtualDevice> m_xDevice;
10825 cairo_surface_t* m_pSurface;
10826 gulong m_nDrawSignalId;
10827 gulong m_nStyleUpdatedSignalId;
10828 gulong m_nQueryTooltip;
10829 gulong m_nPopupMenu;
10830 gulong m_nScrollEvent;
10832 static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
10834 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10835 SolarMutexGuard aGuard;
10836 pThis->signal_draw(cr);
10837 return false;
10839 void signal_draw(cairo_t* cr)
10841 GdkRectangle rect;
10842 if (!m_pSurface || !gdk_cairo_get_clip_rectangle(cr, &rect))
10843 return;
10844 tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
10845 aRect = m_xDevice->PixelToLogic(aRect);
10846 m_xDevice->Erase(aRect);
10847 m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
10848 cairo_surface_mark_dirty(m_pSurface);
10850 cairo_set_source_surface(cr, m_pSurface, 0, 0);
10851 cairo_paint(cr);
10853 tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
10854 if (!aFocusRect.IsEmpty())
10856 gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
10857 aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
10860 virtual void signal_size_allocate(guint nWidth, guint nHeight) override
10862 m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
10863 m_pSurface = get_underlying_cairo_surface(*m_xDevice);
10864 GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
10866 static void signalStyleUpdated(GtkWidget*, gpointer widget)
10868 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10869 SolarMutexGuard aGuard;
10870 return pThis->signal_style_updated();
10872 void signal_style_updated()
10874 m_aStyleUpdatedHdl.Call(*this);
10876 static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
10877 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
10878 gpointer widget)
10880 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10881 tools::Rectangle aHelpArea(x, y);
10882 OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
10883 if (aTooltip.isEmpty())
10884 return false;
10885 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
10886 GdkRectangle aGdkHelpArea;
10887 aGdkHelpArea.x = aHelpArea.Left();
10888 aGdkHelpArea.y = aHelpArea.Top();
10889 aGdkHelpArea.width = aHelpArea.GetWidth();
10890 aGdkHelpArea.height = aHelpArea.GetHeight();
10891 if (pThis->SwapForRTL())
10892 aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
10893 gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
10894 return true;
10896 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
10898 return m_aCommandHdl.Call(rCEvt);
10900 bool signal_scroll(GdkEventScroll* pEvent)
10902 SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
10904 if (SwapForRTL())
10905 aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
10907 CommandWheelMode nMode;
10908 sal_uInt16 nCode = aEvt.mnCode;
10909 bool bHorz = aEvt.mbHorz;
10910 if (nCode & KEY_MOD1)
10911 nMode = CommandWheelMode::ZOOM;
10912 else if (nCode & KEY_MOD2)
10913 nMode = CommandWheelMode::DATAZOOM;
10914 else
10916 nMode = CommandWheelMode::SCROLL;
10917 // #i85450# interpret shift-wheel as horizontal wheel action
10918 if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
10919 bHorz = true;
10922 CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
10923 nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
10924 CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
10925 return m_aCommandHdl.Call(aCEvt);
10927 static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
10929 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
10930 return pThis->signal_scroll(pEvent);
10932 public:
10933 GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
10934 : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
10935 , m_pDrawingArea(pDrawingArea)
10936 , m_xAccessible(rA11y)
10937 , m_pAccessible(nullptr)
10938 , m_xDevice(DeviceFormat::DEFAULT)
10939 , m_pSurface(nullptr)
10940 , m_nDrawSignalId(g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this))
10941 , m_nStyleUpdatedSignalId(g_signal_connect(m_pDrawingArea,"style-updated", G_CALLBACK(signalStyleUpdated), this))
10942 , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
10943 , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
10944 , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
10946 gtk_widget_set_has_tooltip(m_pWidget, true);
10947 g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
10948 m_xDevice->EnableRTL(get_direction());
10951 AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
10953 if (!m_pAccessible && m_xAccessible.is())
10955 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
10956 m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
10957 g_object_ref(m_pAccessible);
10959 return m_pAccessible;
10962 virtual void set_direction(bool bRTL) override
10964 GtkInstanceWidget::set_direction(bRTL);
10965 m_xDevice->EnableRTL(bRTL);
10968 virtual void set_cursor(PointerStyle ePointerStyle) override
10970 GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
10971 if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
10972 gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
10973 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(m_pDrawingArea)), pCursor);
10976 virtual void queue_draw() override
10978 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
10981 virtual void queue_draw_area(int x, int y, int width, int height) override
10983 tools::Rectangle aRect(Point(x, y), Size(width, height));
10984 aRect = m_xDevice->LogicToPixel(aRect);
10985 gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
10988 virtual void queue_resize() override
10990 gtk_widget_queue_resize(GTK_WIDGET(m_pDrawingArea));
10993 virtual a11yref get_accessible_parent() override
10995 //get_accessible_parent should only be needed for the vcl implementation,
10996 //in the gtk impl the native AtkObject parent set via
10997 //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
10998 //should negate the need.
10999 assert(false && "get_accessible_parent should only be called on a vcl impl");
11000 return uno::Reference<css::accessibility::XAccessible>();
11003 virtual a11yrelationset get_accessible_relation_set() override
11005 //get_accessible_relation_set should only be needed for the vcl implementation,
11006 //in the gtk impl the native equivalent should negate the need.
11007 assert(false && "get_accessible_parent should only be called on a vcl impl");
11008 return uno::Reference<css::accessibility::XAccessibleRelationSet>();
11011 virtual Point get_accessible_location() override
11013 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11014 gint x(0), y(0);
11015 if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
11016 atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_WINDOW);
11017 return Point(x, y);
11020 virtual void set_accessible_name(const OUString& rName) override
11022 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11023 if (!pAtkObject)
11024 return;
11025 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
11028 virtual OUString get_accessible_name() const override
11030 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11031 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
11032 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11035 virtual OUString get_accessible_description() const override
11037 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
11038 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
11039 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11042 virtual ~GtkInstanceDrawingArea() override
11044 g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
11045 if (m_pAccessible)
11046 g_object_unref(m_pAccessible);
11047 css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
11048 if (xComp.is())
11049 xComp->dispose();
11050 g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
11051 g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
11052 g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
11053 g_signal_handler_disconnect(m_pDrawingArea, m_nStyleUpdatedSignalId);
11054 g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
11057 virtual OutputDevice& get_ref_device() override
11059 return *m_xDevice;
11063 #define g_signal_handlers_block_by_data(instance, data) \
11064 g_signal_handlers_block_matched ((instance), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, (data))
11066 /* tdf#125388 on measuring each row, the GtkComboBox GtkTreeMenu will call
11067 its area_apply_attributes_cb function on the row, but that calls
11068 gtk_tree_menu_get_path_item which then loops through each child of the
11069 menu looking for the widget of the row, so performance drops to useless.
11071 All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
11072 with fragile hackery which assumes that the unwanted callback is the only one with a
11073 user_data of the ComboBox GtkTreeMenu */
11074 static void disable_area_apply_attributes_cb(GtkWidget* pItem, gpointer userdata)
11076 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
11077 GtkWidget* child = gtk_bin_get_child(GTK_BIN(pMenuItem));
11078 if (!child)
11079 return;
11080 GtkCellView* pCellView = GTK_CELL_VIEW(child);
11081 GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(pCellView);
11082 GtkCellArea* pCellArea = gtk_cell_layout_get_area(pCellLayout);
11083 g_signal_handlers_block_by_data(pCellArea, userdata);
11086 class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
11088 private:
11089 GtkComboBox* m_pComboBox;
11090 GtkTreeModel* m_pTreeModel;
11091 GtkCellRenderer* m_pTextRenderer;
11092 GtkMenu* m_pMenu;
11093 GtkWidget* m_pToggleButton;
11094 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
11095 vcl::QuickSelectionEngine m_aQuickSelectionEngine;
11096 std::vector<int> m_aSeparatorRows;
11097 bool m_bPopupActive;
11098 bool m_bAutoComplete;
11099 bool m_bAutoCompleteCaseSensitive;
11100 gulong m_nToggleFocusInSignalId;
11101 gulong m_nToggleFocusOutSignalId;
11102 gulong m_nChangedSignalId;
11103 gulong m_nPopupShownSignalId;
11104 gulong m_nKeyPressEventSignalId;
11105 gulong m_nEntryInsertTextSignalId;
11106 gulong m_nEntryActivateSignalId;
11107 gulong m_nEntryFocusInSignalId;
11108 gulong m_nEntryFocusOutSignalId;
11109 guint m_nAutoCompleteIdleId;
11111 static gboolean idleAutoComplete(gpointer widget)
11113 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11114 pThis->auto_complete();
11115 return false;
11118 void auto_complete()
11120 m_nAutoCompleteIdleId = 0;
11121 OUString aStartText = get_active_text();
11122 int nStartPos, nEndPos;
11123 get_entry_selection_bounds(nStartPos, nEndPos);
11124 int nMaxSelection = std::max(nStartPos, nEndPos);
11125 if (nMaxSelection != aStartText.getLength())
11126 return;
11128 disable_notify_events();
11129 int nActive = get_active();
11130 int nStart = nActive;
11132 if (nStart == -1)
11133 nStart = 0;
11135 int nPos = -1;
11137 if (!m_bAutoCompleteCaseSensitive)
11139 // Try match case insensitive from current position
11140 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
11141 if (nPos == -1 && nStart != 0)
11143 // Try match case insensitive, but from start
11144 nPos = starts_with(m_pTreeModel, aStartText, 0, 0, false);
11148 if (nPos == -1)
11150 // Try match case sensitive from current position
11151 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
11152 if (nPos == -1 && nStart != 0)
11154 // Try match case sensitive, but from start
11155 nPos = starts_with(m_pTreeModel, aStartText, 0, 0, true);
11159 if (nPos != -1)
11161 OUString aText = get_text(nPos);
11162 if (aText != aStartText)
11163 set_active_text(aText);
11164 select_entry_region(aText.getLength(), aStartText.getLength());
11166 enable_notify_events();
11169 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
11170 gint* position, gpointer widget)
11172 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11173 SolarMutexGuard aGuard;
11174 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
11177 void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
11179 // first filter inserted text
11180 if (m_aEntryInsertTextHdl.IsSet())
11182 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
11183 const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
11184 if (bContinue && !sText.isEmpty())
11186 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
11187 g_signal_handlers_block_by_func(pEntry, gpointer(signalEntryInsertText), this);
11188 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
11189 g_signal_handlers_unblock_by_func(pEntry, gpointer(signalEntryInsertText), this);
11191 g_signal_stop_emission_by_name(pEntry, "insert-text");
11193 if (m_bAutoComplete)
11195 // now check for autocompletes
11196 if (m_nAutoCompleteIdleId)
11197 g_source_remove(m_nAutoCompleteIdleId);
11198 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
11202 static void signalChanged(GtkComboBox*, gpointer widget)
11204 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11205 SolarMutexGuard aGuard;
11206 pThis->signal_changed();
11209 static void signalPopupToggled(GtkComboBox*, GParamSpec*, gpointer widget)
11211 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11212 pThis->signal_popup_toggled();
11215 virtual void signal_popup_toggled() override
11217 m_aQuickSelectionEngine.Reset();
11218 gboolean bIsShown(false);
11219 g_object_get(m_pComboBox, "popup-shown", &bIsShown, nullptr);
11220 if (m_bPopupActive != bool(bIsShown))
11222 m_bPopupActive = bIsShown;
11223 ComboBox::signal_popup_toggled();
11224 //restore focus to the entry view when the popup is gone, which
11225 //is what the vcl case does, to ease the transition a little
11226 gtk_widget_grab_focus(m_pWidget);
11230 static void signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
11232 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11233 pThis->signal_entry_focus_in();
11236 void signal_entry_focus_in()
11238 signal_focus_in();
11241 static void signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
11243 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11244 pThis->signal_entry_focus_out();
11247 void signal_entry_focus_out()
11249 // if we have an untidy selection on losing focus remove the selection
11250 int nStartPos, nEndPos;
11251 if (get_entry_selection_bounds(nStartPos, nEndPos))
11253 int nMin = std::min(nStartPos, nEndPos);
11254 int nMax = std::max(nStartPos, nEndPos);
11255 if (nMin != 0 || nMax != get_active_text().getLength())
11256 select_entry_region(0, 0);
11258 signal_focus_out();
11261 static void signalEntryActivate(GtkEntry*, gpointer widget)
11263 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11264 pThis->signal_entry_activate();
11267 void signal_entry_activate()
11269 if (m_aEntryActivateHdl.IsSet())
11271 SolarMutexGuard aGuard;
11272 if (m_aEntryActivateHdl.Call(*this))
11273 g_signal_stop_emission_by_name(get_entry(), "activate");
11277 OUString get(int pos, int col) const
11279 OUString sRet;
11280 GtkTreeIter iter;
11281 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
11283 gchar* pStr;
11284 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
11285 sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11286 g_free(pStr);
11288 return sRet;
11291 void set(int pos, int col, const OUString& rText)
11293 GtkTreeIter iter;
11294 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
11296 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
11297 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
11301 int find(const OUString& rStr, int col) const
11303 GtkTreeIter iter;
11304 if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
11305 return -1;
11307 OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
11308 int nRet = 0;
11311 gchar* pStr;
11312 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
11313 const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
11314 g_free(pStr);
11315 if (bEqual)
11316 return nRet;
11317 ++nRet;
11318 } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
11320 return -1;
11323 GtkEntry* get_entry()
11325 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11326 if (!GTK_IS_ENTRY(pChild))
11327 return nullptr;
11328 return GTK_ENTRY(pChild);
11331 bool separator_function(int nIndex)
11333 return std::find(m_aSeparatorRows.begin(), m_aSeparatorRows.end(), nIndex) != m_aSeparatorRows.end();
11336 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
11338 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11339 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
11341 gint depth;
11342 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
11343 int nIndex = indices[depth-1];
11345 gtk_tree_path_free(path);
11346 return pThis->separator_function(nIndex);
11349 // https://gitlab.gnome.org/GNOME/gtk/issues/310
11351 // in the absence of a built-in solution
11352 // a) support typeahead for the case where there is no entry widget, typing ahead
11353 // into the button itself will select via the vcl selection engine, a matching
11354 // entry
11355 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
11357 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
11358 return pThis->signal_key_press(pEvent);
11361 bool signal_key_press(const GdkEventKey* pEvent)
11363 KeyEvent aKEvt(GtkToVcl(*pEvent));
11365 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
11367 bool bDone = false;
11369 switch (aKeyCode.GetCode())
11371 case KEY_DOWN:
11372 case KEY_UP:
11373 case KEY_PAGEUP:
11374 case KEY_PAGEDOWN:
11375 case KEY_HOME:
11376 case KEY_END:
11377 case KEY_LEFT:
11378 case KEY_RIGHT:
11379 case KEY_RETURN:
11380 m_aQuickSelectionEngine.Reset();
11381 break;
11382 default:
11383 bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
11384 break;
11387 return bDone;
11390 vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
11392 int nEntryCount(get_count());
11393 if (nPos >= nEntryCount)
11394 nPos = 0;
11395 out_entryText = get_text(nPos);
11397 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
11398 // => normalize
11399 return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
11402 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
11404 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
11405 return reinterpret_cast<sal_Int64>(entry) - 1;
11408 int get_selected_entry() const
11410 if (m_bPopupActive && m_pMenu)
11412 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
11413 auto nRet = g_list_index(pChildren, gtk_menu_shell_get_selected_item(GTK_MENU_SHELL(m_pMenu)));
11414 g_list_free(pChildren);
11415 return nRet;
11417 else
11418 return get_active();
11421 void set_selected_entry(int nSelect)
11423 if (m_bPopupActive && m_pMenu)
11425 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
11426 gtk_menu_shell_select_item(GTK_MENU_SHELL(m_pMenu), GTK_WIDGET(g_list_nth_data(pChildren, nSelect)));
11427 g_list_free(pChildren);
11429 else
11430 set_active(nSelect);
11433 virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
11435 int nCurrentPos = get_selected_entry();
11436 return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
11439 virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
11441 int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
11442 return typeahead_getEntry(nNextPos, out_entryText);
11445 virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
11447 int nSelect = typeahead_getEntryPos(entry);
11448 if (nSelect == get_selected_entry())
11450 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
11451 // to select the given entry by typing its starting letters. No need to act.
11452 return;
11455 // normalize
11456 int nCount = get_count();
11457 if (nSelect >= nCount)
11458 nSelect = nCount ? nCount-1 : -1;
11460 set_selected_entry(nSelect);
11463 // https://gitlab.gnome.org/GNOME/gtk/issues/310
11465 // in the absence of a built-in solution
11466 // b) support typeahead for the menu itself, typing into the menu will
11467 // select via the vcl selection engine, a matching entry. Clearly
11468 // this is cheating, brittle and not a long term solution.
11469 void install_menu_typeahead()
11471 AtkObject* pAtkObj = gtk_combo_box_get_popup_accessible(m_pComboBox);
11472 if (!pAtkObj)
11473 return;
11474 if (!GTK_IS_ACCESSIBLE(pAtkObj))
11475 return;
11476 GtkWidget* pWidget = gtk_accessible_get_widget(GTK_ACCESSIBLE(pAtkObj));
11477 if (!pWidget)
11478 return;
11479 if (!GTK_IS_MENU(pWidget))
11480 return;
11481 m_pMenu = GTK_MENU(pWidget);
11483 guint nSignalId = g_signal_lookup("key-press-event", GTK_TYPE_MENU);
11484 gulong nOriginalMenuKeyPressEventId = g_signal_handler_find(m_pMenu, G_SIGNAL_MATCH_DATA, nSignalId, 0,
11485 nullptr, nullptr, m_pComboBox);
11487 g_signal_handler_block(m_pMenu, nOriginalMenuKeyPressEventId);
11488 g_signal_connect(m_pMenu, "key-press-event", G_CALLBACK(signalKeyPress), this);
11491 static void find_toggle_button(GtkWidget *pWidget, gpointer user_data)
11493 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkToggleButton") == 0)
11495 GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
11496 *ppToggleButton = pWidget;
11498 else if (GTK_IS_CONTAINER(pWidget))
11499 gtk_container_forall(GTK_CONTAINER(pWidget), find_toggle_button, user_data);
11502 public:
11503 GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
11504 : GtkInstanceContainer(GTK_CONTAINER(pComboBox), pBuilder, bTakeOwnership)
11505 , m_pComboBox(pComboBox)
11506 , m_pTreeModel(gtk_combo_box_get_model(m_pComboBox))
11507 , m_pMenu(nullptr)
11508 , m_pToggleButton(nullptr)
11509 , m_aQuickSelectionEngine(*this)
11510 , m_bPopupActive(false)
11511 , m_bAutoComplete(false)
11512 , m_bAutoCompleteCaseSensitive(false)
11513 , m_nToggleFocusInSignalId(0)
11514 , m_nToggleFocusOutSignalId(0)
11515 , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this))
11516 , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this))
11517 , m_nAutoCompleteIdleId(0)
11519 GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
11520 if (!g_list_length(cells))
11522 //Always use the same text column renderer layout
11523 m_pTextRenderer = gtk_cell_renderer_text_new();
11524 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, true);
11525 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, "text", 0, nullptr);
11527 else
11529 m_pTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
11530 if (g_list_length(cells) == 2)
11532 //The ComboBox is always going to show the column associated with
11533 //the entry when there is one, left to its own devices this image
11534 //column will be after it, but we want it before
11535 gtk_cell_layout_reorder(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, 1);
11538 g_list_free(cells);
11540 if (GtkEntry* pEntry = get_entry())
11542 m_bAutoComplete = true;
11543 m_nEntryInsertTextSignalId = g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
11544 m_nEntryActivateSignalId = g_signal_connect(pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
11545 m_nEntryFocusInSignalId = g_signal_connect(pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
11546 m_nEntryFocusOutSignalId = g_signal_connect(pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
11547 m_nKeyPressEventSignalId = 0;
11549 else
11551 m_nEntryInsertTextSignalId = 0;
11552 m_nEntryActivateSignalId = 0;
11553 m_nEntryFocusInSignalId = 0;
11554 m_nEntryFocusOutSignalId = 0;
11555 m_nKeyPressEventSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
11558 find_toggle_button(GTK_WIDGET(m_pComboBox), &m_pToggleButton);
11560 install_menu_typeahead();
11563 virtual int get_active() const override
11565 return gtk_combo_box_get_active(m_pComboBox);
11568 virtual OUString get_active_id() const override
11570 const gchar* pText = gtk_combo_box_get_active_id(m_pComboBox);
11571 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
11574 virtual void set_active_id(const OUString& rStr) override
11576 disable_notify_events();
11577 OString aId(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
11578 gtk_combo_box_set_active_id(m_pComboBox, aId.getStr());
11579 enable_notify_events();
11582 virtual void set_size_request(int nWidth, int nHeight) override
11584 // tweak the cell render to get a narrower size to stick
11585 GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
11586 GtkCellRenderer* cell = static_cast<GtkCellRenderer*>(cells->data);
11588 if (nWidth != -1)
11590 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
11591 // the popup menu render them in full, in the interim ellipse both of them
11592 g_object_set(G_OBJECT(m_pTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
11594 // to find out how much of the width of the combobox belongs to the cell, set
11595 // the cell and widget to the min cell width and see what the difference is
11596 int min;
11597 gtk_cell_renderer_get_preferred_width(cell, m_pWidget, &min, nullptr);
11598 gtk_cell_renderer_set_fixed_size(cell, min, -1);
11599 gtk_widget_set_size_request(m_pWidget, min, -1);
11600 int nNonCellWidth = get_preferred_size().Width() - min;
11602 int nCellWidth = nWidth - nNonCellWidth;
11603 if (nCellWidth >= 0)
11605 // now set the cell to the max width which it can be within the
11606 // requested widget width
11607 gtk_cell_renderer_set_fixed_size(cell, nWidth - nNonCellWidth, -1);
11610 else
11612 g_object_set(G_OBJECT(m_pTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
11613 gtk_cell_renderer_set_fixed_size(cell, -1, -1);
11616 g_list_free(cells);
11618 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
11621 virtual void set_active(int pos) override
11623 disable_notify_events();
11624 gtk_combo_box_set_active(m_pComboBox, pos);
11625 enable_notify_events();
11628 virtual OUString get_active_text() const override
11630 if (gtk_combo_box_get_has_entry(m_pComboBox))
11632 GtkWidget *pEntry = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11633 const gchar* pText = gtk_entry_get_text(GTK_ENTRY(pEntry));
11634 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
11637 GtkTreeIter iter;
11638 if (!gtk_combo_box_get_active_iter(m_pComboBox, &iter))
11639 return OUString();
11641 gint col = gtk_combo_box_get_entry_text_column(m_pComboBox);
11642 gchar* pStr = nullptr;
11643 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
11644 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
11645 g_free(pStr);
11647 return sRet;
11650 virtual OUString get_text(int pos) const override
11652 return get(pos, 0);
11655 virtual OUString get_id(int pos) const override
11657 gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
11658 return get(pos, id_column);
11661 virtual void set_id(int pos, const OUString& rId) override
11663 gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
11664 set(pos, id_column, rId);
11667 // https://gitlab.gnome.org/GNOME/gtk/issues/94
11668 // when a super tall combobox menu is activated, and the selected entry is sufficiently
11669 // far down the list, then the menu doesn't appear under wayland
11670 void bodge_wayland_menu_not_appearing()
11672 if (get_frozen())
11673 return;
11674 if (has_entry())
11675 return;
11676 #if defined(GDK_WINDOWING_WAYLAND)
11677 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
11678 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
11680 gtk_combo_box_set_wrap_width(m_pComboBox, get_count() > 30 ? 1 : 0);
11682 #endif
11685 // https://gitlab.gnome.org/GNOME/gtk/issues/1910
11686 // has_entry long menus take forever to appear (tdf#125388)
11687 void bodge_area_apply_attributes_cb()
11689 gtk_container_foreach(GTK_CONTAINER(m_pMenu), disable_area_apply_attributes_cb, m_pMenu);
11692 virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
11694 freeze();
11695 if (!bKeepExisting)
11696 clear();
11697 GtkTreeIter iter;
11698 for (const auto& rItem : rItems)
11700 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, -1, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
11701 rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
11703 thaw();
11706 virtual void remove(int pos) override
11708 disable_notify_events();
11709 GtkTreeIter iter;
11710 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
11711 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
11712 m_aSeparatorRows.erase(std::remove(m_aSeparatorRows.begin(), m_aSeparatorRows.end(), pos), m_aSeparatorRows.end());
11713 enable_notify_events();
11714 bodge_wayland_menu_not_appearing();
11717 virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
11719 disable_notify_events();
11720 GtkTreeIter iter;
11721 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
11722 enable_notify_events();
11723 bodge_wayland_menu_not_appearing();
11726 virtual void insert_separator(int pos, const OUString& rId) override
11728 disable_notify_events();
11729 GtkTreeIter iter;
11730 pos = pos == -1 ? get_count() : pos;
11731 m_aSeparatorRows.push_back(pos);
11732 if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
11733 gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
11734 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, "", nullptr, nullptr);
11735 enable_notify_events();
11736 bodge_wayland_menu_not_appearing();
11739 virtual int get_count() const override
11741 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
11744 virtual int find_text(const OUString& rStr) const override
11746 return find(rStr, 0);
11749 virtual int find_id(const OUString& rId) const override
11751 gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
11752 return find(rId, id_column);
11755 virtual void clear() override
11757 disable_notify_events();
11758 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
11759 m_aSeparatorRows.clear();
11760 gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
11761 enable_notify_events();
11762 bodge_wayland_menu_not_appearing();
11765 virtual void make_sorted() override
11767 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
11768 ::comphelper::getProcessComponentContext(),
11769 Application::GetSettings().GetUILanguageTag().getLocale()));
11770 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11771 gtk_tree_sortable_set_sort_column_id(pSortable, 0, GTK_SORT_ASCENDING);
11772 gtk_tree_sortable_set_sort_func(pSortable, 0, default_sort_func, m_xSorter.get(), nullptr);
11775 virtual bool has_entry() const override
11777 return gtk_combo_box_get_has_entry(m_pComboBox);
11780 virtual void set_entry_message_type(weld::EntryMessageType eType) override
11782 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11783 assert(GTK_IS_ENTRY(pChild));
11784 GtkEntry* pEntry = GTK_ENTRY(pChild);
11785 ::set_entry_message_type(pEntry, eType);
11788 virtual void set_entry_text(const OUString& rText) override
11790 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11791 assert(pChild && GTK_IS_ENTRY(pChild));
11792 GtkEntry* pEntry = GTK_ENTRY(pChild);
11793 disable_notify_events();
11794 gtk_entry_set_text(pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
11795 enable_notify_events();
11798 virtual void set_entry_width_chars(int nChars) override
11800 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11801 assert(pChild && GTK_IS_ENTRY(pChild));
11802 GtkEntry* pEntry = GTK_ENTRY(pChild);
11803 disable_notify_events();
11804 gtk_entry_set_width_chars(pEntry, nChars);
11805 gtk_entry_set_max_width_chars(pEntry, nChars);
11806 enable_notify_events();
11809 virtual void set_entry_max_length(int nChars) override
11811 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11812 assert(pChild && GTK_IS_ENTRY(pChild));
11813 GtkEntry* pEntry = GTK_ENTRY(pChild);
11814 disable_notify_events();
11815 gtk_entry_set_max_length(pEntry, nChars);
11816 enable_notify_events();
11819 virtual void select_entry_region(int nStartPos, int nEndPos) override
11821 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11822 assert(pChild && GTK_IS_ENTRY(pChild));
11823 GtkEntry* pEntry = GTK_ENTRY(pChild);
11824 disable_notify_events();
11825 gtk_editable_select_region(GTK_EDITABLE(pEntry), nStartPos, nEndPos);
11826 enable_notify_events();
11829 virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
11831 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
11832 assert(pChild && GTK_IS_ENTRY(pChild));
11833 GtkEntry* pEntry = GTK_ENTRY(pChild);
11834 return gtk_editable_get_selection_bounds(GTK_EDITABLE(pEntry), &rStartPos, &rEndPos);
11837 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
11839 m_bAutoComplete = bEnable;
11840 m_bAutoCompleteCaseSensitive = bCaseSensitive;
11843 virtual void disable_notify_events() override
11845 if (GtkEntry* pEntry = get_entry())
11847 g_signal_handler_block(pEntry, m_nEntryInsertTextSignalId);
11848 g_signal_handler_block(pEntry, m_nEntryActivateSignalId);
11849 g_signal_handler_block(pEntry, m_nEntryFocusInSignalId);
11850 g_signal_handler_block(pEntry, m_nEntryFocusOutSignalId);
11852 else
11853 g_signal_handler_block(m_pComboBox, m_nKeyPressEventSignalId);
11854 if (m_nToggleFocusInSignalId)
11855 g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
11856 if (m_nToggleFocusOutSignalId)
11857 g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
11858 g_signal_handler_block(m_pComboBox, m_nChangedSignalId);
11859 g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
11860 GtkInstanceContainer::disable_notify_events();
11863 virtual void enable_notify_events() override
11865 GtkInstanceContainer::enable_notify_events();
11866 g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId);
11867 g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
11868 if (m_nToggleFocusInSignalId)
11869 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
11870 if (m_nToggleFocusOutSignalId)
11871 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
11872 if (GtkEntry* pEntry = get_entry())
11874 g_signal_handler_unblock(pEntry, m_nEntryActivateSignalId);
11875 g_signal_handler_unblock(pEntry, m_nEntryFocusInSignalId);
11876 g_signal_handler_unblock(pEntry, m_nEntryFocusOutSignalId);
11877 g_signal_handler_unblock(pEntry, m_nEntryInsertTextSignalId);
11879 else
11880 g_signal_handler_unblock(m_pComboBox, m_nKeyPressEventSignalId);
11883 virtual void freeze() override
11885 disable_notify_events();
11886 g_object_ref(m_pTreeModel);
11887 GtkInstanceContainer::freeze();
11888 gtk_combo_box_set_model(m_pComboBox, nullptr);
11889 if (m_xSorter)
11891 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11892 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
11894 enable_notify_events();
11897 virtual void thaw() override
11899 disable_notify_events();
11900 if (m_xSorter)
11902 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11903 gtk_tree_sortable_set_sort_column_id(pSortable, 0, GTK_SORT_ASCENDING);
11905 gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
11906 GtkInstanceContainer::thaw();
11907 g_object_unref(m_pTreeModel);
11908 enable_notify_events();
11910 bodge_wayland_menu_not_appearing();
11911 bodge_area_apply_attributes_cb();
11914 virtual bool get_popup_shown() const override
11916 return m_bPopupActive;
11919 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
11921 m_nToggleFocusInSignalId = g_signal_connect(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
11922 weld::Widget::connect_focus_in(rLink);
11925 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
11927 m_nToggleFocusOutSignalId = g_signal_connect(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
11928 weld::Widget::connect_focus_out(rLink);
11931 virtual bool has_focus() const override
11933 return gtk_widget_has_focus(m_pToggleButton) || GtkInstanceWidget::has_focus();
11936 virtual ~GtkInstanceComboBox() override
11938 if (m_nAutoCompleteIdleId)
11939 g_source_remove(m_nAutoCompleteIdleId);
11940 if (GtkEntry* pEntry = get_entry())
11942 g_signal_handler_disconnect(pEntry, m_nEntryInsertTextSignalId);
11943 g_signal_handler_disconnect(pEntry, m_nEntryActivateSignalId);
11944 g_signal_handler_disconnect(pEntry, m_nEntryFocusInSignalId);
11945 g_signal_handler_disconnect(pEntry, m_nEntryFocusOutSignalId);
11947 else
11948 g_signal_handler_disconnect(m_pComboBox, m_nKeyPressEventSignalId);
11949 if (m_nToggleFocusInSignalId)
11950 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
11951 if (m_nToggleFocusOutSignalId)
11952 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
11953 g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId);
11954 g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
11958 class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
11960 private:
11961 GtkInstanceEntry* m_pEntry;
11962 GtkInstanceTreeView* m_pTreeView;
11963 gulong m_nKeyPressSignalId;
11964 gulong m_nEntryInsertTextSignalId;
11965 guint m_nAutoCompleteIdleId;
11966 bool m_bAutoCompleteCaseSensitive;
11968 bool signal_key_press(GdkEventKey* pEvent)
11970 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 ||
11971 pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
11973 gboolean ret;
11974 disable_notify_events();
11975 GtkWidget* pWidget = m_pTreeView->getWidget();
11976 if (m_pTreeView->get_selected_index() == -1)
11978 m_pTreeView->set_cursor(0);
11979 m_pTreeView->select(0);
11980 m_xEntry->set_text(m_xTreeView->get_selected_text());
11982 else
11984 gtk_widget_grab_focus(pWidget);
11985 g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
11986 m_xEntry->set_text(m_xTreeView->get_selected_text());
11987 gtk_widget_grab_focus(m_pEntry->getWidget());
11989 m_xEntry->select_region(0, -1);
11990 enable_notify_events();
11991 m_pEntry->fire_signal_changed();
11992 return true;
11994 return false;
11997 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
11999 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
12000 return pThis->signal_key_press(pEvent);
12003 static gboolean idleAutoComplete(gpointer widget)
12005 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
12006 pThis->auto_complete();
12007 return false;
12010 void auto_complete()
12012 m_nAutoCompleteIdleId = 0;
12013 OUString aStartText = get_active_text();
12014 int nStartPos, nEndPos;
12015 get_entry_selection_bounds(nStartPos, nEndPos);
12016 int nMaxSelection = std::max(nStartPos, nEndPos);
12017 if (nMaxSelection != aStartText.getLength())
12018 return;
12020 disable_notify_events();
12021 int nActive = get_active();
12022 int nStart = nActive;
12024 if (nStart == -1)
12025 nStart = 0;
12027 // Try match case insensitive from current position
12028 int nPos = m_pTreeView->starts_with(aStartText, 0, nStart, true);
12029 if (nPos == -1 && nStart != 0)
12031 // Try match case insensitive, but from start
12032 nPos = m_pTreeView->starts_with(aStartText, 0, 0, true);
12035 if (!m_bAutoCompleteCaseSensitive)
12037 // Try match case insensitive from current position
12038 nPos = m_pTreeView->starts_with(aStartText, 0, nStart, false);
12039 if (nPos == -1 && nStart != 0)
12041 // Try match case insensitive, but from start
12042 nPos = m_pTreeView->starts_with(aStartText, 0, 0, false);
12046 if (nPos == -1)
12048 // Try match case sensitive from current position
12049 nPos = m_pTreeView->starts_with(aStartText, 0, nStart, true);
12050 if (nPos == -1 && nStart != 0)
12052 // Try match case sensitive, but from start
12053 nPos = m_pTreeView->starts_with(aStartText, 0, 0, true);
12057 if (nPos != -1)
12059 OUString aText = get_text(nPos);
12060 if (aText != aStartText)
12061 set_active_text(aText);
12062 select_entry_region(aText.getLength(), aStartText.getLength());
12064 enable_notify_events();
12067 void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
12069 // now check for autocompletes
12070 if (m_nAutoCompleteIdleId)
12071 g_source_remove(m_nAutoCompleteIdleId);
12072 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
12075 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
12076 gint* position, gpointer widget)
12078 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
12079 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
12083 public:
12084 GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
12085 std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
12086 : EntryTreeView(std::move(xEntry), std::move(xTreeView))
12087 , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
12088 , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
12089 , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
12090 , m_nAutoCompleteIdleId(0)
12091 , m_bAutoCompleteCaseSensitive(false)
12093 assert(m_pEntry);
12094 GtkWidget* pWidget = m_pEntry->getWidget();
12095 m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
12096 m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
12099 virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
12101 assert(false);
12104 virtual void make_sorted() override
12106 GtkWidget* pTreeView = m_pTreeView->getWidget();
12107 GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
12108 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
12109 gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
12112 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
12114 assert(!bEnable && "not implemented yet"); (void)bEnable;
12115 m_bAutoCompleteCaseSensitive = bCaseSensitive;
12118 virtual void grab_focus() override { m_xEntry->grab_focus(); }
12120 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
12122 m_xEntry->connect_focus_in(rLink);
12125 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
12127 m_xEntry->connect_focus_out(rLink);
12130 virtual void disable_notify_events() override
12132 GtkWidget* pWidget = m_pEntry->getWidget();
12133 g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
12134 g_signal_handler_block(pWidget, m_nKeyPressSignalId);
12135 m_pTreeView->disable_notify_events();
12136 GtkInstanceContainer::disable_notify_events();
12139 virtual void enable_notify_events() override
12141 GtkWidget* pWidget = m_pEntry->getWidget();
12142 g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
12143 g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
12144 m_pTreeView->enable_notify_events();
12145 GtkInstanceContainer::disable_notify_events();
12148 virtual ~GtkInstanceEntryTreeView() override
12150 if (m_nAutoCompleteIdleId)
12151 g_source_remove(m_nAutoCompleteIdleId);
12152 GtkWidget* pWidget = m_pEntry->getWidget();
12153 g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
12154 g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
12158 class GtkInstanceExpander : public GtkInstanceContainer, public virtual weld::Expander
12160 private:
12161 GtkExpander* m_pExpander;
12162 gulong m_nSignalId;
12164 static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
12166 GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
12167 SolarMutexGuard aGuard;
12168 pThis->signal_expanded();
12170 GtkWidget *pToplevel = gtk_widget_get_toplevel(GTK_WIDGET(pExpander));
12172 // https://gitlab.gnome.org/GNOME/gtk/issues/70
12173 // I imagine at some point a release with a fix will be available in which
12174 // case this can be avoided depending on version number
12175 if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
12177 int nToplevelWidth, nToplevelHeight;
12178 int nChildHeight;
12180 GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
12181 gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
12182 gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
12184 if (pThis->get_expanded())
12185 nToplevelHeight += nChildHeight;
12186 else
12187 nToplevelHeight -= nChildHeight;
12189 gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
12193 public:
12194 GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12195 : GtkInstanceContainer(GTK_CONTAINER(pExpander), pBuilder, bTakeOwnership)
12196 , m_pExpander(pExpander)
12197 , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
12201 virtual bool get_expanded() const override
12203 return gtk_expander_get_expanded(m_pExpander);
12206 virtual void set_expanded(bool bExpand) override
12208 gtk_expander_set_expanded(m_pExpander, bExpand);
12211 virtual ~GtkInstanceExpander() override
12213 g_signal_handler_disconnect(m_pExpander, m_nSignalId);
12217 namespace
12219 gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
12220 gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
12222 const ImplSVData* pSVData = ImplGetSVData();
12223 if (pSVData->maHelpData.mbBalloonHelp)
12225 /*Current mechanism which needs help installed*/
12226 OString sHelpId = ::get_help_id(pWidget);
12227 Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
12228 if (pHelp)
12230 OUString sHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr));
12231 if (!sHelpText.isEmpty())
12233 gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
12234 return true;
12238 /*This is how I would prefer things to be, only a few like this though*/
12239 AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
12240 const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
12241 if (pDesc && pDesc[0])
12243 gtk_tooltip_set_text(tooltip, pDesc);
12244 return true;
12248 const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
12249 if (pDesc && pDesc[0])
12251 gtk_tooltip_set_text(tooltip, pDesc);
12252 return true;
12255 return false;
12259 namespace
12262 AtkObject* drawing_area_get_accessibity(GtkWidget *pWidget)
12264 AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
12265 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
12266 GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
12267 AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
12268 if (pAtkObj)
12269 return pAtkObj;
12270 return pDefaultAccessible;
12273 void ensure_intercept_drawing_area_accessibility()
12275 static bool bDone;
12276 if (!bDone)
12278 gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
12279 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
12280 default_drawing_area_get_accessible = pWidgetClass->get_accessible;
12281 pWidgetClass->get_accessible = drawing_area_get_accessibity;
12282 g_type_class_unref(pClass);
12283 bDone = true;
12289 class GtkInstanceBuilder : public weld::Builder
12291 private:
12292 ResHookProc m_pStringReplace;
12293 OUString m_sHelpRoot;
12294 OString m_aUtf8HelpRoot;
12295 OUString m_aIconTheme;
12296 OUString m_aUILang;
12297 GtkBuilder* m_pBuilder;
12298 GSList* m_pObjectList;
12299 GtkWidget* m_pParentWidget;
12300 gulong m_nNotifySignalId;
12301 std::vector<GtkButton*> m_aMnemonicButtons;
12302 std::vector<GtkLabel*> m_aMnemonicLabels;
12304 void postprocess_widget(GtkWidget* pWidget)
12306 //fixup icons
12307 //wanted: better way to do this, e.g. make gtk use gio for
12308 //loading from a filename and provide gio protocol handler
12309 //for our image in a zip urls
12311 //unpack the images and keep them as dirs and just
12312 //add the paths to the gtk icon theme dir
12313 if (GTK_IS_IMAGE(pWidget))
12315 GtkImage* pImage = GTK_IMAGE(pWidget);
12316 const gchar* icon_name;
12317 gtk_image_get_icon_name(pImage, &icon_name, nullptr);
12318 if (icon_name)
12320 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
12321 GdkPixbuf* pixbuf = load_icon_by_name(aIconName, m_aIconTheme, m_aUILang);
12322 if (pixbuf)
12324 gtk_image_set_from_pixbuf(pImage, pixbuf);
12325 g_object_unref(pixbuf);
12329 else if (GTK_IS_TOOL_BUTTON(pWidget))
12331 GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
12332 const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton);
12333 if (icon_name)
12335 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
12336 GdkPixbuf* pixbuf = load_icon_by_name(aIconName, m_aIconTheme, m_aUILang);
12337 if (pixbuf)
12339 GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
12340 g_object_unref(pixbuf);
12341 gtk_tool_button_set_icon_widget(pToolButton, pImage);
12342 gtk_widget_show(pImage);
12346 // if no tooltip reuse the label as default tooltip
12347 if (!gtk_widget_get_tooltip_text(pWidget))
12349 if (const gchar* label = gtk_tool_button_get_label(pToolButton))
12350 gtk_widget_set_tooltip_text(pWidget, label);
12354 //set helpids
12355 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
12356 size_t nLen = pStr ? strlen(pStr) : 0;
12357 if (!nLen)
12358 return;
12359 OString sHelpId = m_aUtf8HelpRoot + OString(pStr, nLen);
12360 set_help_id(pWidget, sHelpId);
12361 //hook up for extended help
12362 const ImplSVData* pSVData = ImplGetSVData();
12363 if (pSVData->maHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
12365 gtk_widget_set_has_tooltip(pWidget, true);
12366 g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
12369 // expand placeholder and collect potentially missing mnemonics
12370 if (GTK_IS_BUTTON(pWidget))
12372 GtkButton* pButton = GTK_BUTTON(pWidget);
12373 if (m_pStringReplace != nullptr)
12375 OUString aLabel(get_label(pButton));
12376 if (!aLabel.isEmpty())
12377 set_label(pButton, (*m_pStringReplace)(aLabel));
12379 if (gtk_button_get_use_underline(pButton) && !gtk_button_get_use_stock(pButton))
12380 m_aMnemonicButtons.push_back(pButton);
12382 else if (GTK_IS_LABEL(pWidget))
12384 GtkLabel* pLabel = GTK_LABEL(pWidget);
12385 if (m_pStringReplace != nullptr)
12387 OUString aLabel(get_label(pLabel));
12388 if (!aLabel.isEmpty())
12389 set_label(pLabel, (*m_pStringReplace)(aLabel));
12391 if (gtk_label_get_use_underline(pLabel))
12392 m_aMnemonicLabels.push_back(pLabel);
12394 else if (GTK_IS_TEXT_VIEW(pWidget))
12396 GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
12397 if (m_pStringReplace != nullptr)
12399 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
12400 GtkTextIter start, end;
12401 gtk_text_buffer_get_bounds(pBuffer, &start, &end);
12402 char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
12403 int nTextLen = pTextStr ? strlen(pTextStr) : 0;
12404 if (nTextLen)
12406 OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
12407 OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
12408 gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
12410 g_free(pTextStr);
12413 else if (GTK_IS_WINDOW(pWidget))
12415 if (m_pStringReplace != nullptr) {
12416 GtkWindow* pWindow = GTK_WINDOW(pWidget);
12417 set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
12418 if (GTK_IS_MESSAGE_DIALOG(pWindow))
12420 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
12421 set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
12422 set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
12424 else if (GTK_IS_ABOUT_DIALOG(pWindow))
12426 GtkAboutDialog* pAboutDialog = GTK_ABOUT_DIALOG(pWindow);
12427 const gchar *pComments = gtk_about_dialog_get_comments(pAboutDialog);
12428 if (pComments)
12430 OUString sComments(pComments, strlen(pComments), RTL_TEXTENCODING_UTF8);
12431 sComments = (*m_pStringReplace)(sComments);
12432 gtk_about_dialog_set_comments(pAboutDialog, OUStringToOString(sComments, RTL_TEXTENCODING_UTF8).getStr());
12434 const gchar *pProgramName = gtk_about_dialog_get_program_name(pAboutDialog);
12435 if (pProgramName)
12437 OUString sProgramName(pProgramName, strlen(pProgramName), RTL_TEXTENCODING_UTF8);
12438 sProgramName = (*m_pStringReplace)(sProgramName);
12439 gtk_about_dialog_set_program_name(pAboutDialog, OUStringToOString(sProgramName, RTL_TEXTENCODING_UTF8).getStr());
12446 //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
12447 //In order for GtkBuilder to find the translations bindtextdomain has to be called
12448 //for the domain. So here on the first setting of "domain" we call Translate::Create
12449 //to make sure that happens. Without this, if some other part of LibreOffice has
12450 //used the translation machinery for this domain it will still work, but if it
12451 //hasn't, e.g. tdf#119929, then the translation fails
12452 void translation_domain_set()
12454 Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
12455 g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
12458 static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
12460 g_return_if_fail(pSpec != nullptr);
12461 if (strcmp(pSpec->name, "translation-domain") == 0)
12463 GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
12464 pBuilder->translation_domain_set();
12468 static void postprocess(gpointer data, gpointer user_data)
12470 GObject* pObject = static_cast<GObject*>(data);
12471 if (!GTK_IS_WIDGET(pObject))
12472 return;
12473 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
12474 pThis->postprocess_widget(GTK_WIDGET(pObject));
12476 public:
12477 GtkInstanceBuilder(GtkWidget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
12478 : weld::Builder(rUIFile)
12479 , m_pStringReplace(Translate::GetReadStringHook())
12480 , m_sHelpRoot(rUIFile)
12481 , m_pParentWidget(pParent)
12482 , m_nNotifySignalId(0)
12484 ensure_intercept_drawing_area_accessibility();
12486 sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.');
12487 if (nIdx != -1)
12488 m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
12489 m_sHelpRoot += OUString('/');
12490 m_aUtf8HelpRoot = OUStringToOString(m_sHelpRoot, RTL_TEXTENCODING_UTF8);
12491 m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
12492 m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
12494 OUString aUri(rUIRoot + rUIFile);
12495 OUString aPath;
12496 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
12497 m_pBuilder = gtk_builder_new();
12498 m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
12499 auto rc = gtk_builder_add_from_file(m_pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), nullptr);
12500 assert(rc && "could not load UI file");
12501 (void) rc;
12503 m_pObjectList = gtk_builder_get_objects(m_pBuilder);
12504 g_slist_foreach(m_pObjectList, postprocess, this);
12506 GenerateMissingMnemonics();
12509 void GenerateMissingMnemonics()
12511 MnemonicGenerator aMnemonicGenerator('_');
12512 for (const auto a : m_aMnemonicButtons)
12513 aMnemonicGenerator.RegisterMnemonic(get_label(a));
12514 for (const auto a : m_aMnemonicLabels)
12515 aMnemonicGenerator.RegisterMnemonic(get_label(a));
12517 for (const auto a : m_aMnemonicButtons)
12519 OUString aLabel(get_label(a));
12520 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
12521 if (aLabel == aNewLabel)
12522 continue;
12523 set_label(a, aNewLabel);
12525 for (const auto a : m_aMnemonicLabels)
12527 OUString aLabel(get_label(a));
12528 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
12529 if (aLabel == aNewLabel)
12530 continue;
12531 set_label(a, aNewLabel);
12534 m_aMnemonicLabels.clear();
12535 m_aMnemonicButtons.clear();
12538 OString get_current_page_help_id()
12540 OString sPageHelpId;
12541 // check to see if there is a notebook called tabcontrol and get the
12542 // helpid for the current page of that
12543 std::unique_ptr<weld::Notebook> xNotebook(weld_notebook("tabcontrol", false));
12544 if (xNotebook)
12546 if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
12548 GtkWidget* pContainer = pPage->getWidget();
12549 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
12550 GList* pChild = g_list_first(pChildren);
12551 if (pChild)
12553 GtkWidget* pPageWidget = static_cast<GtkWidget*>(pChild->data);
12554 sPageHelpId = ::get_help_id(pPageWidget);
12556 g_list_free(pChildren);
12559 return sPageHelpId;
12562 virtual ~GtkInstanceBuilder() override
12564 g_slist_free(m_pObjectList);
12565 g_object_unref(m_pBuilder);
12568 //ideally we would have/use weld::Container add and explicitly
12569 //call add when we want to do this, but in the vcl impl the
12570 //parent has to be set when the child is created, so for the
12571 //gtk impl emulate this by doing this implicitly at weld time
12572 void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
12574 if (gtk_widget_get_toplevel(pWidget) == pWidget && !GTK_IS_POPOVER(pWidget))
12575 gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
12578 virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id, bool bTakeOwnership) override
12580 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
12581 if (!pMessageDialog)
12582 return nullptr;
12583 gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12584 return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, bTakeOwnership);
12587 virtual std::unique_ptr<weld::AboutDialog> weld_about_dialog(const OString &id, bool bTakeOwnership) override
12589 GtkAboutDialog* pAboutDialog = GTK_ABOUT_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
12590 if (!pAboutDialog)
12591 return nullptr;
12592 gtk_window_set_transient_for(GTK_WINDOW(pAboutDialog), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12593 return std::make_unique<GtkInstanceAboutDialog>(pAboutDialog, this, bTakeOwnership);
12596 virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id, bool bTakeOwnership) override
12598 GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, id.getStr()));
12599 if (!pAssistant)
12600 return nullptr;
12601 if (m_pParentWidget)
12602 gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12603 return std::make_unique<GtkInstanceAssistant>(pAssistant, this, bTakeOwnership);
12606 virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id, bool bTakeOwnership) override
12608 GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12609 if (!pDialog)
12610 return nullptr;
12611 if (m_pParentWidget)
12612 gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12613 return std::make_unique<GtkInstanceDialog>(pDialog, this, bTakeOwnership);
12616 virtual std::unique_ptr<weld::Window> create_screenshot_window() override
12618 GtkWidget* pTopLevel = nullptr;
12620 for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
12622 GObject* pObj = static_cast<GObject*>(l->data);
12624 if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
12625 continue;
12627 if (!pTopLevel)
12628 pTopLevel = GTK_WIDGET(pObj);
12629 else if (GTK_IS_WINDOW(pObj))
12630 pTopLevel = GTK_WIDGET(pObj);
12633 if (!pTopLevel)
12634 return nullptr;
12636 GtkWindow* pDialog;
12637 if (GTK_IS_WINDOW(pTopLevel))
12638 pDialog = GTK_WINDOW(pTopLevel);
12639 else
12641 pDialog = GTK_WINDOW(gtk_dialog_new());
12642 ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
12644 GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
12645 gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
12646 gtk_widget_show_all(pTopLevel);
12649 if (m_pParentWidget)
12650 gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
12651 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
12654 virtual std::unique_ptr<weld::Window> weld_window(const OString &id, bool bTakeOwnership) override
12656 GtkWindow* pWindow = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12657 return pWindow ? std::make_unique<GtkInstanceWindow>(pWindow, this, bTakeOwnership) : nullptr;
12660 virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id, bool bTakeOwnership) override
12662 GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
12663 if (!pWidget)
12664 return nullptr;
12665 auto_add_parentless_widgets_to_container(pWidget);
12666 return std::make_unique<GtkInstanceWidget>(pWidget, this, bTakeOwnership);
12669 virtual std::unique_ptr<weld::Container> weld_container(const OString &id, bool bTakeOwnership) override
12671 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
12672 if (!pContainer)
12673 return nullptr;
12674 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
12675 return std::make_unique<GtkInstanceContainer>(pContainer, this, bTakeOwnership);
12678 virtual std::unique_ptr<weld::Box> weld_box(const OString &id, bool bTakeOwnership) override
12680 GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
12681 if (!pBox)
12682 return nullptr;
12683 auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
12684 return std::make_unique<GtkInstanceBox>(pBox, this, bTakeOwnership);
12687 virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id, bool bTakeOwnership) override
12689 GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, id.getStr()));
12690 if (!pFrame)
12691 return nullptr;
12692 auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
12693 return std::make_unique<GtkInstanceFrame>(pFrame, this, bTakeOwnership);
12696 virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bTakeOwnership) override
12698 GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12699 if (!pScrolledWindow)
12700 return nullptr;
12701 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
12702 return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, bTakeOwnership);
12705 virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id, bool bTakeOwnership) override
12707 GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, id.getStr()));
12708 if (!pNotebook)
12709 return nullptr;
12710 auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
12711 return std::make_unique<GtkInstanceNotebook>(pNotebook, this, bTakeOwnership);
12714 virtual std::unique_ptr<weld::Button> weld_button(const OString &id, bool bTakeOwnership) override
12716 GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12717 if (!pButton)
12718 return nullptr;
12719 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
12720 return std::make_unique<GtkInstanceButton>(pButton, this, bTakeOwnership);
12723 virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id, bool bTakeOwnership) override
12725 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12726 if (!pButton)
12727 return nullptr;
12728 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
12729 return std::make_unique<GtkInstanceMenuButton>(pButton, this, bTakeOwnership);
12732 virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id, bool bTakeOwnership) override
12734 GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12735 if (!pButton)
12736 return nullptr;
12737 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
12738 return std::make_unique<GtkInstanceLinkButton>(pButton, this, bTakeOwnership);
12741 virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id, bool bTakeOwnership) override
12743 GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12744 if (!pToggleButton)
12745 return nullptr;
12746 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
12747 return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, bTakeOwnership);
12750 virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id, bool bTakeOwnership) override
12752 GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12753 if (!pRadioButton)
12754 return nullptr;
12755 auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
12756 return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, bTakeOwnership);
12759 virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id, bool bTakeOwnership) override
12761 GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12762 if (!pCheckButton)
12763 return nullptr;
12764 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
12765 return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, bTakeOwnership);
12768 virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id, bool bTakeOwnership) override
12770 GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, id.getStr()));
12771 if (!pScale)
12772 return nullptr;
12773 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
12774 return std::make_unique<GtkInstanceScale>(pScale, this, bTakeOwnership);
12777 virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id, bool bTakeOwnership) override
12779 GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
12780 if (!pProgressBar)
12781 return nullptr;
12782 auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
12783 return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, bTakeOwnership);
12786 virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id, bool bTakeOwnership) override
12788 GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
12789 if (!pSpinner)
12790 return nullptr;
12791 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
12792 return std::make_unique<GtkInstanceSpinner>(pSpinner, this, bTakeOwnership);
12795 virtual std::unique_ptr<weld::Image> weld_image(const OString &id, bool bTakeOwnership) override
12797 GtkImage* pImage = GTK_IMAGE(gtk_builder_get_object(m_pBuilder, id.getStr()));
12798 if (!pImage)
12799 return nullptr;
12800 auto_add_parentless_widgets_to_container(GTK_WIDGET(pImage));
12801 return std::make_unique<GtkInstanceImage>(pImage, this, bTakeOwnership);
12804 virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id, bool bTakeOwnership) override
12806 GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
12807 if (!pCalendar)
12808 return nullptr;
12809 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
12810 return std::make_unique<GtkInstanceCalendar>(pCalendar, this, bTakeOwnership);
12813 virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id, bool bTakeOwnership) override
12815 GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, id.getStr()));
12816 if (!pEntry)
12817 return nullptr;
12818 auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
12819 return std::make_unique<GtkInstanceEntry>(pEntry, this, bTakeOwnership);
12822 virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id, bool bTakeOwnership) override
12824 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12825 if (!pSpinButton)
12826 return nullptr;
12827 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
12828 return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, bTakeOwnership);
12831 virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit,
12832 bool bTakeOwnership) override
12834 return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id, bTakeOwnership), eUnit);
12837 virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id, bool bTakeOwnership) override
12839 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
12840 if (!pSpinButton)
12841 return nullptr;
12842 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
12843 return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, bTakeOwnership);
12846 virtual std::unique_ptr<weld::TimeSpinButton> weld_time_spin_button(const OString& id, TimeFieldFormat eFormat,
12847 bool bTakeOwnership) override
12849 return std::make_unique<weld::TimeSpinButton>(weld_spin_button(id, bTakeOwnership), eFormat);
12852 virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id, bool bTakeOwnership) override
12854 GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
12855 if (!pComboBox)
12856 return nullptr;
12857 auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
12858 return std::make_unique<GtkInstanceComboBox>(pComboBox, this, bTakeOwnership);
12861 virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id, bool bTakeOwnership) override
12863 GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12864 if (!pTreeView)
12865 return nullptr;
12866 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
12867 return std::make_unique<GtkInstanceTreeView>(pTreeView, this, bTakeOwnership);
12870 virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id, bool bTakeOwnership) override
12872 GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12873 if (!pIconView)
12874 return nullptr;
12875 auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
12876 return std::make_unique<GtkInstanceIconView>(pIconView, this, bTakeOwnership);
12879 virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid, bool bTakeOwnership) override
12881 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
12882 if (!pContainer)
12883 return nullptr;
12884 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
12885 return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, bTakeOwnership,
12886 weld_entry(entryid, bTakeOwnership),
12887 weld_tree_view(treeviewid, bTakeOwnership));
12890 virtual std::unique_ptr<weld::Label> weld_label(const OString &id, bool bTakeOwnership) override
12892 GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, id.getStr()));
12893 if (!pLabel)
12894 return nullptr;
12895 auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
12896 return std::make_unique<GtkInstanceLabel>(pLabel, this, bTakeOwnership);
12899 virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id, bool bTakeOwnership) override
12901 GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
12902 if (!pTextView)
12903 return nullptr;
12904 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
12905 return std::make_unique<GtkInstanceTextView>(pTextView, this, bTakeOwnership);
12908 virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id, bool bTakeOwnership) override
12910 GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, id.getStr()));
12911 if (!pExpander)
12912 return nullptr;
12913 auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
12914 return std::make_unique<GtkInstanceExpander>(pExpander, this, bTakeOwnership);
12917 virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
12918 FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/, bool bTakeOwnership) override
12920 GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, id.getStr()));
12921 if (!pDrawingArea)
12922 return nullptr;
12923 auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
12924 return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, bTakeOwnership);
12927 virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id, bool bTakeOwnership) override
12929 GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
12930 if (!pMenu)
12931 return nullptr;
12932 return std::make_unique<GtkInstanceMenu>(pMenu, bTakeOwnership);
12935 virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id, bool bTakeOwnership) override
12937 GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
12938 if (!pToolbar)
12939 return nullptr;
12940 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
12941 return std::make_unique<GtkInstanceToolbar>(pToolbar, this, bTakeOwnership);
12944 virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
12946 return std::make_unique<GtkInstanceSizeGroup>();
12950 void GtkInstanceWindow::help()
12952 //show help for widget with keyboard focus
12953 GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
12954 if (!pWidget)
12955 pWidget = GTK_WIDGET(m_pWindow);
12956 OString sHelpId = ::get_help_id(pWidget);
12957 while (sHelpId.isEmpty())
12959 pWidget = gtk_widget_get_parent(pWidget);
12960 if (!pWidget)
12961 break;
12962 sHelpId = ::get_help_id(pWidget);
12964 std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
12965 weld::Widget* pSource = xTemp ? xTemp.get() : this;
12966 bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
12967 Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
12968 if (pHelp)
12970 // tdf#126007, there's a nice fallback route for offline help where
12971 // the current page of a notebook will get checked when the help
12972 // button is pressed and there was no help for the dialog found.
12974 // But for online help that route doesn't get taken, so bodge this here
12975 // by using the page help id if available and if the help button itself
12976 // was the original id
12977 if (m_pBuilder && sHelpId.endsWith("/help"))
12979 OString sPageId = m_pBuilder->get_current_page_help_id();
12980 if (!sPageId.isEmpty())
12981 sHelpId = sPageId;
12983 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
12987 //iterate upwards through the hierarchy from this widgets through its parents
12988 //calling func with their helpid until func returns true or we run out of parents
12989 void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
12991 GtkWidget* pParent = m_pWidget;
12992 while ((pParent = gtk_widget_get_parent(pParent)))
12994 if (func(::get_help_id(pParent)))
12995 return;
12999 weld::Builder* GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
13001 GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
13002 if (pParent && !pParentWidget) //remove when complete
13003 return SalInstance::CreateBuilder(pParent, rUIRoot, rUIFile);
13004 GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
13005 return new GtkInstanceBuilder(pBuilderParent, rUIRoot, rUIFile);
13008 weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
13010 GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
13011 GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
13012 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
13013 VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
13014 OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
13015 return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
13018 weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
13020 if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
13021 return pGtkXWindow->getFrameWeld();
13022 return SalInstance::GetFrameWeld(rWindow);
13025 weld::Window* GtkSalFrame::GetFrameWeld() const
13027 if (!m_xFrameWeld)
13028 m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(gtk_widget_get_toplevel(getWindow())), nullptr, false));
13029 return m_xFrameWeld.get();
13032 void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
13034 #if ENABLE_GSTREAMER_1_0
13035 auto aSymbol = gstElementFactoryNameSymbol();
13036 if (!aSymbol)
13037 return nullptr;
13039 const SystemEnvData* pEnvData = pWindow->GetSystemData();
13040 if (!pEnvData)
13041 return nullptr;
13043 GstElement* pVideosink = aSymbol("gtksink", "gtksink");
13044 if (!pVideosink)
13045 return nullptr;
13047 GtkWidget *pGstWidget;
13048 g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
13049 gtk_widget_set_vexpand(pGstWidget, true);
13050 gtk_widget_set_hexpand(pGstWidget, true);
13052 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
13053 gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
13054 g_object_unref(pGstWidget);
13055 gtk_widget_show_all(pParent);
13057 return pVideosink;
13058 #else
13059 (void)pWindow;
13060 return nullptr;
13061 #endif
13064 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */